odf-report 0.7.3 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/gem-push.yml +7 -4
- data/CHANGELOG.md +14 -1
- data/README.md +2 -2
- data/lib/odf-report/data_source.rb +6 -10
- data/lib/odf-report/field.rb +3 -8
- data/lib/odf-report/image.rb +6 -11
- data/lib/odf-report/nestable.rb +16 -15
- data/lib/odf-report/parser/default.rb +62 -71
- data/lib/odf-report/report.rb +58 -66
- data/lib/odf-report/section.rb +7 -14
- data/lib/odf-report/table.rb +10 -15
- data/lib/odf-report/template.rb +1 -13
- data/lib/odf-report/text.rb +4 -10
- data/lib/odf-report/version.rb +1 -1
- data/lib/odf-report.rb +16 -16
- data/odf-report.gemspec +1 -1
- data/spec/templates/specs.odt +0 -0
- data/spec/texts_spec.rb +48 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65db96f6bec66147072afafb0d4193df34d188f4c0479361c7dfb34b36300930
|
4
|
+
data.tar.gz: c7f903e4fa5cb6a5fa811da28339fb3a27d0f9a7d9d2910107efb9cc941823e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82b5cf956e054ed3eec7d93e3b1bcb2244a3922df1faa3d7145a00fbca4a79d7c0d0a7cbb0dd05916dcd020f38305aa33083564812eb1d2417050c26010dc4cd
|
7
|
+
data.tar.gz: f0850ee3d3fa433cc55d38136f78c269796159feb07307a39c996af80da6f2597fe6928ec0cdcb38174044ba3899c399e73e6f0fd714df518f316a618ca7b912
|
@@ -8,13 +8,16 @@ jobs:
|
|
8
8
|
build:
|
9
9
|
name: Build + Publish
|
10
10
|
runs-on: ubuntu-latest
|
11
|
+
permissions:
|
12
|
+
contents: read
|
13
|
+
packages: write
|
11
14
|
|
12
15
|
steps:
|
13
|
-
- uses: actions/checkout@
|
14
|
-
-
|
15
|
-
uses: actions/setup-ruby@v1
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
- uses: ruby/setup-ruby@v1
|
16
18
|
with:
|
17
|
-
version: 2
|
19
|
+
ruby-version: '3.2'
|
20
|
+
bundler-cache: true
|
18
21
|
|
19
22
|
# - name: Publish to GPR
|
20
23
|
# run: |
|
data/CHANGELOG.md
CHANGED
@@ -18,10 +18,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
18
18
|
|
19
19
|
- None
|
20
20
|
|
21
|
+
## 0.8.1
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
- \<br\> replacement in default text parser #130
|
25
|
+
|
26
|
+
## 0.8.0
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
- Use Nokogiri HTML5 parser for text nodes parsing #129
|
30
|
+
|
31
|
+
### Dependencies
|
32
|
+
- nokogiri >= 1.12.0 (was >= 1.10.0)
|
33
|
+
|
21
34
|
## 0.7.3
|
22
35
|
|
23
36
|
### Fixed
|
24
|
-
- newer versions (> 1.
|
37
|
+
- newer versions (> 1.13.0) of Nokogiri where presenting "Nokogiri::CSS::SyntaxError: unexpected '|'" #120
|
25
38
|
- prevent unnecessary memory expensive operations with missing placeholders #117
|
26
39
|
|
27
40
|
## 0.7.2
|
data/README.md
CHANGED
@@ -56,7 +56,7 @@ report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
|
56
56
|
r.add_field "USER_NAME", @user.nome
|
57
57
|
r.add_field "ADDRESS", @user.address
|
58
58
|
|
59
|
-
r.add_table("TABLE_1", @
|
59
|
+
r.add_table("TABLE_1", @list_of_items, :header=>true) do |t|
|
60
60
|
t.add_column(:item_id, :id)
|
61
61
|
t.add_column(:description) { |item| "==> #{item.description}" }
|
62
62
|
end
|
@@ -71,7 +71,7 @@ and considering you have a table like this in your template
|
|
71
71
|
| [ITEM_ID] | [DESCRIPTION] |
|
72
72
|
|
73
73
|
|
74
|
-
and a collection `@
|
74
|
+
and a collection `@list_of_items`, it will create one row for each item in the collection, and the replacement will take place accordingly.
|
75
75
|
|
76
76
|
Any format applied to the fields in the template will be preserved.
|
77
77
|
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class DataSource
|
3
|
-
|
4
3
|
attr_reader :value
|
5
4
|
|
6
5
|
def initialize(opts, &block)
|
7
|
-
@value
|
6
|
+
@value = opts[:value] || opts[:collection]
|
8
7
|
@data_field = opts[:data_field] || opts[:collection_field] || opts[:name]
|
9
|
-
@block
|
8
|
+
@block = block
|
10
9
|
end
|
11
10
|
|
12
11
|
def set_source(record)
|
@@ -25,7 +24,6 @@ module ODFReport
|
|
25
24
|
private
|
26
25
|
|
27
26
|
def extract_value_from_item(record)
|
28
|
-
|
29
27
|
if @block
|
30
28
|
@block.call(record)
|
31
29
|
|
@@ -43,23 +41,21 @@ module ODFReport
|
|
43
41
|
record.send(@data_field)
|
44
42
|
|
45
43
|
else
|
46
|
-
raise "Can't find [#{@data_field
|
44
|
+
raise "Can't find [#{@data_field}] in this #{record.class}"
|
47
45
|
|
48
46
|
end
|
49
|
-
|
50
47
|
end
|
51
48
|
|
52
49
|
def execute_methods_on_item(record)
|
53
50
|
tmp = record.dup
|
54
51
|
@data_field.each do |f|
|
55
|
-
if f.is_a?(Hash)
|
56
|
-
tmp
|
52
|
+
tmp = if f.is_a?(Hash)
|
53
|
+
tmp.send(f.keys[0], f.values[0])
|
57
54
|
else
|
58
|
-
tmp
|
55
|
+
tmp.send(f)
|
59
56
|
end
|
60
57
|
end
|
61
58
|
tmp
|
62
59
|
end
|
63
|
-
|
64
60
|
end
|
65
61
|
end
|
data/lib/odf-report/field.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Field
|
3
|
-
|
4
3
|
DELIMITERS = %w([ ])
|
5
4
|
|
6
5
|
def initialize(opts, &block)
|
@@ -14,16 +13,14 @@ module ODFReport
|
|
14
13
|
end
|
15
14
|
|
16
15
|
def replace!(content, data_item = nil)
|
17
|
-
|
18
16
|
txt = content.inner_html
|
19
17
|
|
20
18
|
if txt.gsub!(to_placeholder, sanitize(@data_source.value))
|
21
19
|
content.inner_html = txt
|
22
20
|
end
|
23
|
-
|
24
21
|
end
|
25
22
|
|
26
|
-
|
23
|
+
private
|
27
24
|
|
28
25
|
def to_placeholder
|
29
26
|
if DELIMITERS.is_a?(Array)
|
@@ -35,11 +32,10 @@ module ODFReport
|
|
35
32
|
|
36
33
|
def sanitize(txt)
|
37
34
|
txt = html_escape(txt)
|
38
|
-
|
39
|
-
txt
|
35
|
+
odf_linebreak(txt)
|
40
36
|
end
|
41
37
|
|
42
|
-
HTML_ESCAPE = {
|
38
|
+
HTML_ESCAPE = {"&" => "&", ">" => ">", "<" => "<", '"' => """}
|
43
39
|
|
44
40
|
def html_escape(s)
|
45
41
|
return "" unless s
|
@@ -50,6 +46,5 @@ module ODFReport
|
|
50
46
|
return "" unless s
|
51
47
|
s.to_s.gsub("\n", "<text:line-break/>")
|
52
48
|
end
|
53
|
-
|
54
49
|
end
|
55
50
|
end
|
data/lib/odf-report/image.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Image < Field
|
3
|
-
|
4
3
|
IMAGE_DIR_NAME = "Pictures"
|
5
4
|
|
6
5
|
attr_reader :files
|
@@ -11,7 +10,6 @@ module ODFReport
|
|
11
10
|
end
|
12
11
|
|
13
12
|
def replace!(doc, data_item = nil)
|
14
|
-
|
15
13
|
frame = doc.xpath("//draw:frame[@draw:name='#{@name}']").first
|
16
14
|
image = doc.xpath("//draw:frame[@draw:name='#{@name}']/draw:image").first
|
17
15
|
|
@@ -20,14 +18,13 @@ module ODFReport
|
|
20
18
|
file = @data_source.value
|
21
19
|
|
22
20
|
if file
|
23
|
-
image.attribute(
|
24
|
-
frame.attribute(
|
21
|
+
image.attribute("href").content = File.join(IMAGE_DIR_NAME, File.basename(file))
|
22
|
+
frame.attribute("name").content = SecureRandom.uuid
|
25
23
|
|
26
24
|
@files << file
|
27
25
|
else
|
28
26
|
frame.remove
|
29
27
|
end
|
30
|
-
|
31
28
|
end
|
32
29
|
|
33
30
|
def self.include_image_file(zip_file, image_file)
|
@@ -41,17 +38,15 @@ module ODFReport
|
|
41
38
|
def self.include_manifest_entry(content, image_file)
|
42
39
|
return unless image_file
|
43
40
|
|
44
|
-
return unless root_node = content.at("//manifest:manifest")
|
41
|
+
return unless (root_node = content.at("//manifest:manifest"))
|
45
42
|
|
46
43
|
href = File.join(IMAGE_DIR_NAME, File.basename(image_file))
|
47
44
|
|
48
|
-
entry = content.create_element(
|
49
|
-
entry[
|
50
|
-
entry[
|
45
|
+
entry = content.create_element("manifest:file-entry")
|
46
|
+
entry["manifest:full-path"] = href
|
47
|
+
entry["manifest:media-type"] = MIME::Types.type_for(href)[0].content_type
|
51
48
|
|
52
49
|
root_node.add_child entry
|
53
|
-
|
54
50
|
end
|
55
|
-
|
56
51
|
end
|
57
52
|
end
|
data/lib/odf-report/nestable.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Nestable
|
3
|
-
|
4
3
|
def initialize(opts)
|
5
4
|
@name = opts[:name]
|
6
5
|
|
7
6
|
@data_source = DataSource.new(opts)
|
8
7
|
|
9
|
-
@fields
|
10
|
-
@texts
|
11
|
-
@tables
|
8
|
+
@fields = []
|
9
|
+
@texts = []
|
10
|
+
@tables = []
|
12
11
|
@sections = []
|
13
|
-
@images
|
14
|
-
|
12
|
+
@images = []
|
15
13
|
end
|
16
14
|
|
17
15
|
def set_source(data_item)
|
@@ -19,32 +17,36 @@ module ODFReport
|
|
19
17
|
self
|
20
18
|
end
|
21
19
|
|
22
|
-
def add_field(name, data_field=nil, &block)
|
23
|
-
opts = {
|
20
|
+
def add_field(name, data_field = nil, &block)
|
21
|
+
opts = {name: name, data_field: data_field}
|
24
22
|
@fields << Field.new(opts, &block)
|
25
23
|
end
|
26
24
|
alias_method :add_column, :add_field
|
27
25
|
|
28
|
-
def add_text(name, data_field=nil, &block)
|
26
|
+
def add_text(name, data_field = nil, &block)
|
29
27
|
opts = {name: name, data_field: data_field}
|
30
28
|
@texts << Text.new(opts, &block)
|
31
29
|
end
|
32
30
|
|
33
|
-
def add_image(name, data_field=nil, &block)
|
31
|
+
def add_image(name, data_field = nil, &block)
|
34
32
|
opts = {name: name, data_field: data_field}
|
35
33
|
@images << Image.new(opts, &block)
|
36
34
|
end
|
37
35
|
|
38
|
-
def add_table(table_name, collection_field, opts={})
|
39
|
-
opts
|
36
|
+
def add_table(table_name, collection_field, opts = {})
|
37
|
+
opts[:name] = table_name
|
38
|
+
opts[:collection_field] = collection_field
|
39
|
+
|
40
40
|
tab = Table.new(opts)
|
41
41
|
@tables << tab
|
42
42
|
|
43
43
|
yield(tab)
|
44
44
|
end
|
45
45
|
|
46
|
-
def add_section(section_name, collection_field, opts={})
|
47
|
-
opts
|
46
|
+
def add_section(section_name, collection_field, opts = {})
|
47
|
+
opts[:name] = section_name
|
48
|
+
opts[:collection_field] = collection_field
|
49
|
+
|
48
50
|
sec = Section.new(opts)
|
49
51
|
@sections << sec
|
50
52
|
|
@@ -60,6 +62,5 @@ module ODFReport
|
|
60
62
|
<root xmlns:draw="a" xmlns:xlink="b" xmlns:text="c" xmlns:table="d">#{node.to_xml}</root>
|
61
63
|
XML
|
62
64
|
end
|
63
|
-
|
64
65
|
end
|
65
66
|
end
|
@@ -1,91 +1,82 @@
|
|
1
1
|
module ODFReport
|
2
|
+
module Parser
|
3
|
+
# Default HTML parser
|
4
|
+
#
|
5
|
+
# sample HTML
|
6
|
+
#
|
7
|
+
# <p> first paragraph </p>
|
8
|
+
# <p> second <strong>paragraph</strong> </p>
|
9
|
+
# <blockquote>
|
10
|
+
# <p> first <em>quote paragraph</em> </p>
|
11
|
+
# <p> first quote paragraph </p>
|
12
|
+
# <p> first quote paragraph </p>
|
13
|
+
# </blockquote>
|
14
|
+
# <p> third <strong>paragraph</strong> </p>
|
15
|
+
#
|
16
|
+
# <p style="margin: 100px"> fourth <em>paragraph</em> </p>
|
17
|
+
# <p style="margin: 120px"> fifth paragraph </p>
|
18
|
+
# <p> sixth <strong>paragraph</strong> </p>
|
19
|
+
#
|
20
|
+
|
21
|
+
class Default
|
22
|
+
attr_accessor :paragraphs
|
23
|
+
|
24
|
+
def initialize(text, template_node)
|
25
|
+
@text = text
|
26
|
+
@paragraphs = []
|
27
|
+
@template_node = template_node
|
28
|
+
|
29
|
+
parse
|
30
|
+
end
|
2
31
|
|
3
|
-
|
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|
|
32
|
+
def parse
|
33
|
+
html = Nokogiri::HTML5.fragment(@text)
|
41
34
|
|
42
|
-
|
43
|
-
|
35
|
+
html.css("p", "h1", "h2").each do |p|
|
36
|
+
style = check_style(p)
|
37
|
+
text = parse_formatting(p.inner_html)
|
44
38
|
|
45
|
-
|
39
|
+
add_paragraph(text, style)
|
40
|
+
end
|
46
41
|
end
|
47
|
-
end
|
48
42
|
|
49
|
-
|
43
|
+
def add_paragraph(text, style)
|
44
|
+
node = @template_node.dup
|
50
45
|
|
51
|
-
|
46
|
+
node["text:style-name"] = style if style
|
47
|
+
node.children = text
|
52
48
|
|
53
|
-
|
54
|
-
|
49
|
+
@paragraphs << node
|
50
|
+
end
|
55
51
|
|
56
|
-
|
57
|
-
end
|
52
|
+
private
|
58
53
|
|
59
|
-
|
54
|
+
def parse_formatting(text)
|
55
|
+
text.strip!
|
56
|
+
text.gsub!(/<strong.*?>(.+?)<\/strong>/) { "<text:span text:style-name=\"bold\">#{$1}</text:span>" }
|
57
|
+
text.gsub!(/<em.*?>(.+?)<\/em>/) { "<text:span text:style-name=\"italic\">#{$1}</text:span>" }
|
58
|
+
text.gsub!(/<u.*?>(.+?)<\/u>/) { "<text:span text:style-name=\"underline\">#{$1}</text:span>" }
|
59
|
+
text.gsub!(/<br\/?>/, "<text:line-break/>")
|
60
|
+
text.delete!("\n")
|
61
|
+
text
|
62
|
+
end
|
60
63
|
|
61
|
-
|
62
|
-
|
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
|
64
|
+
def check_style(node)
|
65
|
+
style = nil
|
69
66
|
|
70
|
-
|
71
|
-
|
67
|
+
if /h\d/i.match?(node.name)
|
68
|
+
style = "title"
|
72
69
|
|
73
|
-
|
74
|
-
|
70
|
+
elsif node.parent && node.parent.name == "blockquote"
|
71
|
+
style = "quote"
|
75
72
|
|
76
|
-
|
77
|
-
|
73
|
+
elsif /margin/.match?(node["style"])
|
74
|
+
style = "quote"
|
78
75
|
|
79
|
-
|
80
|
-
style = "quote"
|
76
|
+
end
|
81
77
|
|
78
|
+
style
|
82
79
|
end
|
83
|
-
|
84
|
-
style
|
85
80
|
end
|
86
|
-
|
87
81
|
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
82
|
end
|
data/lib/odf-report/report.rb
CHANGED
@@ -1,92 +1,84 @@
|
|
1
1
|
module ODFReport
|
2
|
+
class Report
|
3
|
+
def initialize(template_name = nil, io: nil)
|
4
|
+
@template = ODFReport::Template.new(template_name, io: io)
|
2
5
|
|
3
|
-
|
6
|
+
@texts = []
|
7
|
+
@fields = []
|
8
|
+
@tables = []
|
9
|
+
@sections = []
|
4
10
|
|
5
|
-
|
11
|
+
@images = []
|
6
12
|
|
7
|
-
|
8
|
-
|
9
|
-
@texts = []
|
10
|
-
@fields = []
|
11
|
-
@tables = []
|
12
|
-
@sections = []
|
13
|
-
|
14
|
-
@images = []
|
15
|
-
|
16
|
-
yield(self) if block_given?
|
17
|
-
|
18
|
-
end
|
13
|
+
yield(self) if block_given?
|
14
|
+
end
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
def add_field(field_tag, value = "")
|
17
|
+
opts = {name: field_tag, value: value}
|
18
|
+
field = Field.new(opts)
|
19
|
+
@fields << field
|
20
|
+
end
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
def add_text(field_tag, value = "")
|
23
|
+
opts = {name: field_tag, value: value}
|
24
|
+
text = Text.new(opts)
|
25
|
+
@texts << text
|
26
|
+
end
|
31
27
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@tables << tab
|
28
|
+
def add_table(table_name, collection, opts = {})
|
29
|
+
opts[:name] = table_name
|
30
|
+
opts[:collection] = collection
|
36
31
|
|
37
|
-
|
38
|
-
|
32
|
+
tab = Table.new(opts)
|
33
|
+
@tables << tab
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
sec = Section.new(opts)
|
43
|
-
@sections << sec
|
35
|
+
yield(tab)
|
36
|
+
end
|
44
37
|
|
45
|
-
|
46
|
-
|
38
|
+
def add_section(section_name, collection, opts = {})
|
39
|
+
opts[:name] = section_name
|
40
|
+
opts[:collection] = collection
|
47
41
|
|
48
|
-
|
49
|
-
|
50
|
-
image = Image.new(opts)
|
51
|
-
@images << image
|
52
|
-
end
|
42
|
+
sec = Section.new(opts)
|
43
|
+
@sections << sec
|
53
44
|
|
54
|
-
|
45
|
+
yield(sec)
|
46
|
+
end
|
55
47
|
|
56
|
-
|
48
|
+
def add_image(image_name, value = nil)
|
49
|
+
opts = {name: image_name, value: value}
|
50
|
+
image = Image.new(opts)
|
51
|
+
@images << image
|
52
|
+
end
|
57
53
|
|
58
|
-
|
54
|
+
def generate(dest = nil)
|
55
|
+
@template.update_content do |file|
|
56
|
+
file.update_files do |doc|
|
57
|
+
@sections.each { |c| c.replace!(doc) }
|
58
|
+
@tables.each { |c| c.replace!(doc) }
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
@texts.each { |c| c.replace!(doc) }
|
61
|
+
@fields.each { |c| c.replace!(doc) }
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
@images.each { |c| c.replace!(doc) }
|
64
|
+
end
|
65
65
|
|
66
|
-
|
66
|
+
all_images.each { |i| Image.include_image_file(file, i) }
|
67
67
|
|
68
|
+
file.update_manifest do |content|
|
69
|
+
all_images.each { |i| Image.include_manifest_entry(content, i) }
|
70
|
+
end
|
68
71
|
end
|
69
72
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
73
|
+
if dest
|
74
|
+
File.binwrite(dest, @template.data)
|
75
|
+
else
|
76
|
+
@template.data
|
74
77
|
end
|
75
|
-
|
76
78
|
end
|
77
79
|
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
@template.data
|
80
|
+
def all_images
|
81
|
+
@all_images ||= (@images.map(&:files) + @sections.map(&:all_images) + @tables.map(&:all_images)).flatten.uniq
|
82
82
|
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
def all_images
|
87
|
-
@all_images ||= (@images.map(&:files) + @sections.map(&:all_images) + @tables.map(&:all_images)).flatten.uniq
|
88
83
|
end
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
84
|
end
|
data/lib/odf-report/section.rb
CHANGED
@@ -1,29 +1,24 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Section < Nestable
|
3
|
-
|
4
3
|
def replace!(doc)
|
5
|
-
|
6
4
|
return unless find_section_node(doc)
|
7
5
|
|
8
6
|
@data_source.each do |record|
|
9
|
-
|
10
7
|
new_section = deep_clone(@section_node)
|
11
8
|
|
12
|
-
@tables.each
|
13
|
-
@sections.each
|
14
|
-
@texts.each
|
15
|
-
@fields.each
|
16
|
-
@images.each
|
9
|
+
@tables.each { |t| t.set_source(record).replace!(new_section) }
|
10
|
+
@sections.each { |s| s.set_source(record).replace!(new_section) }
|
11
|
+
@texts.each { |t| t.set_source(record).replace!(new_section) }
|
12
|
+
@fields.each { |f| f.set_source(record).replace!(new_section) }
|
13
|
+
@images.each { |i| i.set_source(record).replace!(new_section) }
|
17
14
|
|
18
15
|
@section_node.before(new_section.to_xml)
|
19
|
-
|
20
16
|
end
|
21
17
|
|
22
18
|
@section_node.remove
|
23
|
-
|
24
19
|
end # replace_section
|
25
20
|
|
26
|
-
|
21
|
+
private
|
27
22
|
|
28
23
|
def find_section_node(doc)
|
29
24
|
@section_node = doc.at_xpath("//text:section[@text:name='#{@name}']")
|
@@ -31,9 +26,7 @@ module ODFReport
|
|
31
26
|
|
32
27
|
def deep_clone(node)
|
33
28
|
Nokogiri::XML(wrap_with_ns(node)).at_xpath("//text:section")
|
34
|
-
|
35
|
-
|
29
|
+
.tap { |n| n.attribute("name").content = SecureRandom.uuid }
|
36
30
|
end
|
37
|
-
|
38
31
|
end
|
39
32
|
end
|
data/lib/odf-report/table.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Table < Nestable
|
3
|
-
|
4
3
|
def initialize(opts)
|
5
4
|
super(opts)
|
6
5
|
|
7
6
|
@template_rows = []
|
8
|
-
@header
|
9
|
-
@skip_if_empty
|
7
|
+
@header = opts[:header] || false
|
8
|
+
@skip_if_empty = opts[:skip_if_empty] || false
|
10
9
|
end
|
11
10
|
|
12
11
|
def replace!(doc)
|
13
|
-
return unless table = find_table_node(doc)
|
12
|
+
return unless (table = find_table_node(doc))
|
14
13
|
|
15
14
|
@template_rows = table.xpath("table:table-row")
|
16
15
|
|
@@ -22,26 +21,23 @@ module ODFReport
|
|
22
21
|
end
|
23
22
|
|
24
23
|
@data_source.each do |record|
|
25
|
-
|
26
24
|
new_node = get_next_row
|
27
25
|
|
28
|
-
@tables.each
|
29
|
-
@sections.each
|
30
|
-
@texts.each
|
31
|
-
@fields.each
|
32
|
-
@images.each
|
26
|
+
@tables.each { |n| n.set_source(record).replace!(new_node) }
|
27
|
+
@sections.each { |n| n.set_source(record).replace!(new_node) }
|
28
|
+
@texts.each { |n| n.set_source(record).replace!(new_node) }
|
29
|
+
@fields.each { |n| n.set_source(record).replace!(new_node) }
|
30
|
+
@images.each { |n| n.set_source(record).replace!(new_node) }
|
33
31
|
|
34
32
|
table.add_child(new_node.to_xml)
|
35
|
-
|
36
33
|
end
|
37
34
|
|
38
35
|
@template_rows.each_with_index do |r, i|
|
39
36
|
r.remove if (get_start_node..template_length) === i
|
40
37
|
end
|
41
|
-
|
42
38
|
end # replace
|
43
39
|
|
44
|
-
|
40
|
+
private
|
45
41
|
|
46
42
|
def get_next_row
|
47
43
|
if @template_rows.size == 1
|
@@ -59,7 +55,7 @@ module ODFReport
|
|
59
55
|
end
|
60
56
|
end
|
61
57
|
|
62
|
-
|
58
|
+
deep_clone(ret)
|
63
59
|
end
|
64
60
|
|
65
61
|
def get_start_node
|
@@ -77,6 +73,5 @@ module ODFReport
|
|
77
73
|
def deep_clone(node)
|
78
74
|
Nokogiri::XML(wrap_with_ns(node)).at_xpath("//table:table-row")
|
79
75
|
end
|
80
|
-
|
81
76
|
end
|
82
77
|
end
|
data/lib/odf-report/template.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module ODFReport
|
2
2
|
class Template
|
3
|
-
|
4
|
-
CONTENT_FILES = ['content.xml', 'styles.xml']
|
3
|
+
CONTENT_FILES = ["content.xml", "styles.xml"]
|
5
4
|
MANIFEST_FILE = "META-INF/manifest.xml"
|
6
5
|
|
7
6
|
attr_accessor :output_stream
|
@@ -22,13 +21,10 @@ module ODFReport
|
|
22
21
|
end
|
23
22
|
|
24
23
|
def update_files(&block)
|
25
|
-
|
26
24
|
get_template_entries.each do |entry|
|
27
|
-
|
28
25
|
next if entry.directory?
|
29
26
|
|
30
27
|
entry.get_input_stream do |is|
|
31
|
-
|
32
28
|
data = is.sysread
|
33
29
|
|
34
30
|
if CONTENT_FILES.include?(entry.name)
|
@@ -36,25 +32,20 @@ module ODFReport
|
|
36
32
|
end
|
37
33
|
|
38
34
|
update_file(entry.name, data) unless entry.name == MANIFEST_FILE
|
39
|
-
|
40
35
|
end
|
41
36
|
end
|
42
|
-
|
43
37
|
end
|
44
38
|
|
45
39
|
def update_manifest(&block)
|
46
40
|
entry = get_template_entries.find_entry(MANIFEST_FILE)
|
47
41
|
|
48
42
|
entry.get_input_stream do |is|
|
49
|
-
|
50
43
|
data = is.sysread
|
51
44
|
|
52
45
|
process_entry(data, &block)
|
53
46
|
|
54
47
|
update_file(MANIFEST_FILE, data)
|
55
|
-
|
56
48
|
end
|
57
|
-
|
58
49
|
end
|
59
50
|
|
60
51
|
def data
|
@@ -69,13 +60,11 @@ module ODFReport
|
|
69
60
|
private
|
70
61
|
|
71
62
|
def get_template_entries
|
72
|
-
|
73
63
|
if @template
|
74
64
|
Zip::File.open(@template)
|
75
65
|
else
|
76
66
|
Zip::File.open_buffer(@io.force_encoding("ASCII-8BIT"))
|
77
67
|
end
|
78
|
-
|
79
68
|
end
|
80
69
|
|
81
70
|
def process_entry(entry)
|
@@ -83,6 +72,5 @@ module ODFReport
|
|
83
72
|
yield doc
|
84
73
|
entry.replace(doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML))
|
85
74
|
end
|
86
|
-
|
87
75
|
end
|
88
76
|
end
|
data/lib/odf-report/text.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
module ODFReport
|
2
|
-
|
3
2
|
class Text < Field
|
4
|
-
|
5
3
|
attr_accessor :parser
|
6
4
|
|
7
5
|
def replace!(doc, data_item = nil)
|
8
|
-
|
9
|
-
return unless node = find_text_node(doc)
|
6
|
+
return unless (node = find_text_node(doc))
|
10
7
|
|
11
8
|
@parser = Parser::Default.new(@data_source.value, node)
|
12
9
|
|
@@ -15,7 +12,6 @@ module ODFReport
|
|
15
12
|
end
|
16
13
|
|
17
14
|
node.remove
|
18
|
-
|
19
15
|
end
|
20
16
|
|
21
17
|
private
|
@@ -24,10 +20,10 @@ module ODFReport
|
|
24
20
|
texts = doc.xpath(".//text:p[text()='#{to_placeholder}']")
|
25
21
|
if texts.empty?
|
26
22
|
texts = doc.xpath(".//text:p/text:span[text()='#{to_placeholder}']")
|
27
|
-
if texts.empty?
|
28
|
-
|
23
|
+
texts = if texts.empty?
|
24
|
+
nil
|
29
25
|
else
|
30
|
-
texts
|
26
|
+
texts.first.parent
|
31
27
|
end
|
32
28
|
else
|
33
29
|
texts = texts.first
|
@@ -35,7 +31,5 @@ module ODFReport
|
|
35
31
|
|
36
32
|
texts
|
37
33
|
end
|
38
|
-
|
39
34
|
end
|
40
|
-
|
41
35
|
end
|
data/lib/odf-report/version.rb
CHANGED
data/lib/odf-report.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "zip"
|
3
|
+
require "fileutils"
|
4
|
+
require "nokogiri"
|
5
|
+
require "mime/types"
|
6
|
+
require "securerandom"
|
7
7
|
|
8
|
-
require File.expand_path(
|
8
|
+
require File.expand_path("../odf-report/parser/default", __FILE__)
|
9
9
|
|
10
|
-
require File.expand_path(
|
11
|
-
require File.expand_path(
|
12
|
-
require File.expand_path(
|
13
|
-
require File.expand_path(
|
14
|
-
require File.expand_path(
|
15
|
-
require File.expand_path(
|
16
|
-
require File.expand_path(
|
17
|
-
require File.expand_path(
|
18
|
-
require File.expand_path(
|
10
|
+
require File.expand_path("../odf-report/data_source", __FILE__)
|
11
|
+
require File.expand_path("../odf-report/field", __FILE__)
|
12
|
+
require File.expand_path("../odf-report/text", __FILE__)
|
13
|
+
require File.expand_path("../odf-report/template", __FILE__)
|
14
|
+
require File.expand_path("../odf-report/image", __FILE__)
|
15
|
+
require File.expand_path("../odf-report/nestable", __FILE__)
|
16
|
+
require File.expand_path("../odf-report/section", __FILE__)
|
17
|
+
require File.expand_path("../odf-report/table", __FILE__)
|
18
|
+
require File.expand_path("../odf-report/report", __FILE__)
|
data/odf-report.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.add_development_dependency "launchy"
|
26
26
|
|
27
27
|
s.add_runtime_dependency('rubyzip', ">= 1.3.0")
|
28
|
-
s.add_runtime_dependency('nokogiri', ">= 1.
|
28
|
+
s.add_runtime_dependency('nokogiri', ">= 1.12.0")
|
29
29
|
s.add_runtime_dependency('mime-types')
|
30
30
|
|
31
31
|
end
|
data/spec/templates/specs.odt
CHANGED
Binary file
|
data/spec/texts_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.describe "Texts" do
|
2
|
+
before(:context) do
|
3
|
+
|
4
|
+
@text1 = <<-HTML
|
5
|
+
<p>This is some text in a paragraph</p>
|
6
|
+
HTML
|
7
|
+
|
8
|
+
@text2 = <<-HTML
|
9
|
+
<p>Text before line break <br> text after line break</p>
|
10
|
+
HTML
|
11
|
+
|
12
|
+
@text3 = <<-HTML
|
13
|
+
<p>Text before entities
|
14
|
+
Maurício
|
15
|
+
Text after entities</p>
|
16
|
+
HTML
|
17
|
+
|
18
|
+
|
19
|
+
report = ODFReport::Report.new("spec/templates/specs.odt") do |r|
|
20
|
+
|
21
|
+
r.add_text(:text1, @text1)
|
22
|
+
r.add_text(:text2, @text2)
|
23
|
+
r.add_text(:text3, @text3)
|
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
|
+
it "simple text replacement" do
|
34
|
+
expect(@data.text).to include "This is some text in a paragraph"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "text replacement with <br>" do
|
38
|
+
expect(@data.text).to include "Text before line break"
|
39
|
+
expect(@data.text).to include "text after line break"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "text replacement with html entities" do
|
43
|
+
expect(@data.text).to include "Text before entities"
|
44
|
+
expect(@data.text).to include "Maurício"
|
45
|
+
expect(@data.text).to include "Text after entities"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: odf-report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Duarte
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.
|
103
|
+
version: 1.12.0
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 1.
|
110
|
+
version: 1.12.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: mime-types
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- spec/template_spec.rb
|
170
170
|
- spec/templates/images.odt
|
171
171
|
- spec/templates/specs.odt
|
172
|
+
- spec/texts_spec.rb
|
172
173
|
- test/fields_inside_text_test.rb
|
173
174
|
- test/images_test.rb
|
174
175
|
- test/nested_tables_test.rb
|
@@ -211,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
212
|
- !ruby/object:Gem::Version
|
212
213
|
version: '0'
|
213
214
|
requirements: []
|
214
|
-
rubygems_version: 3.
|
215
|
+
rubygems_version: 3.4.19
|
215
216
|
signing_key:
|
216
217
|
specification_version: 4
|
217
218
|
summary: Generates ODF files, given a template (.odt) and data, replacing tags
|
@@ -230,6 +231,7 @@ test_files:
|
|
230
231
|
- spec/template_spec.rb
|
231
232
|
- spec/templates/images.odt
|
232
233
|
- spec/templates/specs.odt
|
234
|
+
- spec/texts_spec.rb
|
233
235
|
- test/fields_inside_text_test.rb
|
234
236
|
- test/images_test.rb
|
235
237
|
- test/nested_tables_test.rb
|