cure-odf-report 0.5.1b

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +4 -0
  4. data/Gemfile +4 -0
  5. data/MIT-LICENSE +20 -0
  6. data/Manifest +12 -0
  7. data/README.textile +223 -0
  8. data/Rakefile +9 -0
  9. data/lib/odf-report.rb +16 -0
  10. data/lib/odf-report/actions/remove_section.rb +22 -0
  11. data/lib/odf-report/field.rb +88 -0
  12. data/lib/odf-report/file.rb +50 -0
  13. data/lib/odf-report/images.rb +44 -0
  14. data/lib/odf-report/nested.rb +62 -0
  15. data/lib/odf-report/parser/default.rb +91 -0
  16. data/lib/odf-report/report.rb +103 -0
  17. data/lib/odf-report/section.rb +64 -0
  18. data/lib/odf-report/table.rb +88 -0
  19. data/lib/odf-report/text.rb +43 -0
  20. data/lib/odf-report/version.rb +3 -0
  21. data/odf-report.gemspec +31 -0
  22. data/spec/fields_spec.rb +77 -0
  23. data/spec/result/specs.odt +0 -0
  24. data/spec/result/tables.rb +38 -0
  25. data/spec/spec_helper.rb +45 -0
  26. data/spec/specs.odt +0 -0
  27. data/spec/tables_spec.rb +39 -0
  28. data/test/fields_inside_text_test.rb +38 -0
  29. data/test/nested_tables_test.rb +43 -0
  30. data/test/sections_test.rb +44 -0
  31. data/test/sub_sections_test.rb +58 -0
  32. data/test/table_headers_test.rb +41 -0
  33. data/test/tables_test.rb +67 -0
  34. data/test/templates/piriapolis.jpg +0 -0
  35. data/test/templates/rails.png +0 -0
  36. data/test/templates/test_fields_inside_text.odt +0 -0
  37. data/test/templates/test_nested_tables.odt +0 -0
  38. data/test/templates/test_sections.odt +0 -0
  39. data/test/templates/test_sub_sections.odt +0 -0
  40. data/test/templates/test_table_headers.odt +0 -0
  41. data/test/templates/test_tables.odt +0 -0
  42. data/test/templates/test_text.odt +0 -0
  43. data/test/text_test.rb +56 -0
  44. metadata +204 -0
@@ -0,0 +1,44 @@
1
+ module ODFReport
2
+
3
+ module Images
4
+
5
+ IMAGE_DIR_NAME = "Pictures"
6
+
7
+ def find_image_name_matches(content)
8
+
9
+ @images.each_pair do |image_name, path|
10
+ if node = content.xpath("//draw:frame[@draw:name='#{image_name}']/draw:image").first
11
+ placeholder_path = node.attribute('href').value
12
+ @image_names_replacements[path] = ::File.join(IMAGE_DIR_NAME, ::File.basename(placeholder_path))
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ def replace_images(file)
19
+
20
+ return if @images.empty?
21
+
22
+ @image_names_replacements.each_pair do |path, template_image|
23
+
24
+ file.output_stream.put_next_entry(template_image)
25
+ file.output_stream.write ::File.read(path)
26
+
27
+ end
28
+
29
+ end # replace_images
30
+
31
+ # newer versions of LibreOffice can't open files with duplicates image names
32
+ def avoid_duplicate_image_names(content)
33
+
34
+ nodes = content.xpath("//draw:frame[@draw:name]")
35
+
36
+ nodes.each_with_index do |node, i|
37
+ node.attribute('name').value = "pic_#{i}"
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,62 @@
1
+ module ODFReport
2
+
3
+ module Nested
4
+
5
+ def add_field(name, data_field=nil, &block)
6
+ opts = {:name => name, :data_field => data_field}
7
+ field = Field.new(opts, &block)
8
+ @fields << field
9
+
10
+ end
11
+ alias_method :add_column, :add_field
12
+
13
+ def add_text(name, data_field=nil, &block)
14
+ opts = {:name => name, :data_field => data_field}
15
+ field = Text.new(opts, &block)
16
+ @texts << field
17
+
18
+ end
19
+
20
+ def add_table(table_name, collection_field, opts={})
21
+ opts.merge!(:name => table_name, :collection_field => collection_field)
22
+ tab = Table.new(opts)
23
+ @tables << tab
24
+
25
+ yield(tab)
26
+ end
27
+
28
+ def add_section(section_name, collection_field, opts={})
29
+ opts.merge!(:name => section_name, :collection_field => collection_field)
30
+ sec = Section.new(opts)
31
+ @sections << sec
32
+
33
+ yield(sec)
34
+ end
35
+
36
+
37
+ def get_collection_from_item(item, collection_field)
38
+
39
+ return item[collection_field] if item.is_a?(Hash)
40
+
41
+ if collection_field.is_a?(Array)
42
+ tmp = item.dup
43
+ collection_field.each do |f|
44
+ if f.is_a?(Hash)
45
+ tmp = tmp.send(f.keys[0], f.values[0])
46
+ else
47
+ tmp = tmp.send(f)
48
+ end
49
+ end
50
+ collection = tmp
51
+ elsif collection_field.is_a?(Hash)
52
+ collection = item.send(collection_field.keys[0], collection_field.values[0])
53
+ else
54
+ collection = item.send(collection_field)
55
+ end
56
+
57
+ return collection
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,91 @@
1
+ module ODFReport
2
+
3
+ module Parser
4
+
5
+
6
+ # Default HTML parser
7
+ #
8
+ # sample HTML
9
+ #
10
+ # <p> first paragraph </p>
11
+ # <p> second <strong>paragraph</strong> </p>
12
+ # <blockquote>
13
+ # <p> first <em>quote paragraph</em> </p>
14
+ # <p> first quote paragraph </p>
15
+ # <p> first quote paragraph </p>
16
+ # </blockquote>
17
+ # <p> third <strong>paragraph</strong> </p>
18
+ #
19
+ # <p style="margin: 100px"> fourth <em>paragraph</em> </p>
20
+ # <p style="margin: 120px"> fifth paragraph </p>
21
+ # <p> sixth <strong>paragraph</strong> </p>
22
+ #
23
+
24
+ class Default
25
+
26
+ attr_accessor :paragraphs
27
+
28
+ def initialize(text, template_node)
29
+ @text = text
30
+ @paragraphs = []
31
+ @template_node = template_node
32
+
33
+ parse
34
+ end
35
+
36
+ def parse
37
+
38
+ xml = @template_node.parse(@text)
39
+
40
+ xml.css("p", "h1", "h2").each do |p|
41
+
42
+ style = check_style(p)
43
+ text = parse_formatting(p.inner_html)
44
+
45
+ add_paragraph(text, style)
46
+ end
47
+ end
48
+
49
+ def add_paragraph(text, style)
50
+
51
+ node = @template_node.dup
52
+
53
+ node['text:style-name'] = style if style
54
+ node.children = text
55
+
56
+ @paragraphs << node
57
+ end
58
+
59
+ private
60
+
61
+ def parse_formatting(text)
62
+ text.strip!
63
+ text.gsub!(/<strong>(.+?)<\/strong>/) { "<text:span text:style-name=\"bold\">#{$1}<\/text:span>" }
64
+ text.gsub!(/<em>(.+?)<\/em>/) { "<text:span text:style-name=\"italic\">#{$1}<\/text:span>" }
65
+ text.gsub!(/<u>(.+?)<\/u>/) { "<text:span text:style-name=\"underline\">#{$1}<\/text:span>" }
66
+ text.gsub!("\n", "")
67
+ text
68
+ end
69
+
70
+ def check_style(node)
71
+ style = nil
72
+
73
+ if node.name =~ /h\d/i
74
+ style = "title"
75
+
76
+ elsif node.parent && node.parent.name == "blockquote"
77
+ style = "quote"
78
+
79
+ elsif node['style'] =~ /margin/
80
+ style = "quote"
81
+
82
+ end
83
+
84
+ style
85
+ end
86
+
87
+ end
88
+
89
+ end
90
+
91
+ end
@@ -0,0 +1,103 @@
1
+ module ODFReport
2
+
3
+ class Report
4
+ include Images
5
+
6
+ def initialize(template_name, &block)
7
+
8
+ @file = ODFReport::File.new(template_name)
9
+
10
+ @texts = []
11
+ @fields = []
12
+ @tables = []
13
+ @images = {}
14
+ @image_names_replacements = {}
15
+ @sections = []
16
+ @actions = []
17
+
18
+ yield(self)
19
+
20
+ end
21
+
22
+ def add_field(field_tag, value='')
23
+ opts = {:name => field_tag, :value => value}
24
+ field = Field.new(opts)
25
+ @fields << field
26
+ end
27
+
28
+ def add_text(field_tag, value='')
29
+ opts = {:name => field_tag, :value => value}
30
+ text = Text.new(opts)
31
+ @texts << text
32
+ end
33
+
34
+ def add_table(table_name, collection, opts={})
35
+ opts.merge!(:name => table_name, :collection => collection)
36
+ tab = Table.new(opts)
37
+ @tables << tab
38
+
39
+ yield(tab)
40
+ end
41
+
42
+ def add_section(section_name, collection, opts={})
43
+ opts.merge!(:name => section_name, :collection => collection)
44
+ sec = Section.new(opts)
45
+ @sections << sec
46
+
47
+ yield(sec)
48
+ end
49
+
50
+ def remove_section(section_name)
51
+ @actions << ODFReport::Actions::RemoveSection.new(section_name)
52
+ end
53
+
54
+ def add_image(name, path)
55
+ @images[name] = path
56
+ end
57
+
58
+ def generate(dest = nil)
59
+
60
+ @file.update_content do |file|
61
+
62
+ file.update_files('content.xml', 'styles.xml') do |txt|
63
+
64
+ parse_document(txt) do |doc|
65
+
66
+ @sections.each { |s| s.replace!(doc) }
67
+ @tables.each { |t| t.replace!(doc) }
68
+
69
+ @texts.each { |t| t.replace!(doc) }
70
+ @fields.each { |f| f.replace!(doc) }
71
+
72
+ @actions.each { |action| action.process!(doc) }
73
+
74
+ find_image_name_matches(doc)
75
+ avoid_duplicate_image_names(doc)
76
+
77
+ end
78
+
79
+ end
80
+
81
+ replace_images(file)
82
+
83
+ end
84
+
85
+ if dest
86
+ ::File.open(dest, "wb") {|f| f.write(@file.data) }
87
+ else
88
+ @file.data
89
+ end
90
+
91
+ end
92
+
93
+ private
94
+
95
+ def parse_document(txt)
96
+ doc = Nokogiri::XML(txt)
97
+ yield doc
98
+ txt.replace(doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML))
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,64 @@
1
+ module ODFReport
2
+
3
+ class Section
4
+ include Nested
5
+
6
+ def initialize(opts)
7
+ @name = opts[:name].to_s.upcase
8
+ @collection_field = opts[:collection_field]
9
+ @collection = opts[:collection]
10
+
11
+ @fields = []
12
+ @texts = []
13
+ @tables = []
14
+ @sections = []
15
+ end
16
+
17
+ def replace!(doc, row = nil)
18
+ return unless @section_node = find_section_node(doc)
19
+
20
+ @collection = get_collection_from_item(row, @collection_field) if row
21
+
22
+ @collection.each do |data_item|
23
+
24
+ new_section = get_section_node
25
+
26
+ @tables.each { |t| t.replace!(new_section, data_item) }
27
+
28
+ @sections.each { |s| s.replace!(new_section, data_item) }
29
+
30
+ @texts.each { |t| t.replace!(new_section, data_item) }
31
+
32
+ @fields.each { |f| f.replace!(new_section, data_item) }
33
+
34
+ @section_node.before(new_section)
35
+
36
+ end
37
+
38
+ @section_node.remove
39
+
40
+ end # replace_section
41
+
42
+ private
43
+
44
+ def find_section_node(doc)
45
+
46
+ sections = doc.xpath(".//text:section[@text:name='#{@name}']")
47
+
48
+ sections.empty? ? nil : sections.first
49
+
50
+ end
51
+
52
+ def get_section_node
53
+ node = @section_node.dup
54
+
55
+ name = node.get_attribute('text:name').to_s
56
+ @idx ||=0; @idx +=1
57
+ node.set_attribute('text:name', "#{name}_#{@idx}")
58
+
59
+ node
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,88 @@
1
+ module ODFReport
2
+
3
+ class Table
4
+ include Nested
5
+
6
+ def initialize(opts)
7
+ @name = opts[:name]
8
+ @collection_field = opts[:collection_field]
9
+ @collection = opts[:collection]
10
+
11
+ @fields = []
12
+ @texts = []
13
+ @tables = []
14
+
15
+ @template_rows = []
16
+ @header = opts[:header] || false
17
+ @skip_if_empty = opts[:skip_if_empty] || false
18
+ end
19
+
20
+ def replace!(doc, row = nil)
21
+
22
+ return unless table = find_table_node(doc)
23
+
24
+ @template_rows = table.xpath("table:table-row")
25
+
26
+ @header = table.xpath("table:table-header-rows").empty? ? @header : false
27
+
28
+
29
+ @collection = get_collection_from_item(row, @collection_field) if row
30
+
31
+ if @skip_if_empty && @collection.empty?
32
+ table.remove
33
+ return
34
+ end
35
+
36
+ @collection.each do |data_item|
37
+
38
+ new_node = get_next_row
39
+
40
+ @tables.each { |t| t.replace!(new_node, data_item) }
41
+
42
+ @texts.each { |t| t.replace!(new_node, data_item) }
43
+
44
+ @fields.each { |f| f.replace!(new_node, data_item) }
45
+
46
+ table.add_child(new_node)
47
+
48
+ end
49
+
50
+ @template_rows.each_with_index do |r, i|
51
+ r.remove if (get_start_node..template_length) === i
52
+ end
53
+
54
+ end # replace
55
+
56
+ private
57
+
58
+ def get_next_row
59
+ @row_cursor = get_start_node unless defined?(@row_cursor)
60
+
61
+ ret = @template_rows[@row_cursor]
62
+ if @template_rows.size == @row_cursor + 1
63
+ @row_cursor = get_start_node
64
+ else
65
+ @row_cursor += 1
66
+ end
67
+ return ret.dup
68
+ end
69
+
70
+ def get_start_node
71
+ @header ? 1 : 0
72
+ end
73
+
74
+ def template_length
75
+ @tl ||= @template_rows.size
76
+ end
77
+
78
+ def find_table_node(doc)
79
+
80
+ tables = doc.xpath(".//table:table[@table:name='#{@name}']")
81
+
82
+ tables.empty? ? nil : tables.first
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end