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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/validate_xml +30 -30
  3. data/lib/duxml.rb +76 -76
  4. data/lib/duxml/doc.rb +108 -91
  5. data/lib/duxml/doc/element.rb +250 -250
  6. data/lib/duxml/doc/lazy_ox.rb +167 -167
  7. data/lib/duxml/doc/node_set.rb +38 -38
  8. data/lib/duxml/meta.rb +72 -72
  9. data/lib/duxml/meta/grammar.rb +133 -133
  10. data/lib/duxml/meta/grammar/pattern.rb +111 -111
  11. data/lib/duxml/meta/grammar/pattern/attr_name_pattern.rb +36 -36
  12. data/lib/duxml/meta/grammar/pattern/attr_val_pattern.rb +36 -36
  13. data/lib/duxml/meta/grammar/pattern/child_pattern.rb +60 -60
  14. data/lib/duxml/meta/grammar/pattern/text_pattern.rb +31 -31
  15. data/lib/duxml/meta/grammar/pattern_maker.rb +68 -68
  16. data/lib/duxml/meta/grammar/relax_ng.rb +39 -39
  17. data/lib/duxml/meta/grammar/relax_ng/attrs_rule.rb +58 -58
  18. data/lib/duxml/meta/grammar/relax_ng/children_rule.rb +82 -82
  19. data/lib/duxml/meta/grammar/relax_ng/value_rule.rb +44 -44
  20. data/lib/duxml/meta/grammar/rule.rb +58 -58
  21. data/lib/duxml/meta/grammar/rule/attrs_rule.rb +77 -77
  22. data/lib/duxml/meta/grammar/rule/children_rule.rb +135 -135
  23. data/lib/duxml/meta/grammar/rule/text_rule.rb +39 -39
  24. data/lib/duxml/meta/grammar/rule/value_rule.rb +110 -110
  25. data/lib/duxml/meta/grammar/spreadsheet.rb +34 -34
  26. data/lib/duxml/meta/history.rb +88 -88
  27. data/lib/duxml/meta/history/add.rb +30 -30
  28. data/lib/duxml/meta/history/change.rb +70 -70
  29. data/lib/duxml/meta/history/change_attr.rb +33 -33
  30. data/lib/duxml/meta/history/change_text.rb +32 -32
  31. data/lib/duxml/meta/history/error.rb +24 -24
  32. data/lib/duxml/meta/history/new_attr.rb +32 -32
  33. data/lib/duxml/meta/history/new_text.rb +36 -36
  34. data/lib/duxml/meta/history/qualify_error.rb +21 -21
  35. data/lib/duxml/meta/history/remove.rb +27 -27
  36. data/lib/duxml/meta/history/undo.rb +23 -23
  37. data/lib/duxml/meta/history/validate_error.rb +20 -20
  38. data/lib/duxml/reportable.rb +28 -28
  39. data/lib/duxml/ruby_ext/fixnum.rb +55 -55
  40. data/lib/duxml/ruby_ext/module.rb +11 -11
  41. data/lib/duxml/ruby_ext/object.rb +13 -13
  42. data/lib/duxml/ruby_ext/regexp.rb +19 -19
  43. data/lib/duxml/ruby_ext/string.rb +37 -37
  44. data/lib/duxml/saxer.rb +75 -75
  45. metadata +12 -12
@@ -1,59 +1,59 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/pattern')
4
- require File.expand_path(File.dirname(__FILE__) + '/../../reportable')
5
-
6
- module Duxml
7
- # scans DTD statements for XML rule operators
8
- Struct.new 'Scanner', :match, :operator
9
-
10
- # contains methods to apply Duxml::RuleClass to XML edit or validate events
11
- module Rule
12
- include Reportable
13
- end
14
-
15
- # as an object, Rules consist of a subject, representing the XML Element, and a statement representing the Rule's logic
16
- # this class cannot be used alone and is rather subclassed, one for each type of possible XML changes
17
- class RuleClass < PatternClass
18
- include Rule
19
-
20
- # @param subj [String] NMTOKEN name of element this rule applies to
21
- # @param _statement [String, Regexp] string statement of rule in DTD declaration form or Regexp
22
- def initialize(subj, _statement)
23
- @statement = _statement
24
- @object = nil
25
- super subj
26
- end
27
-
28
- attr_reader :statement, :object
29
- end
30
-
31
- module Rule
32
- # Duxml::Rule's #qualify is only used to report errors found by its subclasses' #qualify methods
33
- # @param change_or_pattern [Duxml::Pattern, Duxml::Change] Change or Pattern to be reported for Rule violation
34
- # @return [Boolean] always false; this method should always be subclassed to apply that specific rule type's #qualify
35
- def qualify(change_or_pattern)
36
- type = (change_or_pattern.respond_to?(:time_stamp)) ? :QualifyError : :ValidateError
37
- report(type, change_or_pattern)
38
- false
39
- end
40
-
41
- # @return [HistoryClass] history to which this rule will report errors
42
- def history
43
- @observer_peers.first.first if @observer_peers.any? and @observer_peers.first.any?
44
- end
45
-
46
- # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
47
- # @return [Boolean] whether this rule does in fact apply
48
- def applies_to?(change_or_pattern)
49
- pattern_type = change_or_pattern.subject.name
50
- subject == pattern_type
51
- end
52
-
53
- # @return [String] default description for a Rule
54
- def description
55
- statement_str = (statement.is_a?(String) ? statement : statement.inspect).gsub('\b','')
56
- %(#{relationship.capitalize} Rule that <#{subject}>'s #{relationship} must match '#{statement_str}')
57
- end
58
- end # module Rule
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/pattern')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../reportable')
5
+
6
+ module Duxml
7
+ # scans DTD statements for XML rule operators
8
+ Struct.new 'Scanner', :match, :operator
9
+
10
+ # contains methods to apply Duxml::RuleClass to XML edit or validate events
11
+ module Rule
12
+ include Reportable
13
+ end
14
+
15
+ # as an object, Rules consist of a subject, representing the XML Element, and a statement representing the Rule's logic
16
+ # this class cannot be used alone and is rather subclassed, one for each type of possible XML changes
17
+ class RuleClass < PatternClass
18
+ include Rule
19
+
20
+ # @param subj [String] NMTOKEN name of element this rule applies to
21
+ # @param _statement [String, Regexp] string statement of rule in DTD declaration form or Regexp
22
+ def initialize(subj, _statement)
23
+ @statement = _statement
24
+ @object = nil
25
+ super subj
26
+ end
27
+
28
+ attr_reader :statement, :object
29
+ end
30
+
31
+ module Rule
32
+ # Duxml::Rule's #qualify is only used to report errors found by its subclasses' #qualify methods
33
+ # @param change_or_pattern [Duxml::Pattern, Duxml::Change] Change or Pattern to be reported for Rule violation
34
+ # @return [Boolean] always false; this method should always be subclassed to apply that specific rule type's #qualify
35
+ def qualify(change_or_pattern)
36
+ type = (change_or_pattern.respond_to?(:time_stamp)) ? :QualifyError : :ValidateError
37
+ report(type, change_or_pattern)
38
+ false
39
+ end
40
+
41
+ # @return [HistoryClass] history to which this rule will report errors
42
+ def history
43
+ @observer_peers.first.first if @observer_peers.any? and @observer_peers.first.any?
44
+ end
45
+
46
+ # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
47
+ # @return [Boolean] whether this rule does in fact apply
48
+ def applies_to?(change_or_pattern)
49
+ pattern_type = change_or_pattern.subject.name
50
+ subject == pattern_type
51
+ end
52
+
53
+ # @return [String] default description for a Rule
54
+ def description
55
+ statement_str = (statement.is_a?(String) ? statement : statement.inspect).gsub('\b','')
56
+ %(#{relationship.capitalize} Rule that <#{subject}>'s #{relationship} must match '#{statement_str}')
57
+ end
58
+ end # module Rule
59
59
  end # module Duxml
@@ -1,78 +1,78 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
-
5
- module Duxml
6
- # methods for applying a rule on what attribute names a given Element is allowed to have
7
- module AttrsRule; end
8
-
9
- # do not subclass this! as object, consists of subject and a Regexp describing allowable attribute names
10
- # as well as a String indicating with DTD symbols for whether the given attributes are allowed or required
11
- class AttrsRuleClass < RuleClass
12
- include AttrsRule
13
-
14
- # @param _subject [String] name of the Element
15
- # @param attrs [String] the attribute name pattern in Regexp form
16
- # @param required [String] the requirement level - optional i.e. #IMPLIED by default
17
- def initialize(_subject, attrs, required='#IMPLIED')
18
- formatted_statement = attrs.gsub('-', '__dash__').gsub(/\b/, '\b').gsub('-', '__dash__')
19
- @requirement = required
20
- super(_subject, formatted_statement)
21
- end
22
-
23
- attr_reader :requirement
24
- end
25
-
26
- module AttrsRule
27
- # @param change_or_pattern [Duxml::Pattern] checks an doc of type change_or_pattern.subject against change_or_pattern
28
- # @return [Boolean] whether or not given pattern passed this test
29
- def qualify(change_or_pattern)
30
- @object = change_or_pattern
31
- result = pass change_or_pattern
32
- super change_or_pattern unless result
33
- @object = nil
34
- result
35
- end
36
-
37
- # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
38
- # @return [Boolean] whether this rule does in fact apply
39
- def applies_to?(change_or_pattern)
40
- return false unless change_or_pattern.respond_to?(:attr_name)
41
- return false unless super(change_or_pattern)
42
- return false if change_or_pattern.respond_to?(:value) && !change_or_pattern.respond_to?(:time_stamp)
43
- statement.include?(change_or_pattern.attr_name.to_s)
44
- end
45
-
46
- # @return [Boolean] whether or not this attribute is required
47
- def required?
48
- requirement == '#REQUIRED'
49
- end
50
-
51
- # @return [String] name of attribute to which this rule applies
52
- def attr_name
53
- statement.gsub('\b','')
54
- end
55
-
56
- # @return [String] describes relationship in a word
57
- def relationship
58
- 'attributes'
59
- end
60
-
61
- # @return [String] description of self; overrides super to account for cases of missing, required attributes
62
- def description
63
- %(#{relationship.capitalize} Rule that <#{subject}>'s #{relationship} #{required? ? 'must':'can'} include '#{attr_name}')
64
- end
65
-
66
- # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern to be evaluated
67
- # @return [Boolean] true if this rule does not apply to param; false if pattern is for a missing required attribute
68
- # otherwise returns whether or not any illegal attributes exist
69
- def pass(change_or_pattern)
70
- if change_or_pattern.respond_to?(:time_stamp)
71
- an = change_or_pattern.attr_name.to_s
72
- attr_name.include?(an) && !change_or_pattern.abstract?
73
- else
74
- !change_or_pattern.abstract?
75
- end
76
- end # def pass
77
- end # class AttributesRule
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
+
5
+ module Duxml
6
+ # methods for applying a rule on what attribute names a given Element is allowed to have
7
+ module AttrsRule; end
8
+
9
+ # do not subclass this! as object, consists of subject and a Regexp describing allowable attribute names
10
+ # as well as a String indicating with DTD symbols for whether the given attributes are allowed or required
11
+ class AttrsRuleClass < RuleClass
12
+ include AttrsRule
13
+
14
+ # @param _subject [String] name of the Element
15
+ # @param attrs [String] the attribute name pattern in Regexp form
16
+ # @param required [String] the requirement level - optional i.e. #IMPLIED by default
17
+ def initialize(_subject, attrs, required='#IMPLIED')
18
+ formatted_statement = attrs.gsub('-', '__dash__').gsub(/\b/, '\b').gsub('-', '__dash__')
19
+ @requirement = required
20
+ super(_subject, formatted_statement)
21
+ end
22
+
23
+ attr_reader :requirement
24
+ end
25
+
26
+ module AttrsRule
27
+ # @param change_or_pattern [Duxml::Pattern] checks an doc of type change_or_pattern.subject against change_or_pattern
28
+ # @return [Boolean] whether or not given pattern passed this test
29
+ def qualify(change_or_pattern)
30
+ @object = change_or_pattern
31
+ result = pass change_or_pattern
32
+ super change_or_pattern unless result
33
+ @object = nil
34
+ result
35
+ end
36
+
37
+ # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
38
+ # @return [Boolean] whether this rule does in fact apply
39
+ def applies_to?(change_or_pattern)
40
+ return false unless change_or_pattern.respond_to?(:attr_name)
41
+ return false unless super(change_or_pattern)
42
+ return false if change_or_pattern.respond_to?(:value) && !change_or_pattern.respond_to?(:time_stamp)
43
+ statement.include?(change_or_pattern.attr_name.to_s)
44
+ end
45
+
46
+ # @return [Boolean] whether or not this attribute is required
47
+ def required?
48
+ requirement == '#REQUIRED'
49
+ end
50
+
51
+ # @return [String] name of attribute to which this rule applies
52
+ def attr_name
53
+ statement.gsub('\b','')
54
+ end
55
+
56
+ # @return [String] describes relationship in a word
57
+ def relationship
58
+ 'attributes'
59
+ end
60
+
61
+ # @return [String] description of self; overrides super to account for cases of missing, required attributes
62
+ def description
63
+ %(#{relationship.capitalize} Rule that <#{subject}>'s #{relationship} #{required? ? 'must':'can'} include '#{attr_name}')
64
+ end
65
+
66
+ # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern to be evaluated
67
+ # @return [Boolean] true if this rule does not apply to param; false if pattern is for a missing required attribute
68
+ # otherwise returns whether or not any illegal attributes exist
69
+ def pass(change_or_pattern)
70
+ if change_or_pattern.respond_to?(:time_stamp)
71
+ an = change_or_pattern.attr_name.to_s
72
+ attr_name.include?(an) && !change_or_pattern.abstract?
73
+ else
74
+ !change_or_pattern.abstract?
75
+ end
76
+ end # def pass
77
+ end # class AttributesRule
78
78
  end # module Duxml
@@ -1,136 +1,136 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
- require File.expand_path(File.dirname(__FILE__) + '/../../../doc/element')
5
-
6
- module Duxml
7
- # methods to apply rule on what children an Element may have
8
- module ChildrenRule; end
9
-
10
- # do not subclass! rule that states what children and how many a given element is allowed to have where @statement
11
- # is a DTD/Regexp of what children are allowed, how many, in what order and whether they are required
12
- class ChildrenRuleClass < RuleClass
13
- include ChildrenRule
14
- # child rules are initialized from DTD doc declarations e.g. (zeroOrMore|other-first-child)*,second-child-optional?,third-child-gt1+
15
- #
16
- # @param _subject [String] name of the doc the rule applies
17
- # @param _statement [String] DTD-style statement of the rule
18
- def initialize(_subject, _statement)
19
- formatted_statement = _statement
20
- .gsub(/[\<>]/, '')
21
- .gsub(/#PCDATA/, 'PCDATA')
22
- .gsub('-','_dash_')
23
- .gsub(/\b/,'\b')
24
- .gsub(/PCDATA/, '#PCDATA')
25
- .gsub('_dash_', '-')
26
- .gsub(/\s/,'')
27
- super(_subject, formatted_statement)
28
- end
29
- end # class ChildrenRuleClass
30
-
31
- module ChildrenRule
32
- # @param change_or_pattern [Duxml::ChildPattern, Duxml::Add, Duxml::Remove] to be evaluated to see if it follows this rule
33
- # @return [Boolean] whether or not change_or_pattern#subject is allowed to have #object as its child
34
- # if false, Error is reported to History
35
- def qualify(change_or_pattern)
36
- @object = change_or_pattern
37
- result = pass
38
- super change_or_pattern unless result
39
- @object = nil
40
- result
41
- end
42
-
43
- # @return [Array[String]] in order, array of child doc types required by this rule
44
- def required_children
45
- req_scans = get_scanners.select do |scanner| scanner[:operator].match(/[\*\?]/).nil? end
46
- req_scans.collect do |req_scan|
47
- get_child_name req_scan unless get_child_name(req_scan) == 'EMPTY'
48
- end.compact
49
- end
50
-
51
- # @param change_or_pattern [Duxml::Pattern, Duxml::Change] change or pattern to be evaluated
52
- # @return [Boolean] whether subjects agree, and change or pattern is not a Rule and responds to #affected_parent
53
- def applies_to?(change_or_pattern)
54
- case
55
- when change_or_pattern.is_a?(Duxml::Rule) then false
56
- when super(change_or_pattern) && change_or_pattern.respond_to?(:parent)
57
- true
58
- else
59
- false
60
- end
61
- end
62
-
63
- private
64
-
65
- # @param req_scan [Struct::Scanner] scanner with which to identify a child name within @statement
66
- # @return [String] name of child that is currently being checked for by this rule
67
- def get_child_name(req_scan)
68
- req_scan[:match].inspect.split(/[\/(\\b)]/).select do |word| !word.empty? end.first
69
- end
70
-
71
- # @return [[Struct::Scanner]] array of scanners for each logical component of @statement to be applied to target Element's children
72
- def get_scanners
73
- statement.split(',').collect do |rule|
74
- r = rule.gsub(/[\(\)]/, '')
75
- operator = r[-1].match(/[\?\+\*]/) ? r[-1] : ''
76
- r = r[0..-2] unless operator.empty?
77
- Struct::Scanner.new Regexp.new(r), operator
78
- end
79
- end
80
-
81
- attr_reader :child_stack
82
-
83
- # @return [Boolean] whether or not this rule has been passed
84
- def pass
85
- @child_stack = object.child.nil? ? [] : object.parent.nodes.clone
86
- scanners = get_scanners
87
- scanner = scanners.shift
88
- result = false
89
- loop do
90
- child = @child_stack.shift
91
- case
92
- when child.nil?, scanner.nil? then break
93
- when child.is_a?(String)
94
- result = scanner[:match].inspect.include?('#PCDATA')
95
- when child.name.match(scanner[:match]) # scanner matches this child
96
- if scanner[:operator]=='?' or scanner[:operator]=='' # shift scanners if we only need one child of this type
97
- scanner = scanners.shift
98
- result = previous_child.nil? || (previous_child.name != child.name)
99
- else
100
- result = true
101
- end
102
- # scanner does not match this child...
103
- when %w(? *).include?(scanner[:operator]) # optional scanner so try next scanner on same child
104
- scanner = scanners.shift
105
- @child_stack.unshift child
106
- result = !scanner.nil?
107
- else
108
- result = false # else, this scanner will report false
109
- end # case
110
- return result unless child.is_a?(String) or !matching_index?# don't need to keep looping because we've scanned our target
111
- end # loop do
112
-
113
- # checking to see if any required children were not present
114
- result = false if child_stack.empty? && scanners.any? do |scanner|
115
- scanner[:operator] != '*' or scanner[:operator] != '?'
116
- end
117
- result
118
- end # def pass
119
-
120
- # @return [Boolean] whether the child currently being scanned is the one in the pattern we are testing now
121
- def matching_index?
122
- child_index == object.index
123
- end
124
-
125
- # @return [Fixnum] index of child currently being scanned
126
- def child_index
127
- object.parent.nodes.size-child_stack.size-1
128
- end
129
-
130
- # @return [Element] previous element to the one being scanned
131
- def previous_child
132
- index = child_index - 1
133
- index < 0 ? nil : object.parent.nodes[index]
134
- end
135
- end # module ChildrenRule
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../../doc/element')
5
+
6
+ module Duxml
7
+ # methods to apply rule on what children an Element may have
8
+ module ChildrenRule; end
9
+
10
+ # do not subclass! rule that states what children and how many a given element is allowed to have where @statement
11
+ # is a DTD/Regexp of what children are allowed, how many, in what order and whether they are required
12
+ class ChildrenRuleClass < RuleClass
13
+ include ChildrenRule
14
+ # child rules are initialized from DTD doc declarations e.g. (zeroOrMore|other-first-child)*,second-child-optional?,third-child-gt1+
15
+ #
16
+ # @param _subject [String] name of the doc the rule applies
17
+ # @param _statement [String] DTD-style statement of the rule
18
+ def initialize(_subject, _statement)
19
+ formatted_statement = _statement
20
+ .gsub(/[\<>]/, '')
21
+ .gsub(/#PCDATA/, 'PCDATA')
22
+ .gsub('-','_dash_')
23
+ .gsub(/\b/,'\b')
24
+ .gsub(/PCDATA/, '#PCDATA')
25
+ .gsub('_dash_', '-')
26
+ .gsub(/\s/,'')
27
+ super(_subject, formatted_statement)
28
+ end
29
+ end # class ChildrenRuleClass
30
+
31
+ module ChildrenRule
32
+ # @param change_or_pattern [Duxml::ChildPattern, Duxml::Add, Duxml::Remove] to be evaluated to see if it follows this rule
33
+ # @return [Boolean] whether or not change_or_pattern#subject is allowed to have #object as its child
34
+ # if false, Error is reported to History
35
+ def qualify(change_or_pattern)
36
+ @object = change_or_pattern
37
+ result = pass
38
+ super change_or_pattern unless result
39
+ @object = nil
40
+ result
41
+ end
42
+
43
+ # @return [Array[String]] in order, array of child doc types required by this rule
44
+ def required_children
45
+ req_scans = get_scanners.select do |scanner| scanner[:operator].match(/[\*\?]/).nil? end
46
+ req_scans.collect do |req_scan|
47
+ get_child_name req_scan unless get_child_name(req_scan) == 'EMPTY'
48
+ end.compact
49
+ end
50
+
51
+ # @param change_or_pattern [Duxml::Pattern, Duxml::Change] change or pattern to be evaluated
52
+ # @return [Boolean] whether subjects agree, and change or pattern is not a Rule and responds to #affected_parent
53
+ def applies_to?(change_or_pattern)
54
+ case
55
+ when change_or_pattern.is_a?(Duxml::Rule) then false
56
+ when super(change_or_pattern) && change_or_pattern.respond_to?(:parent)
57
+ true
58
+ else
59
+ false
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ # @param req_scan [Struct::Scanner] scanner with which to identify a child name within @statement
66
+ # @return [String] name of child that is currently being checked for by this rule
67
+ def get_child_name(req_scan)
68
+ req_scan[:match].inspect.split(/[\/(\\b)]/).select do |word| !word.empty? end.first
69
+ end
70
+
71
+ # @return [[Struct::Scanner]] array of scanners for each logical component of @statement to be applied to target Element's children
72
+ def get_scanners
73
+ statement.split(',').collect do |rule|
74
+ r = rule.gsub(/[\(\)]/, '')
75
+ operator = r[-1].match(/[\?\+\*]/) ? r[-1] : ''
76
+ r = r[0..-2] unless operator.empty?
77
+ Struct::Scanner.new Regexp.new(r), operator
78
+ end
79
+ end
80
+
81
+ attr_reader :child_stack
82
+
83
+ # @return [Boolean] whether or not this rule has been passed
84
+ def pass
85
+ @child_stack = object.child.nil? ? [] : object.parent.nodes.clone
86
+ scanners = get_scanners
87
+ scanner = scanners.shift
88
+ result = false
89
+ loop do
90
+ child = @child_stack.shift
91
+ case
92
+ when child.nil?, scanner.nil? then break
93
+ when child.is_a?(String)
94
+ result = scanner[:match].inspect.include?('#PCDATA')
95
+ when child.name.match(scanner[:match]) # scanner matches this child
96
+ if scanner[:operator]=='?' or scanner[:operator]=='' # shift scanners if we only need one child of this type
97
+ scanner = scanners.shift
98
+ result = previous_child.nil? || (previous_child.name != child.name)
99
+ else
100
+ result = true
101
+ end
102
+ # scanner does not match this child...
103
+ when %w(? *).include?(scanner[:operator]) # optional scanner so try next scanner on same child
104
+ scanner = scanners.shift
105
+ @child_stack.unshift child
106
+ result = !scanner.nil?
107
+ else
108
+ result = false # else, this scanner will report false
109
+ end # case
110
+ return result unless child.is_a?(String) or !matching_index?# don't need to keep looping because we've scanned our target
111
+ end # loop do
112
+
113
+ # checking to see if any required children were not present
114
+ result = false if child_stack.empty? && scanners.any? do |scanner|
115
+ scanner[:operator] != '*' or scanner[:operator] != '?'
116
+ end
117
+ result
118
+ end # def pass
119
+
120
+ # @return [Boolean] whether the child currently being scanned is the one in the pattern we are testing now
121
+ def matching_index?
122
+ child_index == object.index
123
+ end
124
+
125
+ # @return [Fixnum] index of child currently being scanned
126
+ def child_index
127
+ object.parent.nodes.size-child_stack.size-1
128
+ end
129
+
130
+ # @return [Element] previous element to the one being scanned
131
+ def previous_child
132
+ index = child_index - 1
133
+ index < 0 ? nil : object.parent.nodes[index]
134
+ end
135
+ end # module ChildrenRule
136
136
  end # module Duxml