xmlcodec 0.1.3 → 0.3.0
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.
- data/README.rdoc +13 -9
- data/Rakefile +113 -55
- data/lib/XMLUtils.rb +32 -90
- data/lib/element.rb +85 -50
- data/lib/stream_object_parser.rb +11 -11
- data/lib/stream_parser.rb +8 -11
- data/lib/subelements.rb +2 -2
- data/lib/xmlcodec.rb +6 -0
- data/test/element_test.rb +20 -18
- data/test/multi_format_test.rb +3 -3
- data/test/partial_import_test.rb +38 -0
- data/test/simple_objects.rb +6 -0
- data/test/subelements_test.rb +11 -1
- data/test/test_helper.rb +4 -1
- data/test/utils_test.rb +7 -40
- data/xmlcodec.gemspec +57 -0
- metadata +71 -64
data/README.rdoc
CHANGED
@@ -30,17 +30,21 @@ you would create the following classes:
|
|
30
30
|
|
31
31
|
require 'xmlcodec'
|
32
32
|
|
33
|
-
class
|
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 <
|
42
|
+
class FirstElement < Format
|
39
43
|
elname 'firstelement'
|
40
44
|
xmlsubel_mult :secondelement
|
41
45
|
end
|
42
46
|
|
43
|
-
class SecondElement <
|
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.
|
76
|
+
Root.import_xml File.new('file.xml')
|
73
77
|
|
74
|
-
# From a
|
75
|
-
Root.import_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
|
83
|
-
doc = some_element.create_xml(
|
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
|
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
|
-
|
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 '
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
-
|
40
|
+
def replace_header(head, header_name)
|
41
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
45
42
|
end
|
46
43
|
|
47
|
-
|
48
|
-
|
49
|
-
|
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 |
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
19
|
-
# of the element as a child to a
|
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
|
-
|
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
|
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
|
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.
|
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
|
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.
|
365
|
+
xmlel = parent.add_child Nokogiri::XML::Element.new(self.elname.to_s, parent)
|
367
366
|
if self.hasvalue?
|
368
|
-
xmlel.
|
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
|
381
|
-
#
|
382
|
-
def self.import_xml(
|
383
|
-
if
|
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.
|
389
|
-
if e.
|
390
|
-
elements << e.
|
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
|
-
|
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,
|
399
|
-
attributes[name] = value
|
417
|
+
xmlel.attributes.each do |name, attr|
|
418
|
+
attributes[name] = attr.value
|
400
419
|
end
|
401
420
|
|
402
|
-
|
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.
|
406
|
-
|
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.
|
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
|
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|
|