odf-report 0.5.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +5 -13
  2. data/.github/workflows/gem-push.yml +40 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +4 -0
  5. data/CHANGELOG.md +56 -0
  6. data/README.md +220 -0
  7. data/Rakefile +8 -0
  8. data/bin/odt-extract +10 -0
  9. data/bin/odt-viewer +18 -0
  10. data/lib/odf-report.rb +11 -9
  11. data/lib/odf-report/data_source.rb +65 -0
  12. data/lib/odf-report/field.rb +35 -36
  13. data/lib/odf-report/image.rb +57 -0
  14. data/lib/odf-report/nestable.rb +65 -0
  15. data/lib/odf-report/parser/default.rb +5 -4
  16. data/lib/odf-report/report.rb +29 -57
  17. data/lib/odf-report/section.rb +17 -80
  18. data/lib/odf-report/table.rb +52 -81
  19. data/lib/odf-report/template.rb +88 -0
  20. data/lib/odf-report/text.rb +2 -4
  21. data/lib/odf-report/version.rb +1 -1
  22. data/odf-report.gemspec +7 -4
  23. data/spec/fields_spec.rb +77 -0
  24. data/spec/images/image_1.jpg +0 -0
  25. data/spec/images/image_2.jpg +0 -0
  26. data/spec/images/image_3.jpg +0 -0
  27. data/{test → spec/images}/piriapolis.jpg +0 -0
  28. data/spec/images/placeholder.jpg +0 -0
  29. data/{test → spec/images}/rails.png +0 -0
  30. data/spec/images_spec.rb +159 -0
  31. data/spec/sections_spec.rb +51 -0
  32. data/spec/spec_helper.rb +43 -0
  33. data/spec/tables_spec.rb +39 -0
  34. data/spec/template_spec.rb +45 -0
  35. data/spec/templates/images.odt +0 -0
  36. data/spec/templates/specs.odt +0 -0
  37. data/test/fields_inside_text_test.rb +38 -0
  38. data/test/images_test.rb +32 -0
  39. data/test/nested_tables_test.rb +43 -0
  40. data/test/sections_test.rb +44 -0
  41. data/test/sub_sections_test.rb +58 -0
  42. data/test/table_headers_test.rb +41 -0
  43. data/test/tables_test.rb +67 -0
  44. data/test/templates/images/image_1.jpg +0 -0
  45. data/test/templates/images/image_2.jpg +0 -0
  46. data/test/templates/images/image_3.jpg +0 -0
  47. data/test/templates/images/placeholder.jpg +0 -0
  48. data/test/templates/images/placeholder.png +0 -0
  49. data/test/templates/piriapolis.jpg +0 -0
  50. data/test/templates/rails.png +0 -0
  51. data/test/templates/test_images.odt +0 -0
  52. data/test/templates/test_sub_sections.odt +0 -0
  53. data/test/templates/test_text.odt +0 -0
  54. data/test/test.rb +262 -0
  55. data/test/text_test.rb +56 -0
  56. metadata +151 -46
  57. data/README.textile +0 -225
  58. data/lib/odf-report/fields.rb +0 -40
  59. data/lib/odf-report/file.rb +0 -50
  60. data/lib/odf-report/images.rb +0 -44
  61. data/lib/odf-report/nested.rb +0 -34
  62. data/test/test_fields_inside_text.rb +0 -37
  63. data/test/test_nested_tables.rb +0 -39
  64. data/test/test_sections.rb +0 -39
  65. data/test/test_sub_sections.rb +0 -57
  66. data/test/test_table_headers.rb +0 -39
  67. data/test/test_tables.rb +0 -62
  68. data/test/test_text.rb +0 -48
@@ -1,112 +1,83 @@
1
1
  module ODFReport
2
+ class Table < Nestable
2
3
 
3
- class Table
4
- include Fields, Nested
4
+ def initialize(opts)
5
+ super(opts)
5
6
 
6
- attr_accessor :fields, :rows, :name, :collection_field, :data, :header, :parent, :tables
7
-
8
- def initialize(opts)
9
- @name = opts[:name]
10
- @collection_field = opts[:collection_field]
11
- @collection = opts[:collection]
12
- @parent = opts[:parent]
7
+ @template_rows = []
8
+ @header = opts[:header] || false
9
+ @skip_if_empty = opts[:skip_if_empty] || false
10
+ end
13
11
 
14
- @fields = []
15
- @tables = []
12
+ def replace!(doc)
13
+ return unless table = find_table_node(doc)
16
14
 
17
- @template_rows = []
18
- @header = opts[:header] || false
19
- @skip_if_empty = opts[:skip_if_empty] || false
20
- end
15
+ @template_rows = table.xpath("table:table-row")
21
16
 
22
- def add_column(name, data_field=nil, &block)
23
- opts = {:name => name, :data_field => data_field}
24
- field = Field.new(opts, &block)
25
- @fields << field
17
+ @header = table.xpath("table:table-header-rows").empty? ? @header : false
26
18
 
27
- end
28
-
29
- def add_table(table_name, collection_field, opts={}, &block)
30
- opts.merge!(:name => table_name, :collection_field => collection_field, :parent => self)
31
- tab = Table.new(opts)
32
- @tables << tab
19
+ if @skip_if_empty && @data_source.empty?
20
+ table.remove
21
+ return
22
+ end
33
23
 
34
- yield(tab)
35
- end
24
+ @data_source.each do |record|
36
25
 
37
- def populate!(row)
38
- @collection = get_collection_from_item(row, @collection_field) if row
39
- end
26
+ new_node = get_next_row
40
27
 
41
- def replace!(doc, row = nil)
28
+ @tables.each { |n| n.set_source(record).replace!(new_node) }
29
+ @sections.each { |n| n.set_source(record).replace!(new_node) }
30
+ @texts.each { |n| n.set_source(record).replace!(new_node) }
31
+ @fields.each { |n| n.set_source(record).replace!(new_node) }
32
+ @images.each { |n| n.set_source(record).replace!(new_node) }
42
33
 
43
- return unless table = find_table_node(doc)
34
+ table.add_child(new_node.to_xml)
44
35
 
45
- populate!(row)
36
+ end
46
37
 
47
- if (@skip_if_empty || !@header) && @collection.empty?
48
- table.remove
49
- return
50
- end
38
+ @template_rows.each_with_index do |r, i|
39
+ r.remove if (get_start_node..template_length) === i
40
+ end
51
41
 
52
- @template_rows = table.xpath("table:table-row")
42
+ end # replace
53
43
 
54
- @header = table.xpath("table:table-header-rows").empty? ? @header : false
44
+ private
55
45
 
56
- @collection.each do |data_item|
46
+ def get_next_row
47
+ if @template_rows.size == 1
57
48
 
58
- new_node = get_next_row
49
+ ret = @template_rows.first
59
50
 
60
- replace_fields!(new_node, data_item)
51
+ else
52
+ @row_cursor = get_start_node unless defined?(@row_cursor)
61
53
 
62
- @tables.each do |t|
63
- t.replace!(new_node, data_item)
54
+ ret = @template_rows[@row_cursor]
55
+ if @template_rows.size == @row_cursor + 1
56
+ @row_cursor = get_start_node
57
+ else
58
+ @row_cursor += 1
59
+ end
64
60
  end
65
61
 
66
- table.add_child(new_node)
67
-
62
+ return deep_clone(ret)
63
+ # return ret.dup
68
64
  end
69
65
 
70
- @template_rows.each_with_index do |r, i|
71
- r.remove if (get_start_node..template_length) === i
66
+ def get_start_node
67
+ @header ? 1 : 0
72
68
  end
73
69
 
74
- end # replace
75
-
76
- private
77
-
78
- def get_next_row
79
- @row_cursor = get_start_node unless defined?(@row_cursor)
80
-
81
- ret = @template_rows[@row_cursor]
82
- if @template_rows.size == @row_cursor + 1
83
- @row_cursor = get_start_node
84
- else
85
- @row_cursor += 1
70
+ def template_length
71
+ @tl ||= @template_rows.size
86
72
  end
87
- return ret.dup
88
- end
89
-
90
- def get_start_node
91
- @header ? 1 : 0
92
- end
93
-
94
- def reset
95
- @row_cursor = get_start_node
96
- end
97
-
98
- def template_length
99
- @tl ||= @template_rows.size
100
- end
101
-
102
- def find_table_node(doc)
103
73
 
104
- tables = doc.xpath(".//table:table[@table:name='#{@name}']")
74
+ def find_table_node(doc)
75
+ doc.at_css("table|table[@table|name='#{@name}']")
76
+ end
105
77
 
106
- tables.empty? ? nil : tables.first
78
+ def deep_clone(node)
79
+ Nokogiri::XML(wrap_with_ns(node)).at("table|table-row")
80
+ end
107
81
 
108
82
  end
109
-
110
- end
111
-
112
83
  end
@@ -0,0 +1,88 @@
1
+ module ODFReport
2
+ class Template
3
+
4
+ CONTENT_FILES = ['content.xml', 'styles.xml']
5
+ MANIFEST_FILE = "META-INF/manifest.xml"
6
+
7
+ attr_accessor :output_stream
8
+
9
+ def initialize(template = nil, io: nil)
10
+ raise "You must provide either a filename or an io: string" unless template || io
11
+ raise "Template [#{template}] not found." unless template.nil? || ::File.exist?(template)
12
+
13
+ @template = template
14
+ @io = io
15
+ end
16
+
17
+ def update_content
18
+ @buffer = Zip::OutputStream.write_buffer do |out|
19
+ @output_stream = out
20
+ yield self
21
+ end
22
+ end
23
+
24
+ def update_files(&block)
25
+
26
+ get_template_entries.each do |entry|
27
+
28
+ next if entry.directory?
29
+
30
+ entry.get_input_stream do |is|
31
+
32
+ data = is.sysread
33
+
34
+ if CONTENT_FILES.include?(entry.name)
35
+ process_entry(data, &block)
36
+ end
37
+
38
+ update_file(entry.name, data)
39
+
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ def update_manifest(&block)
46
+ entry = get_template_entries.find_entry(MANIFEST_FILE)
47
+
48
+ entry.get_input_stream do |is|
49
+
50
+ data = is.sysread
51
+
52
+ process_entry(data, &block)
53
+
54
+ update_file(MANIFEST_FILE, data)
55
+
56
+ end
57
+
58
+ end
59
+
60
+ def data
61
+ @buffer.string
62
+ end
63
+
64
+ def update_file(name, data)
65
+ @output_stream.put_next_entry(name)
66
+ @output_stream.write data
67
+ end
68
+
69
+ private
70
+
71
+ def get_template_entries
72
+
73
+ if @template
74
+ Zip::File.open(@template)
75
+ else
76
+ Zip::File.open_buffer(@io.force_encoding("ASCII-8BIT"))
77
+ end
78
+
79
+ end
80
+
81
+ def process_entry(entry)
82
+ doc = Nokogiri::XML(entry, &:noblanks)
83
+ yield doc
84
+ entry.replace(doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML))
85
+ end
86
+
87
+ end
88
+ end
@@ -8,9 +8,7 @@ module ODFReport
8
8
 
9
9
  return unless node = find_text_node(doc)
10
10
 
11
- text_value = get_value(data_item)
12
-
13
- @parser = Parser::Default.new(text_value, node)
11
+ @parser = Parser::Default.new(@data_source.value, node)
14
12
 
15
13
  @parser.paragraphs.each do |p|
16
14
  node.before(p)
@@ -40,4 +38,4 @@ module ODFReport
40
38
 
41
39
  end
42
40
 
43
- end
41
+ end
@@ -1,3 +1,3 @@
1
1
  module ODFReport
2
- VERSION = "0.5.1"
2
+ VERSION = "0.7.1"
3
3
  end
@@ -9,7 +9,6 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Sandro Duarte"]
10
10
  s.description = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
11
11
  s.email = %q{sandrods@gmail.com}
12
- s.has_rdoc = false
13
12
  s.homepage = %q{http://sandrods.github.com/odf-report/}
14
13
  s.rubygems_version = %q{1.3.7}
15
14
  s.summary = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
@@ -19,10 +18,14 @@ Gem::Specification.new do |s|
19
18
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
20
19
  s.require_paths = ["lib"]
21
20
 
22
- s.add_development_dependency "bundler", "~> 1.6"
21
+ s.add_development_dependency "bundler"
23
22
  s.add_development_dependency "rake"
23
+ s.add_development_dependency "rspec", "~> 3.0.0"
24
+ s.add_development_dependency "faker"
25
+ s.add_development_dependency "launchy"
24
26
 
25
- s.add_runtime_dependency('rubyzip', "~> 1.1.0")
26
- s.add_runtime_dependency('nokogiri', ">= 1.5.0")
27
+ s.add_runtime_dependency('rubyzip', ">= 1.3.0")
28
+ s.add_runtime_dependency('nokogiri', ">= 1.10.0")
29
+ s.add_runtime_dependency('mime-types')
27
30
 
28
31
  end
@@ -0,0 +1,77 @@
1
+ RSpec.describe "Fields" do
2
+
3
+ before(:context) do
4
+
5
+ @field_01 = Faker::Company.name
6
+ @field_02 = Faker::Name.name
7
+
8
+ @itens_01 = Item.get_list(3)
9
+
10
+ report = ODFReport::Report.new("spec/templates/specs.odt") do |r|
11
+
12
+ r.add_field(:field_01, @field_01)
13
+ r.add_field(:field_02, @field_02)
14
+
15
+ r.add_table('TABLE_01', @itens_01) do |t|
16
+ t.add_column(:column_01, :id)
17
+ t.add_column(:column_02, :name)
18
+ end
19
+
20
+ r.add_section('SECTION_01', @itens_01) do |t|
21
+ t.add_field(:s01_field_01, :id)
22
+ t.add_field(:s01_field_02, :name)
23
+ end
24
+
25
+ end
26
+
27
+ report.generate("spec/result/specs.odt")
28
+
29
+ @data = Inspector.new("spec/result/specs.odt")
30
+
31
+ end
32
+
33
+
34
+ it "simple fields replacement" do
35
+
36
+ expect(@data.text).not_to match(/\[FIELD_01\]/)
37
+ expect(@data.text).not_to match(/\[FIELD_02\]/)
38
+
39
+ expect(@data.text).to match @field_01
40
+ expect(@data.text).to match @field_02
41
+
42
+ end
43
+
44
+ it "table columns replacement" do
45
+
46
+ table = @data.xml.xpath(".//table:table[@table:name='TABLE_01']").to_s
47
+
48
+ @itens_01.each do |item|
49
+
50
+ expect(table).not_to match(/\[COLUMN_01\]/)
51
+ expect(table).not_to match(/\[COLUMN_02\]/)
52
+ expect(table).to match(/\[COLUMN_03\]/)
53
+
54
+ expect(table).to match(item.id.to_s)
55
+ expect(table).to match(item.name)
56
+
57
+ end
58
+
59
+ end
60
+
61
+ it "section fields replacement" do
62
+
63
+ expect(@data.text).not_to match(/\[S01_FIELD_01\]/)
64
+ expect(@data.text).not_to match(/\[S01_FIELD_02\]/)
65
+
66
+ @itens_01.each_with_index do |item, idx|
67
+
68
+ section = @data.xml.at_xpath(".//text:section[#{idx+1}]").to_s
69
+
70
+ expect(section).to match(item.id.to_s)
71
+ expect(section).to match(item.name)
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
Binary file
Binary file
Binary file
File without changes
File without changes
@@ -0,0 +1,159 @@
1
+ RSpec.describe "Images" do
2
+
3
+ context('Adding Images') do
4
+
5
+ before(:context) do
6
+
7
+ @list = []
8
+ @list << OpenStruct.new({ name: "IMG - [1, 1]", path: 'spec/images/image_1.jpg', path2: 'spec/images/image_1.jpg' })
9
+ @list << OpenStruct.new({ name: "IMG - [2, 1]", path: 'spec/images/image_2.jpg', path2: 'spec/images/image_1.jpg' })
10
+ @list << OpenStruct.new({ name: "IMG - [3, 2]", path: 'spec/images/image_3.jpg', path2: 'spec/images/image_2.jpg' })
11
+ @list << OpenStruct.new({ name: "IMG - [1, 3]", path: 'spec/images/image_1.jpg', path2: 'spec/images/image_3.jpg' })
12
+ @list << OpenStruct.new({ name: "IMG - [2, 2]", path: 'spec/images/image_2.jpg', path2: 'spec/images/image_2.jpg' })
13
+
14
+
15
+ report = ODFReport::Report.new("spec/templates/images.odt") do |r|
16
+
17
+ r.add_image("IMAGE_01", 'spec/images/rails.png')
18
+ r.add_image("IMAGE_02", 'spec/images/piriapolis.jpg')
19
+
20
+ r.add_table('IMAGE_TABLE', @list) do |t|
21
+ t.add_column(:image_name, :name)
22
+ t.add_image('IMAGE_IN_TABLE_01', :path)
23
+ t.add_image('IMAGE_IN_TABLE_02', :path2)
24
+ end
25
+
26
+ r.add_section('SECTION', @list) do |t|
27
+ t.add_field(:image_name, :name)
28
+ t.add_image('IMAGE_IN_SECTION_01', :path2)
29
+ t.add_image('IMAGE_IN_SECTION_02', :path)
30
+ end
31
+
32
+ end
33
+
34
+ report.generate("spec/result/images.odt")
35
+
36
+ @data = Inspector.new("spec/result/images.odt")
37
+
38
+ end
39
+
40
+
41
+ it "simple image replacement" do
42
+
43
+ images = @data.xml.xpath("//draw:image")
44
+
45
+ expect(images[0].attribute('href').value).to eq "Pictures/rails.png"
46
+ expect(images[1].attribute('href').value).to eq "Pictures/piriapolis.jpg"
47
+
48
+ end
49
+
50
+ it "table columns replacement" do
51
+
52
+ table = @data.xml.at_xpath(".//table:table[@table:name='IMAGE_TABLE']")
53
+
54
+ @list.each_with_index do |item, idx|
55
+
56
+ row = table.xpath(".//table:table-row[#{idx+1}]")
57
+
58
+ images = row.xpath(".//draw:image")
59
+
60
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(item.path)
61
+ expect(File.basename(images[1].attribute('href').value)).to eq File.basename(item.path2)
62
+
63
+ end
64
+
65
+ end
66
+
67
+ it "section fields replacement" do
68
+
69
+ @list.each_with_index do |item, idx|
70
+
71
+ section = @data.xml.at_xpath(".//text:section[#{idx+1}]")
72
+
73
+ images = section.xpath(".//draw:image")
74
+
75
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(item.path)
76
+ expect(File.basename(images[1].attribute('href').value)).to eq File.basename(item.path2)
77
+
78
+ end
79
+
80
+ end
81
+ end
82
+
83
+ context "Removing Images" do
84
+
85
+ before(:context) do
86
+
87
+ @list = []
88
+ @list << OpenStruct.new({ name: "IMG - both ok", path: 'spec/images/image_1.jpg', path2: 'spec/images/image_1.jpg' })
89
+ @list << OpenStruct.new({ name: "IMG - 1 ok", path: 'spec/images/image_2.jpg', path2: nil })
90
+ @list << OpenStruct.new({ name: "IMG - 2 ok", path: nil, path2: 'spec/images/image_3.jpg' })
91
+ # @list << OpenStruct.new({ name: "IMG - 2 invalid", path: nil, path2: 'spec/images/invalid.jpg' })
92
+
93
+ report = ODFReport::Report.new("spec/templates/images.odt") do |r|
94
+
95
+ # r.add_image("IMAGE_01")
96
+ r.add_image("IMAGE_02", nil)
97
+
98
+ r.add_table('IMAGE_TABLE', @list) do |t|
99
+ t.add_column(:image_name, :name)
100
+ t.add_image('IMAGE_IN_TABLE_01', :path)
101
+ t.add_image('IMAGE_IN_TABLE_02', :path2)
102
+ end
103
+
104
+ r.add_section('SECTION', @list) do |t|
105
+ t.add_field(:image_name, :name)
106
+ t.add_image('IMAGE_IN_SECTION_01', :path2)
107
+ t.add_image('IMAGE_IN_SECTION_02', :path)
108
+ end
109
+
110
+ end
111
+
112
+ report.generate("spec/result/images.odt")
113
+
114
+ @data = Inspector.new("spec/result/images.odt")
115
+
116
+ end
117
+
118
+ it "removes nil images in report" do
119
+ expect(@data.xml.at_css("draw|frame[@draw|name='IMAGE_01']")).to be
120
+ expect(@data.xml.at_css("draw|frame[@draw|name='IMAGE_02']")).to be_nil
121
+ end
122
+
123
+ it "removes nil images in tables" do
124
+
125
+ table = @data.xml.at_xpath(".//table:table[@table:name='IMAGE_TABLE']")
126
+
127
+ images = table.xpath(".//table:table-row[1]//draw:image")
128
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[0].path)
129
+ expect(File.basename(images[1].attribute('href').value)).to eq File.basename(@list[0].path2)
130
+
131
+ images = table.xpath(".//table:table-row[2]//draw:image")
132
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[1].path)
133
+ expect(images[1]).to be_nil
134
+
135
+ images = table.xpath(".//table:table-row[3]//draw:image")
136
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[2].path2)
137
+ expect(images[1]).to be_nil
138
+
139
+ end
140
+
141
+ it "removes nil images in sections " do
142
+
143
+ images = @data.xml.xpath(".//text:section[1]//draw:image")
144
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[0].path)
145
+ expect(File.basename(images[1].attribute('href').value)).to eq File.basename(@list[0].path2)
146
+
147
+ images = @data.xml.xpath(".//text:section[2]//draw:image")
148
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[1].path)
149
+ expect(images[1]).to be_nil
150
+
151
+ images = @data.xml.xpath(".//text:section[3]//draw:image")
152
+ expect(File.basename(images[0].attribute('href').value)).to eq File.basename(@list[2].path2)
153
+ expect(images[1]).to be_nil
154
+
155
+ end
156
+
157
+ end
158
+
159
+ end