xmlcodec 0.3.1 → 0.3.2
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/Rakefile +10 -6
- data/lib/element.rb +0 -444
- data/lib/element_attrs.rb +36 -0
- data/lib/element_creation.rb +66 -0
- data/lib/element_export.rb +78 -0
- data/lib/element_import.rb +64 -0
- data/lib/element_partial_export.rb +68 -0
- data/lib/element_subel.rb +93 -0
- data/lib/element_subelements.rb +33 -0
- data/lib/element_value.rb +41 -0
- data/lib/xmlcodec.rb +14 -6
- data/xmlcodec.gemspec +11 -2
- metadata +14 -5
data/Rakefile
CHANGED
@@ -105,13 +105,17 @@ end
|
|
105
105
|
#############################################################################
|
106
106
|
|
107
107
|
desc "git tag, build and release gem"
|
108
|
-
task :release => :build do
|
109
|
-
|
110
|
-
|
111
|
-
|
108
|
+
task :release => :build do |t|
|
109
|
+
if not File.exists? "pkg/tag-#{version}"
|
110
|
+
unless `git branch` =~ /^\* master$/
|
111
|
+
puts "You must be on the master branch to release!"
|
112
|
+
exit!
|
113
|
+
end
|
114
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
115
|
+
sh "git tag v#{version}"
|
116
|
+
sh "touch pkg/tag-#{version}"
|
112
117
|
end
|
113
|
-
|
114
|
-
sh "git tag v#{version}"
|
118
|
+
|
115
119
|
sh "git push origin master"
|
116
120
|
sh "git push origin v#{version}"
|
117
121
|
sh "gem push pkg/#{name}-#{version}.gem"
|
data/lib/element.rb
CHANGED
@@ -34,177 +34,6 @@ module XMLCodec
|
|
34
34
|
## A xmlsubel is any subelement of a given element
|
35
35
|
|
36
36
|
private
|
37
|
-
# Class level variable to hold the list of subelements
|
38
|
-
def self.xmlsubels
|
39
|
-
@xmlsubels ||=[]
|
40
|
-
end
|
41
|
-
|
42
|
-
# Class level variable to hold the list of subelements that are multiple
|
43
|
-
def self.xmlsubelmultiples
|
44
|
-
@xmlsubelmultiples ||=[]
|
45
|
-
end
|
46
|
-
|
47
|
-
# Class level variable that holds the list of attributes
|
48
|
-
def self.xmlattrs
|
49
|
-
@xmlattrs ||=[]
|
50
|
-
end
|
51
|
-
|
52
|
-
# Add a name as being a subelement (mult or single)
|
53
|
-
def self._xmlsubel(name)
|
54
|
-
self.xmlsubels << name
|
55
|
-
end
|
56
|
-
|
57
|
-
# Add a xmlsubel type attribute
|
58
|
-
def self.xmlsubel(name) #:doc:
|
59
|
-
name = name.to_sym
|
60
|
-
self._xmlsubel(name)
|
61
|
-
attr_reader name
|
62
|
-
define_method((name.to_s+"=").to_sym) { |value|
|
63
|
-
if value.is_a? String or value.is_a? Fixnum
|
64
|
-
value = self.class.get_element_class(name).new(value)
|
65
|
-
end
|
66
|
-
value.__parent = self if value
|
67
|
-
instance_variable_set "@#{name}", value
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
# Add a xmlsubel_mult type attribute (wrapper around attr_accessor)
|
72
|
-
def self.xmlsubel_mult(name) #:doc:
|
73
|
-
name = name.to_sym
|
74
|
-
self._xmlsubel(name)
|
75
|
-
self.xmlsubelmultiples << name
|
76
|
-
define_method(name){
|
77
|
-
if not self.instance_variables.index("@#{name}".to_sym)
|
78
|
-
instance_variable_set "@#{name}", XMLSubElements.new(self)
|
79
|
-
end
|
80
|
-
instance_variable_get "@#{name}"
|
81
|
-
}
|
82
|
-
end
|
83
|
-
|
84
|
-
# Iterates over the object's XML subelements
|
85
|
-
def self.each_subel
|
86
|
-
if not self.instance_variables.index("@__subel_names".to_sym)
|
87
|
-
names = []
|
88
|
-
# Iterate all the superclasses that are still children of XMLElement
|
89
|
-
# and iterate each of the subelements
|
90
|
-
c = self
|
91
|
-
while c.ancestors.index(XMLCodec::XMLElement)
|
92
|
-
names += c.xmlsubels
|
93
|
-
c = c.superclass
|
94
|
-
end
|
95
|
-
@__subel_names = names
|
96
|
-
end
|
97
|
-
@__subel_names.each {|name| yield name}
|
98
|
-
end
|
99
|
-
|
100
|
-
# Iterate all the superclasses that are still children of XMLElement
|
101
|
-
# and check if any of them have the subelement mult defined
|
102
|
-
def self.subel_mult?(element)
|
103
|
-
if not self.instance_variables.index("@__subel_mult_names".to_sym)
|
104
|
-
names = []
|
105
|
-
c = self
|
106
|
-
while c.ancestors.index(XMLCodec::XMLElement)
|
107
|
-
names += c.xmlsubelmultiples
|
108
|
-
c = c.superclass
|
109
|
-
end
|
110
|
-
@__subel_mult_names = names
|
111
|
-
end
|
112
|
-
return @__subel_mult_names.index(element)? true : false
|
113
|
-
end
|
114
|
-
|
115
|
-
# Iterate all the superclasses that are still children of XMLElement
|
116
|
-
# and check if any of them have any subelements handled by this class
|
117
|
-
def get_subel(elclass)
|
118
|
-
names = elclass.get_elnames
|
119
|
-
c = self.class
|
120
|
-
while c.ancestors.index(XMLCodec::XMLElement)
|
121
|
-
names.each do |name|
|
122
|
-
if c.xmlsubels.index(name.to_sym)
|
123
|
-
return names[0].to_sym
|
124
|
-
end
|
125
|
-
end
|
126
|
-
c = c.superclass
|
127
|
-
end
|
128
|
-
return nil
|
129
|
-
end
|
130
|
-
|
131
|
-
# Iterates over the object's XML atributes
|
132
|
-
def self.each_attr
|
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)
|
138
|
-
names = []
|
139
|
-
# Iterate all the superclasses that are still children of XMLElement
|
140
|
-
# and iterate each of the attributes
|
141
|
-
c = self
|
142
|
-
while c.ancestors.index(XMLCodec::XMLElement)
|
143
|
-
names += c.xmlattrs
|
144
|
-
c = c.superclass
|
145
|
-
end
|
146
|
-
@__attr_names = names
|
147
|
-
end
|
148
|
-
|
149
|
-
@__attr_names
|
150
|
-
end
|
151
|
-
|
152
|
-
# Creates the XML for the atributes
|
153
|
-
def create_xml_attr(parent)
|
154
|
-
self.class.each_attr do |a|
|
155
|
-
value = self.send(a)
|
156
|
-
if value
|
157
|
-
parent.set_attribute(a.to_s, value)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# returns a string with the opening tag for the element
|
163
|
-
def create_open_tag
|
164
|
-
attrs = {}
|
165
|
-
self.class.each_attr do |a|
|
166
|
-
value = self.send(a)
|
167
|
-
if value
|
168
|
-
attrs[a.to_s] = value
|
169
|
-
end
|
170
|
-
end
|
171
|
-
XMLCodec::XMLUtils::create_open_tag(elname.to_s, attrs)
|
172
|
-
end
|
173
|
-
|
174
|
-
# returns a string with the closing tag for the element
|
175
|
-
def create_close_tag
|
176
|
-
XMLCodec::XMLUtils::create_close_tag(elname.to_s)
|
177
|
-
end
|
178
|
-
|
179
|
-
# Declare the class as having many subelements. Instances will have a
|
180
|
-
# method called #subelements that will return an instance of XMLSubElements
|
181
|
-
def self.xmlsubelements #:doc:
|
182
|
-
define_method(:subelements) {
|
183
|
-
@subelements ||= XMLSubElements.new(self)
|
184
|
-
}
|
185
|
-
define_method('<<') {|value|
|
186
|
-
subelements << value
|
187
|
-
}
|
188
|
-
define_method(:find_first_named) {|name|
|
189
|
-
subelements.find_first_named(name)
|
190
|
-
}
|
191
|
-
define_method('[]') {|name|
|
192
|
-
subelements.find_first_named(name)
|
193
|
-
}
|
194
|
-
define_method(:find_all_named) {|name|
|
195
|
-
subelements.find_all_named(name)
|
196
|
-
}
|
197
|
-
self.class_eval do
|
198
|
-
def self.has_subelements?; true; end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# Add a xmlattr type attribute (wrapper around attr_accessor)
|
203
|
-
def self.xmlattr(name) #:doc:
|
204
|
-
self.xmlattrs << name.to_sym
|
205
|
-
attr_accessor name
|
206
|
-
end
|
207
|
-
|
208
37
|
# Defines a new xml format (like XHTML or DocBook). This should be used in
|
209
38
|
# a class that's the super class of all the elements of a format
|
210
39
|
def self.xmlformat(name=nil)
|
@@ -238,48 +67,6 @@ module XMLCodec
|
|
238
67
|
def self.get_elnames
|
239
68
|
@elnames||=[]
|
240
69
|
end
|
241
|
-
|
242
|
-
# Set the element as having a value. The element will have an initializer
|
243
|
-
# that takes a value as argument and an accessor named #value. This should
|
244
|
-
# be used for elements that contain only text and no subelements
|
245
|
-
def self.elwithvalue
|
246
|
-
self.class_eval do
|
247
|
-
def self.hasvalue?; true; end
|
248
|
-
end
|
249
|
-
self.class_eval do
|
250
|
-
def initialize(value=nil)
|
251
|
-
@value = value
|
252
|
-
end
|
253
|
-
end
|
254
|
-
attr_accessor :value
|
255
|
-
end
|
256
|
-
|
257
|
-
# Set the element as having a value that eats up any subelements as if they
|
258
|
-
# were text. The element will behave similarly to "elwithvalue" with an
|
259
|
-
# initializar that takes a value as argument and an accessor named #value
|
260
|
-
# and will consume all its subelements as if they were text. This should
|
261
|
-
# be used for elements that contain subelements that you want to treat as
|
262
|
-
# text like <content> in Atom
|
263
|
-
def self.elallvalue
|
264
|
-
self.elwithvalue
|
265
|
-
self.class_eval do
|
266
|
-
def self.allvalue?; true; end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# Creates the XML subelements
|
271
|
-
def create_xml_subel(parent)
|
272
|
-
self.class.each_subel do |a|
|
273
|
-
if value = self.send(a)
|
274
|
-
value.create_xml(parent)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
# Create the XML of the SubElements
|
280
|
-
def create_xml_subelements(parent)
|
281
|
-
self.subelements.create_xml(parent)
|
282
|
-
end
|
283
70
|
|
284
71
|
# Which level of indentation are we in?
|
285
72
|
#
|
@@ -361,236 +148,5 @@ module XMLCodec
|
|
361
148
|
def self.get_element_names(name)
|
362
149
|
get_element_class(name).get_elnames
|
363
150
|
end
|
364
|
-
|
365
|
-
# Method that checks if a given class has subelements. This is usually only
|
366
|
-
# used when exporting stuff.
|
367
|
-
def self.has_subelements?; false end
|
368
|
-
def has_subelements?; self.class.has_subelements? end
|
369
|
-
|
370
|
-
# tests if the element is a value element as defined by 'elwithvalue'
|
371
|
-
def self.hasvalue?; false end
|
372
|
-
def hasvalue?; self.class.hasvalue? end
|
373
|
-
|
374
|
-
# tests if the element is a value element as defined by 'elallvalue'
|
375
|
-
def self.allvalue?; false end
|
376
|
-
def allvalue?; self.class.allvalue?; end
|
377
|
-
|
378
|
-
# Creates the xml for the element inside the parent element. The parent
|
379
|
-
# passed should be a Nokogiri XML Node or Document. This call is recursive
|
380
|
-
# creating the XML for any subelements.
|
381
|
-
def create_xml(parent)
|
382
|
-
xmlel = parent.add_child Nokogiri::XML::Element.new(self.elname.to_s, parent)
|
383
|
-
if self.hasvalue?
|
384
|
-
xmlel.add_child self.value
|
385
|
-
end
|
386
|
-
create_xml_attr(xmlel)
|
387
|
-
create_xml_subel(xmlel)
|
388
|
-
|
389
|
-
if self.has_subelements?
|
390
|
-
create_xml_subelements(xmlel)
|
391
|
-
end
|
392
|
-
|
393
|
-
xmlel
|
394
|
-
end
|
395
|
-
|
396
|
-
# Import the XML into an object from a Nokogiri XML Node or Document or from
|
397
|
-
# a string.
|
398
|
-
def self.import_xml(obj)
|
399
|
-
if obj.instance_of? String
|
400
|
-
_import_xml_text(obj)
|
401
|
-
elsif obj.instance_of? Nokogiri::XML::Node or
|
402
|
-
obj.instance_of? Nokogiri::XML::Document
|
403
|
-
_import_xml_dom(obj)
|
404
|
-
else
|
405
|
-
nil
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
# Import the XML into an object from a Nokogiri XML Node or Document.
|
410
|
-
# This call is recursive and imports any subelements found into the
|
411
|
-
# corresponding objects.
|
412
|
-
def self._import_xml_dom(xmlel)
|
413
|
-
if xmlel.is_a? Nokogiri::XML::Document
|
414
|
-
xmlel = xmlel.root
|
415
|
-
end
|
416
|
-
|
417
|
-
elclass = get_element_class(xmlel.name)
|
418
|
-
if not elclass
|
419
|
-
if class_variable_get(:@@strict_parsing)
|
420
|
-
raise ElementClassNotFound, "No class defined for element type: '#{e.name}'"
|
421
|
-
else
|
422
|
-
return nil
|
423
|
-
end
|
424
|
-
end
|
425
|
-
|
426
|
-
if elclass.allvalue?
|
427
|
-
elements = [xmlel.children.map{|c| c.to_xml(:save_with=>0)}.join]
|
428
|
-
else
|
429
|
-
elements = []
|
430
|
-
xmlel.children.each do |e|
|
431
|
-
if e.text?
|
432
|
-
elements << e.text
|
433
|
-
else
|
434
|
-
element = _import_xml_dom(e)
|
435
|
-
elements << element if element
|
436
|
-
end
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
attributes = {}
|
441
|
-
xmlel.attributes.each do |name, attr|
|
442
|
-
attributes[name] = attr.value
|
443
|
-
end
|
444
|
-
|
445
|
-
elclass.new_with_content(attributes, elements)
|
446
|
-
end
|
447
|
-
|
448
|
-
# Import the XML directly from the text.
|
449
|
-
def self._import_xml_text(text)
|
450
|
-
parser = XMLStreamObjectParser.new(self)
|
451
|
-
parser.parse(text)
|
452
|
-
parser.top_element
|
453
|
-
end
|
454
|
-
|
455
|
-
# Create a new element passing it all the atributes, children and texts
|
456
|
-
def self.new_with_content(attrs, children)
|
457
|
-
text_children = []
|
458
|
-
element_children = []
|
459
|
-
|
460
|
-
children.each do |c|
|
461
|
-
if c.is_a? String
|
462
|
-
text_children << c
|
463
|
-
else
|
464
|
-
element_children << c
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
obj = self.allocate
|
469
|
-
obj.add_attr(attrs)
|
470
|
-
obj.add_subel(element_children)
|
471
|
-
obj.add_texts(text_children)
|
472
|
-
if obj.has_subelements?
|
473
|
-
obj.add_subelements(children)
|
474
|
-
end
|
475
|
-
obj
|
476
|
-
end
|
477
|
-
|
478
|
-
# add the attributes passed as a hash to the element
|
479
|
-
def add_attr(attrs)
|
480
|
-
attrs.each do |name, value|
|
481
|
-
if not self.class.attr_names.include?(name.to_sym)
|
482
|
-
if self.class.class_variable_get(:@@strict_parsing)
|
483
|
-
raise ElementAttributeNotFound, "No attribute '#{name}' defined for class '#{self.class}'"
|
484
|
-
end
|
485
|
-
else
|
486
|
-
self.send("#{name}=", value)
|
487
|
-
end
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
# add the text elements into the element
|
492
|
-
def add_texts(texts)
|
493
|
-
if self.hasvalue?
|
494
|
-
@value = texts.join
|
495
|
-
end
|
496
|
-
end
|
497
|
-
|
498
|
-
# add the subelements into the element
|
499
|
-
def add_subel(children)
|
500
|
-
children.each do |c|
|
501
|
-
if subel_name = get_subel(c.class)
|
502
|
-
if self.class.subel_mult? subel_name
|
503
|
-
self.send(subel_name) << c
|
504
|
-
else
|
505
|
-
self.send(subel_name.to_s+'=', c)
|
506
|
-
end
|
507
|
-
end
|
508
|
-
end
|
509
|
-
end
|
510
|
-
|
511
|
-
# If the class is one with many subelements import all of them into the
|
512
|
-
# object.
|
513
|
-
def add_subelements(all_children)
|
514
|
-
all_children.each {|c| self.subelements << c}
|
515
|
-
end
|
516
|
-
|
517
|
-
|
518
|
-
# create the XML text of the element
|
519
|
-
def xml_text
|
520
|
-
str = create_open_tag
|
521
|
-
if self.hasvalue?
|
522
|
-
str << XMLCodec::XMLUtils::escape_xml(self.value)
|
523
|
-
end
|
524
|
-
|
525
|
-
each_subelement do |e|
|
526
|
-
str << e.xml_text
|
527
|
-
end
|
528
|
-
|
529
|
-
str << create_close_tag
|
530
|
-
str
|
531
|
-
end
|
532
|
-
|
533
|
-
# Have we already started the partial export of this element?
|
534
|
-
def already_partial_exported?
|
535
|
-
(@already_partial_exported ||= false)
|
536
|
-
end
|
537
|
-
|
538
|
-
# Have we already ended the partial export of this element?
|
539
|
-
def already_partial_export_ended?
|
540
|
-
(@already_partial_export_ended ||= false)
|
541
|
-
end
|
542
|
-
|
543
|
-
# Export this element into a file. Will also start to export the parents of
|
544
|
-
# the element. It's equivalent to calling start_partial_export followed by
|
545
|
-
# end_partial_export.
|
546
|
-
def partial_export(file)
|
547
|
-
if not already_partial_exported?
|
548
|
-
start_partial_export(file)
|
549
|
-
end_partial_export(file)
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
|
-
# Starts to export the element to a file. all the existing elements will be
|
554
|
-
# exported. After calling this you should only add stuff that you will
|
555
|
-
# export explicitly by calling partial_export or start_partial_export.
|
556
|
-
def start_partial_export(file)
|
557
|
-
if not already_partial_exported?
|
558
|
-
@already_partial_exported = true
|
559
|
-
if self.__parent
|
560
|
-
self.__parent.start_partial_export(file)
|
561
|
-
end
|
562
|
-
|
563
|
-
file << create_open_tag
|
564
|
-
if self.hasvalue?
|
565
|
-
file << XMLCodec::XMLUtils::escape_xml(self.value)
|
566
|
-
end
|
567
|
-
|
568
|
-
each_subelement do |e|
|
569
|
-
e.partial_export(file)
|
570
|
-
end
|
571
|
-
end
|
572
|
-
end
|
573
|
-
|
574
|
-
# Ends the partial exporting of the element.
|
575
|
-
def end_partial_export(file)
|
576
|
-
if not already_partial_export_ended?
|
577
|
-
@already_partial_export_ended = true
|
578
|
-
|
579
|
-
if not already_partial_exported?
|
580
|
-
raise "<#{self} Trying to end the export of an element that hasn't"+
|
581
|
-
" been started yet"
|
582
|
-
end
|
583
|
-
|
584
|
-
each_subelement do |e|
|
585
|
-
e.end_partial_export(file)
|
586
|
-
end
|
587
|
-
|
588
|
-
file << create_close_tag
|
589
|
-
|
590
|
-
if self.__parent
|
591
|
-
self.__parent.delete_element(self)
|
592
|
-
end
|
593
|
-
end
|
594
|
-
end
|
595
151
|
end
|
596
152
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
private
|
4
|
+
# Add a xmlattr type attribute (wrapper around attr_accessor)
|
5
|
+
def self.xmlattr(name) #:doc:
|
6
|
+
self.xmlattrs << name.to_sym
|
7
|
+
attr_accessor name
|
8
|
+
end
|
9
|
+
|
10
|
+
# Class level variable that holds the list of attributes
|
11
|
+
def self.xmlattrs
|
12
|
+
@xmlattrs ||=[]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Iterates over the object's XML atributes
|
16
|
+
def self.each_attr
|
17
|
+
attr_names.each {|name| yield name}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.attr_names
|
21
|
+
if not self.instance_variables.index("@__attr_names".to_sym)
|
22
|
+
names = []
|
23
|
+
# Iterate all the superclasses that are still children of XMLElement
|
24
|
+
# and iterate each of the attributes
|
25
|
+
c = self
|
26
|
+
while c.ancestors.index(XMLCodec::XMLElement)
|
27
|
+
names += c.xmlattrs
|
28
|
+
c = c.superclass
|
29
|
+
end
|
30
|
+
@__attr_names = names
|
31
|
+
end
|
32
|
+
|
33
|
+
@__attr_names
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
public
|
4
|
+
# Create a new element passing it all the atributes, children and texts
|
5
|
+
def self.new_with_content(attrs, children)
|
6
|
+
text_children = []
|
7
|
+
element_children = []
|
8
|
+
|
9
|
+
children.each do |c|
|
10
|
+
if c.is_a? String
|
11
|
+
text_children << c
|
12
|
+
else
|
13
|
+
element_children << c
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
obj = self.allocate
|
18
|
+
obj.add_attr(attrs)
|
19
|
+
obj.add_subel(element_children)
|
20
|
+
obj.add_texts(text_children)
|
21
|
+
if obj.has_subelements?
|
22
|
+
obj.add_subelements(children)
|
23
|
+
end
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
|
27
|
+
# add the attributes passed as a hash to the element
|
28
|
+
def add_attr(attrs)
|
29
|
+
attrs.each do |name, value|
|
30
|
+
if not self.class.attr_names.include?(name.to_sym)
|
31
|
+
if self.class.class_variable_get(:@@strict_parsing)
|
32
|
+
raise ElementAttributeNotFound, "No attribute '#{name}' defined for class '#{self.class}'"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
self.send("#{name}=", value)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# add the text elements into the element
|
41
|
+
def add_texts(texts)
|
42
|
+
if self.hasvalue?
|
43
|
+
@value = texts.join
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# add the subelements into the element
|
48
|
+
def add_subel(children)
|
49
|
+
children.each do |c|
|
50
|
+
if subel_name = get_subel(c.class)
|
51
|
+
if self.class.subel_mult? subel_name
|
52
|
+
self.send(subel_name) << c
|
53
|
+
else
|
54
|
+
self.send(subel_name.to_s+'=', c)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# If the class is one with many subelements import all of them into the
|
61
|
+
# object.
|
62
|
+
def add_subelements(all_children)
|
63
|
+
all_children.each {|c| self.subelements << c}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
public
|
4
|
+
# Creates the xml for the element inside the parent element. The parent
|
5
|
+
# passed should be a Nokogiri XML Node or Document. This call is recursive
|
6
|
+
# creating the XML for any subelements.
|
7
|
+
def create_xml(parent)
|
8
|
+
xmlel = parent.add_child Nokogiri::XML::Element.new(self.elname.to_s, parent)
|
9
|
+
if self.hasvalue?
|
10
|
+
xmlel.add_child self.value
|
11
|
+
end
|
12
|
+
create_xml_attr(xmlel)
|
13
|
+
create_xml_subel(xmlel)
|
14
|
+
|
15
|
+
if self.has_subelements?
|
16
|
+
create_xml_subelements(xmlel)
|
17
|
+
end
|
18
|
+
|
19
|
+
xmlel
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates the XML subelements
|
23
|
+
def create_xml_subel(parent)
|
24
|
+
self.class.each_subel do |a|
|
25
|
+
if value = self.send(a)
|
26
|
+
value.create_xml(parent)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create the XML of the SubElements
|
32
|
+
def create_xml_subelements(parent)
|
33
|
+
self.subelements.create_xml(parent)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates the XML for the atributes
|
37
|
+
def create_xml_attr(parent)
|
38
|
+
self.class.each_attr do |a|
|
39
|
+
value = self.send(a)
|
40
|
+
if value
|
41
|
+
parent.set_attribute(a.to_s, value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# create the XML text of the element
|
47
|
+
def xml_text
|
48
|
+
str = create_open_tag
|
49
|
+
if self.hasvalue?
|
50
|
+
str << XMLCodec::XMLUtils::escape_xml(self.value)
|
51
|
+
end
|
52
|
+
|
53
|
+
each_subelement do |e|
|
54
|
+
str << e.xml_text
|
55
|
+
end
|
56
|
+
|
57
|
+
str << create_close_tag
|
58
|
+
str
|
59
|
+
end
|
60
|
+
|
61
|
+
# returns a string with the opening tag for the element
|
62
|
+
def create_open_tag
|
63
|
+
attrs = {}
|
64
|
+
self.class.each_attr do |a|
|
65
|
+
value = self.send(a)
|
66
|
+
if value
|
67
|
+
attrs[a.to_s] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
XMLCodec::XMLUtils::create_open_tag(elname.to_s, attrs)
|
71
|
+
end
|
72
|
+
|
73
|
+
# returns a string with the closing tag for the element
|
74
|
+
def create_close_tag
|
75
|
+
XMLCodec::XMLUtils::create_close_tag(elname.to_s)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
public
|
4
|
+
# Import the XML into an object from a Nokogiri XML Node or Document or from
|
5
|
+
# a string.
|
6
|
+
def self.import_xml(obj)
|
7
|
+
if obj.instance_of? String
|
8
|
+
_import_xml_text(obj)
|
9
|
+
elsif obj.instance_of? Nokogiri::XML::Node or
|
10
|
+
obj.instance_of? Nokogiri::XML::Document
|
11
|
+
_import_xml_dom(obj)
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
# Import the XML into an object from a Nokogiri XML Node or Document.
|
19
|
+
# This call is recursive and imports any subelements found into the
|
20
|
+
# corresponding objects.
|
21
|
+
def self._import_xml_dom(xmlel)
|
22
|
+
if xmlel.is_a? Nokogiri::XML::Document
|
23
|
+
xmlel = xmlel.root
|
24
|
+
end
|
25
|
+
|
26
|
+
elclass = get_element_class(xmlel.name)
|
27
|
+
if not elclass
|
28
|
+
if class_variable_get(:@@strict_parsing)
|
29
|
+
raise ElementClassNotFound, "No class defined for element type: '#{e.name}'"
|
30
|
+
else
|
31
|
+
return nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if elclass.allvalue?
|
36
|
+
elements = [xmlel.children.map{|c| c.to_xml(:save_with=>0)}.join]
|
37
|
+
else
|
38
|
+
elements = []
|
39
|
+
xmlel.children.each do |e|
|
40
|
+
if e.text?
|
41
|
+
elements << e.text
|
42
|
+
else
|
43
|
+
element = _import_xml_dom(e)
|
44
|
+
elements << element if element
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
attributes = {}
|
50
|
+
xmlel.attributes.each do |name, attr|
|
51
|
+
attributes[name] = attr.value
|
52
|
+
end
|
53
|
+
|
54
|
+
elclass.new_with_content(attributes, elements)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Import the XML directly from the text.
|
58
|
+
def self._import_xml_text(text)
|
59
|
+
parser = XMLStreamObjectParser.new(self)
|
60
|
+
parser.parse(text)
|
61
|
+
parser.top_element
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
private
|
4
|
+
# Have we already started the partial export of this element?
|
5
|
+
def already_partial_exported?
|
6
|
+
(@already_partial_exported ||= false)
|
7
|
+
end
|
8
|
+
|
9
|
+
# Have we already ended the partial export of this element?
|
10
|
+
def already_partial_export_ended?
|
11
|
+
(@already_partial_export_ended ||= false)
|
12
|
+
end
|
13
|
+
|
14
|
+
public
|
15
|
+
# Export this element into a file. Will also start to export the parents of
|
16
|
+
# the element. It's equivalent to calling start_partial_export followed by
|
17
|
+
# end_partial_export.
|
18
|
+
def partial_export(file)
|
19
|
+
if not already_partial_exported?
|
20
|
+
start_partial_export(file)
|
21
|
+
end_partial_export(file)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Starts to export the element to a file. all the existing elements will be
|
26
|
+
# exported. After calling this you should only add stuff that you will
|
27
|
+
# export explicitly by calling partial_export or start_partial_export.
|
28
|
+
def start_partial_export(file)
|
29
|
+
if not already_partial_exported?
|
30
|
+
@already_partial_exported = true
|
31
|
+
if self.__parent
|
32
|
+
self.__parent.start_partial_export(file)
|
33
|
+
end
|
34
|
+
|
35
|
+
file << create_open_tag
|
36
|
+
if self.hasvalue?
|
37
|
+
file << XMLCodec::XMLUtils::escape_xml(self.value)
|
38
|
+
end
|
39
|
+
|
40
|
+
each_subelement do |e|
|
41
|
+
e.partial_export(file)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Ends the partial exporting of the element.
|
47
|
+
def end_partial_export(file)
|
48
|
+
if not already_partial_export_ended?
|
49
|
+
@already_partial_export_ended = true
|
50
|
+
|
51
|
+
if not already_partial_exported?
|
52
|
+
raise "<#{self} Trying to end the export of an element that hasn't"+
|
53
|
+
" been started yet"
|
54
|
+
end
|
55
|
+
|
56
|
+
each_subelement do |e|
|
57
|
+
e.end_partial_export(file)
|
58
|
+
end
|
59
|
+
|
60
|
+
file << create_close_tag
|
61
|
+
|
62
|
+
if self.__parent
|
63
|
+
self.__parent.delete_element(self)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
private
|
4
|
+
# Add a xmlsubel type attribute
|
5
|
+
def self.xmlsubel(name) #:doc:
|
6
|
+
name = name.to_sym
|
7
|
+
self._xmlsubel(name)
|
8
|
+
attr_reader name
|
9
|
+
define_method((name.to_s+"=").to_sym) { |value|
|
10
|
+
if value.is_a? String or value.is_a? Fixnum
|
11
|
+
value = self.class.get_element_class(name).new(value)
|
12
|
+
end
|
13
|
+
value.__parent = self if value
|
14
|
+
instance_variable_set "@#{name}", value
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Add a xmlsubel_mult type attribute (wrapper around attr_accessor)
|
19
|
+
def self.xmlsubel_mult(name) #:doc:
|
20
|
+
name = name.to_sym
|
21
|
+
self._xmlsubel(name)
|
22
|
+
self.xmlsubelmultiples << name
|
23
|
+
define_method(name){
|
24
|
+
if not self.instance_variables.index("@#{name}".to_sym)
|
25
|
+
instance_variable_set "@#{name}", XMLSubElements.new(self)
|
26
|
+
end
|
27
|
+
instance_variable_get "@#{name}"
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Add a name as being a subelement (mult or single)
|
32
|
+
def self._xmlsubel(name)
|
33
|
+
self.xmlsubels << name
|
34
|
+
end
|
35
|
+
|
36
|
+
# Class level variable to hold the list of subelements
|
37
|
+
def self.xmlsubels
|
38
|
+
@xmlsubels ||=[]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Class level variable to hold the list of subelements that are multiple
|
42
|
+
def self.xmlsubelmultiples
|
43
|
+
@xmlsubelmultiples ||=[]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Iterates over the object's XML subelements
|
47
|
+
def self.each_subel
|
48
|
+
if not self.instance_variables.index("@__subel_names".to_sym)
|
49
|
+
names = []
|
50
|
+
# Iterate all the superclasses that are still children of XMLElement
|
51
|
+
# and iterate each of the subelements
|
52
|
+
c = self
|
53
|
+
while c.ancestors.index(XMLCodec::XMLElement)
|
54
|
+
names += c.xmlsubels
|
55
|
+
c = c.superclass
|
56
|
+
end
|
57
|
+
@__subel_names = names
|
58
|
+
end
|
59
|
+
@__subel_names.each {|name| yield name}
|
60
|
+
end
|
61
|
+
|
62
|
+
# Iterate all the superclasses that are still children of XMLElement
|
63
|
+
# and check if any of them have the subelement mult defined
|
64
|
+
def self.subel_mult?(element)
|
65
|
+
if not self.instance_variables.index("@__subel_mult_names".to_sym)
|
66
|
+
names = []
|
67
|
+
c = self
|
68
|
+
while c.ancestors.index(XMLCodec::XMLElement)
|
69
|
+
names += c.xmlsubelmultiples
|
70
|
+
c = c.superclass
|
71
|
+
end
|
72
|
+
@__subel_mult_names = names
|
73
|
+
end
|
74
|
+
return @__subel_mult_names.index(element)? true : false
|
75
|
+
end
|
76
|
+
|
77
|
+
# Iterate all the superclasses that are still children of XMLElement
|
78
|
+
# and check if any of them have any subelements handled by this class
|
79
|
+
def get_subel(elclass)
|
80
|
+
names = elclass.get_elnames
|
81
|
+
c = self.class
|
82
|
+
while c.ancestors.index(XMLCodec::XMLElement)
|
83
|
+
names.each do |name|
|
84
|
+
if c.xmlsubels.index(name.to_sym)
|
85
|
+
return names[0].to_sym
|
86
|
+
end
|
87
|
+
end
|
88
|
+
c = c.superclass
|
89
|
+
end
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
public
|
4
|
+
# Method that checks if a given class has subelements. This is usually only
|
5
|
+
# used when exporting stuff.
|
6
|
+
def self.has_subelements?; false end
|
7
|
+
def has_subelements?; self.class.has_subelements? end
|
8
|
+
|
9
|
+
private
|
10
|
+
# Declare the class as having many subelements. Instances will have a
|
11
|
+
# method called #subelements that will return an instance of XMLSubElements
|
12
|
+
def self.xmlsubelements #:doc:
|
13
|
+
define_method(:subelements) {
|
14
|
+
@subelements ||= XMLSubElements.new(self)
|
15
|
+
}
|
16
|
+
define_method('<<') {|value|
|
17
|
+
subelements << value
|
18
|
+
}
|
19
|
+
define_method(:find_first_named) {|name|
|
20
|
+
subelements.find_first_named(name)
|
21
|
+
}
|
22
|
+
define_method('[]') {|name|
|
23
|
+
subelements.find_first_named(name)
|
24
|
+
}
|
25
|
+
define_method(:find_all_named) {|name|
|
26
|
+
subelements.find_all_named(name)
|
27
|
+
}
|
28
|
+
self.class_eval do
|
29
|
+
def self.has_subelements?; true; end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module XMLCodec
|
2
|
+
class XMLElement
|
3
|
+
public
|
4
|
+
# tests if the element is a value element as defined by 'elwithvalue'
|
5
|
+
def self.hasvalue?; false end
|
6
|
+
def hasvalue?; self.class.hasvalue? end
|
7
|
+
|
8
|
+
# tests if the element is a value element as defined by 'elallvalue'
|
9
|
+
def self.allvalue?; false end
|
10
|
+
def allvalue?; self.class.allvalue?; end
|
11
|
+
|
12
|
+
private
|
13
|
+
# Set the element as having a value. The element will have an initializer
|
14
|
+
# that takes a value as argument and an accessor named #value. This should
|
15
|
+
# be used for elements that contain only text and no subelements
|
16
|
+
def self.elwithvalue
|
17
|
+
self.class_eval do
|
18
|
+
def self.hasvalue?; true; end
|
19
|
+
end
|
20
|
+
self.class_eval do
|
21
|
+
def initialize(value=nil)
|
22
|
+
@value = value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
attr_accessor :value
|
26
|
+
end
|
27
|
+
|
28
|
+
# Set the element as having a value that eats up any subelements as if they
|
29
|
+
# were text. The element will behave similarly to "elwithvalue" with an
|
30
|
+
# initializar that takes a value as argument and an accessor named #value
|
31
|
+
# and will consume all its subelements as if they were text. This should
|
32
|
+
# be used for elements that contain subelements that you want to treat as
|
33
|
+
# text like <content> in Atom
|
34
|
+
def self.elallvalue
|
35
|
+
self.elwithvalue
|
36
|
+
self.class_eval do
|
37
|
+
def self.allvalue?; true; end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/xmlcodec.rb
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
require 'nokogiri'
|
2
2
|
|
3
3
|
module XMLCodec
|
4
|
-
VERSION = '0.3.
|
4
|
+
VERSION = '0.3.2'
|
5
5
|
end
|
6
6
|
|
7
|
-
require File.dirname(__FILE__)
|
8
|
-
require File.dirname(__FILE__)
|
9
|
-
require File.dirname(__FILE__)
|
10
|
-
require File.dirname(__FILE__)
|
11
|
-
require File.dirname(__FILE__)
|
7
|
+
require File.join(File.dirname(__FILE__),'XMLUtils')
|
8
|
+
require File.join(File.dirname(__FILE__),'element')
|
9
|
+
require File.join(File.dirname(__FILE__),'element_creation')
|
10
|
+
require File.join(File.dirname(__FILE__),'element_partial_export')
|
11
|
+
require File.join(File.dirname(__FILE__),'element_import')
|
12
|
+
require File.join(File.dirname(__FILE__),'element_export')
|
13
|
+
require File.join(File.dirname(__FILE__),'element_attrs')
|
14
|
+
require File.join(File.dirname(__FILE__),'element_value')
|
15
|
+
require File.join(File.dirname(__FILE__),'element_subel')
|
16
|
+
require File.join(File.dirname(__FILE__),'element_subelements')
|
17
|
+
require File.join(File.dirname(__FILE__),'subelements')
|
18
|
+
require File.join(File.dirname(__FILE__),'stream_object_parser')
|
19
|
+
require File.join(File.dirname(__FILE__),'stream_parser')
|
data/xmlcodec.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.platform = Gem::Platform::RUBY
|
7
7
|
|
8
8
|
s.name = 'xmlcodec'
|
9
|
-
s.version = '0.3.
|
10
|
-
s.date = '2013-
|
9
|
+
s.version = '0.3.2'
|
10
|
+
s.date = '2013-09-04'
|
11
11
|
|
12
12
|
s.summary = "Generic Importer/Exporter of XML formats"
|
13
13
|
s.description = <<EOF
|
@@ -17,6 +17,7 @@ EOF
|
|
17
17
|
s.authors = ["Pedro Côrte-Real"]
|
18
18
|
s.email = 'pedro@pedrocr.net'
|
19
19
|
s.homepage = 'https://github.com/pedrocr/xmlcodec'
|
20
|
+
s.license = 'LGPL-2.1'
|
20
21
|
|
21
22
|
s.require_paths = %w[lib]
|
22
23
|
|
@@ -35,6 +36,14 @@ EOF
|
|
35
36
|
Rakefile
|
36
37
|
lib/XMLUtils.rb
|
37
38
|
lib/element.rb
|
39
|
+
lib/element_attrs.rb
|
40
|
+
lib/element_creation.rb
|
41
|
+
lib/element_export.rb
|
42
|
+
lib/element_import.rb
|
43
|
+
lib/element_partial_export.rb
|
44
|
+
lib/element_subel.rb
|
45
|
+
lib/element_subelements.rb
|
46
|
+
lib/element_value.rb
|
38
47
|
lib/stream_object_parser.rb
|
39
48
|
lib/stream_parser.rb
|
40
49
|
lib/subelements.rb
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xmlcodec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
16
|
-
requirement: &
|
16
|
+
requirement: &71026940 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *71026940
|
25
25
|
description: ! 'A framework to write object to XML mappers in Ruby that can then function
|
26
26
|
both in whole-document manipulation as well as constant memory unlimited size importing
|
27
27
|
and exporting of XML.
|
@@ -39,6 +39,14 @@ files:
|
|
39
39
|
- Rakefile
|
40
40
|
- lib/XMLUtils.rb
|
41
41
|
- lib/element.rb
|
42
|
+
- lib/element_attrs.rb
|
43
|
+
- lib/element_creation.rb
|
44
|
+
- lib/element_export.rb
|
45
|
+
- lib/element_import.rb
|
46
|
+
- lib/element_partial_export.rb
|
47
|
+
- lib/element_subel.rb
|
48
|
+
- lib/element_subelements.rb
|
49
|
+
- lib/element_value.rb
|
42
50
|
- lib/stream_object_parser.rb
|
43
51
|
- lib/stream_parser.rb
|
44
52
|
- lib/subelements.rb
|
@@ -57,7 +65,8 @@ files:
|
|
57
65
|
- test/utils_test.rb
|
58
66
|
- xmlcodec.gemspec
|
59
67
|
homepage: https://github.com/pedrocr/xmlcodec
|
60
|
-
licenses:
|
68
|
+
licenses:
|
69
|
+
- LGPL-2.1
|
61
70
|
post_install_message:
|
62
71
|
rdoc_options:
|
63
72
|
- -S
|