duxml 0.7.2
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 +7 -0
- data/bin/validate_xml +19 -0
- data/lib/duxml/doc/element.rb +157 -0
- data/lib/duxml/doc/lazy_ox.rb +153 -0
- data/lib/duxml/doc/node_set.rb +39 -0
- data/lib/duxml/doc.rb +26 -0
- data/lib/duxml/meta/grammar/pattern/attr_name_pattern.rb +36 -0
- data/lib/duxml/meta/grammar/pattern/attr_val_pattern.rb +36 -0
- data/lib/duxml/meta/grammar/pattern/child_pattern.rb +72 -0
- data/lib/duxml/meta/grammar/pattern/text_pattern.rb +30 -0
- data/lib/duxml/meta/grammar/pattern.rb +98 -0
- data/lib/duxml/meta/grammar/pattern_maker.rb +69 -0
- data/lib/duxml/meta/grammar/relax_ng/attrs_rule.rb +58 -0
- data/lib/duxml/meta/grammar/relax_ng/children_rule.rb +83 -0
- data/lib/duxml/meta/grammar/relax_ng/value_rule.rb +44 -0
- data/lib/duxml/meta/grammar/relax_ng.rb +40 -0
- data/lib/duxml/meta/grammar/rule/attrs_rule.rb +78 -0
- data/lib/duxml/meta/grammar/rule/children_rule.rb +137 -0
- data/lib/duxml/meta/grammar/rule/text_rule.rb +40 -0
- data/lib/duxml/meta/grammar/rule/value_rule.rb +111 -0
- data/lib/duxml/meta/grammar/rule.rb +59 -0
- data/lib/duxml/meta/grammar/spreadsheet.rb +35 -0
- data/lib/duxml/meta/grammar.rb +134 -0
- data/lib/duxml/meta/history/add.rb +36 -0
- data/lib/duxml/meta/history/change.rb +71 -0
- data/lib/duxml/meta/history/change_attr.rb +34 -0
- data/lib/duxml/meta/history/change_text.rb +33 -0
- data/lib/duxml/meta/history/error.rb +25 -0
- data/lib/duxml/meta/history/new_attr.rb +33 -0
- data/lib/duxml/meta/history/new_text.rb +37 -0
- data/lib/duxml/meta/history/qualify_error.rb +22 -0
- data/lib/duxml/meta/history/remove.rb +28 -0
- data/lib/duxml/meta/history/undo.rb +24 -0
- data/lib/duxml/meta/history/validate_error.rb +21 -0
- data/lib/duxml/meta/history.rb +88 -0
- data/lib/duxml/meta.rb +51 -0
- data/lib/duxml/reportable.rb +27 -0
- data/lib/duxml/ruby_ext/fixnum.rb +56 -0
- data/lib/duxml/ruby_ext/module.rb +12 -0
- data/lib/duxml/ruby_ext/object.rb +14 -0
- data/lib/duxml/ruby_ext/regexp.rb +20 -0
- data/lib/duxml/ruby_ext/string.rb +29 -0
- data/lib/duxml/saxer.rb +71 -0
- data/lib/duxml.rb +97 -0
- data/xml/dita_grammar.xml +2133 -0
- 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
|