duxml 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/bin/validate_xml +19 -0
  3. data/lib/duxml/doc/element.rb +157 -0
  4. data/lib/duxml/doc/lazy_ox.rb +153 -0
  5. data/lib/duxml/doc/node_set.rb +39 -0
  6. data/lib/duxml/doc.rb +26 -0
  7. data/lib/duxml/meta/grammar/pattern/attr_name_pattern.rb +36 -0
  8. data/lib/duxml/meta/grammar/pattern/attr_val_pattern.rb +36 -0
  9. data/lib/duxml/meta/grammar/pattern/child_pattern.rb +72 -0
  10. data/lib/duxml/meta/grammar/pattern/text_pattern.rb +30 -0
  11. data/lib/duxml/meta/grammar/pattern.rb +98 -0
  12. data/lib/duxml/meta/grammar/pattern_maker.rb +69 -0
  13. data/lib/duxml/meta/grammar/relax_ng/attrs_rule.rb +58 -0
  14. data/lib/duxml/meta/grammar/relax_ng/children_rule.rb +83 -0
  15. data/lib/duxml/meta/grammar/relax_ng/value_rule.rb +44 -0
  16. data/lib/duxml/meta/grammar/relax_ng.rb +40 -0
  17. data/lib/duxml/meta/grammar/rule/attrs_rule.rb +78 -0
  18. data/lib/duxml/meta/grammar/rule/children_rule.rb +137 -0
  19. data/lib/duxml/meta/grammar/rule/text_rule.rb +40 -0
  20. data/lib/duxml/meta/grammar/rule/value_rule.rb +111 -0
  21. data/lib/duxml/meta/grammar/rule.rb +59 -0
  22. data/lib/duxml/meta/grammar/spreadsheet.rb +35 -0
  23. data/lib/duxml/meta/grammar.rb +134 -0
  24. data/lib/duxml/meta/history/add.rb +36 -0
  25. data/lib/duxml/meta/history/change.rb +71 -0
  26. data/lib/duxml/meta/history/change_attr.rb +34 -0
  27. data/lib/duxml/meta/history/change_text.rb +33 -0
  28. data/lib/duxml/meta/history/error.rb +25 -0
  29. data/lib/duxml/meta/history/new_attr.rb +33 -0
  30. data/lib/duxml/meta/history/new_text.rb +37 -0
  31. data/lib/duxml/meta/history/qualify_error.rb +22 -0
  32. data/lib/duxml/meta/history/remove.rb +28 -0
  33. data/lib/duxml/meta/history/undo.rb +24 -0
  34. data/lib/duxml/meta/history/validate_error.rb +21 -0
  35. data/lib/duxml/meta/history.rb +88 -0
  36. data/lib/duxml/meta.rb +51 -0
  37. data/lib/duxml/reportable.rb +27 -0
  38. data/lib/duxml/ruby_ext/fixnum.rb +56 -0
  39. data/lib/duxml/ruby_ext/module.rb +12 -0
  40. data/lib/duxml/ruby_ext/object.rb +14 -0
  41. data/lib/duxml/ruby_ext/regexp.rb +20 -0
  42. data/lib/duxml/ruby_ext/string.rb +29 -0
  43. data/lib/duxml/saxer.rb +71 -0
  44. data/lib/duxml.rb +97 -0
  45. data/xml/dita_grammar.xml +2133 -0
  46. metadata +117 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a443f0b41070e85e05a9042711af460f1a9de0ce
4
+ data.tar.gz: c25d6801574a1730e3a38a5ccab2ed91b482d37b
5
+ SHA512:
6
+ metadata.gz: 2804f997a7fa48f9bd6c24ddef5e8b84df058b4bfbc1f6a41cbda340646378ef0d94a95a32b89cc1160819bb2d9bf37926c8196e2e8f5d9d70950a5baa973f3b
7
+ data.tar.gz: d2cac9f2b2c16c07b754f98224eea4da660857a73345215bed10824818cd837de15ce2fe98bb06a2100eb69171537fd270ae27dd6199710c1e740c88fd3d2711
data/bin/validate_xml ADDED
@@ -0,0 +1,19 @@
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}"
@@ -0,0 +1,157 @@
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
+ # operates in two modes:
22
+ # - from Ruby
23
+ # - from file
24
+ # in file mode, args provide Element's line and column location then freezes each Fixnum so it cannot be overwritten
25
+ # in Ruby mode, args are some combination of new attributes/values and/or child nodes (text or XML) with which to initialize this node
26
+ #
27
+ # @param name [String] name of element, in both Ruby and file modes
28
+ # @param _line_content [Fixnum, Array, Hash] line number of element file mode; if Array, new child nodes; if Hash, attributes; can be nil
29
+ # @param _col_or_children [Fixnum, Array] column position in file mode; if Array, new child nodes; can be nil
30
+ def initialize(name, _line_or_content=nil, _col_or_children=nil)
31
+ super name
32
+ @line = _line_or_content if _line_or_content.respond_to?(:%)
33
+ _line_or_content.each do |k,v| self[k] = v end if _line_or_content.respond_to?(:key)
34
+ @nodes = NodeSet.new(self, _line_or_content) if _line_or_content.respond_to?(:pop) && _col_or_children.nil?
35
+ @column = _col_or_children if _col_or_children.respond_to?(:%)
36
+ @nodes = NodeSet.new(self, _col_or_children) if _col_or_children.respond_to?(:pop)
37
+ @nodes = NodeSet.new(self) if @nodes.empty?
38
+ end
39
+
40
+ attr_reader :line, :column
41
+
42
+ attr_accessor :nodes
43
+ end
44
+
45
+ module ElementGuts
46
+ # @return [Boolean] whether or not this has been written to file
47
+ def abstract?
48
+ line < 0 || column < 0
49
+ end
50
+
51
+ # @see Ox::Element#<<
52
+ # this override reports changes to history; NewText for Strings, Add for elements
53
+ #
54
+ # @param obj [Element] element or string to add to this Element
55
+ # @return [Element] self
56
+ def <<(obj)
57
+ case
58
+ when obj.is_a?(Array), obj.is_a?(NodeSet)
59
+ obj.each do |e| self << e end
60
+ when obj.is_a?(String)
61
+ type = :NewText
62
+ super(obj)
63
+ else
64
+ type = :Add
65
+ super(obj)
66
+ if nodes.last.count_observers < 1 && @observer_peers
67
+ nodes.last.add_observer(@observer_peers.first.first)
68
+ end
69
+ end
70
+ report(type, nodes.size - 1)
71
+ self
72
+ end
73
+
74
+ # @param attr_sym [String, Symbol] name of attribute
75
+ # @param val [String]
76
+ # @return [Element] self
77
+ def []=(attr_sym, val)
78
+ attr = attr_sym.to_s
79
+ raise "argument to [] must be a Symbol or a String." unless attr.is_a?(Symbol) or attr.is_a?(String)
80
+ args = [attr]
81
+ args << attributes[attr] if attributes[attr]
82
+ super(attr, val)
83
+ type = args.size == 1 ? :NewAttr : :ChangeAttr
84
+ report(type, *args)
85
+ self
86
+ end
87
+
88
+ # @return [String] self description
89
+ def description
90
+ "<#{name}>"
91
+ end
92
+
93
+ # @return [Element] copy of this Element but with no children
94
+ def stub
95
+ Element.new(name, attributes)
96
+ end
97
+
98
+ # @return [HistoryClass] history that is observing this element for changes
99
+ def history
100
+ @observer_peers.first.first if @observer_peers.respond_to?(:any?) and @observer_peers.any? and @observer_peers.first.any?
101
+ end
102
+
103
+ # @return [String] XML string (overrides Ox's to_s which just prints the object pointer)
104
+ def to_s
105
+ s = %(<#{name})
106
+ attributes.each do |k,v| s << %( #{k.to_s}="#{v}") end
107
+ return s+'/>' if nodes.empty?
108
+ s << ">#{nodes.collect do |n| n.to_s end.join}</#{name}>"
109
+ end
110
+
111
+ # @return #to_s
112
+ def inspect
113
+ to_s
114
+ end
115
+
116
+ # TODO do we need this method to take Fixnum node index as well?
117
+ # @param obj [Element] element child to delete
118
+ # @return [Element] deleted element
119
+ def delete(obj)
120
+ report(:Remove, @nodes.delete(obj))
121
+ obj
122
+ end
123
+
124
+ alias_method :remove, :delete
125
+
126
+ # pre-order traverse through this node and all of its descendants
127
+ #
128
+ # @param &block [block] code to execute for each yielded node
129
+ def traverse(&block)
130
+ return self.to_enum unless block_given?
131
+ node_stack = [self]
132
+
133
+ until node_stack.empty?
134
+ current = node_stack.shift
135
+ if current
136
+ yield current
137
+ node_stack = node_stack.concat(current.nodes) if current.respond_to?(:nodes)
138
+ end
139
+ end
140
+
141
+ self if block_given?
142
+ end
143
+
144
+ # traverse through just the children of this node
145
+ #
146
+ # @param &block [block] code to execute for each child node
147
+ def each(&block)
148
+ @nodes.each(&block)
149
+ end
150
+
151
+ # @return [String] namespace of element, derived from name e.g. '<duxml:element>' => 'duxml'
152
+ def name_space
153
+ return nil unless (i = name.index(':'))
154
+ name[0..i-1]
155
+ end
156
+ end # class Element < Node
157
+ end # module Ox
@@ -0,0 +1,153 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../ruby_ext/string')
4
+
5
+
6
+ module Duxml
7
+ module LazyOx
8
+ # welcome to Lazy-Ox - where any method that doesn't exist, you can create on the fly and assign its methods to
9
+ # a corresponding Duxml::Element. see Regexp.nmtoken and String#nmtokenize and String#constantize to see how a given symbol
10
+ # can be converted into XML element names and vice versa. it can also use Class names as method calls to return children by type
11
+ #
12
+ # this method uses Ox::Element's :method_missing but adds an additional rescue block that:
13
+ # matches namespaced Ruby module to this Element's name and extends this node with module's methods
14
+ # then method matching symbol is called again with given arguments, yielding result to block if given, returning result if not
15
+ # e.g.
16
+ # module Duxml
17
+ # module Throwable
18
+ # def throw
19
+ # puts 'throwing!!'
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # Element.new('throwable').throw => 'throwing!!'
25
+ #
26
+ # if symbol name matches a class then method yields to block or returns as array child nodes that matches class
27
+ # you can further refine search results by adding the symbol of the child instance variable, including name, by which to filter
28
+ # if block given, returns first child for which block evaluates to true
29
+ #
30
+ # e.g.
31
+ # class Child; end
32
+ #
33
+ # n = Element.new('node')
34
+ # n << 'text'
35
+ # n << Element.new('child')
36
+ # n << Element.new('child')
37
+ # n.Element # returns Array of Element nodes
38
+ # => [#<Duxml::Element:0x0002 @value="child" ...>,
39
+ # #<Duxml::Element:0x0003 @value="child" ...>]
40
+ #
41
+ # n.Element.each do |child| child << 'some text' end # adding some text
42
+ # => ['text',
43
+ # #<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>,
44
+ # #<Duxml::Element 0x0003 @value="child" ... @nodes=['some text']>]
45
+ #
46
+ # n.Element do |child| child.nodes.first == 'some text' end # returns all children for which block is true
47
+ # => [#<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>]
48
+ #
49
+ # %w(bar mar).each_with_index do |x, i| next if i.zero?; n.Child[:foo] = x end # adding some attributes
50
+ # => ['text',
51
+ # #<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
52
+ # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
53
+ #
54
+ # n.Element(:foo) # returns array of Child nodes with attribute :foo
55
+ # => [#<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
56
+ # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
57
+ #
58
+ # n.Element(foo: 'bar') # returns array of Child nodes with attribute :foo equal to 'bar'
59
+ # => [#<Duxml::Element:0xfff @value="child" @attributes={foo: 'bar'} ...>]
60
+ #
61
+ #
62
+ # if element name has no matching Class or Module in namespace,
63
+ # if symbol is lower case, it is made into a method, given &block as definition, then called with *args
64
+ # e.g. n.change_color('blue') do |new_color| => #<Duxml::Element:0xfff @value="node" @attributes={color: 'blue'} @nodes=[]>
65
+ # @color = new_color
66
+ # self
67
+ # end
68
+ # n.color => 'blue'
69
+ # n.change_color('mauve').color => 'mauve'
70
+ #
71
+ # @param sym [Symbol] method, class or module
72
+ # @param *args [*several_variants] either arguments to method or initializing values for instance of given class
73
+ # @param &block [block] if yielding result, yields to given block; if defining new method, block defines its contents
74
+ def method_missing(sym, *args, &block)
75
+ super(sym, *args, &block)
76
+ rescue NoMethodError
77
+ # handling Constant look up to dynamically extend or add to element
78
+ if lowercase?(sym)
79
+ if (const = look_up_const) and const.is_a?(Module)
80
+ extend const
81
+ result = method(sym).call(*args)
82
+ return(result) unless block_given?
83
+ yield(result)
84
+ elsif block_given?
85
+ new_method = proc(&block)
86
+ self.const_set(sym, new_method)
87
+ return new_method.call *args
88
+ else
89
+ raise NoMethodError, "undefined method `#{sym.to_s}' for #{description}"
90
+ end # if (const = look_up_const) ... elsif block_given? ... else ...
91
+ else
92
+ results = filter(sym, args)
93
+ return results unless block_given?
94
+ results.keep_if do |node| yield(node) end
95
+ end # if lowercase? ... else ...
96
+ end # def method_missing(sym, *args, &block)
97
+
98
+ private
99
+
100
+ # @param sym [Symbol] indicates which element type is being filtered for
101
+ # @param args [several_variants] arguments for filtering element children that matched 'sym'
102
+ # @return [[Element]] Elements of type 'sym' that match criteria 'args'
103
+ def filter(sym, args)
104
+ class_nodes = nodes.select do |node|
105
+ node.name == sym.to_s.nmtokenize or simple_class(node) == sym.to_s
106
+ end
107
+ class_nodes.keep_if do |node|
108
+ if args.empty?
109
+ true
110
+ else
111
+ args.any? do |arg|
112
+ if arg.is_a?(Hash)
113
+ node[arg.first.first] == arg.first.last
114
+ else
115
+ !node[arg].nil?
116
+ end
117
+ end
118
+ end # if args.empty? ... else ...
119
+ end # class_nodes.keep_if do |node|
120
+ end # def filter(args)
121
+
122
+ # @param maudule [Module] module context in which to look for duck-called method's module
123
+ # @return [Module, Class] requested module or class
124
+ def look_up_const(maudule = Duxml)
125
+ mod_names = name.split(':')
126
+ until mod_names.empty?
127
+ word = mod_names.shift
128
+ k = word.constantize
129
+ if maudule.const_defined?(k, true) or Module.const_defined?(simple_class, true)
130
+ const = maudule.const_get(k)
131
+ if const.is_a?(Module)
132
+ maudule = const
133
+ end
134
+
135
+ return const if mod_names.empty? and [Module, Class].include?(const.class)
136
+ end
137
+ end
138
+ nil
139
+ end
140
+
141
+ # @param sym [Symbol] symbol for a constant
142
+ # @return [Boolean] is symbol lowercase?
143
+ def lowercase?(sym)
144
+ sym.to_s[0].match(/[A-Z]/).nil?
145
+ end
146
+
147
+ # @param obj [Object] usually Element
148
+ # @return [String] name of final Class or Module of self
149
+ def simple_class(obj=self)
150
+ obj.class.to_s.split('::').last
151
+ end
152
+ end # module LazyOx
153
+ end # module Duxml
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require 'observer'
4
+
5
+ module Duxml
6
+ # subclass of Array that is Observable by History
7
+ # used to track changes in String nodes of XML Element
8
+ class NodeSet < Array
9
+ include Observable
10
+
11
+ @parent
12
+
13
+ attr_reader :parent
14
+
15
+ # @param _parent [Element] Element that is parent to this NodeSet's elements
16
+ # @param ary [[String, Element]] child nodes with which to initialize this NodeSet
17
+ def initialize(_parent, ary=[])
18
+ super ary
19
+ @parent = _parent
20
+ end
21
+
22
+ # @return [HistoryClass] object that observes this NodeSet for changes
23
+ def history
24
+ @observer_peers.first.first if @observer_peers and @observer_peers.first.any?
25
+ end
26
+
27
+ # @param index [Fixnum] index of array where old String is to be replaced
28
+ # @param str [String] replacing String
29
+ # @return [self] reports old String and index to history
30
+ def []=(index, str)
31
+ raise Exception if count_observers < 1
32
+ old_str = self[index]
33
+ super(index, str)
34
+ changed
35
+ notify_observers(:ChangeText, parent, index, old_str)
36
+ self
37
+ end
38
+ end # class NodeSet < Array
39
+ end # module Duxml
data/lib/duxml/doc.rb ADDED
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/doc/element')
4
+
5
+ module Duxml
6
+ class Doc < ::Ox::Document
7
+ include ElementGuts
8
+ def initialize(prolog={})
9
+ super(prolog)
10
+ self[:version] ||= '1.0'
11
+ @nodes = NodeSet.new(self)
12
+ # TODO should also create new metadata!!
13
+ end
14
+
15
+ def write_to(path)
16
+ s = attributes.collect do |k, v| %( #{k}="#{v}") end.join
17
+ %(<?xml #{s}?>)+nodes.first.to_s
18
+ File.write(path, s)
19
+ self
20
+ end
21
+
22
+ def to_s
23
+ "#<#{self.class.to_s} @object_id='#{object_id}' @root='#{root.nil? ? '' : root.description}'>"
24
+ end
25
+ end # class Document < Element
26
+ end
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../grammar/pattern')
4
+
5
+ module Duxml
6
+ module AttrNamePattern; end
7
+ # pattern representing relationship between an object and one of its attributes
8
+ class AttrNamePatternClass < PatternClass
9
+ include AttrNamePattern
10
+
11
+ # @param _subject [Duxml::Element] subject doc
12
+ # @param _attr_name [String] name of attribute
13
+ def initialize(_subject, _attr_name)
14
+ @attr_name = _attr_name
15
+ super _subject
16
+ end
17
+
18
+ attr_reader :attr_name
19
+ end
20
+
21
+ module AttrNamePattern
22
+ def relationship
23
+ 'attribute'
24
+ end
25
+
26
+ # @return [Boolean] true if subject does not have the attr_name; false otherwise
27
+ def abstract?
28
+ subject[attr_name].nil?
29
+ end
30
+
31
+ def description
32
+ abstract? ? "#{subject.description} does not have #{relationship} [#{attr_name}]" :
33
+ "#{subject.description}'s #{relationship} [#{attr_name}]"
34
+ end
35
+ end # class AttrNamePattern
36
+ end # module Duxml
@@ -0,0 +1,36 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../grammar/pattern')
4
+
5
+ module Duxml
6
+ module AttrValPattern; end
7
+
8
+ # pattern representing relationship between an object's attribute and its value
9
+ class AttrValPatternClass < PatternClass
10
+ include AttrValPattern
11
+
12
+ # @param _subject [Element] subject element
13
+ # @param _attr_name [String] name of attribute whose value is the object of this pattern
14
+ def initialize(_subject, _attr_name)
15
+ @attr_name = _attr_name
16
+ super _subject
17
+ end
18
+
19
+ attr_reader :subject, :attr_name
20
+ end
21
+
22
+ module AttrValPattern
23
+ def relationship
24
+ 'value'
25
+ end
26
+
27
+ def description
28
+ "#{subject.description}'s @#{attr_name} #{relationship} of '#{value}'"
29
+ end
30
+
31
+ # current value of this attribute
32
+ def value
33
+ subject[attr_name]
34
+ end
35
+ end # class AttrValPattern
36
+ end # module Duxml
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../grammar/pattern')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../../ruby_ext/fixnum')
5
+
6
+ module Duxml
7
+ module ChildPattern; end
8
+ # pattern representing relationship between an object and its child
9
+ class ChildPatternClass < PatternClass
10
+ include ChildPattern
11
+
12
+ # @param _subject [Duxml::Element] parent element
13
+ # @param _index [Fixnum] index of child
14
+ def initialize(_subject, _index)
15
+ @index = _index
16
+ super _subject
17
+ end
18
+
19
+ alias_method :parent, :subject
20
+ attr_reader :index
21
+ end # class ChildPatternClass
22
+
23
+ # null child patterns represent and parent child relationship where the child
24
+ # is required by the Grammar but the element is missing that child
25
+ class NullChildPatternClass < PatternClass
26
+ include ChildPattern
27
+
28
+ # @param _subject [Element] parent element
29
+ # @param _missing_child [String] nmtoken for missing child element
30
+ def initialize(_subject, _missing_child)
31
+ @missing_child = _missing_child
32
+ super _subject
33
+ end
34
+
35
+ # @return [-1] class must respond to #index; only NullChildPatternClass is allowed to have a negative index
36
+ def index
37
+ -1
38
+ end
39
+
40
+ def relationship
41
+ 'missing child'
42
+ end
43
+
44
+ # @return [String] description of this child pattern
45
+ def description
46
+ "#{subject.description} #{relationship} <#{child}>"
47
+ end
48
+
49
+ attr_reader :missing_child
50
+ alias_method :child, :missing_child
51
+ alias_method :parent, :subject
52
+ end
53
+
54
+ module ChildPattern
55
+ # @return [Element] child element
56
+ def child
57
+ subject.nodes[index]
58
+ end
59
+
60
+ alias_method :object, :child
61
+
62
+ # @return [String] describes relationship between parent and child
63
+ def relationship
64
+ "#{(index+1).ordinal_name} #{super}"
65
+ end
66
+
67
+ # @return [String] description of this child pattern
68
+ def description
69
+ "#{subject.description}'s #{relationship} #{child.description}"
70
+ end
71
+ end
72
+ end # module Duxml
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../grammar/pattern')
4
+
5
+ module Duxml
6
+ module TextPattern; end
7
+ # pattern representing relationship between an object and its text-only child
8
+ class TextPatternClass < PatternClass
9
+ include TextPattern
10
+
11
+ # @param _subject [Ox::Element] parent of text node
12
+ # @param _index [Fixnum] index of text node
13
+ def initialize(_subject, _index)
14
+ @index = _index
15
+ super _subject
16
+ end
17
+
18
+ attr_reader :subject, :index
19
+ end
20
+
21
+ module TextPattern
22
+ def text
23
+ subject.nodes[index]
24
+ end
25
+
26
+ def description
27
+ "#{subject.description}'s #{relationship} is '#{text}'"
28
+ end
29
+ end # class ContentPattern
30
+ end # module Duxml
@@ -0,0 +1,98 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../../ruby_ext/string')
4
+
5
+ module Duxml
6
+ # Patterns represent a relationship between a specific XML Element and
7
+ # a specific or hypothetical attribute, attribute value, text value or child Element
8
+ # For example, given an XML Element: '<element attr='value'/>', there exists a valid PatternClass
9
+ # that represents the relationship betweeen <element> and the attribute value 'value'
10
+ # There can also exists a hypothetical relationship between <element> and a new child <child/> that
11
+ # is defined as an allowed child Element in the Rules that apply to this Element.
12
+ module Pattern; end
13
+
14
+ # as an object, a Pattern consists of a subject and may or may not have an object
15
+ # a Pattern without an object represents a childless empty node with no attributes
16
+ # this class must be subclassed to be used; there is one for each type of XML relationship
17
+ # that an Element can have up to one degree of separation, that is, grandparent relationships are not considered
18
+ # and neither are the attributes of children
19
+ class PatternClass
20
+ include Pattern
21
+ @subject
22
+
23
+ # @param subj [Element] specific Element that is the subject of this pattern
24
+ def initialize(subj)
25
+ @subject = subj
26
+ end
27
+
28
+ attr_reader :subject
29
+ end
30
+
31
+ module Pattern
32
+ include Duxml
33
+
34
+ # @return [String] nmtoken name of this pattern without namespace prefix e.g. ChildPattern.new(parent, child).name => 'child_pattern'
35
+ def simple_name
36
+ name.split(':').last
37
+ end
38
+
39
+ # @return [Boolean] if either subject or object points to a name/type i.e. not an Element
40
+ def abstract?
41
+ subject.is_a?(String) or object.nil?
42
+ end
43
+
44
+ # @return [Boolean] if both subject and at least one object point to an actual XML Element
45
+ def concrete?
46
+ !abstract?
47
+ end
48
+
49
+ # @return [String] nmtoken name of this pattern e.g. ChildPattern.new(parent, child).name => 'duxml:child_pattern'
50
+ def name
51
+ c = self.class.to_s
52
+ return c.nmtokenize unless c.include?('::')
53
+ a = c.split('::')
54
+ a[-2..-1].collect do |word|
55
+ word.nmtokenize
56
+ end.join(':')
57
+ end
58
+
59
+ # returns relationship description as string by subtracting super class name
60
+ # (e.g. 'pattern' or 'rule') from simple_class
61
+ # Duxml::ChildrenRule#relationship => 'children'
62
+ # Duxml::TextPattern#relationship => 'text'
63
+ # can be overridden if class name does not match human-readable string
64
+ # @return [String] single word to describe relationship of subject to object
65
+ def relationship
66
+ simple_name.split('_').first
67
+ end
68
+
69
+ # @return [String] "#{object.description} is #{relationship} of #{subject.description}"
70
+ def description
71
+ "#{object.description} is #{relationship} of #{subject.description}"
72
+ end
73
+
74
+ # @return [Element] will only return non-nil value when pattern represents relationship with a child Element
75
+ def object
76
+ return @object if instance_variable_defined?(:@object)
77
+ instance_variables.each do |var|
78
+ target = instance_variable_get(var)
79
+ return target unless target.is_a?(String) or target == subject
80
+ end
81
+ nil
82
+ end
83
+
84
+ # @param pattern [Duxml::Pattern] pattern or any subclass object
85
+ # @return [Fixnum] first applies <=> to subjects, and if equal, applies <=> to objects
86
+ def <=>(pattern)
87
+ return 1 unless pattern.respond_to?(:subject)
88
+ case subject <=> pattern.subject
89
+ when -1 then
90
+ -1
91
+ when 0 then
92
+ object <=> pattern.object
93
+ else
94
+ -1
95
+ end
96
+ end # def <=>
97
+ end # module Pattern
98
+ end # module Duxml