ruby_pptx 0.1.0 → 0.2.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/CHANGELOG.md +33 -0
- data/README.md +83 -2
- data/lib/ruby_pptx/action.rb +12 -4
- data/lib/ruby_pptx/autoshape_spec.rb +6 -2
- data/lib/ruby_pptx/builder.rb +3 -1
- data/lib/ruby_pptx/chart/categories.rb +45 -15
- data/lib/ruby_pptx/chart/chart.rb +77 -27
- data/lib/ruby_pptx/chart/combo.rb +9 -3
- data/lib/ruby_pptx/chart/data.rb +45 -15
- data/lib/ruby_pptx/chart/format.rb +229 -76
- data/lib/ruby_pptx/chart/workbook_writer.rb +9 -3
- data/lib/ruby_pptx/chart/xml_writer.rb +54 -18
- data/lib/ruby_pptx/chart/xy_data.rb +68 -22
- data/lib/ruby_pptx/dml/color.rb +27 -9
- data/lib/ruby_pptx/dml/effect.rb +6 -2
- data/lib/ruby_pptx/dml/fill.rb +42 -14
- data/lib/ruby_pptx/element_proxy.rb +12 -4
- data/lib/ruby_pptx/enum/base.rb +30 -10
- data/lib/ruby_pptx/enum/prog_id.rb +3 -1
- data/lib/ruby_pptx/geometry.rb +32 -10
- data/lib/ruby_pptx/image.rb +27 -9
- data/lib/ruby_pptx/length.rb +94 -27
- data/lib/ruby_pptx/media.rb +12 -4
- data/lib/ruby_pptx/numeric_lengths.rb +34 -9
- data/lib/ruby_pptx/opc/oxml.rb +7 -5
- data/lib/ruby_pptx/opc/pack_uri.rb +15 -5
- data/lib/ruby_pptx/opc/package.rb +97 -31
- data/lib/ruby_pptx/opc/serialized.rb +15 -5
- data/lib/ruby_pptx/oxml/backend.rb +585 -0
- data/lib/ruby_pptx/oxml/chart.rb +112 -42
- data/lib/ruby_pptx/oxml/content_model.rb +6 -2
- data/lib/ruby_pptx/oxml/dml/color.rb +6 -2
- data/lib/ruby_pptx/oxml/dml/fill.rb +6 -2
- data/lib/ruby_pptx/oxml/element.rb +138 -101
- data/lib/ruby_pptx/oxml/ns.rb +7 -7
- data/lib/ruby_pptx/oxml/presentation.rb +9 -3
- data/lib/ruby_pptx/oxml/section.rb +3 -1
- data/lib/ruby_pptx/oxml/shapes/autoshape.rb +39 -13
- data/lib/ruby_pptx/oxml/shapes/groupshape.rb +15 -7
- data/lib/ruby_pptx/oxml/shapes/other.rb +47 -16
- data/lib/ruby_pptx/oxml/shapes/shared.rb +96 -30
- data/lib/ruby_pptx/oxml/simple_types.rb +169 -52
- data/lib/ruby_pptx/oxml/slide.rb +24 -8
- data/lib/ruby_pptx/oxml/table.rb +39 -13
- data/lib/ruby_pptx/oxml/text.rb +37 -15
- data/lib/ruby_pptx/oxml/theme.rb +13 -4
- data/lib/ruby_pptx/package.rb +9 -3
- data/lib/ruby_pptx/parts/chart.rb +6 -2
- data/lib/ruby_pptx/parts/image.rb +12 -4
- data/lib/ruby_pptx/parts/media.rb +3 -1
- data/lib/ruby_pptx/parts/presentation.rb +15 -5
- data/lib/ruby_pptx/parts/slide.rb +33 -11
- data/lib/ruby_pptx/parts/theme.rb +3 -1
- data/lib/ruby_pptx/pattern_matching.rb +6 -2
- data/lib/ruby_pptx/presentation.rb +24 -8
- data/lib/ruby_pptx/section.rb +30 -10
- data/lib/ruby_pptx/shapes/adjustments.rb +9 -3
- data/lib/ruby_pptx/shapes/authoring.rb +3 -1
- data/lib/ruby_pptx/shapes/base.rb +74 -24
- data/lib/ruby_pptx/shapes/freeform.rb +36 -12
- data/lib/ruby_pptx/shapes/placeholder.rb +44 -15
- data/lib/ruby_pptx/shapes/shape.rb +91 -30
- data/lib/ruby_pptx/shapes/shape_tree.rb +66 -22
- data/lib/ruby_pptx/slide.rb +123 -41
- data/lib/ruby_pptx/table.rb +105 -35
- data/lib/ruby_pptx/text/font_metrics.rb +30 -9
- data/lib/ruby_pptx/text/text.rb +108 -36
- data/lib/ruby_pptx/theme.rb +30 -10
- data/lib/ruby_pptx/version.rb +1 -1
- data/lib/ruby_pptx.rb +8 -1
- metadata +22 -1
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "nokogiri"
|
|
4
3
|
require "ruby_pptx/errors"
|
|
4
|
+
require "ruby_pptx/oxml/backend"
|
|
5
5
|
require "ruby_pptx/oxml/ns"
|
|
6
6
|
require "ruby_pptx/oxml/simple_types"
|
|
7
7
|
|
|
8
8
|
module Pptx
|
|
9
9
|
module Oxml
|
|
10
|
+
# The XML library in use, chosen once at load; see {Backend}.
|
|
11
|
+
BACKEND = Backend.select
|
|
12
|
+
|
|
10
13
|
# Maps a Clark-notation tag name to the {Element} subclass that models it.
|
|
11
14
|
#
|
|
12
15
|
# python-pptx gets this for free from lxml, whose parser instantiates a
|
|
13
|
-
# custom class per tag. Nokogiri has
|
|
14
|
-
# register themselves here and {Element.wrap} does the
|
|
16
|
+
# custom class per tag. Neither Nokogiri nor REXML has such a hook, so
|
|
17
|
+
# element classes register themselves here and {Element.wrap} does the
|
|
18
|
+
# dispatch.
|
|
15
19
|
module Registry
|
|
16
20
|
@classes = {}
|
|
17
21
|
|
|
@@ -29,17 +33,23 @@ module Pptx
|
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
# @return [Class] the registered class, or {Element} for an unmodelled tag
|
|
32
|
-
def class_for(clark_name)
|
|
36
|
+
def class_for(clark_name)
|
|
37
|
+
@classes.fetch(clark_name, Element)
|
|
38
|
+
end
|
|
33
39
|
|
|
34
|
-
def registered
|
|
40
|
+
def registered
|
|
41
|
+
@classes.dup
|
|
42
|
+
end
|
|
35
43
|
|
|
36
44
|
# Test seam: forget registrations made by a spec.
|
|
37
|
-
def reset!(to)
|
|
45
|
+
def reset!(to)
|
|
46
|
+
(@classes = to)
|
|
47
|
+
end
|
|
38
48
|
end
|
|
39
49
|
end
|
|
40
50
|
|
|
41
|
-
# Wraps
|
|
42
|
-
# one OOXML element type.
|
|
51
|
+
# Wraps an XML node -- Nokogiri's or REXML's, see {Backend} -- and gives
|
|
52
|
+
# it the schema-aware behaviour of one OOXML element type.
|
|
43
53
|
#
|
|
44
54
|
# Subclasses declare their content model with class macros, which generate
|
|
45
55
|
# the accessors that the rest of the library uses:
|
|
@@ -71,7 +81,9 @@ module Pptx
|
|
|
71
81
|
prefixed_tags.each { |t| Registry.register(Ns.qn(t), self) }
|
|
72
82
|
end
|
|
73
83
|
|
|
74
|
-
def self.nsptag
|
|
84
|
+
def self.nsptag
|
|
85
|
+
@nsptags&.first
|
|
86
|
+
end
|
|
75
87
|
|
|
76
88
|
class << self
|
|
77
89
|
attr_reader :nsptags
|
|
@@ -79,33 +91,24 @@ module Pptx
|
|
|
79
91
|
|
|
80
92
|
# Wrap +node+ in the class registered for its tag.
|
|
81
93
|
#
|
|
82
|
-
# @param node [
|
|
94
|
+
# @param node [Object] a node of the active {Backend}
|
|
83
95
|
# @return [Element, nil] nil when +node+ is nil
|
|
84
96
|
def self.wrap(node)
|
|
85
97
|
return nil if node.nil?
|
|
86
98
|
return node if node.is_a?(Element)
|
|
87
99
|
|
|
88
|
-
cache =
|
|
100
|
+
cache = BACKEND.wrapper_cache(node)
|
|
89
101
|
cache[node] ||= Registry.class_for(Ns.clark_name_of(node)).new(node)
|
|
90
102
|
end
|
|
91
103
|
|
|
92
104
|
# Parse +xml+ into a standalone element tree.
|
|
93
105
|
def self.parse(xml)
|
|
94
|
-
|
|
95
|
-
raise InvalidXmlError, doc.errors.first.to_s if doc.errors.any?
|
|
96
|
-
|
|
97
|
-
wrap(doc.root)
|
|
106
|
+
wrap(BACKEND.parse(xml))
|
|
98
107
|
end
|
|
99
108
|
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
document.instance_variable_get(:@pptx_wrappers) ||
|
|
104
|
-
document.instance_variable_set(:@pptx_wrappers, {}.compare_by_identity)
|
|
105
|
-
end
|
|
106
|
-
private_class_method :cache_for
|
|
107
|
-
|
|
108
|
-
# @return [Nokogiri::XML::Node] the wrapped node
|
|
109
|
+
# @return [Object] the wrapped node, a Nokogiri or REXML element
|
|
110
|
+
# depending on the active {Backend}. An escape hatch: code that uses
|
|
111
|
+
# it ties itself to one backend.
|
|
109
112
|
attr_reader :node
|
|
110
113
|
|
|
111
114
|
def initialize(node)
|
|
@@ -115,24 +118,42 @@ module Pptx
|
|
|
115
118
|
# -- tree navigation -------------------------------------------------
|
|
116
119
|
|
|
117
120
|
# @return [String] this element's prefixed tag, e.g. "p:sp"
|
|
118
|
-
def nsptag
|
|
121
|
+
def nsptag
|
|
122
|
+
Ns.prefixed_tag(Ns.clark_name_of(@node))
|
|
123
|
+
end
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
# An opaque token for the document this element belongs to; equal
|
|
126
|
+
# (+equal?+) for two elements exactly when they share a document.
|
|
127
|
+
def document
|
|
128
|
+
BACKEND.document(@node)
|
|
129
|
+
end
|
|
121
130
|
|
|
122
|
-
def parent
|
|
131
|
+
def parent
|
|
132
|
+
Element.wrap(BACKEND.parent_element(@node))
|
|
133
|
+
end
|
|
123
134
|
|
|
124
135
|
# @return [Array<Element>] every child element, in document order
|
|
125
|
-
def element_children
|
|
136
|
+
def element_children
|
|
137
|
+
BACKEND.element_children(@node).map { |child| Element.wrap(child) }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [String] this element's tag in Clark notation, "{uri}local"
|
|
141
|
+
def clark_name
|
|
142
|
+
Ns.clark_name_of(@node)
|
|
143
|
+
end
|
|
126
144
|
|
|
127
145
|
# First child element with +nsptag+, or nil.
|
|
128
|
-
def find(nsptag)
|
|
146
|
+
def find(nsptag)
|
|
147
|
+
Element.wrap(raw_find(nsptag))
|
|
148
|
+
end
|
|
129
149
|
|
|
130
150
|
# Every child element with +nsptag+, in document order.
|
|
131
151
|
def find_all(nsptag)
|
|
132
152
|
pfx, local = Ns.split_tag(nsptag)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
uri = Ns.nsuri(pfx)
|
|
154
|
+
BACKEND.element_children(@node)
|
|
155
|
+
.select { |c| BACKEND.local_name(c) == local && BACKEND.namespace_uri(c) == uri }
|
|
156
|
+
.map { |c| Element.wrap(c) }
|
|
136
157
|
end
|
|
137
158
|
|
|
138
159
|
# The first child matching any of +nsptags+, in the order given -- not in
|
|
@@ -147,8 +168,8 @@ module Pptx
|
|
|
147
168
|
|
|
148
169
|
# Run an XPath expression with the full OOXML namespace map bound.
|
|
149
170
|
def xpath(expression)
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
BACKEND.xpath(@node, expression, Ns::NSMAP).map do |result|
|
|
172
|
+
BACKEND.element?(result) ? Element.wrap(result) : result
|
|
152
173
|
end
|
|
153
174
|
end
|
|
154
175
|
|
|
@@ -159,67 +180,82 @@ module Pptx
|
|
|
159
180
|
def insert_element_before(element, *successor_tags)
|
|
160
181
|
successor = first_child_found_in(*successor_tags)
|
|
161
182
|
if successor
|
|
162
|
-
successor.
|
|
183
|
+
successor.add_previous_sibling(element)
|
|
163
184
|
else
|
|
164
|
-
|
|
185
|
+
append(element)
|
|
165
186
|
end
|
|
166
187
|
element
|
|
167
188
|
end
|
|
168
189
|
|
|
169
190
|
def append(element)
|
|
170
|
-
@node
|
|
191
|
+
BACKEND.append_child(@node, adopt(element))
|
|
192
|
+
element
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Make +element+ this element's first child.
|
|
196
|
+
def prepend(element)
|
|
197
|
+
BACKEND.prepend_child(@node, adopt(element))
|
|
198
|
+
element
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Put +element+ immediately before this one.
|
|
202
|
+
def add_previous_sibling(element)
|
|
203
|
+
BACKEND.insert_before(@node, adopt(element))
|
|
204
|
+
element
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Put +element+ immediately after this one.
|
|
208
|
+
def add_next_sibling(element)
|
|
209
|
+
BACKEND.insert_after(@node, adopt(element))
|
|
171
210
|
element
|
|
172
211
|
end
|
|
173
212
|
|
|
174
213
|
# Remove every child element whose tag is in +nsptags+.
|
|
175
214
|
def remove_all(*nsptags)
|
|
176
|
-
nsptags.each { |nsptag| find_all(nsptag).each { |child| child.node
|
|
215
|
+
nsptags.each { |nsptag| find_all(nsptag).each { |child| BACKEND.unlink(child.node) } }
|
|
177
216
|
nil
|
|
178
217
|
end
|
|
179
218
|
|
|
180
219
|
def remove(element)
|
|
181
|
-
element.node
|
|
220
|
+
BACKEND.unlink(element.node)
|
|
182
221
|
nil
|
|
183
222
|
end
|
|
184
223
|
|
|
185
224
|
# Create a new, empty element with +nsptag+, in *this* element's
|
|
186
|
-
# document.
|
|
225
|
+
# document, reusing a namespace declaration already in scope here.
|
|
187
226
|
#
|
|
188
|
-
#
|
|
189
|
-
# adopted from another document, so the
|
|
190
|
-
# object that ends up in the tree, and every
|
|
191
|
-
#
|
|
227
|
+
# Build new elements this way rather than parsing them separately:
|
|
228
|
+
# Nokogiri re-creates a node adopted from another document, so the
|
|
229
|
+
# object built is not the object that ends up in the tree, and every
|
|
230
|
+
# wrapper held on it goes stale.
|
|
192
231
|
def build(nsptag)
|
|
193
232
|
pfx, local = Ns.split_tag(nsptag)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
# -- as .rels and [Content_Types].xml do -- the child is created
|
|
200
|
-
# unprefixed to match, which matters because C14N preserves prefixes.
|
|
201
|
-
child.namespace = inherited_namespace(pfx, uri) ||
|
|
202
|
-
child.add_namespace_definition(pfx, uri)
|
|
203
|
-
Element.wrap(child)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Parse an XML literal into this element's document.
|
|
233
|
+
Element.wrap(BACKEND.build(@node, pfx, local, Ns.nsuri(pfx)))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Parse an XML literal into this element's document, its prefixes
|
|
237
|
+
# resolved against the namespaces in scope here.
|
|
207
238
|
def build_from_xml(xml)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
fragment = @node.parse(xml, &:noblanks)
|
|
211
|
-
raise InvalidXmlError, "fragment produced no element: #{xml.inspect}" if fragment.first.nil?
|
|
239
|
+
fragment = BACKEND.parse_fragment(@node, xml)
|
|
240
|
+
raise InvalidXmlError, "fragment produced no element: #{xml.inspect}" if fragment.nil?
|
|
212
241
|
|
|
213
|
-
Element.wrap(fragment
|
|
242
|
+
Element.wrap(fragment)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# A copy of +element+, from any document, built into this one.
|
|
246
|
+
def import(element)
|
|
247
|
+
build_from_xml(element.to_xml)
|
|
214
248
|
end
|
|
215
249
|
|
|
216
250
|
# -- text content ----------------------------------------------------
|
|
217
251
|
|
|
218
252
|
# @return [String] the element's text content
|
|
219
|
-
def text
|
|
253
|
+
def text
|
|
254
|
+
BACKEND.text(@node)
|
|
255
|
+
end
|
|
220
256
|
|
|
221
257
|
def text=(value)
|
|
222
|
-
@node
|
|
258
|
+
BACKEND.set_text(@node, value.to_s)
|
|
223
259
|
end
|
|
224
260
|
|
|
225
261
|
# Declare +prefix+ on this element even if nothing uses it yet.
|
|
@@ -227,7 +263,7 @@ module Pptx
|
|
|
227
263
|
# Needed where a descendant carries a namespaced attribute but the
|
|
228
264
|
# declaration belongs on the root, which is where PowerPoint puts it.
|
|
229
265
|
def declare_namespace(prefix)
|
|
230
|
-
|
|
266
|
+
BACKEND.declare_namespace(@node, prefix, Ns.nsuri(prefix))
|
|
231
267
|
self
|
|
232
268
|
end
|
|
233
269
|
|
|
@@ -241,62 +277,79 @@ module Pptx
|
|
|
241
277
|
# from lxml.
|
|
242
278
|
def get(attr_name)
|
|
243
279
|
pfx, local = prefixed_attr_parts(attr_name)
|
|
244
|
-
return @node
|
|
280
|
+
return BACKEND.get_attribute(@node, attr_name.to_s) unless pfx
|
|
245
281
|
|
|
246
|
-
@node
|
|
282
|
+
BACKEND.get_attribute_ns(@node, local, Ns.nsuri(pfx))
|
|
247
283
|
end
|
|
248
284
|
|
|
249
285
|
def set(attr_name, value)
|
|
250
286
|
pfx, local = prefixed_attr_parts(attr_name)
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
uri = Ns.nsuri(pfx)
|
|
254
|
-
if (existing = @node.attribute_with_ns(local, uri))
|
|
255
|
-
existing.value = value.to_s
|
|
287
|
+
if pfx
|
|
288
|
+
BACKEND.set_attribute_ns(@node, pfx, local, Ns.nsuri(pfx), value.to_s)
|
|
256
289
|
else
|
|
257
|
-
|
|
258
|
-
@node["#{pfx}:#{local}"] = value.to_s
|
|
290
|
+
BACKEND.set_attribute(@node, attr_name.to_s, value.to_s)
|
|
259
291
|
end
|
|
260
292
|
value
|
|
261
293
|
end
|
|
262
294
|
|
|
263
295
|
def delete_attribute(attr_name)
|
|
264
296
|
pfx, local = prefixed_attr_parts(attr_name)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
297
|
+
if pfx
|
|
298
|
+
BACKEND.remove_attribute_ns(@node, local, Ns.nsuri(pfx))
|
|
299
|
+
else
|
|
300
|
+
BACKEND.remove_attribute(@node, attr_name.to_s)
|
|
301
|
+
end
|
|
268
302
|
nil
|
|
269
303
|
end
|
|
270
304
|
|
|
271
|
-
def attribute?(attr_name)
|
|
305
|
+
def attribute?(attr_name)
|
|
306
|
+
!get(attr_name).nil?
|
|
307
|
+
end
|
|
272
308
|
|
|
273
309
|
# -- serialization and equality --------------------------------------
|
|
274
310
|
|
|
275
311
|
# Pretty-printed XML for this element, without a declaration. For
|
|
276
312
|
# debugging and specs.
|
|
277
|
-
def xml
|
|
313
|
+
def xml
|
|
314
|
+
BACKEND.pretty_xml(@node)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Compact XML for this element, without a declaration.
|
|
318
|
+
def to_xml
|
|
319
|
+
BACKEND.to_xml(@node)
|
|
320
|
+
end
|
|
278
321
|
|
|
279
|
-
def to_s
|
|
322
|
+
def to_s
|
|
323
|
+
xml
|
|
324
|
+
end
|
|
280
325
|
|
|
281
|
-
def ==(other)
|
|
326
|
+
def ==(other)
|
|
327
|
+
other.is_a?(Element) && other.node.equal?(@node)
|
|
328
|
+
end
|
|
282
329
|
alias eql? ==
|
|
283
330
|
|
|
284
|
-
def hash
|
|
331
|
+
def hash
|
|
332
|
+
@node.object_id.hash
|
|
333
|
+
end
|
|
285
334
|
|
|
286
|
-
def inspect
|
|
335
|
+
def inspect
|
|
336
|
+
"#<#{self.class.name} <#{nsptag}> children=#{BACKEND.element_children(@node).size}>"
|
|
337
|
+
end
|
|
287
338
|
|
|
288
339
|
private
|
|
289
340
|
|
|
290
341
|
def raw_find(nsptag)
|
|
291
342
|
pfx, local = Ns.split_tag(nsptag)
|
|
292
343
|
uri = Ns.nsuri(pfx)
|
|
293
|
-
@node.
|
|
344
|
+
BACKEND.element_children(@node).find do |c|
|
|
345
|
+
BACKEND.local_name(c) == local && BACKEND.namespace_uri(c) == uri
|
|
346
|
+
end
|
|
294
347
|
end
|
|
295
348
|
|
|
296
349
|
# Guard against the cross-document trap described on {#build}.
|
|
297
350
|
def adopt(element)
|
|
298
351
|
node = element.is_a?(Element) ? element.node : element
|
|
299
|
-
return node if
|
|
352
|
+
return node if BACKEND.document(node).equal?(document)
|
|
300
353
|
|
|
301
354
|
raise ArgumentError,
|
|
302
355
|
"cannot insert an element created in a different document; " \
|
|
@@ -308,22 +361,6 @@ module Pptx
|
|
|
308
361
|
|
|
309
362
|
Ns.split_tag(attr_name.to_s)
|
|
310
363
|
end
|
|
311
|
-
|
|
312
|
-
# An in-scope declaration for +uri+, preferring one bound to +prefix+ but
|
|
313
|
-
# accepting any -- including a default (unprefixed) declaration.
|
|
314
|
-
def inherited_namespace(prefix, uri)
|
|
315
|
-
scopes = @node.namespace_scopes
|
|
316
|
-
scopes.find { |ns| ns.href == uri && ns.prefix == prefix } ||
|
|
317
|
-
scopes.find { |ns| ns.href == uri }
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
# Reuse an in-scope namespace declaration when there is one, so the
|
|
321
|
-
# output does not sprout a redundant xmlns on every new element.
|
|
322
|
-
def namespace_for(node, prefix)
|
|
323
|
-
uri = Ns.nsuri(prefix)
|
|
324
|
-
existing = node.namespace_scopes.find { |ns| ns.href == uri && ns.prefix == prefix }
|
|
325
|
-
existing || node.add_namespace_definition(prefix, uri)
|
|
326
|
-
end
|
|
327
364
|
end
|
|
328
365
|
end
|
|
329
366
|
end
|
data/lib/ruby_pptx/oxml/ns.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Pptx
|
|
|
8
8
|
#
|
|
9
9
|
# - prefixed, `"p:cSld"` -- how the spec and this code talk
|
|
10
10
|
# - Clark, `"{http://...}cSld"` -- unambiguous, used as registry keys
|
|
11
|
-
# -
|
|
11
|
+
# - a backend node, name + namespace -- what the parser hands back
|
|
12
12
|
module Ns
|
|
13
13
|
NSMAP = {
|
|
14
14
|
"a" => "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
@@ -78,7 +78,7 @@ module Pptx
|
|
|
78
78
|
"#{pfx}:#{local}"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
# The subset of NSMAP for +prefixes+, in the shape
|
|
81
|
+
# The subset of NSMAP for +prefixes+, in the shape an XPath query wants.
|
|
82
82
|
#
|
|
83
83
|
# namespaces("a", "p") #=> {"a" => "...", "p" => "..."}
|
|
84
84
|
def namespaces(*prefixes)
|
|
@@ -93,12 +93,12 @@ module Pptx
|
|
|
93
93
|
prefixes.map { |pfx| %(xmlns:#{pfx}="#{nsuri(pfx)}") }.join(" ")
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# The Clark name of a
|
|
97
|
-
#
|
|
98
|
-
# @param node [Nokogiri::XML::Node]
|
|
96
|
+
# The Clark name of a node of the active XML backend, suitable as an
|
|
97
|
+
# element-registry key.
|
|
99
98
|
def clark_name_of(node)
|
|
100
|
-
|
|
101
|
-
href
|
|
99
|
+
backend = BACKEND
|
|
100
|
+
href = backend.namespace_uri(node)
|
|
101
|
+
href ? "{#{href}}#{backend.local_name(node)}" : backend.local_name(node)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
# ["p", "cSld"] for "p:cSld".
|
|
@@ -61,9 +61,13 @@ module Pptx
|
|
|
61
61
|
MIN_SLIDE_ID = 256
|
|
62
62
|
MAX_SLIDE_ID = 2_147_483_647
|
|
63
63
|
|
|
64
|
-
def add_slide_id(r_id)
|
|
64
|
+
def add_slide_id(r_id)
|
|
65
|
+
add_sldId(id: next_id, rId: r_id)
|
|
66
|
+
end
|
|
65
67
|
|
|
66
|
-
def size
|
|
68
|
+
def size
|
|
69
|
+
sldId_list.size
|
|
70
|
+
end
|
|
67
71
|
|
|
68
72
|
private
|
|
69
73
|
|
|
@@ -90,7 +94,9 @@ module Pptx
|
|
|
90
94
|
tag "p:sldMasterIdLst"
|
|
91
95
|
zero_or_more "p:sldMasterId", as: :sldMasterId
|
|
92
96
|
|
|
93
|
-
def size
|
|
97
|
+
def size
|
|
98
|
+
sldMasterId_list.size
|
|
99
|
+
end
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
# `p:sldMasterId`, a reference to one slide master.
|
|
@@ -42,7 +42,9 @@ module Pptx
|
|
|
42
42
|
one_and_only_one "p14:sldIdLst"
|
|
43
43
|
|
|
44
44
|
# PowerPoint identifies a section by a brace-wrapped uppercase GUID.
|
|
45
|
-
def self.new_id
|
|
45
|
+
def self.new_id
|
|
46
|
+
"{#{SecureRandom.uuid.upcase}}"
|
|
47
|
+
end
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# `p14:sectionLst`, the sections of a presentation.
|
|
@@ -25,7 +25,9 @@ module Pptx
|
|
|
25
25
|
required_attr "prst", type: Enum::MSO_AUTO_SHAPE_TYPE
|
|
26
26
|
|
|
27
27
|
# The adjustment guides, in document order.
|
|
28
|
-
def gd_list
|
|
28
|
+
def gd_list
|
|
29
|
+
avLst ? avLst.gd_list : []
|
|
30
|
+
end
|
|
29
31
|
|
|
30
32
|
# Replace the adjustment values with +name_value_pairs+.
|
|
31
33
|
def rewrite_guides(name_value_pairs)
|
|
@@ -80,11 +82,17 @@ module Pptx
|
|
|
80
82
|
optional_attr "w", type: SimpleTypes::ST_PositiveCoordinate
|
|
81
83
|
optional_attr "h", type: SimpleTypes::ST_PositiveCoordinate
|
|
82
84
|
|
|
83
|
-
def move_to(x, y)
|
|
85
|
+
def move_to(x, y)
|
|
86
|
+
add_moveTo.point_at(x, y)
|
|
87
|
+
end
|
|
84
88
|
|
|
85
|
-
def line_to(x, y)
|
|
89
|
+
def line_to(x, y)
|
|
90
|
+
add_lnTo.point_at(x, y)
|
|
91
|
+
end
|
|
86
92
|
|
|
87
|
-
def close_contour
|
|
93
|
+
def close_contour
|
|
94
|
+
add_close
|
|
95
|
+
end
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
# `a:pathLst`, the contours of a freeform shape.
|
|
@@ -254,7 +262,9 @@ module Pptx
|
|
|
254
262
|
XML
|
|
255
263
|
end
|
|
256
264
|
|
|
257
|
-
def escape(text)
|
|
265
|
+
def escape(text)
|
|
266
|
+
text.to_s.gsub("&", "&").gsub("<", "<").gsub('"', """)
|
|
267
|
+
end
|
|
258
268
|
|
|
259
269
|
DEFAULT_STYLE = <<~XML
|
|
260
270
|
<p:style>
|
|
@@ -276,7 +286,9 @@ module Pptx
|
|
|
276
286
|
|
|
277
287
|
# A text body added to a shape that has none needs the minimum structure
|
|
278
288
|
# the schema requires, not an empty element.
|
|
279
|
-
def new_txBody
|
|
289
|
+
def new_txBody
|
|
290
|
+
CT_TextBody.new_element(self)
|
|
291
|
+
end
|
|
280
292
|
|
|
281
293
|
# Start a new contour on this shape's custom geometry.
|
|
282
294
|
#
|
|
@@ -288,23 +300,37 @@ module Pptx
|
|
|
288
300
|
geometry.get_or_add_pathLst.add_path_of(width, height)
|
|
289
301
|
end
|
|
290
302
|
|
|
291
|
-
def prstGeom
|
|
303
|
+
def prstGeom
|
|
304
|
+
spPr.prstGeom
|
|
305
|
+
end
|
|
292
306
|
|
|
293
|
-
def prst
|
|
307
|
+
def prst
|
|
308
|
+
prstGeom&.prst
|
|
309
|
+
end
|
|
294
310
|
|
|
295
|
-
def ln
|
|
311
|
+
def ln
|
|
312
|
+
spPr.ln
|
|
313
|
+
end
|
|
296
314
|
|
|
297
|
-
def get_or_add_ln
|
|
315
|
+
def get_or_add_ln
|
|
316
|
+
spPr.get_or_add_ln
|
|
317
|
+
end
|
|
298
318
|
|
|
299
319
|
# A shape has custom geometry -- is a freeform -- when it carries
|
|
300
320
|
# `a:custGeom` in place of `a:prstGeom`.
|
|
301
|
-
def custom_geometry?
|
|
321
|
+
def custom_geometry?
|
|
322
|
+
!spPr.custGeom.nil?
|
|
323
|
+
end
|
|
302
324
|
|
|
303
325
|
# A text box is an `p:sp` whose `p:cNvSpPr` says `txBox="1"`.
|
|
304
|
-
def textbox?
|
|
326
|
+
def textbox?
|
|
327
|
+
nvSpPr.cNvSpPr.txBox == true
|
|
328
|
+
end
|
|
305
329
|
|
|
306
330
|
# An auto shape has preset geometry and is not a text box.
|
|
307
|
-
def autoshape?
|
|
331
|
+
def autoshape?
|
|
332
|
+
!prstGeom.nil? && nvSpPr.cNvSpPr.txBox != true
|
|
333
|
+
end
|
|
308
334
|
end
|
|
309
335
|
end
|
|
310
336
|
end
|
|
@@ -26,21 +26,29 @@ module Pptx
|
|
|
26
26
|
SHAPE_TAGS = %w[p:sp p:grpSp p:graphicFrame p:cxnSp p:pic p:contentPart].freeze
|
|
27
27
|
|
|
28
28
|
# A group's transform hangs off `p:grpSpPr`, not `p:spPr`.
|
|
29
|
-
def xfrm
|
|
29
|
+
def xfrm
|
|
30
|
+
grpSpPr.xfrm
|
|
31
|
+
end
|
|
30
32
|
|
|
31
|
-
def get_or_add_xfrm
|
|
33
|
+
def get_or_add_xfrm
|
|
34
|
+
grpSpPr.get_or_add_xfrm
|
|
35
|
+
end
|
|
32
36
|
|
|
33
|
-
def chOff
|
|
37
|
+
def chOff
|
|
38
|
+
get_or_add_xfrm.get_or_add_chOff
|
|
39
|
+
end
|
|
34
40
|
|
|
35
|
-
def chExt
|
|
41
|
+
def chExt
|
|
42
|
+
get_or_add_xfrm.get_or_add_chExt
|
|
43
|
+
end
|
|
36
44
|
|
|
37
45
|
# Each shape child, in document order.
|
|
38
46
|
def shape_elements
|
|
39
47
|
return enum_for(:shape_elements) unless block_given?
|
|
40
48
|
|
|
41
49
|
tags = SHAPE_TAGS.map { |t| Ns.qn(t) }
|
|
42
|
-
|
|
43
|
-
yield
|
|
50
|
+
element_children.each do |child|
|
|
51
|
+
yield child if tags.include?(child.clark_name)
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
|
|
@@ -210,7 +218,7 @@ module Pptx
|
|
|
210
218
|
def adopt_xml(element)
|
|
211
219
|
return element if element.document.equal?(document)
|
|
212
220
|
|
|
213
|
-
|
|
221
|
+
import(element)
|
|
214
222
|
end
|
|
215
223
|
end
|
|
216
224
|
end
|