swivel 0.0.146 → 0.0.149

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. data/README +4 -1
  2. data/Rakefile +1 -1
  3. data/lib/swivel.rb +1 -1
  4. data/lib/swivel2/benchmarking.rb +1 -0
  5. data/lib/swivel2/config.rb +45 -0
  6. data/lib/swivel2/connection.rb +89 -0
  7. data/lib/swivel2/formats.rb +11 -0
  8. data/lib/swivel2/logging.rb +1 -0
  9. data/lib/swivel2/performance.rb +21 -0
  10. data/lib/swivel2/response.rb +5 -0
  11. data/lib/swivel2/swivelrc.default +5 -0
  12. data/vendor/activeresource-2.0.2-/CHANGELOG +223 -0
  13. data/vendor/activeresource-2.0.2-/README +165 -0
  14. data/vendor/activeresource-2.0.2-/Rakefile +133 -0
  15. data/vendor/activeresource-2.0.2-/lib/active_resource.rb +47 -0
  16. data/vendor/activeresource-2.0.2-/lib/active_resource/base.rb +872 -0
  17. data/vendor/activeresource-2.0.2-/lib/active_resource/connection.rb +172 -0
  18. data/vendor/activeresource-2.0.2-/lib/active_resource/custom_methods.rb +105 -0
  19. data/vendor/activeresource-2.0.2-/lib/active_resource/formats.rb +14 -0
  20. data/vendor/activeresource-2.0.2-/lib/active_resource/formats/json_format.rb +23 -0
  21. data/vendor/activeresource-2.0.2-/lib/active_resource/formats/xml_format.rb +34 -0
  22. data/vendor/activeresource-2.0.2-/lib/active_resource/http_mock.rb +147 -0
  23. data/vendor/activeresource-2.0.2-/lib/active_resource/validations.rb +288 -0
  24. data/vendor/activeresource-2.0.2-/lib/active_resource/version.rb +9 -0
  25. data/vendor/activeresource-2.0.2-/lib/activeresource.rb +1 -0
  26. data/vendor/activeresource-2.0.2-/test/abstract_unit.rb +10 -0
  27. data/vendor/activeresource-2.0.2-/test/authorization_test.rb +82 -0
  28. data/vendor/activeresource-2.0.2-/test/base/custom_methods_test.rb +96 -0
  29. data/vendor/activeresource-2.0.2-/test/base/equality_test.rb +43 -0
  30. data/vendor/activeresource-2.0.2-/test/base/load_test.rb +111 -0
  31. data/vendor/activeresource-2.0.2-/test/base_errors_test.rb +48 -0
  32. data/vendor/activeresource-2.0.2-/test/base_test.rb +454 -0
  33. data/vendor/activeresource-2.0.2-/test/connection_test.rb +170 -0
  34. data/vendor/activeresource-2.0.2-/test/fixtures/beast.rb +14 -0
  35. data/vendor/activeresource-2.0.2-/test/fixtures/person.rb +3 -0
  36. data/vendor/activeresource-2.0.2-/test/fixtures/street_address.rb +4 -0
  37. data/vendor/activeresource-2.0.2-/test/format_test.rb +42 -0
  38. data/vendor/activeresource-2.0.2-/test/setter_trap.rb +27 -0
  39. data/vendor/activesupport-2.0.2-/CHANGELOG +986 -0
  40. data/vendor/activesupport-2.0.2-/README +43 -0
  41. data/vendor/activesupport-2.0.2-/lib/active_support.rb +49 -0
  42. data/vendor/activesupport-2.0.2-/lib/active_support/basic_object.rb +5 -0
  43. data/vendor/activesupport-2.0.2-/lib/active_support/buffered_logger.rb +107 -0
  44. data/vendor/activesupport-2.0.2-/lib/active_support/clean_logger.rb +127 -0
  45. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext.rb +4 -0
  46. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array.rb +13 -0
  47. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array/access.rb +28 -0
  48. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array/conversions.rb +94 -0
  49. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array/extract_options.rb +19 -0
  50. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array/grouping.rb +68 -0
  51. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/array/random_access.rb +12 -0
  52. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/bigdecimal.rb +2 -0
  53. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/bigdecimal/conversions.rb +6 -0
  54. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/blank.rb +50 -0
  55. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/cgi.rb +5 -0
  56. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +14 -0
  57. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/class.rb +4 -0
  58. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/class/attribute_accessors.rb +48 -0
  59. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/class/delegating_attributes.rb +40 -0
  60. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
  61. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/class/removal.rb +24 -0
  62. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date.rb +10 -0
  63. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date/behavior.rb +13 -0
  64. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date/calculations.rb +188 -0
  65. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date/conversions.rb +98 -0
  66. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date_time.rb +10 -0
  67. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date_time/calculations.rb +77 -0
  68. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/date_time/conversions.rb +74 -0
  69. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/duplicable.rb +37 -0
  70. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/enumerable.rb +63 -0
  71. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/exception.rb +33 -0
  72. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/file.rb +21 -0
  73. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/float.rb +5 -0
  74. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/float/rounding.rb +24 -0
  75. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash.rb +13 -0
  76. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/conversions.rb +242 -0
  77. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/diff.rb +19 -0
  78. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/except.rb +24 -0
  79. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/indifferent_access.rb +102 -0
  80. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/keys.rb +54 -0
  81. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  82. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/hash/slice.rb +28 -0
  83. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/integer.rb +7 -0
  84. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/integer/even_odd.rb +24 -0
  85. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/integer/inflections.rb +21 -0
  86. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel.rb +5 -0
  87. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
  88. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel/daemonizing.rb +15 -0
  89. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel/debugger.rb +13 -0
  90. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel/reporting.rb +51 -0
  91. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/kernel/requires.rb +24 -0
  92. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/load_error.rb +38 -0
  93. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/logger.rb +16 -0
  94. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module.rb +8 -0
  95. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/aliasing.rb +70 -0
  96. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  97. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/attr_internal.rb +31 -0
  98. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/attribute_accessors.rb +48 -0
  99. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/delegation.rb +62 -0
  100. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/inclusion.rb +11 -0
  101. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/introspection.rb +35 -0
  102. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/loading.rb +13 -0
  103. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/name_error.rb +17 -0
  104. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/numeric.rb +7 -0
  105. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/numeric/bytes.rb +44 -0
  106. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/numeric/time.rb +91 -0
  107. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/object.rb +4 -0
  108. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/object/conversions.rb +14 -0
  109. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/object/extending.rb +58 -0
  110. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/object/instance_variables.rb +22 -0
  111. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/object/misc.rb +59 -0
  112. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/pathname.rb +7 -0
  113. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/pathname/clean_within.rb +14 -0
  114. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/proc.rb +12 -0
  115. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/range.rb +11 -0
  116. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/range/blockless_step.rb +22 -0
  117. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/range/conversions.rb +23 -0
  118. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/range/include_range.rb +22 -0
  119. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/range/overlaps.rb +12 -0
  120. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string.rb +23 -0
  121. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/access.rb +58 -0
  122. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/conversions.rb +28 -0
  123. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/inflections.rb +153 -0
  124. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/iterators.rb +17 -0
  125. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/starts_ends_with.rb +27 -0
  126. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/unicode.rb +42 -0
  127. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/string/xchar.rb +11 -0
  128. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/symbol.rb +14 -0
  129. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/test.rb +1 -0
  130. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/test/unit/assertions.rb +62 -0
  131. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/time.rb +19 -0
  132. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/time/behavior.rb +13 -0
  133. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/time/calculations.rb +224 -0
  134. data/vendor/activesupport-2.0.2-/lib/active_support/core_ext/time/conversions.rb +94 -0
  135. data/vendor/activesupport-2.0.2-/lib/active_support/dependencies.rb +540 -0
  136. data/vendor/activesupport-2.0.2-/lib/active_support/deprecation.rb +204 -0
  137. data/vendor/activesupport-2.0.2-/lib/active_support/duration.rb +96 -0
  138. data/vendor/activesupport-2.0.2-/lib/active_support/inflections.rb +53 -0
  139. data/vendor/activesupport-2.0.2-/lib/active_support/inflector.rb +282 -0
  140. data/vendor/activesupport-2.0.2-/lib/active_support/json.rb +31 -0
  141. data/vendor/activesupport-2.0.2-/lib/active_support/json/decoding.rb +60 -0
  142. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/date.rb +5 -0
  143. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/date_time.rb +5 -0
  144. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/enumerable.rb +12 -0
  145. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/false_class.rb +5 -0
  146. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/hash.rb +50 -0
  147. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/nil_class.rb +5 -0
  148. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/numeric.rb +5 -0
  149. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/object.rb +6 -0
  150. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/regexp.rb +5 -0
  151. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/string.rb +30 -0
  152. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/symbol.rb +5 -0
  153. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/time.rb +5 -0
  154. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoders/true_class.rb +5 -0
  155. data/vendor/activesupport-2.0.2-/lib/active_support/json/encoding.rb +38 -0
  156. data/vendor/activesupport-2.0.2-/lib/active_support/json/variable.rb +10 -0
  157. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte.rb +9 -0
  158. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte/chars.rb +141 -0
  159. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte/generators/generate_tables.rb +149 -0
  160. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte/handlers/passthru_handler.rb +9 -0
  161. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte/handlers/utf8_handler.rb +564 -0
  162. data/vendor/activesupport-2.0.2-/lib/active_support/multibyte/handlers/utf8_handler_proc.rb +43 -0
  163. data/vendor/activesupport-2.0.2-/lib/active_support/option_merger.rb +25 -0
  164. data/vendor/activesupport-2.0.2-/lib/active_support/ordered_options.rb +49 -0
  165. data/vendor/activesupport-2.0.2-/lib/active_support/test_case.rb +5 -0
  166. data/vendor/activesupport-2.0.2-/lib/active_support/testing.rb +1 -0
  167. data/vendor/activesupport-2.0.2-/lib/active_support/testing/default.rb +12 -0
  168. data/vendor/activesupport-2.0.2-/lib/active_support/values/time_zone.rb +181 -0
  169. data/vendor/activesupport-2.0.2-/lib/active_support/values/unicode_tables.dat +0 -0
  170. data/vendor/activesupport-2.0.2-/lib/active_support/vendor.rb +14 -0
  171. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
  172. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
  173. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
  174. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
  175. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
  176. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
  177. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
  178. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
  179. data/vendor/activesupport-2.0.2-/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
  180. data/vendor/activesupport-2.0.2-/lib/active_support/version.rb +9 -0
  181. data/vendor/activesupport-2.0.2-/lib/active_support/whiny_nil.rb +38 -0
  182. data/vendor/activesupport-2.0.2-/lib/activesupport.rb +1 -0
  183. metadata +222 -2
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
5
+ # All rights reserved.
6
+
7
+ # Permission is granted for use, copying, modification, distribution,
8
+ # and distribution of modified versions of this work as long as the
9
+ # above copyright notice is included.
10
+ #++
11
+
12
+ require 'builder/xmlmarkup'
13
+
14
+ module Builder
15
+
16
+ # Create a series of SAX-like XML events (e.g. start_tag, end_tag)
17
+ # from the markup code. XmlEvent objects are used in a way similar
18
+ # to XmlMarkup objects, except that a series of events are generated
19
+ # and passed to a handler rather than generating character-based
20
+ # markup.
21
+ #
22
+ # Usage:
23
+ # xe = Builder::XmlEvents.new(hander)
24
+ # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler.
25
+ #
26
+ # Indentation may also be selected by providing value for the
27
+ # indentation size and initial indentation level.
28
+ #
29
+ # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
30
+ #
31
+ # == XML Event Handler
32
+ #
33
+ # The handler object must expect the following events.
34
+ #
35
+ # [<tt>start_tag(tag, attrs)</tt>]
36
+ # Announces that a new tag has been found. +tag+ is the name of
37
+ # the tag and +attrs+ is a hash of attributes for the tag.
38
+ #
39
+ # [<tt>end_tag(tag)</tt>]
40
+ # Announces that an end tag for +tag+ has been found.
41
+ #
42
+ # [<tt>text(text)</tt>]
43
+ # Announces that a string of characters (+text+) has been found.
44
+ # A series of characters may be broken up into more than one
45
+ # +text+ call, so the client cannot assume that a single
46
+ # callback contains all the text data.
47
+ #
48
+ class XmlEvents < XmlMarkup
49
+ def text!(text)
50
+ @target.text(text)
51
+ end
52
+
53
+ def _start_tag(sym, attrs, end_too=false)
54
+ @target.start_tag(sym, attrs)
55
+ _end_tag(sym) if end_too
56
+ end
57
+
58
+ def _end_tag(sym)
59
+ @target.end_tag(sym)
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
4
+ # All rights reserved.
5
+
6
+ # Permission is granted for use, copying, modification, distribution,
7
+ # and distribution of modified versions of this work as long as the
8
+ # above copyright notice is included.
9
+ #++
10
+
11
+ # Provide a flexible and easy to use Builder for creating XML markup.
12
+ # See XmlBuilder for usage details.
13
+
14
+ require 'builder/xmlbase'
15
+
16
+ module Builder
17
+
18
+ # Create XML markup easily. All (well, almost all) methods sent to
19
+ # an XmlMarkup object will be translated to the equivalent XML
20
+ # markup. Any method with a block will be treated as an XML markup
21
+ # tag with nested markup in the block.
22
+ #
23
+ # Examples will demonstrate this easier than words. In the
24
+ # following, +xm+ is an +XmlMarkup+ object.
25
+ #
26
+ # xm.em("emphasized") # => <em>emphasized</em>
27
+ # xm.em { xmm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
28
+ # xm.a("A Link", "href"=>"http://onestepback.org")
29
+ # # => <a href="http://onestepback.org">A Link</a>
30
+ # xm.div { br } # => <div><br/></div>
31
+ # xm.target("name"=>"compile", "option"=>"fast")
32
+ # # => <target option="fast" name="compile"\>
33
+ # # NOTE: order of attributes is not specified.
34
+ #
35
+ # xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
36
+ # xm.html { # <html>
37
+ # xm.head { # <head>
38
+ # xm.title("History") # <title>History</title>
39
+ # } # </head>
40
+ # xm.body { # <body>
41
+ # xm.comment! "HI" # <! -- HI -->
42
+ # xm.h1("Header") # <h1>Header</h1>
43
+ # xm.p("paragraph") # <p>paragraph</p>
44
+ # } # </body>
45
+ # } # </html>
46
+ #
47
+ # == Notes:
48
+ #
49
+ # * The order that attributes are inserted in markup tags is
50
+ # undefined.
51
+ #
52
+ # * Sometimes you wish to insert text without enclosing tags. Use
53
+ # the <tt>text!</tt> method to accomplish this.
54
+ #
55
+ # Example:
56
+ #
57
+ # xm.div { # <div>
58
+ # xm.text! "line"; xm.br # line<br/>
59
+ # xm.text! "another line"; xmbr # another line<br/>
60
+ # } # </div>
61
+ #
62
+ # * The special XML characters <, >, and & are converted to &lt;,
63
+ # &gt; and &amp; automatically. Use the <tt><<</tt> operation to
64
+ # insert text without modification.
65
+ #
66
+ # * Sometimes tags use special characters not allowed in ruby
67
+ # identifiers. Use the <tt>tag!</tt> method to handle these
68
+ # cases.
69
+ #
70
+ # Example:
71
+ #
72
+ # xml.tag!("SOAP:Envelope") { ... }
73
+ #
74
+ # will produce ...
75
+ #
76
+ # <SOAP:Envelope> ... </SOAP:Envelope>"
77
+ #
78
+ # <tt>tag!</tt> will also take text and attribute arguments (after
79
+ # the tag name) like normal markup methods. (But see the next
80
+ # bullet item for a better way to handle XML namespaces).
81
+ #
82
+ # * Direct support for XML namespaces is now available. If the
83
+ # first argument to a tag call is a symbol, it will be joined to
84
+ # the tag to produce a namespace:tag combination. It is easier to
85
+ # show this than describe it.
86
+ #
87
+ # xml.SOAP :Envelope do ... end
88
+ #
89
+ # Just put a space before the colon in a namespace to produce the
90
+ # right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
91
+ # "<tt>xml.SOAP :Envelope</tt>")
92
+ #
93
+ # * XmlMarkup builds the markup in any object (called a _target_)
94
+ # that accepts the <tt><<</tt> method. If no target is given,
95
+ # then XmlMarkup defaults to a string target.
96
+ #
97
+ # Examples:
98
+ #
99
+ # xm = Builder::XmlMarkup.new
100
+ # result = xm.title("yada")
101
+ # # result is a string containing the markup.
102
+ #
103
+ # buffer = ""
104
+ # xm = Builder::XmlMarkup.new(buffer)
105
+ # # The markup is appended to buffer (using <<)
106
+ #
107
+ # xm = Builder::XmlMarkup.new(STDOUT)
108
+ # # The markup is written to STDOUT (using <<)
109
+ #
110
+ # xm = Builder::XmlMarkup.new
111
+ # x2 = Builder::XmlMarkup.new(:target=>xm)
112
+ # # Markup written to +x2+ will be send to +xm+.
113
+ #
114
+ # * Indentation is enabled by providing the number of spaces to
115
+ # indent for each level as a second argument to XmlBuilder.new.
116
+ # Initial indentation may be specified using a third parameter.
117
+ #
118
+ # Example:
119
+ #
120
+ # xm = Builder.new(:indent=>2)
121
+ # # xm will produce nicely formatted and indented XML.
122
+ #
123
+ # xm = Builder.new(:indent=>2, :margin=>4)
124
+ # # xm will produce nicely formatted and indented XML with 2
125
+ # # spaces per indent and an over all indentation level of 4.
126
+ #
127
+ # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
128
+ # builder.name { |b| b.first("Jim"); b.last("Weirich) }
129
+ # # prints:
130
+ # # <name>
131
+ # # <first>Jim</first>
132
+ # # <last>Weirich</last>
133
+ # # </name>
134
+ #
135
+ # * The instance_eval implementation which forces self to refer to
136
+ # the message receiver as self is now obsolete. We now use normal
137
+ # block calls to execute the markup block. This means that all
138
+ # markup methods must now be explicitly send to the xml builder.
139
+ # For instance, instead of
140
+ #
141
+ # xml.div { strong("text") }
142
+ #
143
+ # you need to write:
144
+ #
145
+ # xml.div { xml.strong("text") }
146
+ #
147
+ # Although more verbose, the subtle change in semantics within the
148
+ # block was found to be prone to error. To make this change a
149
+ # little less cumbersome, the markup block now gets the markup
150
+ # object sent as an argument, allowing you to use a shorter alias
151
+ # within the block.
152
+ #
153
+ # For example:
154
+ #
155
+ # xml_builder = Builder::XmlMarkup.new
156
+ # xml_builder.div { |xml|
157
+ # xml.stong("text")
158
+ # }
159
+ #
160
+ class XmlMarkup < XmlBase
161
+
162
+ # Create an XML markup builder. Parameters are specified by an
163
+ # option hash.
164
+ #
165
+ # :target=><em>target_object</em>::
166
+ # Object receiving the markup. +out+ must respond to the
167
+ # <tt><<</tt> operator. The default is a plain string target.
168
+ #
169
+ # :indent=><em>indentation</em>::
170
+ # Number of spaces used for indentation. The default is no
171
+ # indentation and no line breaks.
172
+ #
173
+ # :margin=><em>initial_indentation_level</em>::
174
+ # Amount of initial indentation (specified in levels, not
175
+ # spaces).
176
+ #
177
+ # :escape_attrs=><b>OBSOLETE</em>::
178
+ # The :escape_attrs option is no longer supported by builder
179
+ # (and will be quietly ignored). String attribute values are
180
+ # now automatically escaped. If you need unescaped attribute
181
+ # values (perhaps you are using entities in the attribute
182
+ # values), then give the value as a Symbol. This allows much
183
+ # finer control over escaping attribute values.
184
+ #
185
+ def initialize(options={})
186
+ indent = options[:indent] || 0
187
+ margin = options[:margin] || 0
188
+ super(indent, margin)
189
+ @target = options[:target] || ""
190
+ end
191
+
192
+ # Return the target of the builder.
193
+ def target!
194
+ @target
195
+ end
196
+
197
+ def comment!(comment_text)
198
+ _ensure_no_block block_given?
199
+ _special("<!-- ", " -->", comment_text, nil)
200
+ end
201
+
202
+ # Insert an XML declaration into the XML markup.
203
+ #
204
+ # For example:
205
+ #
206
+ # xml.declare! :ELEMENT, :blah, "yada"
207
+ # # => <!ELEMENT blah "yada">
208
+ def declare!(inst, *args, &block)
209
+ _indent
210
+ @target << "<!#{inst}"
211
+ args.each do |arg|
212
+ case arg
213
+ when String
214
+ @target << %{ "#{arg}"} # " WART
215
+ when Symbol
216
+ @target << " #{arg}"
217
+ end
218
+ end
219
+ if block_given?
220
+ @target << " ["
221
+ _newline
222
+ _nested_structures(block)
223
+ @target << "]"
224
+ end
225
+ @target << ">"
226
+ _newline
227
+ end
228
+
229
+ # Insert a processing instruction into the XML markup. E.g.
230
+ #
231
+ # For example:
232
+ #
233
+ # xml.instruct!
234
+ # #=> <?xml version="1.0" encoding="UTF-8"?>
235
+ # xml.instruct! :aaa, :bbb=>"ccc"
236
+ # #=> <?aaa bbb="ccc"?>
237
+ #
238
+ def instruct!(directive_tag=:xml, attrs={})
239
+ _ensure_no_block block_given?
240
+ if directive_tag == :xml
241
+ a = { :version=>"1.0", :encoding=>"UTF-8" }
242
+ attrs = a.merge attrs
243
+ end
244
+ _special(
245
+ "<?#{directive_tag}",
246
+ "?>",
247
+ nil,
248
+ attrs,
249
+ [:version, :encoding, :standalone])
250
+ end
251
+
252
+ # Insert a CDATA section into the XML markup.
253
+ #
254
+ # For example:
255
+ #
256
+ # xml.cdata!("text to be included in cdata")
257
+ # #=> <![CDATA[text to be included in cdata]]>
258
+ #
259
+ def cdata!(text)
260
+ _ensure_no_block block_given?
261
+ _special("<![CDATA[", "]]>", text, nil)
262
+ end
263
+
264
+ private
265
+
266
+ # NOTE: All private methods of a builder object are prefixed when
267
+ # a "_" character to avoid possible conflict with XML tag names.
268
+
269
+ # Insert text directly in to the builder's target.
270
+ def _text(text)
271
+ @target << text
272
+ end
273
+
274
+ # Insert special instruction.
275
+ def _special(open, close, data=nil, attrs=nil, order=[])
276
+ _indent
277
+ @target << open
278
+ @target << data if data
279
+ _insert_attributes(attrs, order) if attrs
280
+ @target << close
281
+ _newline
282
+ end
283
+
284
+ # Start an XML tag. If <tt>end_too</tt> is true, then the start
285
+ # tag is also the end tag (e.g. <br/>
286
+ def _start_tag(sym, attrs, end_too=false)
287
+ @target << "<#{sym}"
288
+ _insert_attributes(attrs)
289
+ @target << "/" if end_too
290
+ @target << ">"
291
+ end
292
+
293
+ # Insert an ending tag.
294
+ def _end_tag(sym)
295
+ @target << "</#{sym}>"
296
+ end
297
+
298
+ # Insert the attributes (given in the hash).
299
+ def _insert_attributes(attrs, order=[])
300
+ return if attrs.nil?
301
+ order.each do |k|
302
+ v = attrs[k]
303
+ @target << %{ #{k}="#{_attr_value(v)}"} if v # " WART
304
+ end
305
+ attrs.each do |k, v|
306
+ @target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART
307
+ end
308
+ end
309
+
310
+ def _attr_value(value)
311
+ case value
312
+ when Symbol
313
+ value.to_s
314
+ else
315
+ _escape_quote(value.to_s)
316
+ end
317
+ end
318
+
319
+ def _ensure_no_block(got_block)
320
+ if got_block
321
+ fail IllegalBlockError,
322
+ "Blocks are not allowed on XML instructions"
323
+ end
324
+ end
325
+
326
+ end
327
+
328
+ end
@@ -0,0 +1,1021 @@
1
+ # = XmlSimple
2
+ #
3
+ # Author:: Maik Schmidt <contact@maik-schmidt.de>
4
+ # Copyright:: Copyright (c) 2003-2006 Maik Schmidt
5
+ # License:: Distributes under the same terms as Ruby.
6
+ #
7
+ require 'rexml/document'
8
+ require 'stringio'
9
+
10
+ # Easy API to maintain XML (especially configuration files).
11
+ class XmlSimple
12
+ include REXML
13
+
14
+ @@VERSION = '1.0.11'
15
+
16
+ # A simple cache for XML documents that were already transformed
17
+ # by xml_in.
18
+ class Cache
19
+ # Creates and initializes a new Cache object.
20
+ def initialize
21
+ @mem_share_cache = {}
22
+ @mem_copy_cache = {}
23
+ end
24
+
25
+ # Saves a data structure into a file.
26
+ #
27
+ # data::
28
+ # Data structure to be saved.
29
+ # filename::
30
+ # Name of the file belonging to the data structure.
31
+ def save_storable(data, filename)
32
+ cache_file = get_cache_filename(filename)
33
+ File.open(cache_file, "w+") { |f| Marshal.dump(data, f) }
34
+ end
35
+
36
+ # Restores a data structure from a file. If restoring the data
37
+ # structure failed for any reason, nil will be returned.
38
+ #
39
+ # filename::
40
+ # Name of the file belonging to the data structure.
41
+ def restore_storable(filename)
42
+ cache_file = get_cache_filename(filename)
43
+ return nil unless File::exist?(cache_file)
44
+ return nil unless File::mtime(cache_file).to_i > File::mtime(filename).to_i
45
+ data = nil
46
+ File.open(cache_file) { |f| data = Marshal.load(f) }
47
+ data
48
+ end
49
+
50
+ # Saves a data structure in a shared memory cache.
51
+ #
52
+ # data::
53
+ # Data structure to be saved.
54
+ # filename::
55
+ # Name of the file belonging to the data structure.
56
+ def save_mem_share(data, filename)
57
+ @mem_share_cache[filename] = [Time::now.to_i, data]
58
+ end
59
+
60
+ # Restores a data structure from a shared memory cache. You
61
+ # should consider these elements as "read only". If restoring
62
+ # the data structure failed for any reason, nil will be
63
+ # returned.
64
+ #
65
+ # filename::
66
+ # Name of the file belonging to the data structure.
67
+ def restore_mem_share(filename)
68
+ get_from_memory_cache(filename, @mem_share_cache)
69
+ end
70
+
71
+ # Copies a data structure to a memory cache.
72
+ #
73
+ # data::
74
+ # Data structure to be copied.
75
+ # filename::
76
+ # Name of the file belonging to the data structure.
77
+ def save_mem_copy(data, filename)
78
+ @mem_share_cache[filename] = [Time::now.to_i, Marshal.dump(data)]
79
+ end
80
+
81
+ # Restores a data structure from a memory cache. If restoring
82
+ # the data structure failed for any reason, nil will be
83
+ # returned.
84
+ #
85
+ # filename::
86
+ # Name of the file belonging to the data structure.
87
+ def restore_mem_copy(filename)
88
+ data = get_from_memory_cache(filename, @mem_share_cache)
89
+ data = Marshal.load(data) unless data.nil?
90
+ data
91
+ end
92
+
93
+ private
94
+
95
+ # Returns the "cache filename" belonging to a filename, i.e.
96
+ # the extension '.xml' in the original filename will be replaced
97
+ # by '.stor'. If filename does not have this extension, '.stor'
98
+ # will be appended.
99
+ #
100
+ # filename::
101
+ # Filename to get "cache filename" for.
102
+ def get_cache_filename(filename)
103
+ filename.sub(/(\.xml)?$/, '.stor')
104
+ end
105
+
106
+ # Returns a cache entry from a memory cache belonging to a
107
+ # certain filename. If no entry could be found for any reason,
108
+ # nil will be returned.
109
+ #
110
+ # filename::
111
+ # Name of the file the cache entry belongs to.
112
+ # cache::
113
+ # Memory cache to get entry from.
114
+ def get_from_memory_cache(filename, cache)
115
+ return nil unless cache[filename]
116
+ return nil unless cache[filename][0] > File::mtime(filename).to_i
117
+ return cache[filename][1]
118
+ end
119
+ end
120
+
121
+ # Create a "global" cache.
122
+ @@cache = Cache.new
123
+
124
+ # Creates and intializes a new XmlSimple object.
125
+ #
126
+ # defaults::
127
+ # Default values for options.
128
+ def initialize(defaults = nil)
129
+ unless defaults.nil? || defaults.instance_of?(Hash)
130
+ raise ArgumentError, "Options have to be a Hash."
131
+ end
132
+ @default_options = normalize_option_names(defaults, (KNOWN_OPTIONS['in'] + KNOWN_OPTIONS['out']).uniq)
133
+ @options = Hash.new
134
+ @_var_values = nil
135
+ end
136
+
137
+ # Converts an XML document in the same way as the Perl module XML::Simple.
138
+ #
139
+ # string::
140
+ # XML source. Could be one of the following:
141
+ #
142
+ # - nil: Tries to load and parse '<scriptname>.xml'.
143
+ # - filename: Tries to load and parse filename.
144
+ # - IO object: Reads from object until EOF is detected and parses result.
145
+ # - XML string: Parses string.
146
+ #
147
+ # options::
148
+ # Options to be used.
149
+ def xml_in(string = nil, options = nil)
150
+ handle_options('in', options)
151
+
152
+ # If no XML string or filename was supplied look for scriptname.xml.
153
+ if string.nil?
154
+ string = File::basename($0)
155
+ string.sub!(/\.[^.]+$/, '')
156
+ string += '.xml'
157
+
158
+ directory = File::dirname($0)
159
+ @options['searchpath'].unshift(directory) unless directory.nil?
160
+ end
161
+
162
+ if string.instance_of?(String)
163
+ if string =~ /<.*?>/m
164
+ @doc = parse(string)
165
+ elsif string == '-'
166
+ @doc = parse($stdin.readlines.to_s)
167
+ else
168
+ filename = find_xml_file(string, @options['searchpath'])
169
+
170
+ if @options.has_key?('cache')
171
+ @options['cache'].each { |scheme|
172
+ case(scheme)
173
+ when 'storable'
174
+ content = @@cache.restore_storable(filename)
175
+ when 'mem_share'
176
+ content = @@cache.restore_mem_share(filename)
177
+ when 'mem_copy'
178
+ content = @@cache.restore_mem_copy(filename)
179
+ else
180
+ raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
181
+ end
182
+ return content if content
183
+ }
184
+ end
185
+
186
+ @doc = load_xml_file(filename)
187
+ end
188
+ elsif string.kind_of?(IO) || string.kind_of?(StringIO)
189
+ @doc = parse(string.readlines.to_s)
190
+ else
191
+ raise ArgumentError, "Could not parse object of type: <#{string.type}>."
192
+ end
193
+
194
+ result = collapse(@doc.root)
195
+ result = @options['keeproot'] ? merge({}, @doc.root.name, result) : result
196
+ put_into_cache(result, filename)
197
+ result
198
+ end
199
+
200
+ # This is the functional version of the instance method xml_in.
201
+ def XmlSimple.xml_in(string = nil, options = nil)
202
+ xml_simple = XmlSimple.new
203
+ xml_simple.xml_in(string, options)
204
+ end
205
+
206
+ # Converts a data structure into an XML document.
207
+ #
208
+ # ref::
209
+ # Reference to data structure to be converted into XML.
210
+ # options::
211
+ # Options to be used.
212
+ def xml_out(ref, options = nil)
213
+ handle_options('out', options)
214
+ if ref.instance_of?(Array)
215
+ ref = { @options['anonymoustag'] => ref }
216
+ end
217
+
218
+ if @options['keeproot']
219
+ keys = ref.keys
220
+ if keys.size == 1
221
+ ref = ref[keys[0]]
222
+ @options['rootname'] = keys[0]
223
+ end
224
+ elsif @options['rootname'] == ''
225
+ if ref.instance_of?(Hash)
226
+ refsave = ref
227
+ ref = {}
228
+ refsave.each { |key, value|
229
+ if !scalar(value)
230
+ ref[key] = value
231
+ else
232
+ ref[key] = [ value.to_s ]
233
+ end
234
+ }
235
+ end
236
+ end
237
+
238
+ @ancestors = []
239
+ xml = value_to_xml(ref, @options['rootname'], '')
240
+ @ancestors = nil
241
+
242
+ if @options['xmldeclaration']
243
+ xml = @options['xmldeclaration'] + "\n" + xml
244
+ end
245
+
246
+ if @options.has_key?('outputfile')
247
+ if @options['outputfile'].kind_of?(IO)
248
+ return @options['outputfile'].write(xml)
249
+ else
250
+ File.open(@options['outputfile'], "w") { |file| file.write(xml) }
251
+ end
252
+ end
253
+ xml
254
+ end
255
+
256
+ # This is the functional version of the instance method xml_out.
257
+ def XmlSimple.xml_out(hash, options = nil)
258
+ xml_simple = XmlSimple.new
259
+ xml_simple.xml_out(hash, options)
260
+ end
261
+
262
+ private
263
+
264
+ # Declare options that are valid for xml_in and xml_out.
265
+ KNOWN_OPTIONS = {
266
+ 'in' => %w(
267
+ keyattr keeproot forcecontent contentkey noattr
268
+ searchpath forcearray suppressempty anonymoustag
269
+ cache grouptags normalisespace normalizespace
270
+ variables varattr keytosymbol
271
+ ),
272
+ 'out' => %w(
273
+ keyattr keeproot contentkey noattr rootname
274
+ xmldeclaration outputfile noescape suppressempty
275
+ anonymoustag indent grouptags noindent
276
+ )
277
+ }
278
+
279
+ # Define some reasonable defaults.
280
+ DEF_KEY_ATTRIBUTES = []
281
+ DEF_ROOT_NAME = 'opt'
282
+ DEF_CONTENT_KEY = 'content'
283
+ DEF_XML_DECLARATION = "<?xml version='1.0' standalone='yes'?>"
284
+ DEF_ANONYMOUS_TAG = 'anon'
285
+ DEF_FORCE_ARRAY = true
286
+ DEF_INDENTATION = ' '
287
+ DEF_KEY_TO_SYMBOL = false
288
+
289
+ # Normalizes option names in a hash, i.e., turns all
290
+ # characters to lower case and removes all underscores.
291
+ # Additionally, this method checks, if an unknown option
292
+ # was used and raises an according exception.
293
+ #
294
+ # options::
295
+ # Hash to be normalized.
296
+ # known_options::
297
+ # List of known options.
298
+ def normalize_option_names(options, known_options)
299
+ return nil if options.nil?
300
+ result = Hash.new
301
+ options.each { |key, value|
302
+ lkey = key.downcase
303
+ lkey.gsub!(/_/, '')
304
+ if !known_options.member?(lkey)
305
+ raise ArgumentError, "Unrecognised option: #{lkey}."
306
+ end
307
+ result[lkey] = value
308
+ }
309
+ result
310
+ end
311
+
312
+ # Merges a set of options with the default options.
313
+ #
314
+ # direction::
315
+ # 'in': If options should be handled for xml_in.
316
+ # 'out': If options should be handled for xml_out.
317
+ # options::
318
+ # Options to be merged with the default options.
319
+ def handle_options(direction, options)
320
+ @options = options || Hash.new
321
+
322
+ raise ArgumentError, "Options must be a Hash!" unless @options.instance_of?(Hash)
323
+
324
+ unless KNOWN_OPTIONS.has_key?(direction)
325
+ raise ArgumentError, "Unknown direction: <#{direction}>."
326
+ end
327
+
328
+ known_options = KNOWN_OPTIONS[direction]
329
+ @options = normalize_option_names(@options, known_options)
330
+
331
+ unless @default_options.nil?
332
+ known_options.each { |option|
333
+ unless @options.has_key?(option)
334
+ if @default_options.has_key?(option)
335
+ @options[option] = @default_options[option]
336
+ end
337
+ end
338
+ }
339
+ end
340
+
341
+ unless @options.has_key?('noattr')
342
+ @options['noattr'] = false
343
+ end
344
+
345
+ if @options.has_key?('rootname')
346
+ @options['rootname'] = '' if @options['rootname'].nil?
347
+ else
348
+ @options['rootname'] = DEF_ROOT_NAME
349
+ end
350
+
351
+ if @options.has_key?('xmldeclaration') && @options['xmldeclaration'] == true
352
+ @options['xmldeclaration'] = DEF_XML_DECLARATION
353
+ end
354
+
355
+ @options['keytosymbol'] = DEF_KEY_TO_SYMBOL unless @options.has_key?('keytosymbol')
356
+
357
+ if @options.has_key?('contentkey')
358
+ if @options['contentkey'] =~ /^-(.*)$/
359
+ @options['contentkey'] = $1
360
+ @options['collapseagain'] = true
361
+ end
362
+ else
363
+ @options['contentkey'] = DEF_CONTENT_KEY
364
+ end
365
+
366
+ unless @options.has_key?('normalisespace')
367
+ @options['normalisespace'] = @options['normalizespace']
368
+ end
369
+ @options['normalisespace'] = 0 if @options['normalisespace'].nil?
370
+
371
+ if @options.has_key?('searchpath')
372
+ unless @options['searchpath'].instance_of?(Array)
373
+ @options['searchpath'] = [ @options['searchpath'] ]
374
+ end
375
+ else
376
+ @options['searchpath'] = []
377
+ end
378
+
379
+ if @options.has_key?('cache') && scalar(@options['cache'])
380
+ @options['cache'] = [ @options['cache'] ]
381
+ end
382
+
383
+ @options['anonymoustag'] = DEF_ANONYMOUS_TAG unless @options.has_key?('anonymoustag')
384
+
385
+ if !@options.has_key?('indent') || @options['indent'].nil?
386
+ @options['indent'] = DEF_INDENTATION
387
+ end
388
+
389
+ @options['indent'] = '' if @options.has_key?('noindent')
390
+
391
+ # Special cleanup for 'keyattr' which could be an array or
392
+ # a hash or left to default to array.
393
+ if @options.has_key?('keyattr')
394
+ if !scalar(@options['keyattr'])
395
+ # Convert keyattr => { elem => '+attr' }
396
+ # to keyattr => { elem => ['attr', '+'] }
397
+ if @options['keyattr'].instance_of?(Hash)
398
+ @options['keyattr'].each { |key, value|
399
+ if value =~ /^([-+])?(.*)$/
400
+ @options['keyattr'][key] = [$2, $1 ? $1 : '']
401
+ end
402
+ }
403
+ elsif !@options['keyattr'].instance_of?(Array)
404
+ raise ArgumentError, "'keyattr' must be String, Hash, or Array!"
405
+ end
406
+ else
407
+ @options['keyattr'] = [ @options['keyattr'] ]
408
+ end
409
+ else
410
+ @options['keyattr'] = DEF_KEY_ATTRIBUTES
411
+ end
412
+
413
+ if @options.has_key?('forcearray')
414
+ if @options['forcearray'].instance_of?(Regexp)
415
+ @options['forcearray'] = [ @options['forcearray'] ]
416
+ end
417
+
418
+ if @options['forcearray'].instance_of?(Array)
419
+ force_list = @options['forcearray']
420
+ unless force_list.empty?
421
+ @options['forcearray'] = {}
422
+ force_list.each { |tag|
423
+ if tag.instance_of?(Regexp)
424
+ unless @options['forcearray']['_regex'].instance_of?(Array)
425
+ @options['forcearray']['_regex'] = []
426
+ end
427
+ @options['forcearray']['_regex'] << tag
428
+ else
429
+ @options['forcearray'][tag] = true
430
+ end
431
+ }
432
+ else
433
+ @options['forcearray'] = false
434
+ end
435
+ else
436
+ @options['forcearray'] = @options['forcearray'] ? true : false
437
+ end
438
+ else
439
+ @options['forcearray'] = DEF_FORCE_ARRAY
440
+ end
441
+
442
+ if @options.has_key?('grouptags') && !@options['grouptags'].instance_of?(Hash)
443
+ raise ArgumentError, "Illegal value for 'GroupTags' option - expected a Hash."
444
+ end
445
+
446
+ if @options.has_key?('variables') && !@options['variables'].instance_of?(Hash)
447
+ raise ArgumentError, "Illegal value for 'Variables' option - expected a Hash."
448
+ end
449
+
450
+ if @options.has_key?('variables')
451
+ @_var_values = @options['variables']
452
+ elsif @options.has_key?('varattr')
453
+ @_var_values = {}
454
+ end
455
+ end
456
+
457
+ # Actually converts an XML document element into a data structure.
458
+ #
459
+ # element::
460
+ # The document element to be collapsed.
461
+ def collapse(element)
462
+ result = @options['noattr'] ? {} : get_attributes(element)
463
+
464
+ if @options['normalisespace'] == 2
465
+ result.each { |k, v| result[k] = normalise_space(v) }
466
+ end
467
+
468
+ if element.has_elements?
469
+ element.each_element { |child|
470
+ value = collapse(child)
471
+ if empty(value) && (element.attributes.empty? || @options['noattr'])
472
+ next if @options.has_key?('suppressempty') && @options['suppressempty'] == true
473
+ end
474
+ result = merge(result, child.name, value)
475
+ }
476
+ if has_mixed_content?(element)
477
+ # normalisespace?
478
+ content = element.texts.map { |x| x.to_s }
479
+ content = content[0] if content.size == 1
480
+ result[@options['contentkey']] = content
481
+ end
482
+ elsif element.has_text? # i.e. it has only text.
483
+ return collapse_text_node(result, element)
484
+ end
485
+
486
+ # Turn Arrays into Hashes if key fields present.
487
+ count = fold_arrays(result)
488
+
489
+ # Disintermediate grouped tags.
490
+ if @options.has_key?('grouptags')
491
+ result.each { |key, value|
492
+ next unless (value.instance_of?(Hash) && (value.size == 1))
493
+ child_key, child_value = value.to_a[0]
494
+ if @options['grouptags'][key] == child_key
495
+ result[key] = child_value
496
+ end
497
+ }
498
+ end
499
+
500
+ # Fold Hases containing a single anonymous Array up into just the Array.
501
+ if count == 1
502
+ anonymoustag = @options['anonymoustag']
503
+ if result.has_key?(anonymoustag) && result[anonymoustag].instance_of?(Array)
504
+ return result[anonymoustag]
505
+ end
506
+ end
507
+
508
+ if result.empty? && @options.has_key?('suppressempty')
509
+ return @options['suppressempty'] == '' ? '' : nil
510
+ end
511
+
512
+ result
513
+ end
514
+
515
+ # Collapses a text node and merges it with an existing Hash, if
516
+ # possible.
517
+ # Thanks to Curtis Schofield for reporting a subtle bug.
518
+ #
519
+ # hash::
520
+ # Hash to merge text node value with, if possible.
521
+ # element::
522
+ # Text node to be collapsed.
523
+ def collapse_text_node(hash, element)
524
+ value = node_to_text(element)
525
+ if empty(value) && !element.has_attributes?
526
+ return {}
527
+ end
528
+
529
+ if element.has_attributes? && !@options['noattr']
530
+ return merge(hash, @options['contentkey'], value)
531
+ else
532
+ if @options['forcecontent']
533
+ return merge(hash, @options['contentkey'], value)
534
+ else
535
+ return value
536
+ end
537
+ end
538
+ end
539
+
540
+ # Folds all arrays in a Hash.
541
+ #
542
+ # hash::
543
+ # Hash to be folded.
544
+ def fold_arrays(hash)
545
+ fold_amount = 0
546
+ keyattr = @options['keyattr']
547
+ if (keyattr.instance_of?(Array) || keyattr.instance_of?(Hash))
548
+ hash.each { |key, value|
549
+ if value.instance_of?(Array)
550
+ if keyattr.instance_of?(Array)
551
+ hash[key] = fold_array(value)
552
+ else
553
+ hash[key] = fold_array_by_name(key, value)
554
+ end
555
+ fold_amount += 1
556
+ end
557
+ }
558
+ end
559
+ fold_amount
560
+ end
561
+
562
+ # Folds an Array to a Hash, if possible. Folding happens
563
+ # according to the content of keyattr, which has to be
564
+ # an array.
565
+ #
566
+ # array::
567
+ # Array to be folded.
568
+ def fold_array(array)
569
+ hash = Hash.new
570
+ array.each { |x|
571
+ return array unless x.instance_of?(Hash)
572
+ key_matched = false
573
+ @options['keyattr'].each { |key|
574
+ if x.has_key?(key)
575
+ key_matched = true
576
+ value = x[key]
577
+ return array if value.instance_of?(Hash) || value.instance_of?(Array)
578
+ value = normalise_space(value) if @options['normalisespace'] == 1
579
+ x.delete(key)
580
+ hash[value] = x
581
+ break
582
+ end
583
+ }
584
+ return array unless key_matched
585
+ }
586
+ hash = collapse_content(hash) if @options['collapseagain']
587
+ hash
588
+ end
589
+
590
+ # Folds an Array to a Hash, if possible. Folding happens
591
+ # according to the content of keyattr, which has to be
592
+ # a Hash.
593
+ #
594
+ # name::
595
+ # Name of the attribute to be folded upon.
596
+ # array::
597
+ # Array to be folded.
598
+ def fold_array_by_name(name, array)
599
+ return array unless @options['keyattr'].has_key?(name)
600
+ key, flag = @options['keyattr'][name]
601
+
602
+ hash = Hash.new
603
+ array.each { |x|
604
+ if x.instance_of?(Hash) && x.has_key?(key)
605
+ value = x[key]
606
+ return array if value.instance_of?(Hash) || value.instance_of?(Array)
607
+ value = normalise_space(value) if @options['normalisespace'] == 1
608
+ hash[value] = x
609
+ hash[value]["-#{key}"] = hash[value][key] if flag == '-'
610
+ hash[value].delete(key) unless flag == '+'
611
+ else
612
+ $stderr.puts("Warning: <#{name}> element has no '#{key}' attribute.")
613
+ return array
614
+ end
615
+ }
616
+ hash = collapse_content(hash) if @options['collapseagain']
617
+ hash
618
+ end
619
+
620
+ # Tries to collapse a Hash even more ;-)
621
+ #
622
+ # hash::
623
+ # Hash to be collapsed again.
624
+ def collapse_content(hash)
625
+ content_key = @options['contentkey']
626
+ hash.each_value { |value|
627
+ return hash unless value.instance_of?(Hash) && value.size == 1 && value.has_key?(content_key)
628
+ hash.each_key { |key| hash[key] = hash[key][content_key] }
629
+ }
630
+ hash
631
+ end
632
+
633
+ # Adds a new key/value pair to an existing Hash. If the key to be added
634
+ # does already exist and the existing value associated with key is not
635
+ # an Array, it will be converted into an Array. Then the new value is
636
+ # appended to that Array.
637
+ #
638
+ # hash::
639
+ # Hash to add key/value pair to.
640
+ # key::
641
+ # Key to be added.
642
+ # value::
643
+ # Value to be associated with key.
644
+ def merge(hash, key, value)
645
+ if value.instance_of?(String)
646
+ value = normalise_space(value) if @options['normalisespace'] == 2
647
+
648
+ # do variable substitutions
649
+ unless @_var_values.nil? || @_var_values.empty?
650
+ value.gsub!(/\$\{(\w+)\}/) { |x| get_var($1) }
651
+ end
652
+
653
+ # look for variable definitions
654
+ if @options.has_key?('varattr')
655
+ varattr = @options['varattr']
656
+ if hash.has_key?(varattr)
657
+ set_var(hash[varattr], value)
658
+ end
659
+ end
660
+ end
661
+
662
+ #patch for converting keys to symbols
663
+ if @options.has_key?('keytosymbol')
664
+ if @options['keytosymbol'] == true
665
+ key = key.to_s.downcase.to_sym
666
+ end
667
+ end
668
+
669
+ if hash.has_key?(key)
670
+ if hash[key].instance_of?(Array)
671
+ hash[key] << value
672
+ else
673
+ hash[key] = [ hash[key], value ]
674
+ end
675
+ elsif value.instance_of?(Array) # Handle anonymous arrays.
676
+ hash[key] = [ value ]
677
+ else
678
+ if force_array?(key)
679
+ hash[key] = [ value ]
680
+ else
681
+ hash[key] = value
682
+ end
683
+ end
684
+ hash
685
+ end
686
+
687
+ # Checks, if the 'forcearray' option has to be used for
688
+ # a certain key.
689
+ def force_array?(key)
690
+ return false if key == @options['contentkey']
691
+ return true if @options['forcearray'] == true
692
+ forcearray = @options['forcearray']
693
+ if forcearray.instance_of?(Hash)
694
+ return true if forcearray.has_key?(key)
695
+ return false unless forcearray.has_key?('_regex')
696
+ forcearray['_regex'].each { |x| return true if key =~ x }
697
+ end
698
+ return false
699
+ end
700
+
701
+ # Converts the attributes array of a document node into a Hash.
702
+ # Returns an empty Hash, if node has no attributes.
703
+ #
704
+ # node::
705
+ # Document node to extract attributes from.
706
+ def get_attributes(node)
707
+ attributes = {}
708
+ node.attributes.each { |n,v| attributes[n] = v }
709
+ attributes
710
+ end
711
+
712
+ # Determines, if a document element has mixed content.
713
+ #
714
+ # element::
715
+ # Document element to be checked.
716
+ def has_mixed_content?(element)
717
+ if element.has_text? && element.has_elements?
718
+ return true if element.texts.join('') !~ /^\s*$/s
719
+ end
720
+ false
721
+ end
722
+
723
+ # Called when a variable definition is encountered in the XML.
724
+ # A variable definition looks like
725
+ # <element attrname="name">value</element>
726
+ # where attrname matches the varattr setting.
727
+ def set_var(name, value)
728
+ @_var_values[name] = value
729
+ end
730
+
731
+ # Called during variable substitution to get the value for the
732
+ # named variable.
733
+ def get_var(name)
734
+ if @_var_values.has_key?(name)
735
+ return @_var_values[name]
736
+ else
737
+ return "${#{name}}"
738
+ end
739
+ end
740
+
741
+ # Recurses through a data structure building up and returning an
742
+ # XML representation of that structure as a string.
743
+ #
744
+ # ref::
745
+ # Reference to the data structure to be encoded.
746
+ # name::
747
+ # The XML tag name to be used for this item.
748
+ # indent::
749
+ # A string of spaces for use as the current indent level.
750
+ def value_to_xml(ref, name, indent)
751
+ named = !name.nil? && name != ''
752
+ nl = @options.has_key?('noindent') ? '' : "\n"
753
+
754
+ if !scalar(ref)
755
+ if @ancestors.member?(ref)
756
+ raise ArgumentError, "Circular data structures not supported!"
757
+ end
758
+ @ancestors << ref
759
+ else
760
+ if named
761
+ return [indent, '<', name, '>', @options['noescape'] ? ref.to_s : escape_value(ref.to_s), '</', name, '>', nl].join('')
762
+ else
763
+ return ref.to_s + nl
764
+ end
765
+ end
766
+
767
+ # Unfold hash to array if possible.
768
+ if ref.instance_of?(Hash) && !ref.empty? && !@options['keyattr'].empty? && indent != ''
769
+ ref = hash_to_array(name, ref)
770
+ end
771
+
772
+ result = []
773
+ if ref.instance_of?(Hash)
774
+ # Reintermediate grouped values if applicable.
775
+ if @options.has_key?('grouptags')
776
+ ref.each { |key, value|
777
+ if @options['grouptags'].has_key?(key)
778
+ ref[key] = { @options['grouptags'][key] => value }
779
+ end
780
+ }
781
+ end
782
+
783
+ nested = []
784
+ text_content = nil
785
+ if named
786
+ result << indent << '<' << name
787
+ end
788
+
789
+ if !ref.empty?
790
+ ref.each { |key, value|
791
+ next if !key.nil? && key[0, 1] == '-'
792
+ if value.nil?
793
+ unless @options.has_key?('suppressempty') && @options['suppressempty'].nil?
794
+ raise ArgumentError, "Use of uninitialized value!"
795
+ end
796
+ value = {}
797
+ end
798
+
799
+ if !scalar(value) || @options['noattr']
800
+ nested << value_to_xml(value, key, indent + @options['indent'])
801
+ else
802
+ value = value.to_s
803
+ value = escape_value(value) unless @options['noescape']
804
+ if key == @options['contentkey']
805
+ text_content = value
806
+ else
807
+ result << ' ' << key << '="' << value << '"'
808
+ end
809
+ end
810
+ }
811
+ else
812
+ text_content = ''
813
+ end
814
+
815
+ if !nested.empty? || !text_content.nil?
816
+ if named
817
+ result << '>'
818
+ if !text_content.nil?
819
+ result << text_content
820
+ nested[0].sub!(/^\s+/, '') if !nested.empty?
821
+ else
822
+ result << nl
823
+ end
824
+ if !nested.empty?
825
+ result << nested << indent
826
+ end
827
+ result << '</' << name << '>' << nl
828
+ else
829
+ result << nested
830
+ end
831
+ else
832
+ result << ' />' << nl
833
+ end
834
+ elsif ref.instance_of?(Array)
835
+ ref.each { |value|
836
+ if scalar(value)
837
+ result << indent << '<' << name << '>'
838
+ result << (@options['noescape'] ? value.to_s : escape_value(value.to_s))
839
+ result << '</' << name << '>' << nl
840
+ elsif value.instance_of?(Hash)
841
+ result << value_to_xml(value, name, indent)
842
+ else
843
+ result << indent << '<' << name << '>' << nl
844
+ result << value_to_xml(value, @options['anonymoustag'], indent + @options['indent'])
845
+ result << indent << '</' << name << '>' << nl
846
+ end
847
+ }
848
+ else
849
+ # Probably, this is obsolete.
850
+ raise ArgumentError, "Can't encode a value of type: #{ref.type}."
851
+ end
852
+ @ancestors.pop if !scalar(ref)
853
+ result.join('')
854
+ end
855
+
856
+ # Checks, if a certain value is a "scalar" value. Whatever
857
+ # that will be in Ruby ... ;-)
858
+ #
859
+ # value::
860
+ # Value to be checked.
861
+ def scalar(value)
862
+ return false if value.instance_of?(Hash) || value.instance_of?(Array)
863
+ return true
864
+ end
865
+
866
+ # Attempts to unfold a hash of hashes into an array of hashes. Returns
867
+ # a reference to th array on success or the original hash, if unfolding
868
+ # is not possible.
869
+ #
870
+ # parent::
871
+ #
872
+ # hashref::
873
+ # Reference to the hash to be unfolded.
874
+ def hash_to_array(parent, hashref)
875
+ arrayref = []
876
+ hashref.each { |key, value|
877
+ return hashref unless value.instance_of?(Hash)
878
+
879
+ if @options['keyattr'].instance_of?(Hash)
880
+ return hashref unless @options['keyattr'].has_key?(parent)
881
+ arrayref << { @options['keyattr'][parent][0] => key }.update(value)
882
+ else
883
+ arrayref << { @options['keyattr'][0] => key }.update(value)
884
+ end
885
+ }
886
+ arrayref
887
+ end
888
+
889
+ # Replaces XML markup characters by their external entities.
890
+ #
891
+ # data::
892
+ # The string to be escaped.
893
+ def escape_value(data)
894
+ Text::normalize(data)
895
+ end
896
+
897
+ # Removes leading and trailing whitespace and sequences of
898
+ # whitespaces from a string.
899
+ #
900
+ # text::
901
+ # String to be normalised.
902
+ def normalise_space(text)
903
+ text.strip.gsub(/\s\s+/, ' ')
904
+ end
905
+
906
+ # Checks, if an object is nil, an empty String or an empty Hash.
907
+ # Thanks to Norbert Gawor for a bugfix.
908
+ #
909
+ # value::
910
+ # Value to be checked for emptyness.
911
+ def empty(value)
912
+ case value
913
+ when Hash
914
+ return value.empty?
915
+ when String
916
+ return value !~ /\S/m
917
+ else
918
+ return value.nil?
919
+ end
920
+ end
921
+
922
+ # Converts a document node into a String.
923
+ # If the node could not be converted into a String
924
+ # for any reason, default will be returned.
925
+ #
926
+ # node::
927
+ # Document node to be converted.
928
+ # default::
929
+ # Value to be returned, if node could not be converted.
930
+ def node_to_text(node, default = nil)
931
+ if node.instance_of?(REXML::Element)
932
+ node.texts.map { |t| t.value }.join('')
933
+ elsif node.instance_of?(REXML::Attribute)
934
+ node.value.nil? ? default : node.value.strip
935
+ elsif node.instance_of?(REXML::Text)
936
+ node.value.strip
937
+ else
938
+ default
939
+ end
940
+ end
941
+
942
+ # Parses an XML string and returns the according document.
943
+ #
944
+ # xml_string::
945
+ # XML string to be parsed.
946
+ #
947
+ # The following exception may be raised:
948
+ #
949
+ # REXML::ParseException::
950
+ # If the specified file is not wellformed.
951
+ def parse(xml_string)
952
+ Document.new(xml_string)
953
+ end
954
+
955
+ # Searches in a list of paths for a certain file. Returns
956
+ # the full path to the file, if it could be found. Otherwise,
957
+ # an exception will be raised.
958
+ #
959
+ # filename::
960
+ # Name of the file to search for.
961
+ # searchpath::
962
+ # List of paths to search in.
963
+ def find_xml_file(file, searchpath)
964
+ filename = File::basename(file)
965
+
966
+ if filename != file
967
+ return file if File::file?(file)
968
+ else
969
+ searchpath.each { |path|
970
+ full_path = File::join(path, filename)
971
+ return full_path if File::file?(full_path)
972
+ }
973
+ end
974
+
975
+ if searchpath.empty?
976
+ return file if File::file?(file)
977
+ raise ArgumentError, "File does not exist: #{file}."
978
+ end
979
+ raise ArgumentError, "Could not find <#{filename}> in <#{searchpath.join(':')}>"
980
+ end
981
+
982
+ # Loads and parses an XML configuration file.
983
+ #
984
+ # filename::
985
+ # Name of the configuration file to be loaded.
986
+ #
987
+ # The following exceptions may be raised:
988
+ #
989
+ # Errno::ENOENT::
990
+ # If the specified file does not exist.
991
+ # REXML::ParseException::
992
+ # If the specified file is not wellformed.
993
+ def load_xml_file(filename)
994
+ parse(File.readlines(filename).to_s)
995
+ end
996
+
997
+ # Caches the data belonging to a certain file.
998
+ #
999
+ # data::
1000
+ # Data to be cached.
1001
+ # filename::
1002
+ # Name of file the data was read from.
1003
+ def put_into_cache(data, filename)
1004
+ if @options.has_key?('cache')
1005
+ @options['cache'].each { |scheme|
1006
+ case(scheme)
1007
+ when 'storable'
1008
+ @@cache.save_storable(data, filename)
1009
+ when 'mem_share'
1010
+ @@cache.save_mem_share(data, filename)
1011
+ when 'mem_copy'
1012
+ @@cache.save_mem_copy(data, filename)
1013
+ else
1014
+ raise ArgumentError, "Unsupported caching scheme: <#{scheme}>."
1015
+ end
1016
+ }
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ # vim:sw=2