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,168 +1,168 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/../ruby_ext/string')
4
-
5
-
6
- module Duxml
7
- module LazyOx
8
- # welcome to Lazy-Ox - where any method that doesn't exist, you can create on the fly and assign its methods to
9
- # a corresponding Duxml::Element. see Regexp.nmtoken and String#nmtokenize and String#constantize to see how a given symbol
10
- # can be converted into XML element names and vice versa. it can also use Class names as method calls to return children by type
11
- #
12
- # this method uses Ox::Element's :method_missing but adds an additional rescue block that:
13
- # matches namespaced Ruby module to this Element's name and extends this node with module's methods
14
- # then method matching symbol is called again with given arguments, yielding result to block if given, returning result if not
15
- # e.g.
16
- # module Duxml
17
- # module Throwable
18
- # def throw
19
- # puts 'throwing!!'
20
- # end
21
- # end
22
- # end
23
- #
24
- # Element.new('throwable').throw => 'throwing!!'
25
- #
26
- # if symbol name matches a class then method yields to block or returns as array child nodes that matches class
27
- # you can further refine search results by adding the symbol of the child instance variable, including name, by which to filter
28
- # if block given, returns first child for which block evaluates to true
29
- #
30
- # e.g.
31
- # class Child; end
32
- #
33
- # n = Element.new('node')
34
- # n << 'text'
35
- # n << Element.new('child')
36
- # n << Element.new('child')
37
- # n.Element # returns Array of Element nodes
38
- # => [#<Duxml::Element:0x0002 @value="child" ...>,
39
- # #<Duxml::Element:0x0003 @value="child" ...>]
40
- #
41
- # n.Element.each do |child| child << 'some text' end # adding some text
42
- # => ['text',
43
- # #<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>,
44
- # #<Duxml::Element 0x0003 @value="child" ... @nodes=['some text']>]
45
- #
46
- # n.Element do |child| child.nodes.first == 'some text' end # returns all children for which block is true
47
- # => [#<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>]
48
- #
49
- # %w(bar mar).each_with_index do |x, i| next if i.zero?; n.Child[:foo] = x end # adding some attributes
50
- # => ['text',
51
- # #<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
52
- # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
53
- #
54
- # n.Element(:foo) # returns array of Child nodes with attribute :foo
55
- # => [#<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
56
- # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
57
- #
58
- # n.Element(foo: 'bar') # returns array of Child nodes with attribute :foo equal to 'bar'
59
- # => [#<Duxml::Element:0xfff @value="child" @attributes={foo: 'bar'} ...>]
60
- #
61
- #
62
- # if element name has no matching Class or Module in namespace,
63
- # if symbol is lower case, it is made into a method, given &block as definition, then called with *args
64
- # e.g. n.change_color('blue') do |new_color| => #<Duxml::Element:0xfff @value="node" @attributes={color: 'blue'} @nodes=[]>
65
- # @color = new_color
66
- # self
67
- # end
68
- # n.color => 'blue'
69
- # n.change_color('mauve').color => 'mauve'
70
- #
71
- # @param sym [Symbol] method, class or module
72
- # @param *args [*several_variants] either arguments to method or initializing values for instance of given class
73
- # @param &block [block] if yielding result, yields to given block; if defining new method, block defines its contents
74
- def method_missing(sym, *args, &block)
75
- if respond_to?(:name)
76
- ns = name.split(':').first
77
- if ns and ns != name
78
- target = locate(ns + ':' + sym.to_s).first
79
- return target if target
80
- end
81
- end
82
- super(sym, *args, &block)
83
- rescue Exception => orig_error
84
- begin
85
- # handling Constant look up to dynamically extend or add to element
86
- if lowercase?(sym)
87
- if (const = look_up_const) and const.is_a?(Module)
88
- extend const
89
- result = method(sym).call(*args)
90
- return(result) unless block_given?
91
- yield(result)
92
- elsif block_given?
93
- new_method = proc(&block)
94
- self.const_set(sym, new_method)
95
- return new_method.call *args
96
- else
97
- raise orig_error
98
- end # if (const = look_up_const) ... elsif block_given? ... else ...
99
- else
100
- results = filter(sym, args)
101
- return results unless block_given?
102
- results.keep_if do |node| yield(node) end
103
- end # if lowercase? ... else ...
104
- rescue Exception
105
- raise orig_error
106
- end
107
- end # def method_missing(sym, *args, &block)
108
-
109
- private
110
-
111
- # @param sym [Symbol] indicates which element type is being filtered for
112
- # @param args [several_variants] arguments for filtering element children that matched 'sym'
113
- # @return [[Element]] Elements of type 'sym' that match criteria 'args'
114
- def filter(sym, args)
115
- class_nodes = nodes.select do |node|
116
- node.name == sym.to_s.nmtokenize or simple_class(node) == sym.to_s
117
- end
118
- class_nodes.keep_if do |node|
119
- if args.empty?
120
- true
121
- else
122
- args.any? do |arg|
123
- if arg.is_a?(Hash)
124
- node[arg.first.first] == arg.first.last
125
- else
126
- !node[arg].nil?
127
- end
128
- end
129
- end # if args.empty? ... else ...
130
- end # class_nodes.keep_if do |node|
131
- end # def filter(args)
132
-
133
- # @param maudule [Module] module context in which to look for duck-called method's module
134
- # @return [Module, Class] requested module or class
135
- def look_up_const(maudule = Duxml)
136
- mod_names = name.split(':')
137
- until mod_names.empty?
138
- k = mod_names.shift.constantize
139
- case
140
- when maudule.const_defined?(k, true)
141
- when Module.const_defined?(simple_class, true)
142
- k = simple_class
143
- else
144
- return nil
145
- end
146
- const = maudule.const_get(k)
147
- if const.is_a?(Module)
148
- maudule = const
149
- end
150
-
151
- return const if mod_names.empty? and [Module, Class].include?(const.class)
152
- end
153
- nil
154
- end
155
-
156
- # @param sym [Symbol] symbol for a constant
157
- # @return [Boolean] is symbol lowercase?
158
- def lowercase?(sym)
159
- sym.to_s[0].match(/[A-Z]/).nil?
160
- end
161
-
162
- # @param obj [Object] usually Element
163
- # @return [String] name of final Class or Module of self
164
- def simple_class(obj=self)
165
- obj.class.to_s.split('::').last
166
- end
167
- end # module LazyOx
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/../ruby_ext/string')
4
+
5
+
6
+ module Duxml
7
+ module LazyOx
8
+ # welcome to Lazy-Ox - where any method that doesn't exist, you can create on the fly and assign its methods to
9
+ # a corresponding Duxml::Element. see Regexp.nmtoken and String#nmtokenize and String#constantize to see how a given symbol
10
+ # can be converted into XML element names and vice versa. it can also use Class names as method calls to return children by type
11
+ #
12
+ # this method uses Ox::Element's :method_missing but adds an additional rescue block that:
13
+ # matches namespaced Ruby module to this Element's name and extends this node with module's methods
14
+ # then method matching symbol is called again with given arguments, yielding result to block if given, returning result if not
15
+ # e.g.
16
+ # module Duxml
17
+ # module Throwable
18
+ # def throw
19
+ # puts 'throwing!!'
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # Element.new('throwable').throw => 'throwing!!'
25
+ #
26
+ # if symbol name matches a class then method yields to block or returns as array child nodes that matches class
27
+ # you can further refine search results by adding the symbol of the child instance variable, including name, by which to filter
28
+ # if block given, returns first child for which block evaluates to true
29
+ #
30
+ # e.g.
31
+ # class Child; end
32
+ #
33
+ # n = Element.new('node')
34
+ # n << 'text'
35
+ # n << Element.new('child')
36
+ # n << Element.new('child')
37
+ # n.Element # returns Array of Element nodes
38
+ # => [#<Duxml::Element:0x0002 @value="child" ...>,
39
+ # #<Duxml::Element:0x0003 @value="child" ...>]
40
+ #
41
+ # n.Element.each do |child| child << 'some text' end # adding some text
42
+ # => ['text',
43
+ # #<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>,
44
+ # #<Duxml::Element 0x0003 @value="child" ... @nodes=['some text']>]
45
+ #
46
+ # n.Element do |child| child.nodes.first == 'some text' end # returns all children for which block is true
47
+ # => [#<Duxml::Element:0x0002 @value="child" ... @nodes=['some text']>]
48
+ #
49
+ # %w(bar mar).each_with_index do |x, i| next if i.zero?; n.Child[:foo] = x end # adding some attributes
50
+ # => ['text',
51
+ # #<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
52
+ # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
53
+ #
54
+ # n.Element(:foo) # returns array of Child nodes with attribute :foo
55
+ # => [#<Duxml::Element:0x0002 @value="child" @attributes={foo: 'bar'} ...>,
56
+ # #<Duxml::Element:0x0003 @value="child" @attributes={foo: 'mar'} ...>]
57
+ #
58
+ # n.Element(foo: 'bar') # returns array of Child nodes with attribute :foo equal to 'bar'
59
+ # => [#<Duxml::Element:0xfff @value="child" @attributes={foo: 'bar'} ...>]
60
+ #
61
+ #
62
+ # if element name has no matching Class or Module in namespace,
63
+ # if symbol is lower case, it is made into a method, given &block as definition, then called with *args
64
+ # e.g. n.change_color('blue') do |new_color| => #<Duxml::Element:0xfff @value="node" @attributes={color: 'blue'} @nodes=[]>
65
+ # @color = new_color
66
+ # self
67
+ # end
68
+ # n.color => 'blue'
69
+ # n.change_color('mauve').color => 'mauve'
70
+ #
71
+ # @param sym [Symbol] method, class or module
72
+ # @param *args [*several_variants] either arguments to method or initializing values for instance of given class
73
+ # @param &block [block] if yielding result, yields to given block; if defining new method, block defines its contents
74
+ def method_missing(sym, *args, &block)
75
+ if respond_to?(:name)
76
+ ns = name.split(':').first
77
+ if ns and ns != name
78
+ target = locate(ns + ':' + sym.to_s).first
79
+ return target if target
80
+ end
81
+ end
82
+ super(sym, *args, &block)
83
+ rescue Exception => orig_error
84
+ begin
85
+ # handling Constant look up to dynamically extend or add to element
86
+ if lowercase?(sym)
87
+ if (const = look_up_const) and const.is_a?(Module)
88
+ extend const
89
+ result = method(sym).call(*args)
90
+ return(result) unless block_given?
91
+ yield(result)
92
+ elsif block_given?
93
+ new_method = proc(&block)
94
+ self.const_set(sym, new_method)
95
+ return new_method.call *args
96
+ else
97
+ raise orig_error
98
+ end # if (const = look_up_const) ... elsif block_given? ... else ...
99
+ else
100
+ results = filter(sym, args)
101
+ return results unless block_given?
102
+ results.keep_if do |node| yield(node) end
103
+ end # if lowercase? ... else ...
104
+ rescue Exception
105
+ raise orig_error
106
+ end
107
+ end # def method_missing(sym, *args, &block)
108
+
109
+ private
110
+
111
+ # @param sym [Symbol] indicates which element type is being filtered for
112
+ # @param args [several_variants] arguments for filtering element children that matched 'sym'
113
+ # @return [[Element]] Elements of type 'sym' that match criteria 'args'
114
+ def filter(sym, args)
115
+ class_nodes = nodes.select do |node|
116
+ node.name == sym.to_s.nmtokenize or simple_class(node) == sym.to_s
117
+ end
118
+ class_nodes.keep_if do |node|
119
+ if args.empty?
120
+ true
121
+ else
122
+ args.any? do |arg|
123
+ if arg.is_a?(Hash)
124
+ node[arg.first.first] == arg.first.last
125
+ else
126
+ !node[arg].nil?
127
+ end
128
+ end
129
+ end # if args.empty? ... else ...
130
+ end # class_nodes.keep_if do |node|
131
+ end # def filter(args)
132
+
133
+ # @param maudule [Module] module context in which to look for duck-called method's module
134
+ # @return [Module, Class] requested module or class
135
+ def look_up_const(maudule = Duxml)
136
+ mod_names = name.split(':')
137
+ until mod_names.empty?
138
+ k = mod_names.shift.constantize
139
+ case
140
+ when maudule.const_defined?(k, true)
141
+ when Module.const_defined?(simple_class, true)
142
+ k = simple_class
143
+ else
144
+ return nil
145
+ end
146
+ const = maudule.const_get(k)
147
+ if const.is_a?(Module)
148
+ maudule = const
149
+ end
150
+
151
+ return const if mod_names.empty? and [Module, Class].include?(const.class)
152
+ end
153
+ nil
154
+ end
155
+
156
+ # @param sym [Symbol] symbol for a constant
157
+ # @return [Boolean] is symbol lowercase?
158
+ def lowercase?(sym)
159
+ sym.to_s[0].match(/[A-Z]/).nil?
160
+ end
161
+
162
+ # @param obj [Object] usually Element
163
+ # @return [String] name of final Class or Module of self
164
+ def simple_class(obj=self)
165
+ obj.class.to_s.split('::').last
166
+ end
167
+ end # module LazyOx
168
168
  end # module Duxml
@@ -1,39 +1,39 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require 'observer'
4
-
5
- module Duxml
6
- # subclass of Array that is Observable by History
7
- # used to track changes in String nodes of XML Element
8
- class NodeSet < Array
9
- include Observable
10
-
11
- @parent
12
-
13
- attr_reader :parent
14
-
15
- # @param _parent [Element] Element that is parent to this NodeSet's elements
16
- # @param ary [[String, Element]] child nodes with which to initialize this NodeSet
17
- def initialize(_parent, ary=[])
18
- super ary
19
- @parent = _parent
20
- end
21
-
22
- # @return [HistoryClass] object that observes this NodeSet for changes
23
- def history
24
- @observer_peers.first.first if @observer_peers and @observer_peers.first.any?
25
- end
26
-
27
- # @param index [Fixnum] index of array where old String is to be replaced
28
- # @param str [String] replacing String
29
- # @return [self] reports old String and index to history
30
- def []=(index, str)
31
- raise Exception if count_observers < 1
32
- old_str = self[index]
33
- super(index, str)
34
- changed
35
- notify_observers(:ChangeText, parent, index, old_str)
36
- self
37
- end
38
- end # class NodeSet < Array
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require 'observer'
4
+
5
+ module Duxml
6
+ # subclass of Array that is Observable by History
7
+ # used to track changes in String nodes of XML Element
8
+ class NodeSet < Array
9
+ include Observable
10
+
11
+ @parent
12
+
13
+ attr_reader :parent
14
+
15
+ # @param _parent [Element] Element that is parent to this NodeSet's elements
16
+ # @param ary [[String, Element]] child nodes with which to initialize this NodeSet
17
+ def initialize(_parent, ary=[])
18
+ super ary
19
+ @parent = _parent
20
+ end
21
+
22
+ # @return [HistoryClass] object that observes this NodeSet for changes
23
+ def history
24
+ @observer_peers.first.first if @observer_peers and @observer_peers.first.any?
25
+ end
26
+
27
+ # @param index [Fixnum] index of array where old String is to be replaced
28
+ # @param str [String] replacing String
29
+ # @return [self] reports old String and index to history
30
+ def []=(index, str)
31
+ raise Exception if count_observers < 1
32
+ old_str = self[index]
33
+ super(index, str)
34
+ changed
35
+ notify_observers(:ChangeText, parent, index, old_str)
36
+ self
37
+ end
38
+ end # class NodeSet < Array
39
39
  end # module Duxml
@@ -1,73 +1,73 @@
1
- # Copyright (c) 2016 Freescale Semiconductor Inc.
2
-
3
- require File.expand_path(File.dirname(__FILE__) + '/meta/grammar')
4
- require File.expand_path(File.dirname(__FILE__) + '/meta/history')
5
- require File.expand_path(File.dirname(__FILE__) + '/saxer')
6
-
7
- module Duxml
8
- module Meta
9
-
10
- FILE_EXT = '.duxml'
11
- end
12
-
13
- # all XML files ready by Duxml have a metadata file generated with a modified, matching file name
14
- # @see #Meta.meta_path
15
- class MetaClass
16
- include Meta
17
-
18
- # @param grammar_path [String] optional path of grammar file which can be a spreadsheet or Duxml::Grammar file
19
- def initialize(grammar_path=nil)
20
- @history = HistoryClass.new
21
- self.grammar = grammar_path ? Grammar.import(grammar_path) : GrammarClass.new
22
- @grammar_path = grammar_path
23
- end
24
-
25
- attr_reader :history, :grammar, :grammar_path
26
- end
27
-
28
- module Meta
29
- # @param path [String] path of XML-content file
30
- # @return [String] full path of metadata file based on content file's name e.g.
31
- # 'design.xml' => '.design.xml.duxml'
32
- def self.meta_path(path)
33
- dir = File.dirname(path)
34
- "#{dir}/.#{File.basename(path)}#{FILE_EXT}"
35
- end
36
-
37
- # @param g [String, GrammarClass] either a grammar object or path to one
38
- # @return [GrammarClass] grammar object
39
- def grammar=(g)
40
- @grammar = case g
41
- when GrammarClass then g
42
- when String
43
- if File.exists?(g)
44
- @grammar_path = g
45
- Grammar.import(g)
46
- else
47
- maudule, meth = *g.split('.')
48
- if Module.const_defined?(maudule.to_sym)
49
- Module.const_get(maudule.to_sym).send(meth.to_sym)
50
- else
51
- raise ArgumentError, "#{g.to_s} is not a valid module/grammar symbol"
52
- end
53
- end
54
- else
55
- raise ArgumentError, "#{g.to_s} is not a valid Grammar or path to one"
56
- end
57
- history.delete_observers if history.respond_to?(:delete_observers)
58
- history.add_observer(grammar, :qualify)
59
- grammar.add_observer history
60
- grammar
61
- end
62
-
63
- def xml
64
- if grammar_path
65
- g = Duxml::Element.new('grammar')
66
- g[:ref] = grammar_path
67
- else
68
- g = grammar.xml
69
- end
70
- Duxml::Element.new('meta') << g << history.xml
71
- end
72
- end # module Meta
1
+ # Copyright (c) 2016 Freescale Semiconductor Inc.
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/meta/grammar')
4
+ require File.expand_path(File.dirname(__FILE__) + '/meta/history')
5
+ require File.expand_path(File.dirname(__FILE__) + '/saxer')
6
+
7
+ module Duxml
8
+ module Meta
9
+
10
+ FILE_EXT = '.duxml'
11
+ end
12
+
13
+ # all XML files ready by Duxml have a metadata file generated with a modified, matching file name
14
+ # @see #Meta.meta_path
15
+ class MetaClass
16
+ include Meta
17
+
18
+ # @param grammar_path [String] optional path of grammar file which can be a spreadsheet or Duxml::Grammar file
19
+ def initialize(grammar_path=nil)
20
+ @history = HistoryClass.new
21
+ self.grammar = grammar_path ? Grammar.import(grammar_path) : GrammarClass.new
22
+ @grammar_path = grammar_path
23
+ end
24
+
25
+ attr_reader :history, :grammar, :grammar_path
26
+ end
27
+
28
+ module Meta
29
+ # @param path [String] path of XML-content file
30
+ # @return [String] full path of metadata file based on content file's name e.g.
31
+ # 'design.xml' => '.design.xml.duxml'
32
+ def self.meta_path(path)
33
+ dir = File.dirname(path)
34
+ "#{dir}/.#{File.basename(path)}#{FILE_EXT}"
35
+ end
36
+
37
+ # @param g [String, GrammarClass] either a grammar object or path to one
38
+ # @return [GrammarClass] grammar object
39
+ def grammar=(g)
40
+ @grammar = case g
41
+ when GrammarClass then g
42
+ when String
43
+ if File.exists?(g)
44
+ @grammar_path = g
45
+ Grammar.import(g)
46
+ else
47
+ maudule, meth = *g.split('.')
48
+ if Module.const_defined?(maudule.to_sym)
49
+ Module.const_get(maudule.to_sym).send(meth.to_sym)
50
+ else
51
+ raise ArgumentError, "#{g.to_s} is not a valid module/grammar symbol"
52
+ end
53
+ end
54
+ else
55
+ raise ArgumentError, "#{g.to_s} is not a valid Grammar or path to one"
56
+ end
57
+ history.delete_observers if history.respond_to?(:delete_observers)
58
+ history.add_observer(grammar, :qualify)
59
+ grammar.add_observer history
60
+ grammar
61
+ end
62
+
63
+ def xml
64
+ if grammar_path
65
+ g = Duxml::Element.new('grammar')
66
+ g[:ref] = grammar_path
67
+ else
68
+ g = grammar.xml
69
+ end
70
+ Duxml::Element.new('meta') << g << history.xml
71
+ end
72
+ end # module Meta
73
73
  end # module Duxml