duxml 0.8.8 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/validate_xml +30 -30
  3. data/lib/duxml.rb +76 -76
  4. data/lib/duxml/doc.rb +108 -91
  5. data/lib/duxml/doc/element.rb +250 -250
  6. data/lib/duxml/doc/lazy_ox.rb +167 -167
  7. data/lib/duxml/doc/node_set.rb +38 -38
  8. data/lib/duxml/meta.rb +72 -72
  9. data/lib/duxml/meta/grammar.rb +133 -133
  10. data/lib/duxml/meta/grammar/pattern.rb +111 -111
  11. data/lib/duxml/meta/grammar/pattern/attr_name_pattern.rb +36 -36
  12. data/lib/duxml/meta/grammar/pattern/attr_val_pattern.rb +36 -36
  13. data/lib/duxml/meta/grammar/pattern/child_pattern.rb +60 -60
  14. data/lib/duxml/meta/grammar/pattern/text_pattern.rb +31 -31
  15. data/lib/duxml/meta/grammar/pattern_maker.rb +68 -68
  16. data/lib/duxml/meta/grammar/relax_ng.rb +39 -39
  17. data/lib/duxml/meta/grammar/relax_ng/attrs_rule.rb +58 -58
  18. data/lib/duxml/meta/grammar/relax_ng/children_rule.rb +82 -82
  19. data/lib/duxml/meta/grammar/relax_ng/value_rule.rb +44 -44
  20. data/lib/duxml/meta/grammar/rule.rb +58 -58
  21. data/lib/duxml/meta/grammar/rule/attrs_rule.rb +77 -77
  22. data/lib/duxml/meta/grammar/rule/children_rule.rb +135 -135
  23. data/lib/duxml/meta/grammar/rule/text_rule.rb +39 -39
  24. data/lib/duxml/meta/grammar/rule/value_rule.rb +110 -110
  25. data/lib/duxml/meta/grammar/spreadsheet.rb +34 -34
  26. data/lib/duxml/meta/history.rb +88 -88
  27. data/lib/duxml/meta/history/add.rb +30 -30
  28. data/lib/duxml/meta/history/change.rb +70 -70
  29. data/lib/duxml/meta/history/change_attr.rb +33 -33
  30. data/lib/duxml/meta/history/change_text.rb +32 -32
  31. data/lib/duxml/meta/history/error.rb +24 -24
  32. data/lib/duxml/meta/history/new_attr.rb +32 -32
  33. data/lib/duxml/meta/history/new_text.rb +36 -36
  34. data/lib/duxml/meta/history/qualify_error.rb +21 -21
  35. data/lib/duxml/meta/history/remove.rb +27 -27
  36. data/lib/duxml/meta/history/undo.rb +23 -23
  37. data/lib/duxml/meta/history/validate_error.rb +20 -20
  38. data/lib/duxml/reportable.rb +28 -28
  39. data/lib/duxml/ruby_ext/fixnum.rb +55 -55
  40. data/lib/duxml/ruby_ext/module.rb +11 -11
  41. data/lib/duxml/ruby_ext/object.rb +13 -13
  42. data/lib/duxml/ruby_ext/regexp.rb +19 -19
  43. data/lib/duxml/ruby_ext/string.rb +37 -37
  44. data/lib/duxml/saxer.rb +75 -75
  45. metadata +12 -12
@@ -1,40 +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
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
40
  end # module Duxml
@@ -1,111 +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
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
111
  end # module Duxml
@@ -1,35 +1,35 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require 'rubyXL'
4
-
5
- module Duxml
6
- # contains helper methods to convert spreadsheet of DTD rules into Duxml::Rules
7
- module Spreadsheet
8
- private
9
- # @param path [String] spreadsheet file
10
- def self.sheet_to_xml(path)
11
- worksheet = RubyXL::Parser.parse(path)[0]
12
- attr_val_rule_hash = {}
13
- g = GrammarClass.new
14
- worksheet.each_with_index do |row, index|
15
- next if index == 0
16
- break if row[3].nil? || row[4].nil?
17
- element_name = row[3].value
18
- statement_str = row[4].value
19
- g << ChildrenRuleClass.new(element_name, statement_str)
20
- attribute_rules = row[5].value.split(/\n/)
21
- attribute_rules.each_with_index do |rule, i|
22
- next if i == 0 or rule.empty?
23
- attr_name, value_expr, attr_req = *rule.split
24
- next if [attr_name, value_expr, attr_req].any? do |s| s.empty? or s.match(/\w/).nil? end
25
- g << AttrsRuleClass.new(element_name, attr_name, attr_req)
26
- unless attr_val_rule_hash[attr_name]
27
- g << ValueRuleClass.new(attr_name, value_expr)
28
- attr_val_rule_hash[attr_name] = true
29
- end
30
- end # attribute_rules.each_with_index
31
- end # worksheet.each_with_index
32
- g
33
- end # def sheet_to_xml(path)
34
- end # module Spreadsheet
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require 'rubyXL'
4
+
5
+ module Duxml
6
+ # contains helper methods to convert spreadsheet of DTD rules into Duxml::Rules
7
+ module Spreadsheet
8
+ private
9
+ # @param path [String] spreadsheet file
10
+ def self.sheet_to_xml(path)
11
+ worksheet = RubyXL::Parser.parse(path)[0]
12
+ attr_val_rule_hash = {}
13
+ g = GrammarClass.new
14
+ worksheet.each_with_index do |row, index|
15
+ next if index == 0
16
+ break if row[3].nil? || row[4].nil?
17
+ element_name = row[3].value
18
+ statement_str = row[4].value
19
+ g << ChildrenRuleClass.new(element_name, statement_str)
20
+ attribute_rules = row[5].value.split(/\n/)
21
+ attribute_rules.each_with_index do |rule, i|
22
+ next if i == 0 or rule.empty?
23
+ attr_name, value_expr, attr_req = *rule.split
24
+ next if [attr_name, value_expr, attr_req].any? do |s| s.empty? or s.match(/\w/).nil? end
25
+ g << AttrsRuleClass.new(element_name, attr_name, attr_req)
26
+ unless attr_val_rule_hash[attr_name]
27
+ g << ValueRuleClass.new(attr_name, value_expr)
28
+ attr_val_rule_hash[attr_name] = true
29
+ end
30
+ end # attribute_rules.each_with_index
31
+ end # worksheet.each_with_index
32
+ g
33
+ end # def sheet_to_xml(path)
34
+ end # module Spreadsheet
35
35
  end # module Duxml
@@ -1,89 +1,89 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/history/add')
4
- require File.expand_path(File.dirname(__FILE__) + '/history/remove')
5
- require File.expand_path(File.dirname(__FILE__) + '/history/validate_error')
6
- require File.expand_path(File.dirname(__FILE__) + '/history/qualify_error')
7
- require File.expand_path(File.dirname(__FILE__) + '/history/new_attr')
8
- require File.expand_path(File.dirname(__FILE__) + '/history/change_attr')
9
- require File.expand_path(File.dirname(__FILE__) + '/history/new_text')
10
- require File.expand_path(File.dirname(__FILE__) + '/history/change_text')
11
- require File.expand_path(File.dirname(__FILE__) + '/history/undo')
12
- require File.expand_path(File.dirname(__FILE__) + '/../doc')
13
- require 'forwardable'
14
-
15
- module Duxml
16
- # monitors XML Elements for changes and GrammarClass for errors, recording them and saving to Meta file
17
- module History
18
- include Duxml
19
- include Reportable
20
- end
21
-
22
- # as an object, HistoryClass holds events latest first, earliest last
23
- # it also has delegators that allow the use of Array-style notation e.g. '[]' and #each to search the history.
24
- class HistoryClass
25
- include History
26
- extend Forwardable
27
-
28
- def_delegators :@nodes, :[], :each
29
-
30
- # @param strict_or_false [Boolean] by default strict i.e. true so that if this History detects an error it will raise an Exception; otherwise not
31
- def initialize(strict_or_false = true)
32
- @nodes = []
33
- @strict = strict_or_false
34
- end
35
-
36
- attr_reader :nodes
37
- alias_method :events, :nodes
38
- end
39
-
40
- module History
41
- # used when creating a new metadata file for a static XML file
42
- #
43
- # @return [Doc] returns self as XML document
44
- def xml
45
- h = Element.new('history')
46
- events.each do |event| h << event.xml end
47
- h
48
- end
49
-
50
- # @return [Boolean] toggles (true by default) whether History will raise exception or tolerate qualify errors
51
- def strict?(strict_or_false=nil)
52
- @strict = strict_or_false.nil? ? @strict : strict_or_false
53
- end
54
-
55
- # @return [ChangeClass, ErrorClass] the latest event
56
- def latest
57
- events[0]
58
- end
59
-
60
- # @return [GrammarClass] grammar that is observing this history's events
61
- def grammar
62
- @observer_peers.first.first if @observer_peers and @observer_peers.any? and @observer_peers.first.any?
63
- end
64
-
65
- # @return [String] shortened self description for debugging
66
- def inspect
67
- "#<#{self.class.to_s} #{object_id}: @events=#{nodes.size}>"
68
- end
69
-
70
- # @return [String] returns entire history, calling #description on each event in chronological order
71
- def description
72
- "history follows: \n" +
73
- events.reverse.collect do |change_or_error|
74
- change_or_error.description
75
- end.join("\n")
76
- end
77
-
78
- # @param type [Symbol] category i.e. class symbol of changes/errors reported
79
- # @param *args [*several_variants] information needed to accurately log the event; varies by change/error class
80
- def update(type, *args)
81
- change_class = Duxml::const_get "#{type.to_s}Class".to_sym
82
- change_comp = change_class.new *args
83
- @nodes.unshift change_comp
84
- changed
85
- notify_observers(change_comp) unless change_comp.respond_to?(:error?)
86
- raise(Exception, change_comp.description) if strict? && type == :QualifyError
87
- end
88
- end # module History
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/history/add')
4
+ require File.expand_path(File.dirname(__FILE__) + '/history/remove')
5
+ require File.expand_path(File.dirname(__FILE__) + '/history/validate_error')
6
+ require File.expand_path(File.dirname(__FILE__) + '/history/qualify_error')
7
+ require File.expand_path(File.dirname(__FILE__) + '/history/new_attr')
8
+ require File.expand_path(File.dirname(__FILE__) + '/history/change_attr')
9
+ require File.expand_path(File.dirname(__FILE__) + '/history/new_text')
10
+ require File.expand_path(File.dirname(__FILE__) + '/history/change_text')
11
+ require File.expand_path(File.dirname(__FILE__) + '/history/undo')
12
+ require File.expand_path(File.dirname(__FILE__) + '/../doc')
13
+ require 'forwardable'
14
+
15
+ module Duxml
16
+ # monitors XML Elements for changes and GrammarClass for errors, recording them and saving to Meta file
17
+ module History
18
+ include Duxml
19
+ include Reportable
20
+ end
21
+
22
+ # as an object, HistoryClass holds events latest first, earliest last
23
+ # it also has delegators that allow the use of Array-style notation e.g. '[]' and #each to search the history.
24
+ class HistoryClass
25
+ include History
26
+ extend Forwardable
27
+
28
+ def_delegators :@nodes, :[], :each
29
+
30
+ # @param strict_or_false [Boolean] by default strict i.e. true so that if this History detects an error it will raise an Exception; otherwise not
31
+ def initialize(strict_or_false = true)
32
+ @nodes = []
33
+ @strict = strict_or_false
34
+ end
35
+
36
+ attr_reader :nodes
37
+ alias_method :events, :nodes
38
+ end
39
+
40
+ module History
41
+ # used when creating a new metadata file for a static XML file
42
+ #
43
+ # @return [Doc] returns self as XML document
44
+ def xml
45
+ h = Element.new('history')
46
+ events.each do |event| h << event.xml end
47
+ h
48
+ end
49
+
50
+ # @return [Boolean] toggles (true by default) whether History will raise exception or tolerate qualify errors
51
+ def strict?(strict_or_false=nil)
52
+ @strict = strict_or_false.nil? ? @strict : strict_or_false
53
+ end
54
+
55
+ # @return [ChangeClass, ErrorClass] the latest event
56
+ def latest
57
+ events[0]
58
+ end
59
+
60
+ # @return [GrammarClass] grammar that is observing this history's events
61
+ def grammar
62
+ @observer_peers.first.first if @observer_peers and @observer_peers.any? and @observer_peers.first.any?
63
+ end
64
+
65
+ # @return [String] shortened self description for debugging
66
+ def inspect
67
+ "#<#{self.class.to_s} #{object_id}: @events=#{nodes.size}>"
68
+ end
69
+
70
+ # @return [String] returns entire history, calling #description on each event in chronological order
71
+ def description
72
+ "history follows: \n" +
73
+ events.reverse.collect do |change_or_error|
74
+ change_or_error.description
75
+ end.join("\n")
76
+ end
77
+
78
+ # @param type [Symbol] category i.e. class symbol of changes/errors reported
79
+ # @param *args [*several_variants] information needed to accurately log the event; varies by change/error class
80
+ def update(type, *args)
81
+ change_class = Duxml::const_get "#{type.to_s}Class".to_sym
82
+ change_comp = change_class.new *args
83
+ @nodes.unshift change_comp
84
+ changed
85
+ notify_observers(change_comp) unless change_comp.respond_to?(:error?)
86
+ raise(Exception, change_comp.description) if strict? && type == :QualifyError
87
+ end
88
+ end # module History
89
89
  end # module Duxml