serenity-odt 0.2.3 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17fd0d1ef98dd78013054dd47ddb91141bd90ee83d1c023160421872f3e2be6b
4
- data.tar.gz: 2a66ff91bf4b31bea7f20be8cbc804c26562bafacde52e099ce3d3a99e0213c9
3
+ metadata.gz: a7be056fd011f7a963e1b60902e82dd9e119c88915c5bf9304c1a2095f8a9f35
4
+ data.tar.gz: 6ac2a7386e6235325520d8e4f4a0f2eac72a4316b4bd9bb7de9bb263b4d79105
5
5
  SHA512:
6
- metadata.gz: 42f5842bd6dd034d4a65bbc7133eec279c654c2509b9af2fe4fffae053cc16ad8bd24d8027ee5efb71aecb07e770182cd76b29fe1760e70759080cdd288ef60c
7
- data.tar.gz: a477a692d9afbb4ffea38028ca5c22599a1a3a2c1348bfc68df130203e471c8536e1aedbce21301397244615c65c70b3b714c998e51c24c7e70082197bb338c4
6
+ metadata.gz: b11f27eebe9a903ecb5524bccb07892c27380da92576cb319e4ec9137902359eec6430b0f0e74e501be5f8dd2e675bc4af45fa1a24133b9311388dcb07b0e105
7
+ data.tar.gz: f2a2d498e108ed501999492902de684a38c877cc778441db6835b882c61295a2ef5ff473a3152f04f1940ea18b553941e49fe6b0b42cfe65ed92535a78ea8d7e
@@ -1,18 +1,16 @@
1
1
  class String
2
2
  def escape_xml
3
- mgsub!([[/&/, '&amp;'], [/</, '&lt;'], [/>/, '&gt;']])
3
+ mgsub([[/&/, '&amp;'], [/</, '&lt;'], [/>/, '&gt;']])
4
4
  end
5
5
 
6
6
  def convert_newlines
7
- gsub!("\n", '<text:line-break/>')
8
- self
7
+ gsub("\n", '<text:line-break/>')
9
8
  end
10
9
 
11
- def mgsub!(key_value_pairs=[].freeze)
10
+ def mgsub(key_value_pairs=[].freeze)
12
11
  regexp_fragments = key_value_pairs.collect { |k,v| k }
13
- gsub!(Regexp.union(*regexp_fragments)) do |match|
12
+ gsub(Regexp.union(*regexp_fragments)) do |match|
14
13
  key_value_pairs.detect{|k,v| k =~ match}[1]
15
14
  end
16
- self
17
15
  end
18
16
  end
data/lib/serenity/line.rb CHANGED
@@ -44,7 +44,7 @@ module Serenity
44
44
  end
45
45
 
46
46
  def escape_code code
47
- code.mgsub! [[/&apos;/, "'"], [/&gt;/, '>'], [/&lt/, '<'], [/&quot;/, '"'], [/&amp;/, '&']]
47
+ code.mgsub [[/&apos;/, "'"], [/&gt;/, '>'], [/&lt/, '<'], [/&quot;/, '"'], [/&amp;/, '&']]
48
48
  end
49
49
  end
50
50
 
@@ -21,6 +21,56 @@ module Serenity
21
21
  def process context
22
22
  tmpfiles = []
23
23
  ZipFile.open(@template) do |zipfile|
24
+ # Pre-read embedded object templates for inline processing
25
+ object_entries = zipfile.entries
26
+ .map(&:name)
27
+ .select { |name| name.match?(%r{\AObject \d+/(content|styles)\.xml\z}) }
28
+
29
+ object_templates = {}
30
+ object_entries.each { |name| object_templates[name] = zipfile.read(name) }
31
+
32
+ objects_by_dir = object_templates.keys.group_by { |name| name.split('/').first }
33
+
34
+ # Find highest existing object number for generating unique names
35
+ max_obj_num = zipfile.entries.map(&:name)
36
+ .grep(%r{\AObject (\d+)/}) { $1.to_i }.max || 0
37
+
38
+ # Pre-read all object-related files for copying to duplicates
39
+ object_all_files = {}
40
+ zipfile.entries.each do |entry|
41
+ name = entry.name
42
+ if name.match?(%r{\AObject \d+/}) || name.match?(%r{\AObjectReplacements/Object \d+\z})
43
+ object_all_files[name] = zipfile.read(name)
44
+ end
45
+ end
46
+
47
+ # Counters and results shared by processor lambdas
48
+ obj_counter = Hash.new(0)
49
+ obj_results = {}
50
+
51
+ # Build a processor lambda per object directory.
52
+ # Each call evaluates the embedded template with the current binding
53
+ # (capturing loop variables like `person`), saves/restores _buf to avoid
54
+ # clobbering the outer template buffer, and stores the result keyed by
55
+ # iteration number.
56
+ processors = {}
57
+ objects_by_dir.each do |obj_dir, obj_files|
58
+ processors[obj_dir] = lambda do |ctx|
59
+ buf_save = ctx.local_variable_get(:_buf)
60
+
61
+ obj_counter[obj_dir] += 1
62
+ n = obj_counter[obj_dir]
63
+
64
+ obj_files.each do |name|
65
+ file_part = name.split('/').last
66
+ obj_results["#{obj_dir}__#{n}/#{file_part}"] =
67
+ OdtEruby.new(XmlReader.new(object_templates[name])).evaluate(ctx)
68
+ end
69
+
70
+ ctx.local_variable_set(:_buf, buf_save)
71
+ end
72
+ end
73
+
24
74
  %w(content.xml styles.xml).each do |xml_file|
25
75
  content = zipfile.read(xml_file)
26
76
 
@@ -30,16 +80,118 @@ module Serenity
30
80
  zipfile.replace(r.first, r.last)
31
81
  end
32
82
 
83
+ # Inject inline processing for embedded objects at their draw:object
84
+ # reference points so loop variables are in scope during evaluation
85
+ if xml_file == 'content.xml' && !processors.empty?
86
+ context.local_variable_set(:_serenity_processors, processors)
87
+
88
+ objects_by_dir.each do |obj_dir, _|
89
+ ref_pattern = /(<draw:object[^>]*?xlink:href="\.\/#{Regexp.escape(obj_dir)}"[^>]*?\/>.*?<\/draw:frame>)/m
90
+ content = content.sub(ref_pattern, "\\1{% _serenity_processors['#{obj_dir}'].call(binding) %}")
91
+ end
92
+ end
93
+
33
94
  odteruby = OdtEruby.new(XmlReader.new(content))
34
95
  out = odteruby.evaluate(context)
35
96
  out.force_encoding Encoding.default_external
36
97
 
98
+ # Post-process content.xml: give each loop iteration its own object
99
+ if xml_file == 'content.xml'
100
+ objects_by_dir.each do |obj_dir, obj_files|
101
+ count = obj_counter[obj_dir]
102
+
103
+ # Rename each draw:frame's object references to a unique object dir
104
+ iteration = 0
105
+ out = out.gsub(/<draw:frame[^>]*>.*?<\/draw:frame>/m) do |frame|
106
+ if frame.include?("./#{obj_dir}")
107
+ iteration += 1
108
+ new_obj_dir = iteration == 1 ? obj_dir : "Object #{max_obj_num + iteration - 1}"
109
+ frame.gsub(obj_dir, new_obj_dir)
110
+ else
111
+ frame
112
+ end
113
+ end
114
+
115
+ # Write evaluated templates and supporting files for each iteration
116
+ (1..count).each do |n|
117
+ final_obj = n == 1 ? obj_dir : "Object #{max_obj_num + n - 1}"
118
+
119
+ # Write evaluated content.xml / styles.xml
120
+ obj_files.each do |name|
121
+ file_part = name.split('/').last
122
+ result = obj_results["#{obj_dir}__#{n}/#{file_part}"]
123
+ next unless result
124
+
125
+ result.force_encoding Encoding.default_external
126
+ tmpfiles << (file = Tempfile.new("serenity"))
127
+ file << result
128
+ file.close
129
+
130
+ final_name = "#{final_obj}/#{file_part}"
131
+ if zipfile.find_entry(final_name)
132
+ zipfile.replace(final_name, file.path)
133
+ else
134
+ zipfile.add(final_name, file.path)
135
+ end
136
+ end
137
+
138
+ # Copy non-template files (meta.xml, ObjectReplacements) for new objects
139
+ next if n == 1
140
+ object_all_files.each do |name, data|
141
+ next if object_templates.key?(name)
142
+
143
+ new_name = nil
144
+ if name.start_with?("#{obj_dir}/")
145
+ new_name = name.sub(obj_dir, final_obj)
146
+ elsif name == "ObjectReplacements/#{obj_dir}"
147
+ new_name = "ObjectReplacements/#{final_obj}"
148
+ end
149
+
150
+ if new_name
151
+ tmpfiles << (file = Tempfile.new("serenity"))
152
+ file.binmode
153
+ file << data
154
+ file.close
155
+ zipfile.add(new_name, file.path)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
37
162
  tmpfiles << (file = Tempfile.new("serenity"))
38
163
  file << out
39
164
  file.close
40
-
41
165
  zipfile.replace(xml_file, file.path)
42
166
  end
167
+
168
+ # Update manifest.xml with entries for new object directories
169
+ if obj_counter.values.any? { |c| c > 1 }
170
+ manifest = zipfile.read('META-INF/manifest.xml')
171
+ new_entries = ""
172
+
173
+ objects_by_dir.each do |obj_dir, _|
174
+ count = obj_counter[obj_dir]
175
+ (2..count).each do |n|
176
+ new_obj = "Object #{max_obj_num + n - 1}"
177
+ new_entries << %( <manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.chart" manifest:full-path="#{new_obj}/"/>\n)
178
+ new_entries << %( <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="#{new_obj}/content.xml"/>\n)
179
+ new_entries << %( <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="#{new_obj}/styles.xml"/>\n)
180
+ if object_all_files["#{obj_dir}/meta.xml"]
181
+ new_entries << %( <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="#{new_obj}/meta.xml"/>\n)
182
+ end
183
+ if object_all_files["ObjectReplacements/#{obj_dir}"]
184
+ new_entries << %( <manifest:file-entry manifest:media-type="application/x-openoffice-gdimetafile;windows_formatname=&quot;GDIMetaFile&quot;" manifest:full-path="ObjectReplacements/#{new_obj}"/>\n)
185
+ end
186
+ end
187
+ end
188
+
189
+ manifest = manifest.sub("</manifest:manifest>", "#{new_entries}</manifest:manifest>")
190
+ tmpfiles << (file = Tempfile.new("serenity"))
191
+ file << manifest
192
+ file.close
193
+ zipfile.replace('META-INF/manifest.xml', file.path)
194
+ end
43
195
  end
44
196
  end
45
197
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serenity-odt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kremso