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