pdf-labels 1.0.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/History.txt +8 -0
- data/LICENCE +38 -0
- data/Manifest.txt +141 -0
- data/README.txt +72 -0
- data/Rakefile +30 -0
- data/lib/alias.rb +8 -0
- data/lib/glabel_template.rb +36 -0
- data/lib/label.rb +52 -0
- data/lib/layout.rb +13 -0
- data/lib/length_node.rb +47 -0
- data/lib/markup.rb +25 -0
- data/lib/pdf_label_page.rb +171 -0
- data/lib/pdf_labels.rb +6 -0
- data/lib/template.rb +37 -0
- data/templates/avery-iso-templates.xml +222 -0
- data/templates/avery-other-templates.xml +21 -0
- data/templates/avery-us-templates.xml +599 -0
- data/templates/glabels-2.0.dtd +329 -0
- data/templates/misc-iso-templates.xml +434 -0
- data/templates/misc-other-templates.xml +21 -0
- data/templates/misc-us-templates.xml +183 -0
- data/templates/paper-sizes.xml +37 -0
- data/templates/zweckform-iso-templates.xml +197 -0
- data/test/test_pdf_label_page.rb +91 -0
- data/vendor/color.rb +87 -0
- data/vendor/color/cmyk.rb +182 -0
- data/vendor/color/css.rb +27 -0
- data/vendor/color/grayscale.rb +135 -0
- data/vendor/color/hsl.rb +130 -0
- data/vendor/color/palette.rb +15 -0
- data/vendor/color/palette/gimp.rb +107 -0
- data/vendor/color/palette/monocontrast.rb +180 -0
- data/vendor/color/rgb-colors.rb +189 -0
- data/vendor/color/rgb.rb +311 -0
- data/vendor/color/rgb/metallic.rb +28 -0
- data/vendor/color/yiq.rb +78 -0
- data/vendor/pdf/charts.rb +13 -0
- data/vendor/pdf/charts/stddev.rb +433 -0
- data/vendor/pdf/grid.rb +135 -0
- data/vendor/pdf/math.rb +108 -0
- data/vendor/pdf/pagenumbers.rb +288 -0
- data/vendor/pdf/quickref.rb +331 -0
- data/vendor/pdf/simpletable.rb +947 -0
- data/vendor/pdf/techbook.rb +901 -0
- data/vendor/pdf/writer.rb +2801 -0
- data/vendor/pdf/writer/arc4.rb +63 -0
- data/vendor/pdf/writer/fontmetrics.rb +202 -0
- data/vendor/pdf/writer/fonts/Courier-Bold.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier-Oblique.afm +342 -0
- data/vendor/pdf/writer/fonts/Courier.afm +342 -0
- data/vendor/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
- data/vendor/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/vendor/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
- data/vendor/pdf/writer/fonts/Helvetica.afm +3051 -0
- data/vendor/pdf/writer/fonts/Symbol.afm +213 -0
- data/vendor/pdf/writer/fonts/Times-Bold.afm +2588 -0
- data/vendor/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
- data/vendor/pdf/writer/fonts/Times-Italic.afm +2667 -0
- data/vendor/pdf/writer/fonts/Times-Roman.afm +2419 -0
- data/vendor/pdf/writer/fonts/ZapfDingbats.afm +225 -0
- data/vendor/pdf/writer/graphics.rb +813 -0
- data/vendor/pdf/writer/graphics/imageinfo.rb +365 -0
- data/vendor/pdf/writer/lang.rb +44 -0
- data/vendor/pdf/writer/lang/en.rb +104 -0
- data/vendor/pdf/writer/object.rb +23 -0
- data/vendor/pdf/writer/object/action.rb +40 -0
- data/vendor/pdf/writer/object/annotation.rb +42 -0
- data/vendor/pdf/writer/object/catalog.rb +39 -0
- data/vendor/pdf/writer/object/contents.rb +69 -0
- data/vendor/pdf/writer/object/destination.rb +40 -0
- data/vendor/pdf/writer/object/encryption.rb +53 -0
- data/vendor/pdf/writer/object/font.rb +68 -0
- data/vendor/pdf/writer/object/fontdescriptor.rb +34 -0
- data/vendor/pdf/writer/object/fontencoding.rb +40 -0
- data/vendor/pdf/writer/object/image.rb +308 -0
- data/vendor/pdf/writer/object/info.rb +79 -0
- data/vendor/pdf/writer/object/outline.rb +30 -0
- data/vendor/pdf/writer/object/outlines.rb +30 -0
- data/vendor/pdf/writer/object/page.rb +195 -0
- data/vendor/pdf/writer/object/pages.rb +115 -0
- data/vendor/pdf/writer/object/procset.rb +46 -0
- data/vendor/pdf/writer/object/viewerpreferences.rb +74 -0
- data/vendor/pdf/writer/ohash.rb +58 -0
- data/vendor/pdf/writer/oreader.rb +25 -0
- data/vendor/pdf/writer/state.rb +48 -0
- data/vendor/pdf/writer/strokestyle.rb +140 -0
- data/vendor/transaction/simple.rb +693 -0
- data/vendor/transaction/simple/group.rb +133 -0
- data/vendor/transaction/simple/threadsafe.rb +52 -0
- data/vendor/transaction/simple/threadsafe/group.rb +23 -0
- data/vendor/xml-mapping/ChangeLog +128 -0
- data/vendor/xml-mapping/LICENSE +56 -0
- data/vendor/xml-mapping/README +386 -0
- data/vendor/xml-mapping/README_XPATH +175 -0
- data/vendor/xml-mapping/Rakefile +214 -0
- data/vendor/xml-mapping/TODO.txt +32 -0
- data/vendor/xml-mapping/doc/xpath_impl_notes.txt +119 -0
- data/vendor/xml-mapping/examples/company.rb +34 -0
- data/vendor/xml-mapping/examples/company.xml +26 -0
- data/vendor/xml-mapping/examples/company_usage.intin.rb +19 -0
- data/vendor/xml-mapping/examples/company_usage.intout +39 -0
- data/vendor/xml-mapping/examples/order.rb +61 -0
- data/vendor/xml-mapping/examples/order.xml +54 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced.rb +7 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced.xml +9 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intin.rb +12 -0
- data/vendor/xml-mapping/examples/order_signature_enhanced_usage.intout +16 -0
- data/vendor/xml-mapping/examples/order_usage.intin.rb +73 -0
- data/vendor/xml-mapping/examples/order_usage.intout +147 -0
- data/vendor/xml-mapping/examples/time_augm.intin.rb +19 -0
- data/vendor/xml-mapping/examples/time_augm.intout +23 -0
- data/vendor/xml-mapping/examples/time_node.rb +27 -0
- data/vendor/xml-mapping/examples/xpath_create_new.intin.rb +85 -0
- data/vendor/xml-mapping/examples/xpath_create_new.intout +181 -0
- data/vendor/xml-mapping/examples/xpath_docvsroot.intin.rb +30 -0
- data/vendor/xml-mapping/examples/xpath_docvsroot.intout +34 -0
- data/vendor/xml-mapping/examples/xpath_ensure_created.intin.rb +62 -0
- data/vendor/xml-mapping/examples/xpath_ensure_created.intout +114 -0
- data/vendor/xml-mapping/examples/xpath_pathological.intin.rb +42 -0
- data/vendor/xml-mapping/examples/xpath_pathological.intout +56 -0
- data/vendor/xml-mapping/examples/xpath_usage.intin.rb +51 -0
- data/vendor/xml-mapping/examples/xpath_usage.intout +57 -0
- data/vendor/xml-mapping/install.rb +40 -0
- data/vendor/xml-mapping/lib/xml/mapping.rb +14 -0
- data/vendor/xml-mapping/lib/xml/mapping/base.rb +571 -0
- data/vendor/xml-mapping/lib/xml/mapping/standard_nodes.rb +343 -0
- data/vendor/xml-mapping/lib/xml/mapping/version.rb +8 -0
- data/vendor/xml-mapping/lib/xml/xxpath.rb +354 -0
- data/vendor/xml-mapping/test/all_tests.rb +6 -0
- data/vendor/xml-mapping/test/company.rb +56 -0
- data/vendor/xml-mapping/test/documents_folders.rb +33 -0
- data/vendor/xml-mapping/test/fixtures/bookmarks1.xml +24 -0
- data/vendor/xml-mapping/test/fixtures/company1.xml +85 -0
- data/vendor/xml-mapping/test/fixtures/documents_folders.xml +71 -0
- data/vendor/xml-mapping/test/fixtures/documents_folders2.xml +30 -0
- data/vendor/xml-mapping/test/multiple_mappings.rb +80 -0
- data/vendor/xml-mapping/test/tests_init.rb +2 -0
- data/vendor/xml-mapping/test/xml_mapping_adv_test.rb +84 -0
- data/vendor/xml-mapping/test/xml_mapping_test.rb +201 -0
- data/vendor/xml-mapping/test/xpath_test.rb +273 -0
- metadata +191 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'transaction/simple'
|
|
2
|
+
|
|
3
|
+
# A transaction group is an object wrapper that manages a group of objects
|
|
4
|
+
# as if they were a single object for the purpose of transaction
|
|
5
|
+
# management. All transactions for this group of objects should be
|
|
6
|
+
# performed against the transaction group object, not against individual
|
|
7
|
+
# objects in the group.
|
|
8
|
+
#
|
|
9
|
+
# == Transaction Group Usage
|
|
10
|
+
# require 'transaction/simple/group'
|
|
11
|
+
#
|
|
12
|
+
# x = "Hello, you."
|
|
13
|
+
# y = "And you, too."
|
|
14
|
+
#
|
|
15
|
+
# g = Transaction::Simple::Group.new(x, y)
|
|
16
|
+
# g.start_transaction(:first) # -> [ x, y ]
|
|
17
|
+
# g.transaction_open?(:first) # -> true
|
|
18
|
+
# x.transaction_open?(:first) # -> true
|
|
19
|
+
# y.transaction_open?(:first) # -> true
|
|
20
|
+
#
|
|
21
|
+
# x.gsub!(/you/, "world") # -> "Hello, world."
|
|
22
|
+
# y.gsub!(/you/, "me") # -> "And me, too."
|
|
23
|
+
#
|
|
24
|
+
# g.start_transaction(:second) # -> [ x, y ]
|
|
25
|
+
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
26
|
+
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
|
27
|
+
# g.rewind_transaction(:second) # -> [ x, y ]
|
|
28
|
+
# x # -> "Hello, world."
|
|
29
|
+
# y # -> "And me, too."
|
|
30
|
+
#
|
|
31
|
+
# x.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
32
|
+
# y.gsub!(/me/, "Dave") # -> "And Dave, too."
|
|
33
|
+
#
|
|
34
|
+
# g.commit_transaction(:second) # -> [ x, y ]
|
|
35
|
+
# x # -> "Hello, HAL."
|
|
36
|
+
# y # -> "And Dave, too."
|
|
37
|
+
#
|
|
38
|
+
# g.abort_transaction(:first) # -> [ x, y ]
|
|
39
|
+
# x = -> "Hello, you."
|
|
40
|
+
# y = -> "And you, too."
|
|
41
|
+
class Transaction::Simple::Group
|
|
42
|
+
# Creates a transaction group for the provided objects. If a block is
|
|
43
|
+
# provided, the transaction group object is yielded to the block; when
|
|
44
|
+
# the block is finished, the transaction group object will be cleared
|
|
45
|
+
# with #clear.
|
|
46
|
+
def initialize(*objects)
|
|
47
|
+
@objects = objects || []
|
|
48
|
+
@objects.freeze
|
|
49
|
+
@objects.each { |obj| obj.extend(Transaction::Simple) }
|
|
50
|
+
|
|
51
|
+
if block_given?
|
|
52
|
+
begin
|
|
53
|
+
yield self
|
|
54
|
+
ensure
|
|
55
|
+
self.clear
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the objects that are covered by this transaction group.
|
|
61
|
+
attr_reader :objects
|
|
62
|
+
|
|
63
|
+
# Clears the object group. Removes references to the objects so that
|
|
64
|
+
# they can be garbage collected.
|
|
65
|
+
def clear
|
|
66
|
+
@objects = @objects.dup.clear
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Tests to see if all of the objects in the group have an open
|
|
70
|
+
# transaction. See Transaction::Simple#transaction_open? for more
|
|
71
|
+
# information.
|
|
72
|
+
def transaction_open?(name = nil)
|
|
73
|
+
@objects.inject(true) do |val, obj|
|
|
74
|
+
val = val and obj.transaction_open?(name)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Returns the current name of the transaction for the group.
|
|
79
|
+
# Transactions not explicitly named are named +nil+.
|
|
80
|
+
def transaction_name
|
|
81
|
+
@objects[0].transaction_name
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Starts a transaction for the group. Stores the current object state.
|
|
85
|
+
# If a transaction name is specified, the transaction will be named.
|
|
86
|
+
# Transaction names must be unique. Transaction names of +nil+ will be
|
|
87
|
+
# treated as unnamed transactions.
|
|
88
|
+
def start_transaction(name = nil)
|
|
89
|
+
@objects.each { |obj| obj.start_transaction(name) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Rewinds the transaction. If +name+ is specified, then the intervening
|
|
93
|
+
# transactions will be aborted and the named transaction will be
|
|
94
|
+
# rewound. Otherwise, only the current transaction is rewound.
|
|
95
|
+
def rewind_transaction(name = nil)
|
|
96
|
+
@objects.each { |obj| obj.rewind_transaction(name) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Aborts the transaction. Resets the object state to what it was before
|
|
100
|
+
# the transaction was started and closes the transaction. If +name+ is
|
|
101
|
+
# specified, then the intervening transactions and the named transaction
|
|
102
|
+
# will be aborted. Otherwise, only the current transaction is aborted.
|
|
103
|
+
#
|
|
104
|
+
# If the current or named transaction has been started by a block
|
|
105
|
+
# (Transaction::Simple.start), then the execution of the block will be
|
|
106
|
+
# halted with +break+ +self+.
|
|
107
|
+
def abort_transaction(name = nil)
|
|
108
|
+
@objects.each { |obj| obj.abort_transaction(name) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# If +name+ is +nil+ (default), the current transaction level is closed
|
|
112
|
+
# out and the changes are committed.
|
|
113
|
+
#
|
|
114
|
+
# If +name+ is specified and +name+ is in the list of named
|
|
115
|
+
# transactions, then all transactions are closed and committed until the
|
|
116
|
+
# named transaction is reached.
|
|
117
|
+
def commit_transaction(name = nil)
|
|
118
|
+
@objects.each { |obj| obj.commit_transaction(name) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Alternative method for calling the transaction methods. An optional
|
|
122
|
+
# name can be specified for named transaction support.
|
|
123
|
+
#
|
|
124
|
+
# #transaction(:start):: #start_transaction
|
|
125
|
+
# #transaction(:rewind):: #rewind_transaction
|
|
126
|
+
# #transaction(:abort):: #abort_transaction
|
|
127
|
+
# #transaction(:commit):: #commit_transaction
|
|
128
|
+
# #transaction(:name):: #transaction_name
|
|
129
|
+
# #transaction:: #transaction_open?
|
|
130
|
+
def transaction(action = nil, name = nil)
|
|
131
|
+
@objects.each { |obj| obj.transaction(action, name) }
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'transaction/simple'
|
|
2
|
+
require 'thread'
|
|
3
|
+
|
|
4
|
+
class Transaction::TransactionThreadError < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# = Transaction::Simple::ThreadSafe
|
|
8
|
+
# Thread-safe simple object transaction support for Ruby.
|
|
9
|
+
# Transaction::Simple::ThreadSafe is used in the same way as
|
|
10
|
+
# Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex object
|
|
11
|
+
# to ensure atomicity at the cost of performance in threaded applications.
|
|
12
|
+
#
|
|
13
|
+
# Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
|
|
14
|
+
# lock cannot be obtained immediately, a
|
|
15
|
+
# Transaction::TransactionThreadError will be raised.
|
|
16
|
+
#
|
|
17
|
+
# Thanks to Mauricio Fern�ndez for help with getting this part working.
|
|
18
|
+
#
|
|
19
|
+
# Threadsafe transactions can be used in any place that normal
|
|
20
|
+
# transactions would. The main difference would be in setup:
|
|
21
|
+
#
|
|
22
|
+
# require 'transaction/simple/threadsafe'
|
|
23
|
+
#
|
|
24
|
+
# x = "Hello, you."
|
|
25
|
+
# x.extend(Transaction::Simple::ThreadSafe) # Threadsafe
|
|
26
|
+
#
|
|
27
|
+
# y = "Hello, you."
|
|
28
|
+
# y.extend(Transaction::Simple) # Not threadsafe
|
|
29
|
+
module Transaction::Simple::ThreadSafe
|
|
30
|
+
include Transaction::Simple
|
|
31
|
+
|
|
32
|
+
SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
|
|
33
|
+
SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
|
|
34
|
+
|
|
35
|
+
Transaction::Simple.instance_methods(false) do |meth|
|
|
36
|
+
next if meth == "transaction"
|
|
37
|
+
arg = "(name = nil)" unless meth == "transaction_name"
|
|
38
|
+
module_eval <<-EOS
|
|
39
|
+
def #{meth}#{arg}
|
|
40
|
+
if (@__transaction_mutex__ ||= Mutex.new).try_lock
|
|
41
|
+
result = super
|
|
42
|
+
@__transaction_mutex__.unlock
|
|
43
|
+
return result
|
|
44
|
+
else
|
|
45
|
+
raise TransactionThreadError, Messages[:cannot_obtain_transaction_lock] % meth
|
|
46
|
+
end
|
|
47
|
+
ensure
|
|
48
|
+
@__transaction_mutex__.unlock
|
|
49
|
+
end
|
|
50
|
+
EOS
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'transaction/simple/threadsafe'
|
|
2
|
+
|
|
3
|
+
# A transaction group is an object wrapper that manages a group of objects
|
|
4
|
+
# as if they were a single object for the purpose of transaction
|
|
5
|
+
# management. All transactions for this group of objects should be
|
|
6
|
+
# performed against the transaction group object, not against individual
|
|
7
|
+
# objects in the group. This is the threadsafe version of a transaction
|
|
8
|
+
# group.
|
|
9
|
+
class Transaction::Simple::ThreadSafe::Group < Transaction::Simple::Group
|
|
10
|
+
def initialize(*objects)
|
|
11
|
+
@objects = objects || []
|
|
12
|
+
@objects.freeze
|
|
13
|
+
@objects.each { |obj| obj.extend(Transaction::Simple::ThreadSafe) }
|
|
14
|
+
|
|
15
|
+
if block_given?
|
|
16
|
+
begin
|
|
17
|
+
yield self
|
|
18
|
+
ensure
|
|
19
|
+
self.clear
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
2005-12-07 Olaf Klischat
|
|
2
|
+
|
|
3
|
+
* release 0.8.1
|
|
4
|
+
|
|
5
|
+
2005-12-07 Olaf Klischat
|
|
6
|
+
|
|
7
|
+
* ChangeLog file
|
|
8
|
+
|
|
9
|
+
2005/11/30 Olaf Klischat
|
|
10
|
+
|
|
11
|
+
* bugfix: clone default values to avoid external modifications
|
|
12
|
+
|
|
13
|
+
2005/07/07 Olaf Klischat
|
|
14
|
+
|
|
15
|
+
* release 0.8
|
|
16
|
+
|
|
17
|
+
2005/07/04 Olaf Klischat
|
|
18
|
+
|
|
19
|
+
* xml/xpath / XML::XPath -> xml/xxpath / XML::XXPath, license ->
|
|
20
|
+
Ruby's
|
|
21
|
+
|
|
22
|
+
2005/06/29 Olaf Klischat
|
|
23
|
+
|
|
24
|
+
* when creating elt[@attr='value'] path elements, add a new
|
|
25
|
+
element if one with @attr='value' already existed
|
|
26
|
+
|
|
27
|
+
2005/03/30 Olaf Klischat
|
|
28
|
+
|
|
29
|
+
* add_accessor: check for existing accessors.
|
|
30
|
+
|
|
31
|
+
2005/03/05 Olaf Klischat
|
|
32
|
+
|
|
33
|
+
* better support for inheritance among mapping
|
|
34
|
+
classes
|
|
35
|
+
|
|
36
|
+
2005/03/03 Olaf Klischat
|
|
37
|
+
|
|
38
|
+
* "polymorphic" nodes via root element
|
|
39
|
+
name. SubObjectBaseNode-based nodes es use node polymorphy when
|
|
40
|
+
no explicit node marshaller/unmarshaller has been sp ecified.
|
|
41
|
+
|
|
42
|
+
2005/02/28 Olaf Klischat
|
|
43
|
+
|
|
44
|
+
* mapping root elt name => mapping class;
|
|
45
|
+
XML::Mapping::load_object_from_* implemented
|
|
46
|
+
|
|
47
|
+
2005/02/13 Olaf Klischat
|
|
48
|
+
|
|
49
|
+
* IntNode renamed & generalized to NumericNode
|
|
50
|
+
|
|
51
|
+
2005/02/12 Olaf Klischat
|
|
52
|
+
|
|
53
|
+
* renaming *_rexml => *_xml
|
|
54
|
+
|
|
55
|
+
2005/01/27 Olaf Klischat
|
|
56
|
+
|
|
57
|
+
* special exception NoAttrValueSet for indicating absence of a
|
|
58
|
+
specific attribute in an XML source
|
|
59
|
+
|
|
60
|
+
2005/01/23 Olaf Klischat
|
|
61
|
+
|
|
62
|
+
* some more documentation, Node.obj_initializing, setting node
|
|
63
|
+
values to defaults on initialization
|
|
64
|
+
|
|
65
|
+
2005/01/10 Olaf Klischat
|
|
66
|
+
|
|
67
|
+
* root_element_name
|
|
68
|
+
|
|
69
|
+
2005/01/07 Olaf Klischat
|
|
70
|
+
|
|
71
|
+
* refactoring:
|
|
72
|
+
|
|
73
|
+
Made node types (classes) dynamically addable via
|
|
74
|
+
XML::Mapping.add_node_class, xml/mapping.rb moved to
|
|
75
|
+
xml/mapping/base.rb, node types moved to
|
|
76
|
+
xml/mapping/standard_nodes.rb, xml/mapping.rb now requires base
|
|
77
|
+
and standard_nodes and adds all standard node types to
|
|
78
|
+
XML::Mapping.
|
|
79
|
+
|
|
80
|
+
* additional node class SingleAttributeNode < Node for nodes that
|
|
81
|
+
map to a single attribute in their class (that's true for all
|
|
82
|
+
nodes we have so far). Call to add_attribute moved from "core"
|
|
83
|
+
to SingleAttributeNode.initialize.
|
|
84
|
+
|
|
85
|
+
* XML::Mapping: @nodes renamed to @xml_mapping_nodes to minimize
|
|
86
|
+
chance of name clashes.
|
|
87
|
+
|
|
88
|
+
2004/12/30 Olaf Klischat
|
|
89
|
+
|
|
90
|
+
* array node writing, hash node writing
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
2004/12/30 Olaf Klischat
|
|
94
|
+
|
|
95
|
+
* xpath: create_new flag, + convenience method
|
|
96
|
+
|
|
97
|
+
2004/12/21 Olaf Klischat
|
|
98
|
+
|
|
99
|
+
* node classes
|
|
100
|
+
|
|
101
|
+
2004/12/20 Olaf Klischat
|
|
102
|
+
|
|
103
|
+
* hash_node
|
|
104
|
+
|
|
105
|
+
2004/12/08 Olaf Klischat
|
|
106
|
+
|
|
107
|
+
* xpath: attribute nodes
|
|
108
|
+
|
|
109
|
+
* xml_mapping: retargeted from REXML::XPath to XML::XPath
|
|
110
|
+
|
|
111
|
+
2004/12/02 Olaf Klischat
|
|
112
|
+
|
|
113
|
+
* xpath: write accessors
|
|
114
|
+
|
|
115
|
+
2004/11/27 Olaf Klischat
|
|
116
|
+
|
|
117
|
+
* xpath: read access seems to work
|
|
118
|
+
|
|
119
|
+
2004/11/25 Olaf Klischat
|
|
120
|
+
|
|
121
|
+
* array_node
|
|
122
|
+
|
|
123
|
+
stone age Olaf Klischat
|
|
124
|
+
|
|
125
|
+
* see http://rubygarden.org/ruby?XmlMapping
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Xml-mapping is copyrighted free software by Olaf Klischat
|
|
2
|
+
<klischat@cs.tu-berlin.de>. You can redistribute it and/or modify it
|
|
3
|
+
under either the terms of the GPL, or the conditions below:
|
|
4
|
+
|
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
|
6
|
+
software without restriction, provided that you duplicate all of the
|
|
7
|
+
original copyright notices and associated disclaimers.
|
|
8
|
+
|
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
|
10
|
+
you do at least ONE of the following:
|
|
11
|
+
|
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
|
13
|
+
make them Freely Available, such as by posting said
|
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
|
15
|
+
the author to include your modifications in the software.
|
|
16
|
+
|
|
17
|
+
b) use the modified software only within your corporation or
|
|
18
|
+
organization.
|
|
19
|
+
|
|
20
|
+
c) give non-standard binaries non-standard names, with
|
|
21
|
+
instructions on where to get the original software distribution.
|
|
22
|
+
|
|
23
|
+
d) make other distribution arrangements with the author.
|
|
24
|
+
|
|
25
|
+
3. You may distribute the software in object code or binary form,
|
|
26
|
+
provided that you do at least ONE of the following:
|
|
27
|
+
|
|
28
|
+
a) distribute the binaries and library files of the software,
|
|
29
|
+
together with instructions (in the manual page or equivalent)
|
|
30
|
+
on where to get the original distribution.
|
|
31
|
+
|
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
|
33
|
+
the software.
|
|
34
|
+
|
|
35
|
+
c) give non-standard binaries non-standard names, with
|
|
36
|
+
instructions on where to get the original software distribution.
|
|
37
|
+
|
|
38
|
+
d) make other distribution arrangements with the author.
|
|
39
|
+
|
|
40
|
+
4. You may modify and include the part of the software into any other
|
|
41
|
+
software (possibly commercial). But some files in the distribution
|
|
42
|
+
are not written by the author, so that they are not under these terms.
|
|
43
|
+
|
|
44
|
+
For the list of those files and their copying conditions, see the
|
|
45
|
+
file LEGAL.
|
|
46
|
+
|
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
|
48
|
+
output from the software do not automatically fall under the
|
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
|
51
|
+
software.
|
|
52
|
+
|
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
56
|
+
PURPOSE.
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
= XML-MAPPING: XML-to-object (and back) mapper for Ruby, including XPath interpreter
|
|
2
|
+
|
|
3
|
+
Xml-mapping is an easy to use, extensible library that allows you to
|
|
4
|
+
semi-automatically map Ruby objects to XML trees and vice versa. It is
|
|
5
|
+
easy to use and has a modular design that allows for easy extension of
|
|
6
|
+
its functionality.
|
|
7
|
+
|
|
8
|
+
== Download
|
|
9
|
+
|
|
10
|
+
For downloading the latest version, CVS repository access etc. go to:
|
|
11
|
+
|
|
12
|
+
http://rubyforge.org/projects/xml-mapping/
|
|
13
|
+
|
|
14
|
+
== Example
|
|
15
|
+
|
|
16
|
+
(example document stolen + extended from
|
|
17
|
+
http://www.castor.org/xml-mapping.html)
|
|
18
|
+
|
|
19
|
+
=== Input document:
|
|
20
|
+
|
|
21
|
+
:include: order.xml
|
|
22
|
+
|
|
23
|
+
=== Mapping class declaration:
|
|
24
|
+
|
|
25
|
+
:include: order.rb
|
|
26
|
+
|
|
27
|
+
=== Usage:
|
|
28
|
+
|
|
29
|
+
:include: order_usage.intout
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
== Description
|
|
33
|
+
|
|
34
|
+
As shown in the example, you have to include XML::Mapping into a class
|
|
35
|
+
to turn it into a "mapping class". There are no other restrictions
|
|
36
|
+
imposed on mapping classes; you can add attributes and methods to
|
|
37
|
+
them, include additional modules in them, derive them from other
|
|
38
|
+
classes, derive other classes from them etc.pp.
|
|
39
|
+
|
|
40
|
+
An instance of a mapping class can be created from/converted into an
|
|
41
|
+
XML node by means of instance methods like XML::Mapping.load_from_xml,
|
|
42
|
+
XML::Mapping#save_to_xml, XML::Mapping.load_from_file,
|
|
43
|
+
XML::Mapping#save_to_file. Special class methods like "text_node",
|
|
44
|
+
"array_node" etc., called "node factory methods", may be called from
|
|
45
|
+
the body of the class definition to define instance attributes that
|
|
46
|
+
are automatically and bidirectionally mapped to subtrees of the XML
|
|
47
|
+
element an instance of the class is mapped to. For example, in the
|
|
48
|
+
definition
|
|
49
|
+
|
|
50
|
+
class Address
|
|
51
|
+
include XML::Mapping
|
|
52
|
+
|
|
53
|
+
text_node :city, "City"
|
|
54
|
+
text_node :state, "State"
|
|
55
|
+
numeric_node :zip, "ZIP"
|
|
56
|
+
text_node :street, "Street"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
the first call to #text_node creates an attribute named "city" which
|
|
60
|
+
is mapped to the text of the XML child element defined by the XPath
|
|
61
|
+
expression "City" (xml-mapping includes an XPath interpreter that can
|
|
62
|
+
also be used seperately; see below). When you create an instance of
|
|
63
|
+
+Address+ from an XML element (using Address.load_from_file(file_name)
|
|
64
|
+
or Address.load_from_xml(rexml_element)), that instance's "city"
|
|
65
|
+
attribute will be set to the text of the XML element's "City" child
|
|
66
|
+
element. When you convert an instance of Address into an XML element,
|
|
67
|
+
a sub-element "City" is added and it text is set to the current value
|
|
68
|
+
of the +city+ attribute. The other node types (numeric_node,
|
|
69
|
+
array_node etc.) work analogously. The node types +object_node+,
|
|
70
|
+
+array_node+, and +hash_node+ recursively map sub-trees to instances
|
|
71
|
+
of mapping classes (as opposed to simple types like String
|
|
72
|
+
etc.). For example, with the line
|
|
73
|
+
|
|
74
|
+
array_node :signatures, "Signed-By", "Signature", :class=>Signature, :default_value=>[]
|
|
75
|
+
|
|
76
|
+
, an attribute named "signatures" is added to the surrounding class
|
|
77
|
+
(here: Order); the attribute will be an array whose elements
|
|
78
|
+
correspond to the XML elements yielded by the XPath
|
|
79
|
+
"Signed-By/Signature". Each element will be of class +Signature+ (each
|
|
80
|
+
array element is created from the corresponding XML element by just
|
|
81
|
+
calling <tt>Signature.load_from_xml(the_xml_element)</tt>). The reason
|
|
82
|
+
why the path "Signed-By/Signature" is provieded in two arguments
|
|
83
|
+
instead of just one combined one becomes apparent when marshalling the
|
|
84
|
+
array (along with the surrounding object) back into a sequence of XML
|
|
85
|
+
elements. When that happens, "Signed-By" names the common base element
|
|
86
|
+
for all those elements, and "Signature" is the path that will be
|
|
87
|
+
duplicated for each element. The input document in the example above
|
|
88
|
+
shows how this ends up looking.
|
|
89
|
+
|
|
90
|
+
Hash nodes work similarly, but they define hash-valued attributes
|
|
91
|
+
instead of array-valued ones.
|
|
92
|
+
|
|
93
|
+
Refer to the reference documentation for details about the node types
|
|
94
|
+
that are included in the xml-mapping library.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
=== Default values
|
|
98
|
+
|
|
99
|
+
For each node you may define a _default value_ which will be set if
|
|
100
|
+
there was no value defined for the attribute in the XML source.
|
|
101
|
+
|
|
102
|
+
From the example:
|
|
103
|
+
|
|
104
|
+
class Signature
|
|
105
|
+
include XML::Mapping
|
|
106
|
+
|
|
107
|
+
text_node :position, "Position", :default_value=>"Some Employee"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
The semantics of default values are as follows:
|
|
111
|
+
|
|
112
|
+
- when creating a new instance from scratch:
|
|
113
|
+
|
|
114
|
+
- attributes with default values are set to their default values
|
|
115
|
+
|
|
116
|
+
- attributes without default values are left unset
|
|
117
|
+
|
|
118
|
+
(when defining your own initializer, you'll have to call the
|
|
119
|
+
inherited _initialize_ method in order to get this behaviour)
|
|
120
|
+
|
|
121
|
+
- when loading:
|
|
122
|
+
|
|
123
|
+
- attributes without default values that are not represented in the
|
|
124
|
+
XML raise an error
|
|
125
|
+
|
|
126
|
+
- attributes with default values that are not represented in the XML
|
|
127
|
+
are set to their default values
|
|
128
|
+
|
|
129
|
+
- all other attributes are set to their respective values as present
|
|
130
|
+
in the XML
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
- when saving:
|
|
134
|
+
|
|
135
|
+
- unset attributes without default values raise an error
|
|
136
|
+
|
|
137
|
+
- attributes with default values that are set to their default
|
|
138
|
+
values are not saved
|
|
139
|
+
|
|
140
|
+
- all other attributes are saved
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
This implies that:
|
|
144
|
+
|
|
145
|
+
- attributes that are set to their respective default values are not
|
|
146
|
+
represented in the XML
|
|
147
|
+
|
|
148
|
+
- attributes without default values must be set explicitly before
|
|
149
|
+
saving
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
=== Attribute handling details, augmenting existing classes
|
|
154
|
+
|
|
155
|
+
I'll shed some more light on how xml-mapping adds mapped attributes to
|
|
156
|
+
Ruby classes. An attribute declaration like
|
|
157
|
+
|
|
158
|
+
text_node :city, "City"
|
|
159
|
+
|
|
160
|
+
maps some portion of the XML tree (here: the "City" sub-element) to an
|
|
161
|
+
attribute (here: "city") of the class whose body the declaration
|
|
162
|
+
appears in. When writing (marshalling) instances of the surrounding
|
|
163
|
+
class into an XML document, xml-mapping will read the attribute value
|
|
164
|
+
from the instance using the function named +city+; when reading
|
|
165
|
+
(unmarshalling) an instance from an XML document, xml-mapping will use
|
|
166
|
+
the one-parameter function <tt>city=</tt> to set the attribute in the
|
|
167
|
+
instance to the value read from the XML document.
|
|
168
|
+
|
|
169
|
+
If these functions don't exist at the time the node declaration is
|
|
170
|
+
executed, xml-mapping adds default implementations that simply
|
|
171
|
+
read/write the attribute value to instance variables that have the
|
|
172
|
+
same name as the attribute. For example, the +city+ attribute
|
|
173
|
+
declaration in the +Address+ class in the example added functions
|
|
174
|
+
+city+ and <tt>city=</tt> that read/write from/to the instance
|
|
175
|
+
variable <tt>@city</tt>.
|
|
176
|
+
|
|
177
|
+
If, however, these functions already exist prior to defining the
|
|
178
|
+
attributes, xml-mapping will leave them untouched, so your precious
|
|
179
|
+
self-written accessor methods that do whatever complicated internal
|
|
180
|
+
processing of the data won't be overwritten.
|
|
181
|
+
|
|
182
|
+
This means that you can not only create new mapping classes from
|
|
183
|
+
scratch, you can also take existing classes that contain some
|
|
184
|
+
"business logic" and "augment" them with xml-mapping capabilities. As
|
|
185
|
+
a simple example, let's augment Ruby's "Time" class with node
|
|
186
|
+
declarations that declare XML mappings for the day, month etc. fields:
|
|
187
|
+
|
|
188
|
+
:include: time_augm.intout
|
|
189
|
+
|
|
190
|
+
Here XML mappings are defined for the existing fields +year+, +month+
|
|
191
|
+
etc. Xml-apping noticed that the getter methods for those attributes
|
|
192
|
+
existed, so it didn't overwrite them. When calling +save_to_xml+ on a
|
|
193
|
+
+Time+ object, these methods are called and return the object's values
|
|
194
|
+
for those fields, which then get written to the output XML. Of course
|
|
195
|
+
you could also derive a new class from a pre-existing one and
|
|
196
|
+
implement the XML::Mapping stuff there, or even derive several such
|
|
197
|
+
classes in order to define more than one XML mapping for one existing
|
|
198
|
+
class.
|
|
199
|
+
|
|
200
|
+
It should be mentioned that in the +Time+ example above, the setter
|
|
201
|
+
methods (<tt>year=</tt>, <tt>month=</tt> etc.) didn't exist in +Time+
|
|
202
|
+
(+Time+ objects are immutable), so xml-mapping defined its own setter
|
|
203
|
+
methods that just set <tt>@year</tt>, <tt>@month</tt> etc., which is
|
|
204
|
+
pretty useless for this case. So you can't really read +Time+ values
|
|
205
|
+
back from an XML representation in this example. For that to work,
|
|
206
|
+
you'd need functioning <tt>blah=(x)</tt> methods for each +blah+
|
|
207
|
+
attribute that you want to define an XML mapping for.
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
=== Defining your own node types
|
|
211
|
+
|
|
212
|
+
It's easy to write additional node types and register them with the
|
|
213
|
+
xml-mapping library. Let's say we want to extend the +Signature+ class
|
|
214
|
+
from the example to include the time at which the signature was
|
|
215
|
+
created. We want the new XML representation of such a signature to
|
|
216
|
+
look like this:
|
|
217
|
+
|
|
218
|
+
:include: order_signature_enhanced.xml
|
|
219
|
+
|
|
220
|
+
(we only save year, month and day to make this example shorter), and
|
|
221
|
+
the mapping class declaration to look like this:
|
|
222
|
+
|
|
223
|
+
:include: order_signature_enhanced.rb
|
|
224
|
+
|
|
225
|
+
(i.e. a new "time_node" declaration was added).
|
|
226
|
+
|
|
227
|
+
We want this +signed_on+ call to define an attribute named +signed_on+
|
|
228
|
+
which holds the date value from the XML in an instance of class
|
|
229
|
+
+Time+.
|
|
230
|
+
|
|
231
|
+
This node type can be defined with this piece of code:
|
|
232
|
+
|
|
233
|
+
:include: time_node.rb
|
|
234
|
+
|
|
235
|
+
The last line registers the new node type with the xml-mapping
|
|
236
|
+
library. The name of the node factory method ("time_node") is
|
|
237
|
+
automatically derived from the class name of the node type
|
|
238
|
+
("TimeNode").
|
|
239
|
+
|
|
240
|
+
There will be one instance of the node type per mapping class (not per
|
|
241
|
+
mapping class instance). That instance will be created by the node
|
|
242
|
+
factory method (+time_node+); there's no need to instantiate the node
|
|
243
|
+
type directly. Whenever an instance of the mapping class needs to be
|
|
244
|
+
marshalled/unmarshalled to/from XML, +set_attr_value+
|
|
245
|
+
resp. +extract_attr_value+ will be called on the node type instance
|
|
246
|
+
("node" for short). The node factory method places the node into the
|
|
247
|
+
mapping class; the @owner attribute of the node is set to reference
|
|
248
|
+
the mapping class. The node factory method passes its arguments (in
|
|
249
|
+
the example, that would be <tt>:signed_on, "signed-on",
|
|
250
|
+
:default_value=>Time.now</tt>) to the node's initializer. TimeNode's
|
|
251
|
+
parent class XML::Mapping::SingleAttributeNode already handles the
|
|
252
|
+
<tt>:signed_on</tt> and <tt>:default_value=>Time.now</tt> arguments --
|
|
253
|
+
<tt>:signed_on</tt> is stored into <tt>@attrname</tt>, and the default
|
|
254
|
+
value declarations will be described in a moment. The remaining
|
|
255
|
+
argument <tt>"signed-on"</tt> gets passed to our +initialize_impl+
|
|
256
|
+
method as parameter _path_. We'll interpret it as an XPath expression
|
|
257
|
+
that locates the time value relative to the parent mapping object's
|
|
258
|
+
XML tree (in this case, this would be the XML tree rooted at the
|
|
259
|
+
+<Signature>+ element, i.e. the tree the +Signature+ instance was read
|
|
260
|
+
from). We'll later have to read/store the year, month, and day values
|
|
261
|
+
from <tt>path+"/year"</tt>, <tt>path+"/month"</tt>, and
|
|
262
|
+
<tt>path+"/day"</tt>, respectively, so we create (and precompile)
|
|
263
|
+
three corresponding XPath expressions using XML::XXPath.new and store
|
|
264
|
+
them into member variables of the node. XML::XXPath is an XPath
|
|
265
|
+
implementation that is bundled with xml-mapping. It is very
|
|
266
|
+
incomplete, but it supports writing (not just reading) of XML nodes,
|
|
267
|
+
which is needed to support writing data back to XML. The XML::XXPath
|
|
268
|
+
library is explained in more detail below.
|
|
269
|
+
|
|
270
|
+
The +extract_attr_value+ method is called whenever an instance of the
|
|
271
|
+
class the node belongs to (+Signature+ in the example) is being
|
|
272
|
+
created from an XML tree. The parameter _xml_ is that tree (again,
|
|
273
|
+
this is the tree rooted at the +<Signature>+ element in this
|
|
274
|
+
example). The method implementation is expected to extract the
|
|
275
|
+
attribute's value from _xml_ and return it, or raise
|
|
276
|
+
XML::Mapping::SingleAttributeNode::NoAttrValueSet if the attribute was
|
|
277
|
+
"unset" in the XML (so the default value should be put in place if it
|
|
278
|
+
was defined), or raise any other exception to signal an error and
|
|
279
|
+
abort the whole process. In our implementation, we apply the xpath
|
|
280
|
+
expressions created at initialization to _xml_
|
|
281
|
+
(e.g. <tt>@y_path.first(xml)</tt>). An expression
|
|
282
|
+
_xpath_expr_.first(_xml_) returns (as a REXML element) the first
|
|
283
|
+
sub-element of _xml_ that matches _xpath_expr_, or raises
|
|
284
|
+
XML::XXPathError if there was no such element. We apply REXML's _text_
|
|
285
|
+
method to the returned element to get out the element's text, convert
|
|
286
|
+
it to integer, and supply it to the constructor of the +Time+ object
|
|
287
|
+
to be returned. (as a side note, if an XPath expression matches XML
|
|
288
|
+
attributes, XML::XXPath methods like _first_ will return "Attribute"
|
|
289
|
+
nodes that behave similarly to REXML::Element nodes, including
|
|
290
|
+
messages like _name_ and _text_ (XML::XXPath extends REXML to support
|
|
291
|
+
this because REXML's Attribute class is too incompatible), so this
|
|
292
|
+
would've worked also if our XPath expressions named XML attributes,
|
|
293
|
+
not elements). The +default_when_xpath_err+ thing calls the supplied
|
|
294
|
+
block and returns its value, but maps the exception XML::XXPathError to
|
|
295
|
+
the mentioned XML::Mapping::SingleAttributeNode::NoAttrValueSet (any
|
|
296
|
+
other exceptions fall through unchanged). As said above,
|
|
297
|
+
XML::Mapping::NoAttrValueSet is then caught by our superclass
|
|
298
|
+
(XML::Mapping::SingleAttributeNode), and the default value is set if
|
|
299
|
+
it was provided. So you should just wrap +default_when_xpath_err+
|
|
300
|
+
around any applications of XPath expressions whose non-presence in the
|
|
301
|
+
XML you want to be considered a non-presence of the attribute you're
|
|
302
|
+
trying to extract. (XML::XXPath is designed to know knothing about
|
|
303
|
+
XML::Mapping, so it doesn't raise
|
|
304
|
+
XML::Mapping::SingleAttributeNode::NoAttrValueSet directly)
|
|
305
|
+
|
|
306
|
+
The +set_attr_value+ method is called whenever an instance of the
|
|
307
|
+
class the node belongs to (+Signature+ in the example) is being stored
|
|
308
|
+
into an XML tree. The _xml_ parameter is the XML tree (a REXML element
|
|
309
|
+
node; here this is again the tree rooted at the +<Signature>+
|
|
310
|
+
element); _value_ is the current value of the attribute. _xml_ will
|
|
311
|
+
most probably be "half-populated" by the time this method is called --
|
|
312
|
+
the framework calls the +set_attr_value+ methods of all nodes of a
|
|
313
|
+
mapping class in the order of their definition, letting each node fill
|
|
314
|
+
its "bit" into _xml_. The method implementation is expected to write
|
|
315
|
+
_value_ into (the correct sub-elements of) _xml_, or raise an
|
|
316
|
+
exception to signal an error and abort the whole process. No default
|
|
317
|
+
value handling is done here; +set_attr_value+ won't be called at all
|
|
318
|
+
if the attribute had been set to its default value. In our
|
|
319
|
+
implementation we grab the year, month and day values from _value_
|
|
320
|
+
(which must be a +Time+), and store it into the sub-elements of _xml_
|
|
321
|
+
identified by XPath expressions <tt>@y_path</tt>, <tt>@m_path</tt> and
|
|
322
|
+
<tt>@d_path</tt>, respectively. We do this by calling XML::XXPath#first
|
|
323
|
+
with an additional parameter <tt>:ensure_created=>true</tt>. An
|
|
324
|
+
expression _xpath_expr_.first(_xml_,:ensure_created=>true) works just
|
|
325
|
+
like _xpath_expr_.first(_xml_) if _xpath_expr_ was already present in
|
|
326
|
+
_xml_. If it was not, it is created (preferable at the end of _xml_'s
|
|
327
|
+
list of sub-nodes), and returned. See below for a more detailed
|
|
328
|
+
documentation of the XPath interpreter.
|
|
329
|
+
|
|
330
|
+
=== Element order in created XML documents
|
|
331
|
+
|
|
332
|
+
As just said, XML::XXPath, when used to create new XML nodes, generally
|
|
333
|
+
appends those nodes to the end of the list of subnodes of the node the
|
|
334
|
+
xpath expression was applied to. All xml-mapping nodes that come with
|
|
335
|
+
xml-mapping use XML::XXPath when writing data to XML, and therefore
|
|
336
|
+
also append their data to the XML data written by preceding nodes (the
|
|
337
|
+
nodes are invoked in the order of their definition). This means that,
|
|
338
|
+
generally, your output data will appear in the XML document in the
|
|
339
|
+
same order in which the corresponding xml-mapping node definitions
|
|
340
|
+
appeared in the mapping class (unless you used XPath expressions like
|
|
341
|
+
foo[number] which explicitly dictate a fixed position in the sequence
|
|
342
|
+
of XML nodes). For instance, in the example from the beginning of this
|
|
343
|
+
document, if we put the <tt>:signatures</tt> node _before_ the
|
|
344
|
+
<tt>:items</tt> node, the <tt><Signed-By></tt> element will appear
|
|
345
|
+
_before_ the sequence of <tt><Item></tt> elements in the output XML.
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
== XPath interpreter
|
|
350
|
+
|
|
351
|
+
XML::XXPath is an XPath parser. It is used in xml-mapping node type
|
|
352
|
+
definitions, but can just as well be utilized stand-alone (it does
|
|
353
|
+
not depend on xml-mapping). XML::XXPath is very incomplete and probably
|
|
354
|
+
will always be (it only supports path elements of types _elt_name_,
|
|
355
|
+
@_attr_name_, _elt_name_[@_attr_name_=_attr_value_],
|
|
356
|
+
_elt_name_[_index_], and *), but it should be reasonably efficient
|
|
357
|
+
(XPath expressions are precompiled), and, most importantly, it
|
|
358
|
+
supports write access. For example, if you create the path
|
|
359
|
+
"/foo/bar[3]/baz[@key='hiho']" in the XML document
|
|
360
|
+
|
|
361
|
+
<foo>
|
|
362
|
+
<bar>
|
|
363
|
+
<baz key="ab">hello</baz>
|
|
364
|
+
<baz key="xy">goodbye</baz>
|
|
365
|
+
</bar>
|
|
366
|
+
</foo>
|
|
367
|
+
|
|
368
|
+
, you'll get:
|
|
369
|
+
|
|
370
|
+
<foo>
|
|
371
|
+
<bar>
|
|
372
|
+
<baz key='ab'>hello</baz>
|
|
373
|
+
<baz key='xy'>goodbye</baz>
|
|
374
|
+
</bar>
|
|
375
|
+
<bar/>
|
|
376
|
+
<bar>
|
|
377
|
+
<baz key='hiho'/>
|
|
378
|
+
</bar>
|
|
379
|
+
</foo>
|
|
380
|
+
|
|
381
|
+
XML::XXPath is explained in more detail in the reference documentation.
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
== License
|
|
385
|
+
|
|
386
|
+
Ruby's.
|