cure-odf-report 0.5.1b

Sign up to get free protection for your applications and to get access to all the features.
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