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 +4 -4
- data/lib/serenity/escape_xml.rb +4 -6
- data/lib/serenity/line.rb +1 -1
- data/lib/serenity/template.rb +153 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7be056fd011f7a963e1b60902e82dd9e119c88915c5bf9304c1a2095f8a9f35
|
|
4
|
+
data.tar.gz: 6ac2a7386e6235325520d8e4f4a0f2eac72a4316b4bd9bb7de9bb263b4d79105
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b11f27eebe9a903ecb5524bccb07892c27380da92576cb319e4ec9137902359eec6430b0f0e74e501be5f8dd2e675bc4af45fa1a24133b9311388dcb07b0e105
|
|
7
|
+
data.tar.gz: f2a2d498e108ed501999492902de684a38c877cc778441db6835b882c61295a2ef5ff473a3152f04f1940ea18b553941e49fe6b0b42cfe65ed92535a78ea8d7e
|
data/lib/serenity/escape_xml.rb
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
class String
|
|
2
2
|
def escape_xml
|
|
3
|
-
mgsub
|
|
3
|
+
mgsub([[/&/, '&'], [/</, '<'], [/>/, '>']])
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
def convert_newlines
|
|
7
|
-
gsub
|
|
8
|
-
self
|
|
7
|
+
gsub("\n", '<text:line-break/>')
|
|
9
8
|
end
|
|
10
9
|
|
|
11
|
-
def mgsub
|
|
10
|
+
def mgsub(key_value_pairs=[].freeze)
|
|
12
11
|
regexp_fragments = key_value_pairs.collect { |k,v| k }
|
|
13
|
-
gsub
|
|
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
data/lib/serenity/template.rb
CHANGED
|
@@ -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="GDIMetaFile"" 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
|