xmlcodec 0.1.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -30,17 +30,21 @@ you would create the following classes:
30
30
 
31
31
  require 'xmlcodec'
32
32
 
33
- class Root < XMLCodec::XMLElement
33
+ class Format < XMLCodec::XMLElement
34
+ xmlformat "Some XML Format Name"
35
+ end
36
+
37
+ class Root < Format
34
38
  elname 'root'
35
39
  xmlsubel :firstelement
36
40
  end
37
41
 
38
- class FirstElement < XMLCodec::XMLElement
42
+ class FirstElement < Format
39
43
  elname 'firstelement'
40
44
  xmlsubel_mult :secondelement
41
45
  end
42
46
 
43
- class SecondElement < XMLCodec::XMLElement
47
+ class SecondElement < Format
44
48
  elname 'secondelement'
45
49
  elwithvalue
46
50
  xmlattr :firstattr
@@ -69,21 +73,21 @@ format.
69
73
  To import XML just do:
70
74
 
71
75
  # From text
72
- Root.import_xml_text(File.new('file.xml'))
76
+ Root.import_xml File.new('file.xml')
73
77
 
74
- # From a REXML DOM
75
- Root.import_xml(REXML::Document.new(File.new('file.xml')))
78
+ # From a Nokogiri DOM
79
+ Root.import_xml Nokogiri::XML::Document.parse(File.read('file.xml'))
76
80
 
77
81
  To export do:
78
82
 
79
83
  # To generate XML text
80
84
  string = some_element.xml_text
81
85
 
82
- # To generate REXML DOM
83
- doc = some_element.create_xml(REXML::Document.new)
86
+ # To generate Nokogiri DOM
87
+ doc = some_element.create_xml(Nokogiri::XML::Document.new)
84
88
 
85
89
  All these calls require keeping the whole contents of the document in memory.
86
- The ones that use the REXML DOM will have it twice. To handle large documents with constant memory usage another set of APIs is available.
90
+ The ones that use the Nokogiri DOM will have it twice. To handle large documents with constant memory usage another set of APIs is available.
87
91
 
88
92
  To stream parse a large document you'd do something like:
89
93
 
data/Rakefile CHANGED
@@ -1,64 +1,77 @@
1
- PKG_NAME = 'xmlcodec'
2
- PKG_VERSION = '0.1.3'
1
+ # Based on the jekyll Rakefile (http://github.com/mojombo/jekyll)
3
2
 
3
+ require 'rubygems'
4
4
  require 'rake'
5
+ require 'date'
5
6
  require 'rake/testtask'
6
- require 'rake/rdoctask'
7
- require 'rake/gempackagetask'
8
- require 'rcov/rcovtask'
9
- require 'rubygems'
7
+ require 'rdoc/task'
8
+
9
+ #############################################################################
10
+ #
11
+ # Helper functions
12
+ #
13
+ #############################################################################
14
+
15
+ def name
16
+ @name ||= Dir['*.gemspec'].first.split('.').first
17
+ end
18
+
19
+ def version
20
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
21
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
22
+ end
23
+
24
+ def date
25
+ Date.today.to_s
26
+ end
27
+
28
+ def rubyforge_project
29
+ name
30
+ end
31
+
32
+ def gemspec_file
33
+ "#{name}.gemspec"
34
+ end
10
35
 
11
- task :default => ['test']
12
-
13
- TEST_FILES = 'test/**/*.rb'
14
- CODE_FILES = 'lib/**/*.rb'
15
-
16
- PKG_FILES = FileList[TEST_FILES,
17
- CODE_FILES,
18
- 'README*',
19
- 'LICENSE',
20
- 'Rakefile']
21
-
22
- RDOC_OPTIONS = ['-S', '-w 2', '-N']
23
- RDOC_EXTRA_FILES = ['README.rdoc']
24
-
25
- spec = Gem::Specification.new do |s|
26
- s.platform = Gem::Platform::RUBY
27
- s.summary = "Generic Importer/Exporter of XML formats"
28
- s.homepage = "http://github.com/pedrocr/xmlcodec"
29
- s.name = PKG_NAME
30
- s.version = PKG_VERSION
31
- s.author = 'Pedro Côrte-Real'
32
- s.email = 'pedro@pedrocr.net'
33
- s.requirements << 'none'
34
- s.require_path = 'lib'
35
- s.files = PKG_FILES
36
- s.has_rdoc = true
37
- s.rdoc_options = RDOC_OPTIONS
38
- s.extra_rdoc_files = RDOC_EXTRA_FILES
39
- s.description = <<EOF
40
- A library that eases the creation of XML importers and exporters for ruby.
41
- EOF
36
+ def gem_file
37
+ "#{name}-#{version}.gem"
42
38
  end
43
39
 
44
- Rake::GemPackageTask.new(spec) do |pkg|
40
+ def replace_header(head, header_name)
41
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
45
42
  end
46
43
 
47
- Rake::TestTask.new do |t|
48
- t.test_files = FileList['test/*_test.rb']
49
- t.verbose = true
44
+ #############################################################################
45
+ #
46
+ # Standard tasks
47
+ #
48
+ #############################################################################
49
+
50
+ task :default => [:test]
51
+
52
+ Rake::TestTask.new(:test) do |test|
53
+ test.libs << 'lib' << 'test'
54
+ test.pattern = 'test/*_test.rb'
55
+ test.verbose = true
50
56
  end
51
57
 
52
- Rake::RDocTask.new do |rd|
53
- rd.main = "README.rdoc"
54
- rd.name = :docs
55
- rd.rdoc_files.include(RDOC_EXTRA_FILES, CODE_FILES)
56
- rd.rdoc_dir = 'doc'
57
- rd.title = "#{PKG_NAME} API"
58
- rd.options = RDOC_OPTIONS
58
+ Rake::RDocTask.new do |rdoc|
59
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = "#{name} #{version}"
61
+ rdoc.rdoc_files.include('README*')
62
+ rdoc.rdoc_files.include('lib/**/*.rb')
59
63
  end
60
64
 
65
+ desc "Open an irb session preloaded with this library"
66
+ task :console do
67
+ sh "irb -rubygems -r ./lib/#{name}.rb"
68
+ end
69
+
70
+ desc "Prints code to test ratio stats"
61
71
  task :stats do
72
+ CODE_FILES = "lib/**/*.rb"
73
+ TEST_FILES = "test/*_test.rb"
74
+
62
75
  code_code, code_comments = count_lines(FileList[CODE_FILES])
63
76
  test_code, test_comments = count_lines(FileList[TEST_FILES])
64
77
 
@@ -70,13 +83,6 @@ task :stats do
70
83
  puts "Code to test ratio: 1:%.2f" % ratio
71
84
  end
72
85
 
73
- Rcov::RcovTask.new do |t|
74
- t.libs << "test"
75
- t.test_files = FileList['test/*_test.rb']
76
- t.output_dir = 'test/coverage'
77
- t.verbose = true
78
- end
79
-
80
86
  def count_lines(files)
81
87
  code = 0
82
88
  comments = 0
@@ -91,3 +97,55 @@ def count_lines(files)
91
97
  end
92
98
  [code, comments]
93
99
  end
100
+
101
+ #############################################################################
102
+ #
103
+ # Packaging tasks
104
+ #
105
+ #############################################################################
106
+
107
+ desc "git tag, build and release gem"
108
+ task :release => :build do
109
+ unless `git branch` =~ /^\* master$/
110
+ puts "You must be on the master branch to release!"
111
+ exit!
112
+ end
113
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
114
+ sh "git tag v#{version}"
115
+ sh "git push origin master"
116
+ sh "git push origin v#{version}"
117
+ sh "gem push pkg/#{name}-#{version}.gem"
118
+ end
119
+
120
+ desc "Build gem"
121
+ task :build => :gemspec do
122
+ sh "mkdir -p pkg"
123
+ sh "gem build #{gemspec_file}"
124
+ sh "mv #{gem_file} pkg"
125
+ end
126
+
127
+ task :gemspec do
128
+ # read spec file and split out manifest section
129
+ spec = File.read(gemspec_file)
130
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
131
+
132
+ # replace name version and date
133
+ replace_header(head, :name)
134
+ replace_header(head, :version)
135
+ replace_header(head, :date)
136
+
137
+ # determine file list from git ls-files
138
+ files = `git ls-files`.
139
+ split("\n").
140
+ sort.
141
+ reject { |file| file =~ /^\./ }.
142
+ reject { |file| file =~ /^(rdoc|pkg|coverage)/ }.
143
+ map { |file| " #{file}" }.
144
+ join("\n")
145
+
146
+ # piece file back together and write
147
+ manifest = " s.files = %w[\n#{files}\n ]\n"
148
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
149
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
150
+ puts "Updated #{gemspec_file}"
151
+ end
data/lib/XMLUtils.rb CHANGED
@@ -6,104 +6,46 @@ require 'stringio'
6
6
  # parser whose events are the text of whole elements instead of start and end
7
7
  # tags.
8
8
 
9
- module XMLUtils
10
- # Gets the REXML DOM for a given filename that must be a XML file.
11
- def self.getdoc(filename)
12
- file = File.new(filename, 'r')
13
- REXML::Document.new file
14
- end
9
+ module XMLCodec
10
+ module XMLUtils
11
+ # Gets the Nokogiri DOM for a given filename that must be a XML file.
12
+ def self.getdoc(filename)
13
+ Nokogiri::XML::Document.parse File.new(filename, 'r')
14
+ end
15
15
 
16
- # Count the number of elements that correspond to a given xpath in a file.
17
- def self.count_elements(path, filename)
18
- doc = getdoc(filename)
19
- i = 0
20
- REXML::XPath.each(doc, path) {|element| i+=1}
21
- return i
22
- end
16
+ # Count the number of elements that correspond to a given xpath in a file.
17
+ def self.count_elements(path, filename)
18
+ getdoc(filename).xpath(path).size
19
+ end
23
20
 
24
- # Test if a given xpath exists in the file.
25
- def self.element_exists(path, filename)
26
- count_elements(path,filename)>0
27
- end
21
+ # Test if a given xpath exists in the file.
22
+ def self.element_exists(path, filename)
23
+ count_elements(path,filename)>0
24
+ end
28
25
 
29
- # Get a xpath from a REXML document.
30
- def self.select_path_doc(path, doc)
31
- element = REXML::XPath.first(doc, path)
32
- return "" if not element
33
- if element.respond_to?("value")
34
- return element.value || ""
26
+ # Get a xpath from a Nokogiri::XML::Document.
27
+ def self.select_path_doc(path, doc)
28
+ els = doc.xpath(path)
29
+ return "" if !els[0]
30
+ els[0].children.to_s
35
31
  end
36
- return element.text || ""
37
- end
38
32
 
39
- # Get a xpath from a file.
40
- def self.select_path(path, filename)
41
- XMLUtils::select_path_doc(path, getdoc(filename))
42
- end
43
-
44
- # Create an open tag.
45
- def self.create_open_tag(name, attrs)
46
- str = "<"+name
47
- attrs.each {|name, value| str << " #{name}='#{value}'"}
48
- str << ">"
49
- str
50
- end
51
-
52
- # Escape a string so that it can be included in a XML document
53
- def self.escape_xml(string)
54
- t = REXML::Text.new('')
55
- str = ''
56
- t.write_with_substitution(str, string)
57
- str
58
- end
59
-
60
- # Gets the xpath inside a given document that can either be a string or a
61
- # REXML::Document
62
- #
63
- # Supported options (boolean):
64
- # [:multiple]
65
- # fetch all the occurences of the xpath
66
- # [:with_attrs]
67
- # include the attribute contents in the result
68
- # [:recursive]
69
- # recursively include all the subelements of the matches
70
- def self.get_xpath(path, doc, opts={})
71
- if doc.is_a? REXML::Document
72
- doc = doc
73
- else
74
- doc = REXML::Document.new(doc)
33
+ # Get a xpath from a file.
34
+ def self.select_path(path, filename)
35
+ select_path_doc(path, getdoc(filename))
75
36
  end
76
37
 
77
- if opts[:multiple]
78
- strs = []
79
- REXML::XPath.each(doc.root, path) do |e|
80
- t = get_rexml_content(e, opts)
81
- strs << t if t != ''
82
- end
83
- return strs.join(' ')
84
- else
85
- return get_rexml_content(REXML::XPath.first(doc, path), opts)
38
+ # Create an open tag.
39
+ def self.create_open_tag(name, attrs)
40
+ str = "<"+name
41
+ attrs.each {|name, value| str << " #{name}='#{value}'"}
42
+ str << ">"
43
+ str
86
44
  end
87
- end
88
-
89
- def self.get_rexml_content(element, opts={})
90
- return '' if not element
91
- strs = []
92
- if element.respond_to?('value')
93
- return element.value || ''
94
- else
95
- if opts[:with_attrs]
96
- element.attributes.each {|name, value| strs << value if value != ''}
97
- end
98
- if not opts[:recursive]
99
- element.texts.each {|t| strs << t if t != ''}
100
- else
101
- element.to_a.each do |e|
102
- t = get_rexml_content(e, opts)
103
- strs << t if t != ''
104
- end
105
- end
45
+
46
+ # Escape a string so that it can be included in a XML document
47
+ def self.escape_xml(string)
48
+ Nokogiri::XML::Text.new(string, Nokogiri::XML::Document.new).to_s
106
49
  end
107
- strs.join(' ')
108
50
  end
109
51
  end
data/lib/element.rb CHANGED
@@ -2,6 +2,9 @@ module XMLCodec
2
2
  class ElementClassNotFound < RuntimeError
3
3
  end
4
4
 
5
+ class ElementAttributeNotFound < RuntimeError
6
+ end
7
+
5
8
  # This class should be inherited from to create classes that are able to
6
9
  # import and export XML elements and their children. It provides three main
7
10
  # functions: xmlattr, xmlsubel and xmlsubel_mult.
@@ -15,16 +18,13 @@ module XMLCodec
15
18
  # has no subelements and includes only text content.
16
19
  #
17
20
  # After the class is defined import_xml can be used to import the content from
18
- # a REXML Element or Document and create_xml can be used to create the XML DOM
19
- # of the element as a child to a REXML Element or Document. For big documents
21
+ # a Nokogiri Element or Document and create_xml can be used to create the XML DOM
22
+ # of the element as a child to a Nokogiri Element or Document. For big documents
20
23
  # these are usually too slow and memory hungry, using xml_text to export to
21
24
  # XML and import_xml_text to import XML are probably better ideas.
22
25
  # import_xml_text is just a utility function around XMLStreamObjectParser,
23
26
  # that allow more flexible stream parsing of XML files while still using the
24
27
  # same XMLElement objects.
25
- #
26
- # <b>WARNING</b>: This API is still very much a work in progress and very
27
- # rough in certain places. Changes will surely be made.
28
28
  class XMLElement
29
29
  INDENT_STR = ' '
30
30
  CACHE = {}
@@ -48,7 +48,7 @@ module XMLCodec
48
48
  def self.xmlattrs
49
49
  @xmlattrs ||=[]
50
50
  end
51
-
51
+
52
52
  # Add a name as being a subelement (mult or single)
53
53
  def self._xmlsubel(name)
54
54
  self.xmlsubels << name
@@ -74,7 +74,7 @@ module XMLCodec
74
74
  self._xmlsubel(name)
75
75
  self.xmlsubelmultiples << name
76
76
  define_method(name){
77
- if not self.instance_variables.index("@#{name}")
77
+ if not self.instance_variables.index("@#{name}".to_sym)
78
78
  instance_variable_set "@#{name}", XMLSubElements.new(self)
79
79
  end
80
80
  instance_variable_get "@#{name}"
@@ -83,7 +83,7 @@ module XMLCodec
83
83
 
84
84
  # Iterates over the object's XML subelements
85
85
  def self.each_subel
86
- if not self.instance_variables.index("@__subel_names")
86
+ if not self.instance_variables.index("@__subel_names".to_sym)
87
87
  names = []
88
88
  # Iterate all the superclasses that are still children of XMLElement
89
89
  # and iterate each of the subelements
@@ -100,7 +100,7 @@ module XMLCodec
100
100
  # Iterate all the superclasses that are still children of XMLElement
101
101
  # and check if any of them have the subelement mult defined
102
102
  def self.subel_mult?(element)
103
- if not self.instance_variables.index("@__subel_mult_names")
103
+ if not self.instance_variables.index("@__subel_mult_names".to_sym)
104
104
  names = []
105
105
  c = self
106
106
  while c.ancestors.index(XMLCodec::XMLElement)
@@ -130,9 +130,13 @@ module XMLCodec
130
130
 
131
131
  # Iterates over the object's XML atributes
132
132
  def self.each_attr
133
- if not self.instance_variables.index("@__attr_names")
133
+ attr_names.each {|name| yield name}
134
+ end
135
+
136
+ def self.attr_names
137
+ if not self.instance_variables.index("@__attr_names".to_sym)
134
138
  names = []
135
- # Iterate all the superclasses that are still children of EADElement
139
+ # Iterate all the superclasses that are still children of XMLElement
136
140
  # and iterate each of the attributes
137
141
  c = self
138
142
  while c.ancestors.index(XMLCodec::XMLElement)
@@ -142,7 +146,7 @@ module XMLCodec
142
146
  @__attr_names = names
143
147
  end
144
148
 
145
- @__attr_names.each {|name| yield name}
149
+ @__attr_names
146
150
  end
147
151
 
148
152
  # Creates the XML for the atributes
@@ -150,7 +154,7 @@ module XMLCodec
150
154
  self.class.each_attr do |a|
151
155
  value = self.send(a)
152
156
  if value
153
- parent.add_attribute(a.to_s, value)
157
+ parent.set_attribute(a.to_s, value)
154
158
  end
155
159
  end
156
160
  end
@@ -164,7 +168,7 @@ module XMLCodec
164
168
  attrs[a.to_s] = value
165
169
  end
166
170
  end
167
- XMLUtils::create_open_tag(elname.to_s, attrs)
171
+ XMLCodec::XMLUtils::create_open_tag(elname.to_s, attrs)
168
172
  end
169
173
 
170
174
  # returns a string with the closing tag for the element
@@ -176,7 +180,7 @@ module XMLCodec
176
180
  # method called #subelements that will return an instance of XMLSubElements
177
181
  def self.xmlsubelements #:doc:
178
182
  define_method(:subelements) {
179
- if not self.instance_variables.index("@subelements")
183
+ if not self.instance_variables.index("@subelements".to_sym)
180
184
  @subelements = XMLSubElements.new(self)
181
185
  end
182
186
  @subelements
@@ -198,7 +202,7 @@ module XMLCodec
198
202
 
199
203
  # Add a xmlattr type attribute (wrapper around attr_accessor)
200
204
  def self.xmlattr(name) #:doc:
201
- self.xmlattrs << name
205
+ self.xmlattrs << name.to_sym
202
206
  attr_accessor name
203
207
  end
204
208
 
@@ -206,6 +210,11 @@ module XMLCodec
206
210
  # a class that's the super class of all the elements of a format
207
211
  def self.xmlformat(name=nil)
208
212
  class_variable_set('@@elclasses', {})
213
+ class_variable_set('@@strict_parsing', false)
214
+ end
215
+
216
+ def self.xml_strict_parsing
217
+ class_variable_set('@@strict_parsing', true)
209
218
  end
210
219
 
211
220
  def self.elclasses
@@ -258,22 +267,12 @@ module XMLCodec
258
267
  self.subelements.create_xml(parent)
259
268
  end
260
269
 
261
- # Have we already started the partial export of this element?
262
- def already_partial_exported?
263
- (@already_partial_exported ||= false)
264
- end
265
-
266
- # Have we already ended the partial export of this element?
267
- def already_partial_export_ended?
268
- (@already_partial_export_ended ||= false)
269
- end
270
-
271
270
  # Which level of indentation are we in?
272
271
  #
273
272
  # This is currently disabled until I get around to implementing proper and
274
273
  # tested indent support.
275
274
  #def indent_level
276
- # if not self.instance_variables.index '@indent_level'
275
+ # if not self.instance_variables.index '@indent_level'.to_sym
277
276
  # curr = self
278
277
  # level = 0
279
278
  # while curr = curr.__parent
@@ -338,7 +337,7 @@ module XMLCodec
338
337
  # Gets the class for a certain element name.
339
338
  def self.get_element_class(name)
340
339
  cl = elclasses[name.to_sym]
341
- if not cl
340
+ if not cl and class_variable_get('@@strict_parsing')
342
341
  raise ElementClassNotFound, "No class defined for element type: '" + name.to_s + "'"
343
342
  end
344
343
  cl
@@ -360,12 +359,12 @@ module XMLCodec
360
359
 
361
360
 
362
361
  # Creates the xml for the element inside the parent element. The parent
363
- # passed should be a REXML element or document. This call is recursive
362
+ # passed should be a Nokogiri XML Node or Document. This call is recursive
364
363
  # creating the XML for any subelements.
365
364
  def create_xml(parent)
366
- xmlel = parent.add_element self.elname.to_s
365
+ xmlel = parent.add_child Nokogiri::XML::Element.new(self.elname.to_s, parent)
367
366
  if self.hasvalue?
368
- xmlel.text = self.value
367
+ xmlel.add_child self.value
369
368
  end
370
369
  create_xml_attr(xmlel)
371
370
  create_xml_subel(xmlel)
@@ -377,34 +376,55 @@ module XMLCodec
377
376
  xmlel
378
377
  end
379
378
 
380
- # Import the XML into an object from a REXML element. This call is recursive
381
- # and imports any subelements found into the corresponding objects.
382
- def self.import_xml(xmlel)
383
- if xmlel.is_a? REXML::Document
379
+ # Import the XML into an object from a Nokogiri XML Node or Document or from
380
+ # a string.
381
+ def self.import_xml(obj)
382
+ if obj.instance_of? String
383
+ _import_xml_text(obj)
384
+ elsif obj.instance_of? Nokogiri::XML::Node or
385
+ obj.instance_of? Nokogiri::XML::Document
386
+ _import_xml_dom(obj)
387
+ else
388
+ nil
389
+ end
390
+ end
391
+
392
+ # Import the XML into an object from a Nokogiri XML Node or Document.
393
+ # This call is recursive and imports any subelements found into the
394
+ # corresponding objects.
395
+ def self._import_xml_dom(xmlel)
396
+ if xmlel.is_a? Nokogiri::XML::Document
384
397
  xmlel = xmlel.root
385
398
  end
386
399
 
387
400
  elements = []
388
- xmlel.to_a.each do |e|
389
- if e.is_a? REXML::Text
390
- elements << e.value
401
+ xmlel.children.each do |e|
402
+ if e.text?
403
+ elements << e.text
391
404
  else
392
405
  elclass = get_element_class(e.name)
393
- elements << elclass.import_xml(e)
406
+ if not elclass
407
+ if class_variable_get('@@strict_parsing')
408
+ raise ElementClassNotFound, "No class defined for element type: '#{e.name}'"
409
+ end
410
+ else
411
+ elements << elclass._import_xml_dom(e)
412
+ end
394
413
  end
395
414
  end
396
415
 
397
416
  attributes = {}
398
- xmlel.attributes.each do |name, value|
399
- attributes[name] = value
417
+ xmlel.attributes.each do |name, attr|
418
+ attributes[name] = attr.value
400
419
  end
401
420
 
402
- new_with_content(attributes, elements)
421
+ elclass = get_element_class(xmlel.name)
422
+ return nil if not elclass
423
+ elclass.new_with_content(attributes, elements)
403
424
  end
404
425
 
405
- # Import the XML directly from the text. This call receives the text and the
406
- # classes that should be used to import the subelements.
407
- def self.import_xml_text(text)
426
+ # Import the XML directly from the text.
427
+ def self._import_xml_text(text)
408
428
  parser = XMLStreamObjectParser.new(self)
409
429
  parser.parse(text)
410
430
  parser.top_element
@@ -436,7 +456,13 @@ module XMLCodec
436
456
  # add the attributes passed as a hash to the element
437
457
  def add_attr(attrs)
438
458
  attrs.each do |name, value|
439
- self.send("#{name}=", value)
459
+ if not self.class.attr_names.include?(name.to_sym)
460
+ if self.class.class_variable_get('@@strict_parsing')
461
+ raise ElementAttributeNotFound, "No attribute '#{name}' defined for class '#{self.class}'"
462
+ end
463
+ else
464
+ self.send("#{name}=", value)
465
+ end
440
466
  end
441
467
  end
442
468
 
@@ -467,12 +493,11 @@ module XMLCodec
467
493
  end
468
494
 
469
495
 
470
- # create the XML text of the element. This does not use REXML so should be
471
- # pretty fast.
496
+ # create the XML text of the element
472
497
  def xml_text
473
498
  str = create_open_tag
474
499
  if self.hasvalue?
475
- str << XMLUtils::escape_xml(self.value)
500
+ str << XMLCodec::XMLUtils::escape_xml(self.value)
476
501
  end
477
502
 
478
503
  each_subelement do |e|
@@ -483,6 +508,16 @@ module XMLCodec
483
508
  str
484
509
  end
485
510
 
511
+ # Have we already started the partial export of this element?
512
+ def already_partial_exported?
513
+ (@already_partial_exported ||= false)
514
+ end
515
+
516
+ # Have we already ended the partial export of this element?
517
+ def already_partial_export_ended?
518
+ (@already_partial_export_ended ||= false)
519
+ end
520
+
486
521
  # Export this element into a file. Will also start to export the parents of
487
522
  # the element. It's equivalent to calling start_partial_export followed by
488
523
  # end_partial_export.
@@ -505,7 +540,7 @@ module XMLCodec
505
540
 
506
541
  file << create_open_tag
507
542
  if self.hasvalue?
508
- file << XMLUtils::escape_xml(self.value)
543
+ file << XMLCodec::XMLUtils::escape_xml(self.value)
509
544
  end
510
545
 
511
546
  each_subelement do |e|