duxml 0.8.8 → 0.8.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|