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
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/pattern/child_pattern')
4
+ require File.expand_path(File.dirname(__FILE__) + '/pattern/text_pattern')
5
+ require File.expand_path(File.dirname(__FILE__) + '/pattern/attr_name_pattern')
6
+ require File.expand_path(File.dirname(__FILE__) + '/pattern/attr_val_pattern')
7
+
8
+ module Duxml
9
+ # helper methods to create Patterns from a given Element's relationships with its members
10
+ module PatternMaker
11
+ include Duxml
12
+ # @param node [Duxml::Element] doc whose relationships are to be made into patterns
13
+ # @return [Array[Duxml::Pattern]] array of patterns representing every relationship of this XMl node and its members
14
+ def get_relationships(node)
15
+ [get_child_patterns(node),
16
+ get_null_child_patterns(node),
17
+ get_existing_attr_patterns(node),
18
+ get_null_attr_patterns(node)].flatten
19
+ end
20
+
21
+ # @param node [Duxml::Element] doc whose relationships are to be made into patterns
22
+ # @return [Array[Duxml::AttrNamePattern, Duxml::AttrValPattern]] one pattern for each existing attribute
23
+ def get_existing_attr_patterns(node)
24
+ # check existing attributes
25
+ node.attributes.collect do |k, v|
26
+ [AttrNamePatternClass.new(node, k), AttrValPatternClass.new(node, k)]
27
+ end.flatten
28
+ end
29
+
30
+ # @param node [Element] doc whose relationships are to be made into patterns
31
+ # @return [Array[AttrNamePattern]] one pattern for each attribute that should but does not exist
32
+ def get_null_attr_patterns(node)
33
+ self.AttrsRuleClass.collect do |attr_rule|
34
+ if attr_rule.required? && node.name == attr_rule.subject
35
+ AttrNamePatternClass.new(node, attr_rule.attr_name) unless node[attr_rule.attr_name]
36
+ end
37
+ end.compact
38
+ end
39
+
40
+ # @param node [Duxml::Element] doc whose relationships are to be made into patterns
41
+ # @return [Array[ChildPattern]] one pattern for each child that should be there but isn't
42
+ def get_null_child_patterns(node)
43
+ self.ChildrenRuleClass.each do |child_rule|
44
+ if node.name == child_rule.subject
45
+ return child_rule.required_children.collect do |required_child_type|
46
+ unless node.nodes.any? do |n| n.name == required_child_type end
47
+ NullChildPatternClass.new(node, required_child_type)
48
+ end
49
+ end.compact
50
+ end
51
+ end
52
+ []
53
+ end
54
+
55
+ # @param node [Duxml::Element] object whose relationships are to be made into patterns
56
+ # @return [Array[Duxml::ChildPattern, Duxml::ContentPattern]] one pattern for each child that exists
57
+ def get_child_patterns(node)
58
+ i = -1
59
+ node.nodes.collect do |child|
60
+ i += 1
61
+ if child.is_a?(String)
62
+ TextPatternClass.new(node, i)
63
+ else
64
+ ChildPatternClass.new(node, i)
65
+ end
66
+ end
67
+ end # def get_child_patterns
68
+ end # module PatternMaker
69
+ end # module Duxml
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule/attrs_rule')
4
+ require File.expand_path(File.dirname(__FILE__) + '/../../../doc/lazy_ox')
5
+ require 'ox'
6
+
7
+ module Duxml
8
+ module RngAttrsRule; end
9
+
10
+ class AttrsRuleClass
11
+ include RngAttrsRule
12
+ end
13
+
14
+ module RngAttrsRule
15
+ include Duxml::LazyOx
16
+ include Ox
17
+
18
+ # @param parent [Nokogiri::XML::Node] should be <grammar>
19
+ # @return [Nokogiri::XML::Node] parent, but with additions of <define><attribute> to parent if does not already exist and <ref> to respective <define><doc>
20
+ def relaxng(parent)
21
+ # TODO this is here just to skip generation from namespaced attributes - fix later!!!
22
+ return parent if attr_name.include?(':')
23
+ # TODO
24
+
25
+ # if new attribute declaration needed
26
+ unless parent.Define(name: attr_name).any?
27
+ new_def = Element.new('define')
28
+ new_def[:name] = attr_name
29
+ new_attr_def = Element.new('attribute')
30
+ new_attr_def[:name] = attr_name
31
+ new_def << new_attr_def
32
+ parent << new_def
33
+ end
34
+
35
+ # update doc with ref, updating previous <optional> if available
36
+ parent.nodes.reverse.each do |define|
37
+ if define[:name] == subject
38
+ element_def = define.nodes.first
39
+ if requirement == '#REQUIRED'
40
+ cur_element = element_def
41
+ else
42
+ if element_def.nodes.any? and element_def.nodes.last.name == 'optional'
43
+ cur_element = element_def.nodes.last
44
+ else
45
+ cur_element = Element.new('optional')
46
+ element_def << cur_element
47
+ end
48
+ end # if self[:requirement]=='#REQUIRED'
49
+ new_ref = Element.new('ref')
50
+ new_ref[:name] = attr_name
51
+ cur_element << new_ref
52
+ break
53
+ end # if define[:name] == subject
54
+ end # parent.element_children.any?
55
+ parent
56
+ end # def relaxng(parent)
57
+ end # module RngAttrsRule
58
+ end # module Duxml
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule/children_rule')
4
+
5
+ module Duxml
6
+ module RngChildrenRule; end
7
+
8
+ class ChildrenRuleClass
9
+ include RngChildrenRule
10
+ end
11
+
12
+ module RngChildrenRule
13
+ include Duxml::LazyOx
14
+ include Ox
15
+
16
+ # @param parent [Ox::Element] parent from RelaxNG document under construction (should be <grammar/>)
17
+ # @return [Ox::Element] same parent but with addition of <define><doc> with #statement converted into <ref>'s
18
+ # these are wrapped as needed in <zeroOrMore>,<oneOrMore>, or <optional>
19
+ def relaxng(parent)
20
+ nodes = parent.Define(name: subject)
21
+ raise Exception if nodes.size > 1
22
+
23
+ if nodes.first.nil?
24
+ element_def = Element.new('element')
25
+ element_def[:name] = subject
26
+ define = Element.new('define')
27
+ define[:name] = subject
28
+ define << element_def
29
+ parent << define
30
+ else
31
+ element_def = nodes.first.nodes.first
32
+ end
33
+
34
+ # loop through child requirements
35
+ get_scanners.each do |scanner|
36
+ # wrap in requirement node if needed
37
+ operator_name = case scanner[:operator]
38
+ when '?' then :optional
39
+ when '*' then :zeroOrMore
40
+ when '+' then :oneOrMore
41
+ else nil
42
+ end
43
+ if operator_name
44
+ cur_element = Element.new(operator_name.to_s)
45
+ element_def << cur_element
46
+ else
47
+ cur_element = element_def
48
+ end
49
+
50
+ # if child requirement has enumerated options, wrap in <choice>
51
+ element_array = scanner[:match].source.gsub('\b','').scan(Regexp.nmtoken).flatten.keep_if do |e| !e.empty? end
52
+ if element_array.size > 1
53
+ choice_el = Element.new 'choice'
54
+ cur_element << choice_el
55
+ cur_element = choice_el
56
+ end
57
+
58
+ # adding enumerated options as new element defs if needed
59
+ element_array.each do |element_name|
60
+ existing_defs = parent.Define(name: element_name)
61
+ raise Exception if existing_defs.size > 1
62
+ if existing_defs.empty?
63
+ new_def = Element.new('define')
64
+ new_def[:name] = element_name
65
+ child_el_def = Element.new('element')
66
+ child_el_def[:name] = element_name
67
+ new_def << child_el_def
68
+ parent << new_def
69
+ end
70
+
71
+ if element_name == '#PCDATA'
72
+ cur_element << Element.new('text')
73
+ else
74
+ ref_node = Element.new('ref')
75
+ ref_node[:name] = element_name
76
+ cur_element << ref_node
77
+ end
78
+ end # element_array.each
79
+ end # get_scanners.each
80
+ parent
81
+ end # def relaxng
82
+ end # module RngChildrenRule
83
+ end # module Duxml
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule/value_rule')
4
+
5
+ module Duxml
6
+ module RngValueRule; end
7
+
8
+ class ValueRuleClass
9
+ include RngValueRule
10
+ end
11
+
12
+ module RngValueRule
13
+ include Duxml::LazyOx
14
+ # @param parent [Nokogiri::XML::Node] <grammar> i.e. parent node in RelaxNG document, NOT this Rule's document
15
+ # @return [Nokogiri::XML::Node] parent, but adds to corresponding <define><attribute> a child <data type="#{statement}">
16
+ # where #statement can be 'CDATA', 'NMTOKEN', etc.
17
+ def relaxng(parent)
18
+ parent.Define.each do |define|
19
+ if define[:name] == attr_name
20
+ attr_def = define.nodes.first
21
+ unless attr_def.nodes.any?
22
+ data_type = statement == 'CDATA' ? 'string' : statement
23
+ if data_type.include?('|')
24
+ choice_node = Element.new('choice')
25
+ attr_def << choice_node
26
+ data_type.split(/[\(\|\)]/).each do |en_val|
27
+ if !en_val.empty? && Regexp.nmtoken.match(en_val)
28
+ value_def = Element.new('value')
29
+ value_def << en_val
30
+ choice_node << value_def
31
+ end
32
+ end
33
+ else
34
+ data_def = Element.new('data')
35
+ data_def[:type] = data_type
36
+ attr_def << data_def
37
+ end
38
+ end # unless attr_def.nodes.any?
39
+ return parent
40
+ end # if define[:name] == attr_name
41
+ end # parent.nodes.each
42
+ end # def relaxng
43
+ end # module RngValuesRule
44
+ end # module Duxml
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/relax_ng/children_rule')
4
+ require File.expand_path(File.dirname(__FILE__) + '/relax_ng/attrs_rule')
5
+ require File.expand_path(File.dirname(__FILE__) + '/relax_ng/value_rule')
6
+ require File.expand_path(File.dirname(__FILE__) + '/../../doc')
7
+
8
+ module Duxml
9
+ # contains methods to convert Rules into a valid RelaxNG schema file
10
+ module RelaxNG
11
+ include Duxml
12
+ # @param output_path [String] optional, output path for .rng file
13
+ # @return [Nokogiri::XML::RelaxNG] RelaxNG schema object
14
+ def relaxng(output_path=nil)
15
+ doc = Doc.new
16
+ doc << Element.new('grammar')
17
+ doc.grammar[:xmlns] = 'http://relaxng.org/ns/structure/1.0'
18
+ doc.grammar[:datatypeLibrary] = 'http://www.w3.org/2001/XMLSchema-datatypes'
19
+ start = Element.new('start')
20
+ start[:combine] = 'choice'
21
+ ref = Element.new('ref')
22
+ ref[:name] = rules.first.subject
23
+ start << ref
24
+ doc.grammar << start
25
+ rules.each do |rule|
26
+ rule.relaxng doc.grammar
27
+ end
28
+
29
+ # fill in empty doc definitions to make them legal
30
+ element_defs = doc.grammar.Define.collect do |d|
31
+ d.element if d.nodes.first.name == 'element' and d.element.nodes.empty?
32
+ end.compact
33
+ element_defs.each do |element_def|
34
+ element_def << Element.new('empty')
35
+ end
36
+ doc.write_to output_path if output_path
37
+ doc
38
+ end # def relaxng
39
+ end # module RelaxNG
40
+ end # module Duxml
@@ -0,0 +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
78
+ end # module Duxml
@@ -0,0 +1,137 @@
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 currenlty being scanned
126
+ def child_index
127
+ i = object.parent.nodes.size-child_stack.size-1
128
+ i
129
+ end
130
+
131
+ # @return [Element] previous element to the one being scanned
132
+ def previous_child
133
+ index = child_index - 1
134
+ index < 0 ? nil : object.parent.nodes[index]
135
+ end
136
+ end # module ChildrenRule
137
+ end # module Duxml
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
+
5
+ module Duxml
6
+ module TextRule; end
7
+
8
+ class TextRuleClass < RuleClass
9
+ include TextRule
10
+ end
11
+
12
+ module TextRule
13
+ # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
14
+ # @return [Boolean] whether this rule does in fact apply
15
+ def applies_to?(change_or_pattern)
16
+ super(change_or_pattern) &&
17
+ change_or_pattern.respond_to?(:text)
18
+ end
19
+
20
+ # applies Regexp statement to text content of this node; returns false if content has XML
21
+ def qualify(change_or_pattern)
22
+ @object = change_or_pattern
23
+ result = pass
24
+ super change_or_pattern unless result
25
+ @object = nil
26
+ result
27
+ end
28
+
29
+ private
30
+
31
+ def pass
32
+ return false unless object.text.is_a?(String)
33
+ get_scanner.match(object.text).to_s == object.text
34
+ end
35
+
36
+ def get_scanner
37
+ Regexp.new(statement)
38
+ end
39
+ end # module TextRule
40
+ end # module Duxml
@@ -0,0 +1,111 @@
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../rule')
4
+
5
+ module Duxml
6
+ module ValueRule; end
7
+
8
+ # rule that states what values a given attribute name is allowed to have
9
+ class ValueRuleClass < RuleClass
10
+ include ValueRule
11
+
12
+ # @param _attr_name [String] the attribute name
13
+ # @param _statement [String] data type or expression of the rule for the given attribute's value
14
+ def initialize(_attr_name, _statement)
15
+ @attr_name, @statement = _attr_name, _statement
16
+ end
17
+
18
+ attr_reader :attr_name
19
+ end
20
+
21
+ module ValueRule
22
+ CDATA_EXPR = /(\]\]>)/
23
+ ENTITY_EXPR = /(\b[\S]+\b)/
24
+ ID_EXPR = Regexp.nmtoken
25
+ NMTOKEN_EXPR = ID_EXPR
26
+ NOTATION_EXPR = //
27
+
28
+ # @return [true]
29
+ def abstract?
30
+ true
31
+ end
32
+
33
+ # @return [String] description of this rule
34
+ def description
35
+ %(#{relationship.capitalize} Rule that @#{attr_name}'s #{relationship} must match '#{statement}')
36
+ end
37
+
38
+ # @param change_or_pattern [Duxml::Pattern, Duxml::Change] change or pattern to be evaluated
39
+ # @return [Boolean] whether change_or_pattern#subject is allowed to have value of type #object
40
+ # if false, reports Error to History
41
+ def qualify(change_or_pattern)
42
+ value = change_or_pattern.value
43
+ s = change_or_pattern.subject
44
+ raise Exception if value.nil?
45
+ result = pass value
46
+ super change_or_pattern unless result
47
+ result
48
+ end
49
+
50
+ # @param change_or_pattern [Duxml::Change, Duxml::Pattern] change or pattern that rule may apply to
51
+ # @return [Boolean] whether this rule does in fact apply
52
+ def applies_to?(change_or_pattern)
53
+ return false unless change_or_pattern.respond_to?(:attr_name)
54
+ return false unless change_or_pattern.respond_to?(:value)
55
+ change_or_pattern.attr_name == attr_name
56
+ end
57
+
58
+ private
59
+
60
+ def pass(value)
61
+ matcher = find_method_or_expr
62
+ if matcher.respond_to?(:match)
63
+ matcher.match(value).to_s == value
64
+ else
65
+ matcher.call(value)
66
+ end
67
+ end
68
+
69
+ def find_method_or_expr
70
+ s = statement
71
+ case s
72
+ when 'CDATA' # unparsed character data e.g. '<not-xml>'; may not contain string ']]>'
73
+ proc do |val| val.match(CDATA_EXPR).nil? end
74
+ when 'ID' then ID_EXPR # does not check for uniqueness!
75
+ when 'IDREF' # id of another doc
76
+ proc do |val| val.match(ID_EXPR) && resolve_ref(val, subject.meta) end
77
+ when 'IDREFS' # ids of other elements
78
+ proc do |val|
79
+ separate_list val do |sub_val|
80
+ sub_val.match(ID_EXPR) && resolve_ref(sub_val, subject.meta)
81
+ end
82
+ end
83
+ when 'NMTOKEN' then NMTOKEN_EXPR # valid XML name
84
+ when 'NMTOKENS' # a list of valid XML names
85
+ proc do |val|
86
+ separate_list val do |sub_val|
87
+ sub_val.match NMTOKEN_EXPR
88
+ end
89
+ end
90
+ when 'ENTITY' then ENTITY_EXPR # an entity
91
+ when 'ENTITIES' # list of entities
92
+ proc do |val|
93
+ separate_list val do |sub_val|
94
+ sub_val.match ENTITY_EXPR
95
+ end
96
+ end
97
+ when 'NOTATION' then // # TODO name of a notation
98
+ when 'xml:' then // # TODO predefined XML value
99
+ else # '|'-separated list of allowable values i.e. Regexp-style DTD declaration
100
+ Regexp.new(s)
101
+ end
102
+ end # def find_method_or_expr
103
+
104
+ def separate_list(spc_sep_vals, &block)
105
+ spc_sep_vals.split(' ').any? do |sub_val|
106
+ result = block.call sub_val
107
+ !result
108
+ end
109
+ end
110
+ end # module ValueRule
111
+ end # module Duxml