pdf-labels 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|