duxml 0.8.8 → 0.8.9
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/bin/validate_xml +30 -30
- data/lib/duxml.rb +76 -76
- data/lib/duxml/doc.rb +108 -91
- data/lib/duxml/doc/element.rb +250 -250
- data/lib/duxml/doc/lazy_ox.rb +167 -167
- data/lib/duxml/doc/node_set.rb +38 -38
- data/lib/duxml/meta.rb +72 -72
- data/lib/duxml/meta/grammar.rb +133 -133
- data/lib/duxml/meta/grammar/pattern.rb +111 -111
- data/lib/duxml/meta/grammar/pattern/attr_name_pattern.rb +36 -36
- data/lib/duxml/meta/grammar/pattern/attr_val_pattern.rb +36 -36
- data/lib/duxml/meta/grammar/pattern/child_pattern.rb +60 -60
- data/lib/duxml/meta/grammar/pattern/text_pattern.rb +31 -31
- data/lib/duxml/meta/grammar/pattern_maker.rb +68 -68
- data/lib/duxml/meta/grammar/relax_ng.rb +39 -39
- data/lib/duxml/meta/grammar/relax_ng/attrs_rule.rb +58 -58
- data/lib/duxml/meta/grammar/relax_ng/children_rule.rb +82 -82
- data/lib/duxml/meta/grammar/relax_ng/value_rule.rb +44 -44
- data/lib/duxml/meta/grammar/rule.rb +58 -58
- data/lib/duxml/meta/grammar/rule/attrs_rule.rb +77 -77
- data/lib/duxml/meta/grammar/rule/children_rule.rb +135 -135
- data/lib/duxml/meta/grammar/rule/text_rule.rb +39 -39
- data/lib/duxml/meta/grammar/rule/value_rule.rb +110 -110
- data/lib/duxml/meta/grammar/spreadsheet.rb +34 -34
- data/lib/duxml/meta/history.rb +88 -88
- data/lib/duxml/meta/history/add.rb +30 -30
- data/lib/duxml/meta/history/change.rb +70 -70
- data/lib/duxml/meta/history/change_attr.rb +33 -33
- data/lib/duxml/meta/history/change_text.rb +32 -32
- data/lib/duxml/meta/history/error.rb +24 -24
- data/lib/duxml/meta/history/new_attr.rb +32 -32
- data/lib/duxml/meta/history/new_text.rb +36 -36
- data/lib/duxml/meta/history/qualify_error.rb +21 -21
- data/lib/duxml/meta/history/remove.rb +27 -27
- data/lib/duxml/meta/history/undo.rb +23 -23
- data/lib/duxml/meta/history/validate_error.rb +20 -20
- data/lib/duxml/reportable.rb +28 -28
- data/lib/duxml/ruby_ext/fixnum.rb +55 -55
- data/lib/duxml/ruby_ext/module.rb +11 -11
- data/lib/duxml/ruby_ext/object.rb +13 -13
- data/lib/duxml/ruby_ext/regexp.rb +19 -19
- data/lib/duxml/ruby_ext/string.rb +37 -37
- data/lib/duxml/saxer.rb +75 -75
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad78564c059484c71fa9c061134ba7534da88a5f
|
4
|
+
data.tar.gz: 5f4d4c73d8098085722ed2116173642d3a9996a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6f8d8e57be9041a357fd1067665907726910de00573c4a0647c8cb4b04d3ce29eea68314b61951bdf1384ea18054b8d28da23872caa9d9587ff5f36e11bd230
|
7
|
+
data.tar.gz: 903c91e7740a7093764c4405bd62dc29d21ff661f381def99b34eef087785b240f98e30ae06d09ead3f704cee471a916410e7c2b265807cd26172b4e47cbb404
|
data/bin/validate_xml
CHANGED
@@ -1,31 +1,31 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
3
|
-
|
4
|
-
require File.expand_path(File.dirname(__FILE__) + '/../lib/duxml')
|
5
|
-
|
6
|
-
include Duxml
|
7
|
-
|
8
|
-
DITA_GRAMMAR_FILE = File.expand_path(File.dirname(__FILE__) + '/../xml/test_grammar.xml')
|
9
|
-
xml_file = ARGV.first
|
10
|
-
log_file = ARGV[1] || 'log.txt'
|
11
|
-
|
12
|
-
load xml_file
|
13
|
-
puts "loaded XML file: #{xml_file}"
|
14
|
-
meta.grammar = DITA_GRAMMAR_FILE
|
15
|
-
puts "loaded grammar file: #{DITA_GRAMMAR_FILE}"
|
16
|
-
validate
|
17
|
-
puts "validation complete"
|
18
|
-
log log_file
|
19
|
-
puts "logged errors to #{log_file}"
|
20
|
-
|
21
|
-
|
22
|
-
if conditions
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
def conditions
|
28
|
-
asdf and b and c
|
29
|
-
end
|
30
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/../lib/duxml')
|
5
|
+
|
6
|
+
include Duxml
|
7
|
+
|
8
|
+
DITA_GRAMMAR_FILE = File.expand_path(File.dirname(__FILE__) + '/../xml/test_grammar.xml')
|
9
|
+
xml_file = ARGV.first
|
10
|
+
log_file = ARGV[1] || 'log.txt'
|
11
|
+
|
12
|
+
load xml_file
|
13
|
+
puts "loaded XML file: #{xml_file}"
|
14
|
+
meta.grammar = DITA_GRAMMAR_FILE
|
15
|
+
puts "loaded grammar file: #{DITA_GRAMMAR_FILE}"
|
16
|
+
validate
|
17
|
+
puts "validation complete"
|
18
|
+
log log_file
|
19
|
+
puts "logged errors to #{log_file}"
|
20
|
+
|
21
|
+
|
22
|
+
if conditions
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def conditions
|
28
|
+
asdf and b and c
|
29
|
+
end
|
30
|
+
|
31
31
|
File
|
data/lib/duxml.rb
CHANGED
@@ -1,76 +1,76 @@
|
|
1
|
-
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
-
|
3
|
-
require File.expand_path(File.dirname(__FILE__) + '/duxml/saxer')
|
4
|
-
|
5
|
-
module Duxml
|
6
|
-
include Saxer
|
7
|
-
|
8
|
-
# most recently processed document - default location for validated documents that are not loaded explicitly
|
9
|
-
@doc
|
10
|
-
|
11
|
-
attr_reader :doc
|
12
|
-
|
13
|
-
# @param _file [String] loads XML file from given path and finds or creates corresponding metadata file e.g. '.xml_file.duxml'
|
14
|
-
# @param grammar_path [nil, String, Duxml::Grammar] optional - provide an external grammar file or object
|
15
|
-
# @return [Doc] XML document as Ruby object
|
16
|
-
def load(_file, grammar_path=nil)
|
17
|
-
meta_path = Meta.meta_path(_file)
|
18
|
-
|
19
|
-
if File.exists?(meta_path)
|
20
|
-
meta = Ox.parse_obj(File.read meta_path)
|
21
|
-
meta.grammar = grammar_path unless grammar_path.nil? or meta.grammar.defined?
|
22
|
-
else
|
23
|
-
meta = MetaClass.new(grammar_path)
|
24
|
-
end
|
25
|
-
|
26
|
-
if File.exists?(_file)
|
27
|
-
@doc = sax(_file, meta.history)
|
28
|
-
else
|
29
|
-
@doc = Doc.new
|
30
|
-
@doc.path = _file
|
31
|
-
|
32
|
-
doc.delete_observers if doc.count_observers > 0 and doc.observer_peers.first.object_id != meta.history.object_id
|
33
|
-
doc.add_observer meta.history if doc.count_observers < 1
|
34
|
-
end
|
35
|
-
doc.set_meta meta
|
36
|
-
end # def load
|
37
|
-
|
38
|
-
# @param file [String] saves current content XML to given file path
|
39
|
-
# @param xml [Doc, Element] current Doc by default, but can also be unattached XML Element
|
40
|
-
def save(file, xml=doc)
|
41
|
-
@doc = xml.respond_to?(:root) ? xml : Doc.new << xml
|
42
|
-
doc.write_to file
|
43
|
-
File.write(Meta.meta_path(file), Ox.dump(doc.meta, circular: true))
|
44
|
-
end
|
45
|
-
|
46
|
-
# @param file [String] output file path for logging human-readable validation error messages
|
47
|
-
def log(file)
|
48
|
-
File.write(file, meta.history.description)
|
49
|
-
end
|
50
|
-
|
51
|
-
# @param *Args [String, Doc] if string then path to load Doc, else Doc to validate
|
52
|
-
# @param options [Hash, Symbol] currently just supports suppression of console output of results with :quiet
|
53
|
-
# @return [Boolean] whether file passed validation
|
54
|
-
def validate(path_or_doc, options={})
|
55
|
-
doc = path_or_doc.is_a?(Doc) ? path_or_doc : load(path_or_doc)
|
56
|
-
unless doc.grammar.defined?
|
57
|
-
raise Exception, "grammar not defined!" unless options[:grammar]
|
58
|
-
doc.grammar = options[:grammar]
|
59
|
-
end
|
60
|
-
raise Exception, "XML document has no root element!" unless doc.root
|
61
|
-
results = []
|
62
|
-
doc.root.traverse do |n| results << doc.grammar.validate(n) unless n.is_a?(String) end
|
63
|
-
puts(doc.history.description) unless options[:quiet]
|
64
|
-
!results.any? do |r| !r end
|
65
|
-
end # def validate
|
66
|
-
|
67
|
-
private
|
68
|
-
# @param g_path [String, GrammarClass] grammar to associate with @doc, which must be set to a Doc and not nil
|
69
|
-
def associate_grammar(g_path)
|
70
|
-
doc.grammar = g_path if g_path
|
71
|
-
end
|
72
|
-
|
73
|
-
def find_meta
|
74
|
-
|
75
|
-
end
|
76
|
-
end # module Duxml
|
1
|
+
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/duxml/saxer')
|
4
|
+
|
5
|
+
module Duxml
|
6
|
+
include Saxer
|
7
|
+
|
8
|
+
# most recently processed document - default location for validated documents that are not loaded explicitly
|
9
|
+
@doc
|
10
|
+
|
11
|
+
attr_reader :doc
|
12
|
+
|
13
|
+
# @param _file [String] loads XML file from given path and finds or creates corresponding metadata file e.g. '.xml_file.duxml'
|
14
|
+
# @param grammar_path [nil, String, Duxml::Grammar] optional - provide an external grammar file or object
|
15
|
+
# @return [Doc] XML document as Ruby object
|
16
|
+
def load(_file, grammar_path=nil)
|
17
|
+
meta_path = Meta.meta_path(_file)
|
18
|
+
|
19
|
+
if File.exists?(meta_path)
|
20
|
+
meta = Ox.parse_obj(File.read meta_path)
|
21
|
+
meta.grammar = grammar_path unless grammar_path.nil? or meta.grammar.defined?
|
22
|
+
else
|
23
|
+
meta = MetaClass.new(grammar_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
if File.exists?(_file)
|
27
|
+
@doc = sax(_file, meta.history)
|
28
|
+
else
|
29
|
+
@doc = Doc.new
|
30
|
+
@doc.path = _file
|
31
|
+
|
32
|
+
doc.delete_observers if doc.count_observers > 0 and doc.observer_peers.first.object_id != meta.history.object_id
|
33
|
+
doc.add_observer meta.history if doc.count_observers < 1
|
34
|
+
end
|
35
|
+
doc.set_meta meta
|
36
|
+
end # def load
|
37
|
+
|
38
|
+
# @param file [String] saves current content XML to given file path
|
39
|
+
# @param xml [Doc, Element] current Doc by default, but can also be unattached XML Element
|
40
|
+
def save(file, xml=doc)
|
41
|
+
@doc = xml.respond_to?(:root) ? xml : Doc.new << xml
|
42
|
+
doc.write_to file
|
43
|
+
File.write(Meta.meta_path(file), Ox.dump(doc.meta, circular: true))
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param file [String] output file path for logging human-readable validation error messages
|
47
|
+
def log(file)
|
48
|
+
File.write(file, meta.history.description)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param *Args [String, Doc] if string then path to load Doc, else Doc to validate
|
52
|
+
# @param options [Hash, Symbol] currently just supports suppression of console output of results with :quiet
|
53
|
+
# @return [Boolean] whether file passed validation
|
54
|
+
def validate(path_or_doc, options={})
|
55
|
+
doc = path_or_doc.is_a?(Doc) ? path_or_doc : load(path_or_doc)
|
56
|
+
unless doc.grammar.defined?
|
57
|
+
raise Exception, "grammar not defined!" unless options[:grammar]
|
58
|
+
doc.grammar = options[:grammar]
|
59
|
+
end
|
60
|
+
raise Exception, "XML document has no root element!" unless doc.root
|
61
|
+
results = []
|
62
|
+
doc.root.traverse do |n| results << doc.grammar.validate(n) unless n.is_a?(String) end
|
63
|
+
puts(doc.history.description) unless options[:quiet]
|
64
|
+
!results.any? do |r| !r end
|
65
|
+
end # def validate
|
66
|
+
|
67
|
+
private
|
68
|
+
# @param g_path [String, GrammarClass] grammar to associate with @doc, which must be set to a Doc and not nil
|
69
|
+
def associate_grammar(g_path)
|
70
|
+
doc.grammar = g_path if g_path
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_meta
|
74
|
+
|
75
|
+
end
|
76
|
+
end # module Duxml
|
data/lib/duxml/doc.rb
CHANGED
@@ -1,92 +1,109 @@
|
|
1
|
-
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
-
|
3
|
-
require File.expand_path(File.dirname(__FILE__) + '/doc/element')
|
4
|
-
require File.expand_path(File.dirname(__FILE__) + '/meta')
|
5
|
-
|
6
|
-
module Duxml
|
7
|
-
class Doc < ::Ox::Document
|
8
|
-
include ElementGuts
|
9
|
-
|
10
|
-
# path of file where this Doc is saved to
|
11
|
-
@path
|
12
|
-
|
13
|
-
# meta data for this Doc; contains reference to grammar if it exists and history
|
14
|
-
@meta
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
1
|
+
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/doc/element')
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/meta')
|
5
|
+
|
6
|
+
module Duxml
|
7
|
+
class Doc < ::Ox::Document
|
8
|
+
include ElementGuts
|
9
|
+
|
10
|
+
# path of file where this Doc is saved to
|
11
|
+
@path
|
12
|
+
|
13
|
+
# meta data for this Doc; contains reference to grammar if it exists and history
|
14
|
+
@meta
|
15
|
+
|
16
|
+
# hash of all unique-id elements within document; gets initialized as each is searched for
|
17
|
+
@id_hash
|
18
|
+
|
19
|
+
attr_reader :meta, :path, :id_hash
|
20
|
+
|
21
|
+
def initialize(prolog={})
|
22
|
+
super(prolog)
|
23
|
+
self[:version] ||= '1.0'
|
24
|
+
@id_hash = {}
|
25
|
+
@meta = MetaClass.new
|
26
|
+
@nodes = NodeSet.new(self)
|
27
|
+
add_observer meta.history
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param _path [String] assigns file path to document, creating it if it does not already exist along with metadata file
|
31
|
+
def path=(_path)
|
32
|
+
@path = _path
|
33
|
+
set_meta _path unless path
|
34
|
+
unless File.exists?(path)
|
35
|
+
write_to(path)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String] summary of XML document as Ruby object and description of root element
|
40
|
+
def to_s
|
41
|
+
"#<#{self.class.to_s} @object_id='#{object_id}' @root='#{root.nil? ? '' : root.description}'>"
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param path_or_obj [String, MetaClass] metadata object itself or path of metadata for this file; if none given, saves existing metadata to file using @path
|
45
|
+
# @return [Doc] self
|
46
|
+
def set_meta(path_or_obj=nil)
|
47
|
+
@meta = case path_or_obj
|
48
|
+
when MetaClass, Element then path_or_obj
|
49
|
+
when String && File.exists?(path_or_obj)
|
50
|
+
Ox.parse_obj(path_or_obj)
|
51
|
+
else
|
52
|
+
File.write(Meta.meta_path(path), Ox.dump(meta)) if path
|
53
|
+
meta
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# shortcut method @see Meta#grammar
|
59
|
+
def grammar
|
60
|
+
meta.grammar
|
61
|
+
end
|
62
|
+
|
63
|
+
# shortcut method @see Meta#grammar=
|
64
|
+
def grammar=(grammar_or_file)
|
65
|
+
meta.grammar = grammar_or_file
|
66
|
+
end
|
67
|
+
|
68
|
+
# shortcut method @see Meta#history
|
69
|
+
def history
|
70
|
+
meta.history
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] one word description of what this object is: 'document'
|
74
|
+
def description
|
75
|
+
'document'
|
76
|
+
end
|
77
|
+
|
78
|
+
# @param path [String] document's file path
|
79
|
+
# @return [Doc] returns self after writing contents to file
|
80
|
+
def write_to(path)
|
81
|
+
s = attributes.collect do |k, v| %( #{k}="#{v}") end.join
|
82
|
+
File.write(path, %(<?xml #{s}?>\n) + root.to_s)
|
83
|
+
x = meta.xml
|
84
|
+
File.write(Meta.meta_path(path), meta.xml.to_s)
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def <<(obj)
|
89
|
+
super(obj)
|
90
|
+
obj.set_doc! self
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# @param id [String, Symbol] document-unique id attribute value
|
95
|
+
# @return [Element, NilClass] found element or nil if not found
|
96
|
+
def find_by_id(id)
|
97
|
+
id_str = id.to_s
|
98
|
+
return @id_hash[id_str] if @id_hash[id_str]
|
99
|
+
root.traverse do |node|
|
100
|
+
if node.respond_to?(:nodes) and node[:id] == id_str
|
101
|
+
return @id_hash[id_str] = node
|
102
|
+
end
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end # class Document < Element
|
107
|
+
end
|
108
|
+
|
92
109
|
Hash
|
data/lib/duxml/doc/element.rb
CHANGED
@@ -1,251 +1,251 @@
|
|
1
|
-
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
-
|
3
|
-
require 'ox'
|
4
|
-
require File.expand_path(File.dirname(__FILE__) + '/lazy_ox')
|
5
|
-
require File.expand_path(File.dirname(__FILE__) + '/node_set')
|
6
|
-
require File.expand_path(File.dirname(__FILE__) + '/../reportable')
|
7
|
-
|
8
|
-
module Duxml
|
9
|
-
# contains actual methods of XML Element
|
10
|
-
module ElementGuts
|
11
|
-
include Duxml
|
12
|
-
include Enumerable
|
13
|
-
include Reportable
|
14
|
-
include LazyOx
|
15
|
-
end
|
16
|
-
|
17
|
-
# basic component of XML file that can possess attributes and take sub Elements or String content
|
18
|
-
class Element < ::Ox::Element
|
19
|
-
include ElementGuts
|
20
|
-
|
21
|
-
# line number
|
22
|
-
@line
|
23
|
-
|
24
|
-
# column
|
25
|
-
@column
|
26
|
-
|
27
|
-
# document to which this Element belongs
|
28
|
-
@doc
|
29
|
-
|
30
|
-
# operates in two modes:
|
31
|
-
# - from Ruby
|
32
|
-
# - from file
|
33
|
-
# in file mode, args provide Element's line and column location then freezes each Fixnum so it cannot be overwritten
|
34
|
-
# in Ruby mode, args are some combination of new attributes/values and/or child nodes (text or XML) with which to initialize this node
|
35
|
-
#
|
36
|
-
# @param name [String] name of element, in both Ruby and file modes
|
37
|
-
# @param _line_or_content [Fixnum, Array, Hash] line number of element file mode; if Array, new child nodes; if Hash, attributes; can be nil
|
38
|
-
# @param _col_or_children [Fixnum, Array] column position in file mode; if Array, new child nodes; can be nil
|
39
|
-
# @return [Element] new XML Element
|
40
|
-
def initialize(name, _line_or_content=nil, _col_or_children=nil)
|
41
|
-
super name
|
42
|
-
@line = _line_or_content if _line_or_content.respond_to?(:%)
|
43
|
-
_line_or_content.each do |k,v| self[k] = v end if _line_or_content.respond_to?(:key)
|
44
|
-
@nodes = NodeSet.new(self, _line_or_content) if _line_or_content.respond_to?(:pop) && _col_or_children.nil?
|
45
|
-
@column = _col_or_children if _col_or_children.respond_to?(:%)
|
46
|
-
@nodes = NodeSet.new(self, _col_or_children) if _col_or_children.respond_to?(:pop)
|
47
|
-
@nodes = NodeSet.new(self) if @nodes.empty?
|
48
|
-
end
|
49
|
-
|
50
|
-
attr_reader :line, :column, :doc
|
51
|
-
|
52
|
-
attr_accessor :nodes
|
53
|
-
end
|
54
|
-
|
55
|
-
module ElementGuts
|
56
|
-
# @return [Boolean] whether or not this has been written to file
|
57
|
-
def abstract?
|
58
|
-
line < 0 || column < 0
|
59
|
-
end
|
60
|
-
|
61
|
-
# @param _doc [Doc] document to which this element belongs - recursively applies to all descendants of this node
|
62
|
-
# @return [Element] self
|
63
|
-
def set_doc!(_doc)
|
64
|
-
@doc = _doc
|
65
|
-
traverse do |node|
|
66
|
-
next if node === self or node.is_a?(String)
|
67
|
-
node.set_doc!(_doc)
|
68
|
-
end
|
69
|
-
self
|
70
|
-
end
|
71
|
-
|
72
|
-
# @see Ox::Element#<<
|
73
|
-
# this override reports changes to history; NewText for Strings, Add for elements
|
74
|
-
#
|
75
|
-
# @param obj [Element, Array] element or string to add to this Element; can insert arrays which are always inserted in order
|
76
|
-
# @return [Element] self
|
77
|
-
def <<(obj)
|
78
|
-
add(obj)
|
79
|
-
end
|
80
|
-
|
81
|
-
# @see #<<
|
82
|
-
# this version of the method allows insertions between existing elements
|
83
|
-
#
|
84
|
-
# @param obj [Element, Array] element or string to add to this Element; can insert arrays which are always inserted in order
|
85
|
-
# @param index [Fixnum] index at which to insert new node; inserts at end of element by default; when inserting arrays, index is incremented for each item to avoid reversing array order
|
86
|
-
# @return [Element] self
|
87
|
-
def add(obj, index=-1)
|
88
|
-
case
|
89
|
-
when obj.is_a?(Array), obj.is_a?(NodeSet)
|
90
|
-
obj.each_with_index do |e, i|
|
91
|
-
add(e, index == -1 ? index : index+i)
|
92
|
-
end
|
93
|
-
when obj.is_a?(String)
|
94
|
-
if obj[0] == '<' and obj[-1] == '>' and (s = Ox.parse(obj))
|
95
|
-
add dclone s
|
96
|
-
else
|
97
|
-
type = :NewText
|
98
|
-
nodes.insert(index, obj)
|
99
|
-
end
|
100
|
-
else
|
101
|
-
type = :Add
|
102
|
-
nodes.insert(index, obj)
|
103
|
-
if obj.count_observers < 1 && @observer_peers
|
104
|
-
obj.add_observer(@observer_peers.first.first)
|
105
|
-
end
|
106
|
-
obj.set_doc! @doc
|
107
|
-
end
|
108
|
-
report(type, obj, index)
|
109
|
-
self
|
110
|
-
end
|
111
|
-
|
112
|
-
# @param index_or_attr [String, Symbol, Fixnum] string or symbol of attribute or index of child node
|
113
|
-
# @return [Element, String] string if attribute value or text node; Element if XML node
|
114
|
-
def [](index_or_attr)
|
115
|
-
index_or_attr.is_a?(Fixnum) ? nodes[index_or_attr] : super(index_or_attr)
|
116
|
-
end
|
117
|
-
|
118
|
-
# @param attr_sym [String, Symbol, Fixnum] name of attribute or index of child to replace
|
119
|
-
# @param val [String] new attribute value or replacement child node
|
120
|
-
# @return [Element] self
|
121
|
-
def []=(attr_sym, val)
|
122
|
-
if attr_sym.is_a?(Fixnum)
|
123
|
-
remove nodes[attr_sym]
|
124
|
-
add(val, attr_sym)
|
125
|
-
return self
|
126
|
-
end
|
127
|
-
attr = attr_sym.to_s
|
128
|
-
raise "argument to [] must be a Symbol or a String." unless attr.is_a?(Symbol) or attr.is_a?(String)
|
129
|
-
args = [attr]
|
130
|
-
args << attributes[attr] if attributes[attr]
|
131
|
-
super(attr, val)
|
132
|
-
type = args.size == 1 ? :NewAttr : :ChangeAttr
|
133
|
-
report(type, *args)
|
134
|
-
self
|
135
|
-
end
|
136
|
-
|
137
|
-
# @return [String] self description
|
138
|
-
def description
|
139
|
-
"<#{name}>"
|
140
|
-
end
|
141
|
-
|
142
|
-
# @return [Element] copy of this Element but with no children
|
143
|
-
def stub
|
144
|
-
Element.new(name, attributes)
|
145
|
-
end
|
146
|
-
|
147
|
-
# @return [HistoryClass] history that is observing this element for changes
|
148
|
-
def history
|
149
|
-
@observer_peers.first.first if @observer_peers.respond_to?(:any?) and @observer_peers.any? and @observer_peers.first.any?
|
150
|
-
end
|
151
|
-
|
152
|
-
# @return [String] XML string (overrides Ox's to_s which just prints the object pointer)
|
153
|
-
def to_s
|
154
|
-
s = %(<#{name})
|
155
|
-
attributes.each do |k,v| s << %( #{k.to_s}="#{v}") end
|
156
|
-
return s+'/>' if nodes.empty?
|
157
|
-
s << ">#{nodes.collect do |n| n.to_s end.join}</#{name}>"
|
158
|
-
end
|
159
|
-
|
160
|
-
# @return #to_s
|
161
|
-
def inspect
|
162
|
-
to_s
|
163
|
-
end
|
164
|
-
|
165
|
-
# TODO do we need this method to take Fixnum node index as well?
|
166
|
-
# @param obj [Element] element child to delete
|
167
|
-
# @return [Element] deleted element
|
168
|
-
def delete(obj)
|
169
|
-
report(:Remove, @nodes.delete(obj))
|
170
|
-
obj
|
171
|
-
end
|
172
|
-
|
173
|
-
alias_method :remove, :delete
|
174
|
-
|
175
|
-
# pre-order traverse through this node and all of its descendants
|
176
|
-
#
|
177
|
-
# @param &block [block] code to execute for each yielded node
|
178
|
-
def traverse(node=nil, &block)
|
179
|
-
return self.to_enum unless block_given?
|
180
|
-
node_stack = [node || self]
|
181
|
-
|
182
|
-
until node_stack.empty?
|
183
|
-
current = node_stack.shift
|
184
|
-
if current
|
185
|
-
yield current
|
186
|
-
node_stack = node_stack.insert(0, *current.nodes) if current.respond_to?(:nodes)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
node || self if block_given?
|
191
|
-
end
|
192
|
-
|
193
|
-
# traverse through just the children of this node
|
194
|
-
#
|
195
|
-
# @param &block [block] code to execute for each child node
|
196
|
-
def each(&block)
|
197
|
-
@nodes.each(&block)
|
198
|
-
end
|
199
|
-
|
200
|
-
# @return [String] namespace of element, derived from name e.g. '<duxml:element>' => 'duxml'
|
201
|
-
def name_space
|
202
|
-
return nil unless (i = name.index(':'))
|
203
|
-
name[0..i-1]
|
204
|
-
end
|
205
|
-
|
206
|
-
|
207
|
-
# @param source [Element] if not explicitly provided, creates deep clone of this element; source can be any XML element (not only Duxml) that responds to traverse with pre-order traversal
|
208
|
-
# @return [Element] deep clone of source, including its attributes and recursively cloned children
|
209
|
-
def dclone(source = self)
|
210
|
-
input_stack = []
|
211
|
-
output_stack = []
|
212
|
-
traverse(source) do |node|
|
213
|
-
if node.is_a?(String)
|
214
|
-
output_stack.last << node
|
215
|
-
next
|
216
|
-
end
|
217
|
-
copy = Element.new(node.name, node.attributes)
|
218
|
-
if output_stack.empty?
|
219
|
-
output_stack << copy
|
220
|
-
input_stack << node
|
221
|
-
else
|
222
|
-
|
223
|
-
if input_stack.last.nodes.none? do |n| n === node end
|
224
|
-
input_stack.pop
|
225
|
-
output_stack.pop
|
226
|
-
end
|
227
|
-
|
228
|
-
output_stack.last << copy
|
229
|
-
if node.nodes.any?
|
230
|
-
output_stack << copy
|
231
|
-
input_stack << node
|
232
|
-
end
|
233
|
-
|
234
|
-
end
|
235
|
-
end
|
236
|
-
output_stack.pop
|
237
|
-
end
|
238
|
-
|
239
|
-
# @return [Element] shallow clone of this element, with all attributes and children only if none are Elements
|
240
|
-
def sclone
|
241
|
-
stub = Element.new(name, attributes)
|
242
|
-
stub << nodes if text?
|
243
|
-
stub
|
244
|
-
end
|
245
|
-
|
246
|
-
# @return [true, false] true if all child nodes are text only; false if any child nodes are XML
|
247
|
-
def text?
|
248
|
-
nodes.all? do |node| node.is_a?(String) end
|
249
|
-
end
|
250
|
-
end # class Element < Node
|
1
|
+
# Copyright (c) 2016 Freescale Semiconductor Inc.
|
2
|
+
|
3
|
+
require 'ox'
|
4
|
+
require File.expand_path(File.dirname(__FILE__) + '/lazy_ox')
|
5
|
+
require File.expand_path(File.dirname(__FILE__) + '/node_set')
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/../reportable')
|
7
|
+
|
8
|
+
module Duxml
|
9
|
+
# contains actual methods of XML Element
|
10
|
+
module ElementGuts
|
11
|
+
include Duxml
|
12
|
+
include Enumerable
|
13
|
+
include Reportable
|
14
|
+
include LazyOx
|
15
|
+
end
|
16
|
+
|
17
|
+
# basic component of XML file that can possess attributes and take sub Elements or String content
|
18
|
+
class Element < ::Ox::Element
|
19
|
+
include ElementGuts
|
20
|
+
|
21
|
+
# line number
|
22
|
+
@line
|
23
|
+
|
24
|
+
# column
|
25
|
+
@column
|
26
|
+
|
27
|
+
# document to which this Element belongs
|
28
|
+
@doc
|
29
|
+
|
30
|
+
# operates in two modes:
|
31
|
+
# - from Ruby
|
32
|
+
# - from file
|
33
|
+
# in file mode, args provide Element's line and column location then freezes each Fixnum so it cannot be overwritten
|
34
|
+
# in Ruby mode, args are some combination of new attributes/values and/or child nodes (text or XML) with which to initialize this node
|
35
|
+
#
|
36
|
+
# @param name [String] name of element, in both Ruby and file modes
|
37
|
+
# @param _line_or_content [Fixnum, Array, Hash] line number of element file mode; if Array, new child nodes; if Hash, attributes; can be nil
|
38
|
+
# @param _col_or_children [Fixnum, Array] column position in file mode; if Array, new child nodes; can be nil
|
39
|
+
# @return [Element] new XML Element
|
40
|
+
def initialize(name, _line_or_content=nil, _col_or_children=nil)
|
41
|
+
super name
|
42
|
+
@line = _line_or_content if _line_or_content.respond_to?(:%)
|
43
|
+
_line_or_content.each do |k,v| self[k] = v end if _line_or_content.respond_to?(:key)
|
44
|
+
@nodes = NodeSet.new(self, _line_or_content) if _line_or_content.respond_to?(:pop) && _col_or_children.nil?
|
45
|
+
@column = _col_or_children if _col_or_children.respond_to?(:%)
|
46
|
+
@nodes = NodeSet.new(self, _col_or_children) if _col_or_children.respond_to?(:pop)
|
47
|
+
@nodes = NodeSet.new(self) if @nodes.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :line, :column, :doc
|
51
|
+
|
52
|
+
attr_accessor :nodes
|
53
|
+
end
|
54
|
+
|
55
|
+
module ElementGuts
|
56
|
+
# @return [Boolean] whether or not this has been written to file
|
57
|
+
def abstract?
|
58
|
+
line < 0 || column < 0
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param _doc [Doc] document to which this element belongs - recursively applies to all descendants of this node
|
62
|
+
# @return [Element] self
|
63
|
+
def set_doc!(_doc)
|
64
|
+
@doc = _doc
|
65
|
+
traverse do |node|
|
66
|
+
next if node === self or node.is_a?(String)
|
67
|
+
node.set_doc!(_doc)
|
68
|
+
end
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
# @see Ox::Element#<<
|
73
|
+
# this override reports changes to history; NewText for Strings, Add for elements
|
74
|
+
#
|
75
|
+
# @param obj [Element, Array] element or string to add to this Element; can insert arrays which are always inserted in order
|
76
|
+
# @return [Element] self
|
77
|
+
def <<(obj)
|
78
|
+
add(obj)
|
79
|
+
end
|
80
|
+
|
81
|
+
# @see #<<
|
82
|
+
# this version of the method allows insertions between existing elements
|
83
|
+
#
|
84
|
+
# @param obj [Element, Array] element or string to add to this Element; can insert arrays which are always inserted in order
|
85
|
+
# @param index [Fixnum] index at which to insert new node; inserts at end of element by default; when inserting arrays, index is incremented for each item to avoid reversing array order
|
86
|
+
# @return [Element] self
|
87
|
+
def add(obj, index=-1)
|
88
|
+
case
|
89
|
+
when obj.is_a?(Array), obj.is_a?(NodeSet)
|
90
|
+
obj.each_with_index do |e, i|
|
91
|
+
add(e, index == -1 ? index : index+i)
|
92
|
+
end
|
93
|
+
when obj.is_a?(String)
|
94
|
+
if obj[0] == '<' and obj[-1] == '>' and (s = Ox.parse(obj))
|
95
|
+
add dclone s
|
96
|
+
else
|
97
|
+
type = :NewText
|
98
|
+
nodes.insert(index, obj)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
type = :Add
|
102
|
+
nodes.insert(index, obj)
|
103
|
+
if obj.count_observers < 1 && @observer_peers
|
104
|
+
obj.add_observer(@observer_peers.first.first)
|
105
|
+
end
|
106
|
+
obj.set_doc! @doc
|
107
|
+
end
|
108
|
+
report(type, obj, index)
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param index_or_attr [String, Symbol, Fixnum] string or symbol of attribute or index of child node
|
113
|
+
# @return [Element, String] string if attribute value or text node; Element if XML node
|
114
|
+
def [](index_or_attr)
|
115
|
+
index_or_attr.is_a?(Fixnum) ? nodes[index_or_attr] : super(index_or_attr)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param attr_sym [String, Symbol, Fixnum] name of attribute or index of child to replace
|
119
|
+
# @param val [String] new attribute value or replacement child node
|
120
|
+
# @return [Element] self
|
121
|
+
def []=(attr_sym, val)
|
122
|
+
if attr_sym.is_a?(Fixnum)
|
123
|
+
remove nodes[attr_sym]
|
124
|
+
add(val, attr_sym) if val
|
125
|
+
return self
|
126
|
+
end
|
127
|
+
attr = attr_sym.to_s
|
128
|
+
raise "argument to [] must be a Symbol or a String." unless attr.is_a?(Symbol) or attr.is_a?(String)
|
129
|
+
args = [attr]
|
130
|
+
args << attributes[attr] if attributes[attr]
|
131
|
+
val.nil? ? @attributes.delete(attr) : super(attr, val)
|
132
|
+
type = args.size == 1 ? :NewAttr : :ChangeAttr
|
133
|
+
report(type, *args)
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [String] self description
|
138
|
+
def description
|
139
|
+
"<#{name}>"
|
140
|
+
end
|
141
|
+
|
142
|
+
# @return [Element] copy of this Element but with no children
|
143
|
+
def stub
|
144
|
+
Element.new(name, attributes)
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [HistoryClass] history that is observing this element for changes
|
148
|
+
def history
|
149
|
+
@observer_peers.first.first if @observer_peers.respond_to?(:any?) and @observer_peers.any? and @observer_peers.first.any?
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [String] XML string (overrides Ox's to_s which just prints the object pointer)
|
153
|
+
def to_s
|
154
|
+
s = %(<#{name})
|
155
|
+
attributes.each do |k,v| s << %( #{k.to_s}="#{v}") end
|
156
|
+
return s+'/>' if nodes.empty?
|
157
|
+
s << ">#{nodes.collect do |n| n.to_s end.join}</#{name}>"
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return #to_s
|
161
|
+
def inspect
|
162
|
+
to_s
|
163
|
+
end
|
164
|
+
|
165
|
+
# TODO do we need this method to take Fixnum node index as well?
|
166
|
+
# @param obj [Element] element child to delete
|
167
|
+
# @return [Element] deleted element
|
168
|
+
def delete(obj)
|
169
|
+
report(:Remove, @nodes.delete(obj))
|
170
|
+
obj
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :remove, :delete
|
174
|
+
|
175
|
+
# pre-order traverse through this node and all of its descendants
|
176
|
+
#
|
177
|
+
# @param &block [block] code to execute for each yielded node
|
178
|
+
def traverse(node=nil, &block)
|
179
|
+
return self.to_enum unless block_given?
|
180
|
+
node_stack = [node || self]
|
181
|
+
|
182
|
+
until node_stack.empty?
|
183
|
+
current = node_stack.shift
|
184
|
+
if current
|
185
|
+
yield current
|
186
|
+
node_stack = node_stack.insert(0, *current.nodes) if current.respond_to?(:nodes)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
node || self if block_given?
|
191
|
+
end
|
192
|
+
|
193
|
+
# traverse through just the children of this node
|
194
|
+
#
|
195
|
+
# @param &block [block] code to execute for each child node
|
196
|
+
def each(&block)
|
197
|
+
@nodes.each(&block)
|
198
|
+
end
|
199
|
+
|
200
|
+
# @return [String] namespace of element, derived from name e.g. '<duxml:element>' => 'duxml'
|
201
|
+
def name_space
|
202
|
+
return nil unless (i = name.index(':'))
|
203
|
+
name[0..i-1]
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# @param source [Element] if not explicitly provided, creates deep clone of this element; source can be any XML element (not only Duxml) that responds to traverse with pre-order traversal
|
208
|
+
# @return [Element] deep clone of source, including its attributes and recursively cloned children
|
209
|
+
def dclone(source = self)
|
210
|
+
input_stack = []
|
211
|
+
output_stack = []
|
212
|
+
traverse(source) do |node|
|
213
|
+
if node.is_a?(String)
|
214
|
+
output_stack.last << node
|
215
|
+
next
|
216
|
+
end
|
217
|
+
copy = Element.new(node.name, node.attributes)
|
218
|
+
if output_stack.empty?
|
219
|
+
output_stack << copy
|
220
|
+
input_stack << node
|
221
|
+
else
|
222
|
+
|
223
|
+
if input_stack.last.nodes.none? do |n| n === node end
|
224
|
+
input_stack.pop
|
225
|
+
output_stack.pop
|
226
|
+
end
|
227
|
+
|
228
|
+
output_stack.last << copy
|
229
|
+
if node.nodes.any?
|
230
|
+
output_stack << copy
|
231
|
+
input_stack << node
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
236
|
+
output_stack.pop
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [Element] shallow clone of this element, with all attributes and children only if none are Elements
|
240
|
+
def sclone
|
241
|
+
stub = Element.new(name, attributes)
|
242
|
+
stub << nodes if text?
|
243
|
+
stub
|
244
|
+
end
|
245
|
+
|
246
|
+
# @return [true, false] true if all child nodes are text only; false if any child nodes are XML
|
247
|
+
def text?
|
248
|
+
nodes.all? do |node| node.is_a?(String) end
|
249
|
+
end
|
250
|
+
end # class Element < Node
|
251
251
|
end # module Ox
|