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 +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|
|