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
@@ -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
|