manticore-smash 3.1.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 +7 -0
- data/LICENSE +661 -0
- data/README.md +492 -0
- data/lib/manticore.rb +24 -0
- data/lib/mdutils/rediscount.rb +871 -0
- data/lib/xmlutils/formatters.rb +91 -0
- data/lib/xmlutils/node.rb +585 -0
- data/lib/xmlutils/tokenizer.rb +282 -0
- data/lib/xmlutils/tree_parser.rb +161 -0
- data/lib/xmlutils/xml_doc.rb +273 -0
- data/lib/xmlutils/xpath.rb +103 -0
- metadata +48 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# Copyright (C) 2024 Manticore Authors
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published
|
|
7
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
module XmlUtils
|
|
19
|
+
class Formatters
|
|
20
|
+
class Default
|
|
21
|
+
attr_accessor :compact
|
|
22
|
+
|
|
23
|
+
def initialize(compact = false)
|
|
24
|
+
@compact = compact
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write(node, output = $stdout)
|
|
28
|
+
case node
|
|
29
|
+
when Document
|
|
30
|
+
node.children.each_with_index do |child, idx|
|
|
31
|
+
write(child, output)
|
|
32
|
+
output << "\n" unless idx == node.children.length - 1
|
|
33
|
+
end
|
|
34
|
+
when Element
|
|
35
|
+
write_element(node, output, 0)
|
|
36
|
+
when Text
|
|
37
|
+
output << node.to_s
|
|
38
|
+
when CData
|
|
39
|
+
output << node.to_s
|
|
40
|
+
when Comment
|
|
41
|
+
output << node.to_s
|
|
42
|
+
when ProcessingInstruction
|
|
43
|
+
output << node.to_s
|
|
44
|
+
when XMLDecl
|
|
45
|
+
output << node.to_s
|
|
46
|
+
when DocType
|
|
47
|
+
output << node.to_s
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def write_element(node, output, indent = 0)
|
|
52
|
+
output << ' ' * indent
|
|
53
|
+
output << "<#{node.name}"
|
|
54
|
+
node.attributes.each_value do |attr|
|
|
55
|
+
output << " #{attr.to_string}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if node.children.empty?
|
|
59
|
+
output << " />"
|
|
60
|
+
else
|
|
61
|
+
output << ">"
|
|
62
|
+
has_only_text = node.children.all? { |c| c.is_a?(Text) || c.is_a?(CData) }
|
|
63
|
+
if has_only_text
|
|
64
|
+
node.children.each { |c| write(c, output) }
|
|
65
|
+
else
|
|
66
|
+
output << "\n"
|
|
67
|
+
node.children.each do |c|
|
|
68
|
+
write(c, output) if c.is_a?(Text) && c.to_s.strip.empty?
|
|
69
|
+
next if c.is_a?(Text) && c.to_s.strip.empty?
|
|
70
|
+
if c.is_a?(Element)
|
|
71
|
+
write_element(c, output, indent + 1)
|
|
72
|
+
else
|
|
73
|
+
output << ' ' * (indent + 1)
|
|
74
|
+
write(c, output)
|
|
75
|
+
end
|
|
76
|
+
output << "\n"
|
|
77
|
+
end
|
|
78
|
+
output << ' ' * indent
|
|
79
|
+
end
|
|
80
|
+
output << "</#{node.name}>"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class Pretty < Default
|
|
86
|
+
def initialize
|
|
87
|
+
super(false)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
# Copyright (C) 2024 Manticore Authors
|
|
4
|
+
#
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU Affero General Public License as published
|
|
7
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU Affero General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
module XmlUtils
|
|
19
|
+
class ParseException < RuntimeError
|
|
20
|
+
attr_reader :line, :position
|
|
21
|
+
|
|
22
|
+
def initialize(message, line = nil, position = nil)
|
|
23
|
+
@line = line
|
|
24
|
+
@position = position
|
|
25
|
+
super(message)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
base = super
|
|
30
|
+
@line ? "#{base} (line #{@line}, pos #{@position})" : base
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class IllegalArgumentError < ArgumentError; end
|
|
35
|
+
class UndefinedNamespaceException < ParseException; end
|
|
36
|
+
|
|
37
|
+
class Node
|
|
38
|
+
include Enumerable
|
|
39
|
+
|
|
40
|
+
attr_accessor :parent
|
|
41
|
+
|
|
42
|
+
def initialize
|
|
43
|
+
@parent = nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def each(&block)
|
|
47
|
+
return to_enum unless block_given?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_s
|
|
51
|
+
output = ""
|
|
52
|
+
write(output)
|
|
53
|
+
output
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def node_type
|
|
57
|
+
self.class.name.split('::').last.downcase.to_sym
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def root
|
|
61
|
+
node = self
|
|
62
|
+
node = node.parent while node.parent
|
|
63
|
+
node
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def document
|
|
67
|
+
r = root
|
|
68
|
+
r.is_a?(Document) ? r : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def next_sibling
|
|
72
|
+
return nil unless @parent
|
|
73
|
+
siblings = @parent.children
|
|
74
|
+
idx = siblings.index(self)
|
|
75
|
+
idx ? siblings[idx + 1] : nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def previous_sibling
|
|
79
|
+
return nil unless @parent
|
|
80
|
+
siblings = @parent.children
|
|
81
|
+
idx = siblings.index(self)
|
|
82
|
+
idx && idx > 0 ? siblings[idx - 1] : nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def remove
|
|
86
|
+
@parent.delete(self) if @parent
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def deep_clone
|
|
91
|
+
Marshal.load(Marshal.dump(self))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class ChildNode < Node
|
|
96
|
+
def write(output, indent = 0)
|
|
97
|
+
output << ' ' * indent
|
|
98
|
+
write_content(output, indent)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def write_content(output, indent)
|
|
102
|
+
raise NotImplementedError
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class Text < ChildNode
|
|
107
|
+
attr_accessor :value, :raw, :unnormalized
|
|
108
|
+
|
|
109
|
+
def initialize(value, respect_whitespace = false, parent = nil)
|
|
110
|
+
super()
|
|
111
|
+
@parent = parent
|
|
112
|
+
@raw = false
|
|
113
|
+
@unnormalized = nil
|
|
114
|
+
@value = respect_whitespace ? value : normalize(value)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def node_type
|
|
118
|
+
:text
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def clone
|
|
122
|
+
Text.new(@value, true)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def empty?
|
|
126
|
+
@value.nil? || @value.empty?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def <=>(other)
|
|
130
|
+
other <=> @value
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def to_s
|
|
134
|
+
@value.to_s
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def value
|
|
138
|
+
@unnormalized || unnormalize(@value)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def value=(val)
|
|
142
|
+
@value = normalize(val)
|
|
143
|
+
@unnormalized = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def write_content(output, indent = 0)
|
|
147
|
+
if @raw
|
|
148
|
+
output << @value
|
|
149
|
+
else
|
|
150
|
+
output << escape(@value)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def normalize(input)
|
|
157
|
+
return '' if input.nil?
|
|
158
|
+
input.gsub(/\r\n?/, "\n")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def unnormalize(input)
|
|
162
|
+
input.gsub(/&(amp|lt|gt|quot|apos);/) do |match|
|
|
163
|
+
case $1
|
|
164
|
+
when 'amp' then '&'
|
|
165
|
+
when 'lt' then '<'
|
|
166
|
+
when 'gt' then '>'
|
|
167
|
+
when 'quot' then '"'
|
|
168
|
+
when 'apos' then "'"
|
|
169
|
+
else match
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def escape(input)
|
|
175
|
+
input.gsub('&', '&')
|
|
176
|
+
.gsub('<', '<')
|
|
177
|
+
.gsub('>', '>')
|
|
178
|
+
.gsub('"', '"')
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class CData < Text
|
|
183
|
+
def initialize(value, respect_whitespace = false, parent = nil)
|
|
184
|
+
super(value, respect_whitespace, parent)
|
|
185
|
+
@raw = true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def node_type
|
|
189
|
+
:cdata
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def clone
|
|
193
|
+
CData.new(@value, true)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def write_content(output, indent = 0)
|
|
197
|
+
output << "<![CDATA[#{@value}]]>"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class Comment < ChildNode
|
|
202
|
+
attr_accessor :string
|
|
203
|
+
|
|
204
|
+
def initialize(string, parent = nil)
|
|
205
|
+
super()
|
|
206
|
+
@parent = parent
|
|
207
|
+
@string = string.to_s
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def node_type
|
|
211
|
+
:comment
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def clone
|
|
215
|
+
Comment.new(@string)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def write_content(output, indent = 0)
|
|
219
|
+
output << "<!--#{@string}-->"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
class ProcessingInstruction < ChildNode
|
|
224
|
+
attr_accessor :target, :content
|
|
225
|
+
|
|
226
|
+
def initialize(target, content = nil, parent = nil)
|
|
227
|
+
super()
|
|
228
|
+
@parent = parent
|
|
229
|
+
@target = target
|
|
230
|
+
@content = content
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def node_type
|
|
234
|
+
:processing_instruction
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def clone
|
|
238
|
+
ProcessingInstruction.new(@target, @content)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def write_content(output, indent = 0)
|
|
242
|
+
if @content && !@content.empty?
|
|
243
|
+
output << "<?#{@target} #{@content}?>"
|
|
244
|
+
else
|
|
245
|
+
output << "<?#{@target}?>"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
class DocType < ChildNode
|
|
251
|
+
attr_accessor :name, :external_id, :system_id, :public_id
|
|
252
|
+
|
|
253
|
+
def initialize(name, external_id = nil, system_id = nil, public_id = nil, parent = nil)
|
|
254
|
+
super()
|
|
255
|
+
@parent = parent
|
|
256
|
+
@name = name
|
|
257
|
+
@external_id = external_id
|
|
258
|
+
@system_id = system_id
|
|
259
|
+
@public_id = public_id
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def node_type
|
|
263
|
+
:doctype
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def write_content(output, indent = 0)
|
|
267
|
+
if @external_id
|
|
268
|
+
if @public_id
|
|
269
|
+
output << "<!DOCTYPE #{@name} PUBLIC \"#{@public_id}\" \"#{@system_id}\">"
|
|
270
|
+
else
|
|
271
|
+
output << "<!DOCTYPE #{@name} SYSTEM \"#{@system_id}\">"
|
|
272
|
+
end
|
|
273
|
+
else
|
|
274
|
+
output << "<!DOCTYPE #{@name}>"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
class XMLDecl < ProcessingInstruction
|
|
280
|
+
attr_accessor :version, :encoding, :standalone
|
|
281
|
+
|
|
282
|
+
def initialize(version = "1.0", encoding = nil, standalone = nil, parent = nil)
|
|
283
|
+
super('xml', nil, parent)
|
|
284
|
+
@version = version
|
|
285
|
+
@encoding = encoding
|
|
286
|
+
@standalone = standalone
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def node_type
|
|
290
|
+
:xmldecl
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def write_content(output, indent = 0)
|
|
294
|
+
attrs = ["version=\"#{@version}\""]
|
|
295
|
+
attrs << "encoding=\"#{@encoding}\"" if @encoding
|
|
296
|
+
attrs << "standalone=\"#{@standalone}\"" if @standalone
|
|
297
|
+
output << "<?xml #{attrs.join(' ')}?>"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
class Attribute
|
|
302
|
+
attr_accessor :name, :value, :normalized, :element
|
|
303
|
+
|
|
304
|
+
def initialize(name, value, normalized = true)
|
|
305
|
+
@name = name.to_s
|
|
306
|
+
@value = normalized ? value.to_s : normalize(value.to_s)
|
|
307
|
+
@normalized = normalized
|
|
308
|
+
@element = nil
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def node_type
|
|
312
|
+
:attribute
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def clone
|
|
316
|
+
Attribute.new(@name, @value, true)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def to_s
|
|
320
|
+
@value
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def to_string
|
|
324
|
+
"#{@name}=\"#{escape(@value)}\""
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def namespace
|
|
328
|
+
prefix = @name.include?(':') ? @name.split(':').first : ''
|
|
329
|
+
prefix == 'xmlns' ? '' : prefix
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def prefix
|
|
333
|
+
@name.include?(':') ? @name.split(':').first : ''
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private
|
|
337
|
+
|
|
338
|
+
def normalize(input)
|
|
339
|
+
input.gsub(/\r\n?/, "\n").gsub(/\n/, ' ')
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def escape(input)
|
|
343
|
+
input.gsub('&', '&')
|
|
344
|
+
.gsub('<', '<')
|
|
345
|
+
.gsub('>', '>')
|
|
346
|
+
.gsub('"', '"')
|
|
347
|
+
.gsub("'", ''')
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
class Element < ChildNode
|
|
352
|
+
attr_accessor :name, :attributes, :children, :prefixes
|
|
353
|
+
|
|
354
|
+
def initialize(name, parent = nil)
|
|
355
|
+
super()
|
|
356
|
+
@parent = parent
|
|
357
|
+
@name = name.to_s
|
|
358
|
+
@attributes = {}
|
|
359
|
+
@children = []
|
|
360
|
+
@prefixes = {}
|
|
361
|
+
@parent.add(self) if @parent && @parent.respond_to?(:add)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def node_type
|
|
365
|
+
:element
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def clone
|
|
369
|
+
cloned = Element.new(@name)
|
|
370
|
+
@attributes.each { |k, v| cloned.add_attribute(k, v.clone) }
|
|
371
|
+
@children.each { |c| cloned.add(c.clone) }
|
|
372
|
+
cloned
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def add(element)
|
|
376
|
+
element.parent = self
|
|
377
|
+
@children << element
|
|
378
|
+
element
|
|
379
|
+
end
|
|
380
|
+
alias << add
|
|
381
|
+
|
|
382
|
+
def delete(element)
|
|
383
|
+
@children.delete(element)
|
|
384
|
+
element.parent = nil if element.respond_to?(:parent=)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def delete_at(index)
|
|
388
|
+
@children.delete_at(index)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def [](name_or_index)
|
|
392
|
+
if name_or_index.is_a?(Integer)
|
|
393
|
+
@children[name_or_index]
|
|
394
|
+
else
|
|
395
|
+
attr = attribute(name_or_index.to_s)
|
|
396
|
+
attr ? attr.value : nil
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def []=(name_or_index, value)
|
|
401
|
+
if name_or_index.is_a?(Integer)
|
|
402
|
+
@children[name_or_index] = value
|
|
403
|
+
else
|
|
404
|
+
add_attribute(name_or_index.to_s, value.to_s)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def add_attribute(name, value)
|
|
409
|
+
attr = value.is_a?(Attribute) ? value : Attribute.new(name, value)
|
|
410
|
+
attr.element = self
|
|
411
|
+
@attributes[name.to_s] = attr
|
|
412
|
+
attr
|
|
413
|
+
end
|
|
414
|
+
alias set_attribute add_attribute
|
|
415
|
+
|
|
416
|
+
def delete_attribute(name)
|
|
417
|
+
@attributes.delete(name.to_s)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def attribute(name)
|
|
421
|
+
@attributes[name.to_s]
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def has_attribute?(name)
|
|
425
|
+
@attributes.key?(name.to_s)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def each_element(&block)
|
|
429
|
+
return to_enum(:each_element) unless block_given?
|
|
430
|
+
@children.select { |c| c.is_a?(Element) }.each(&block)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def elements
|
|
434
|
+
@children.select { |c| c.is_a?(Element) }
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def text(path = nil)
|
|
438
|
+
return XPath.match(self, path).first.text if path
|
|
439
|
+
txt = @children.select { |c| c.is_a?(Text) || c.is_a?(CData) }
|
|
440
|
+
txt.map(&:to_s).join('')
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
def get_text(path = nil)
|
|
444
|
+
return XPath.match(self, path).first if path
|
|
445
|
+
@children.find { |c| c.is_a?(Text) || c.is_a?(CData) }
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def add_text(text)
|
|
449
|
+
t = text.is_a?(Text) ? text : Text.new(text)
|
|
450
|
+
add(t)
|
|
451
|
+
t
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def get_elements(name)
|
|
455
|
+
@children.select { |c| c.is_a?(Element) && c.name == name }
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def each_recursive(&block)
|
|
459
|
+
return to_enum(:each_recursive) unless block_given?
|
|
460
|
+
@children.each do |child|
|
|
461
|
+
block.call(child)
|
|
462
|
+
child.each_recursive(&block) if child.is_a?(Element)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def namespaces
|
|
467
|
+
ns = @prefixes.dup
|
|
468
|
+
@attributes.each do |k, v|
|
|
469
|
+
if k == 'xmlns'
|
|
470
|
+
ns[''] = v.value
|
|
471
|
+
elsif k.start_with?('xmlns:')
|
|
472
|
+
ns[k.sub('xmlns:', '')] = v.value
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
ns
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def namespace(prefix = nil)
|
|
479
|
+
prefix ||= self.prefix
|
|
480
|
+
ns = namespaces
|
|
481
|
+
return ns[prefix] if ns.key?(prefix)
|
|
482
|
+
@parent.respond_to?(:namespace) ? @parent.namespace(prefix) : nil
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def prefix
|
|
486
|
+
@name.include?(':') ? @name.split(':').first : ''
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def expand(name)
|
|
490
|
+
return name unless name.include?(':')
|
|
491
|
+
p, local = name.split(':')
|
|
492
|
+
ns = namespace(p)
|
|
493
|
+
ns ? "{#{ns}}#{local}" : name
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def write_content(output, indent = 0)
|
|
497
|
+
output << "<#{@name}"
|
|
498
|
+
@attributes.each_value { |attr| output << " #{attr.to_string}" }
|
|
499
|
+
if @children.empty?
|
|
500
|
+
output << " />"
|
|
501
|
+
else
|
|
502
|
+
output << ">"
|
|
503
|
+
has_only_text = @children.all? { |c| c.is_a?(Text) || c.is_a?(CData) }
|
|
504
|
+
if has_only_text
|
|
505
|
+
@children.each { |c| c.write(output, 0) }
|
|
506
|
+
else
|
|
507
|
+
output << "\n"
|
|
508
|
+
@children.each { |c| c.write(output, indent + 1); output << "\n" }
|
|
509
|
+
output << ' ' * indent
|
|
510
|
+
end
|
|
511
|
+
output << "</#{@name}>"
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def inspect
|
|
516
|
+
attrs = @attributes.map { |k, v| "#{k}=#{v.to_s.inspect}" }.join(' ')
|
|
517
|
+
attrs = " #{attrs}" unless attrs.empty?
|
|
518
|
+
"<#{@name}#{attrs}>"
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
class Document < Node
|
|
523
|
+
attr_accessor :children
|
|
524
|
+
|
|
525
|
+
def initialize(standalone = nil)
|
|
526
|
+
super()
|
|
527
|
+
@children = []
|
|
528
|
+
@standalone = standalone
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def node_type
|
|
532
|
+
:document
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def add(element)
|
|
536
|
+
element.parent = self
|
|
537
|
+
@children << element
|
|
538
|
+
element
|
|
539
|
+
end
|
|
540
|
+
alias << add
|
|
541
|
+
|
|
542
|
+
def delete(element)
|
|
543
|
+
@children.delete(element)
|
|
544
|
+
element.parent = nil if element.respond_to?(:parent=)
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def root
|
|
548
|
+
@children.find { |c| c.is_a?(Element) }
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def write(output = $stdout, indent = 0)
|
|
552
|
+
@children.each_with_index do |child, idx|
|
|
553
|
+
child.write(output, indent)
|
|
554
|
+
output << "\n" unless idx == @children.length - 1
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def to_s
|
|
559
|
+
output = ""
|
|
560
|
+
write(output)
|
|
561
|
+
output
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
def xml_decl
|
|
565
|
+
@children.find { |c| c.is_a?(XMLDecl) || (c.is_a?(ProcessingInstruction) && c.respond_to?(:target) && c.target == 'xml') }
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def doctype
|
|
569
|
+
@children.find { |c| c.is_a?(DocType) }
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def each_element(&block)
|
|
573
|
+
return to_enum(:each_element) unless block_given?
|
|
574
|
+
@children.select { |c| c.is_a?(Element) }.each(&block)
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def elements
|
|
578
|
+
@children.select { |c| c.is_a?(Element) }
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
def context
|
|
582
|
+
{}
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|