xml-mapping 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +64 -3
- data/README +871 -173
- data/README_XPATH +40 -13
- data/Rakefile +37 -26
- data/TODO.txt +39 -8
- data/examples/README +5 -0
- data/examples/company_usage.intout +34 -22
- data/examples/documents_folders.rb +31 -0
- data/examples/documents_folders.xml +16 -0
- data/examples/documents_folders_usage.intin.rb +18 -0
- data/examples/documents_folders_usage.intout +46 -0
- data/examples/order_signature_enhanced_usage.intout +21 -11
- data/examples/order_usage.intin.rb +52 -5
- data/examples/order_usage.intout +154 -80
- data/examples/person.intin.rb +44 -0
- data/examples/person.intout +27 -0
- data/examples/person_mm.intin.rb +119 -0
- data/examples/person_mm.intout +114 -0
- data/examples/publication.intin.rb +44 -0
- data/examples/publication.intout +20 -0
- data/examples/reader.intin.rb +33 -0
- data/examples/reader.intout +19 -0
- data/examples/stringarray.rb +5 -0
- data/examples/stringarray.xml +10 -0
- data/examples/stringarray_usage.intin.rb +11 -0
- data/examples/stringarray_usage.intout +31 -0
- data/examples/time_augm.intout +19 -7
- data/examples/time_augm_loading.intin.rb +44 -0
- data/examples/time_augm_loading.intout +12 -0
- data/examples/time_node.intin.rb +79 -0
- data/examples/time_node.rb +3 -2
- data/examples/time_node_w_marshallers.intin.rb +48 -0
- data/examples/time_node_w_marshallers.intout +25 -0
- data/examples/time_node_w_marshallers.xml +9 -0
- data/examples/xpath_create_new.intout +132 -114
- data/examples/xpath_ensure_created.intout +86 -65
- data/examples/xpath_pathological.intout +16 -16
- data/examples/xpath_usage.intout +1 -1
- data/install.rb +1 -0
- data/lib/xml/mapping.rb +3 -1
- data/lib/xml/mapping/base.rb +442 -272
- data/lib/xml/mapping/core_classes_mapping.rb +32 -0
- data/lib/xml/mapping/standard_nodes.rb +176 -86
- data/lib/xml/mapping/version.rb +2 -2
- data/lib/xml/rexml_ext.rb +186 -0
- data/lib/xml/xxpath.rb +28 -265
- data/lib/xml/xxpath/steps.rb +345 -0
- data/lib/xml/xxpath_methods.rb +96 -0
- data/test/all_tests.rb +4 -1
- data/test/benchmark_fixtures.rb +14 -0
- data/test/{multiple_mappings.rb → bookmarks.rb} +0 -0
- data/test/company.rb +47 -0
- data/test/documents_folders.rb +11 -1
- data/test/examples_test.rb +29 -0
- data/test/fixtures/benchmark.xml +77 -0
- data/test/fixtures/company1.xml +9 -0
- data/test/fixtures/documents_folders.xml +0 -8
- data/test/fixtures/documents_folders2.xml +13 -19
- data/test/fixtures/triangle_m1.xml +17 -0
- data/test/fixtures/triangle_m2.xml +19 -0
- data/test/inheritance_test.rb +50 -0
- data/test/multiple_mappings_test.rb +155 -0
- data/test/rexml_xpath_benchmark.rb +29 -0
- data/test/triangle_mm.rb +57 -0
- data/test/xml_mapping_adv_test.rb +36 -1
- data/test/xml_mapping_test.rb +136 -7
- data/test/xpath_test.rb +154 -0
- data/test/xxpath_benchmark.rb +36 -0
- data/test/xxpath_benchmark.result1.txt +17 -0
- data/test/xxpath_methods_test.rb +61 -0
- metadata +139 -90
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            class String
         | 
| 2 | 
            +
              def self.load_from_xml(xml, options={:mapping=>:_default})
         | 
| 3 | 
            +
                xml.text
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def fill_into_xml(xml, options={:mapping=>:_default})
         | 
| 7 | 
            +
                xml.text = self
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def text
         | 
| 11 | 
            +
                self
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
             | 
| 16 | 
            +
            class Numeric
         | 
| 17 | 
            +
              def self.load_from_xml(xml, options={:mapping=>:_default})
         | 
| 18 | 
            +
                begin
         | 
| 19 | 
            +
                  Integer(xml.text)
         | 
| 20 | 
            +
                rescue ArgumentError
         | 
| 21 | 
            +
                  Float(xml.text)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def fill_into_xml(xml, options={:mapping=>:_default})
         | 
| 26 | 
            +
                xml.text = self.to_s
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def text
         | 
| 30 | 
            +
                self.to_s
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -9,21 +9,21 @@ module XML | |
| 9 9 | 
             
                # 
         | 
| 10 10 | 
             
                #   text_node :_attrname_, _path_ [, :default_value=>_obj_]
         | 
| 11 11 | 
             
                #                                 [, :optional=>true]
         | 
| 12 | 
            +
                #                                 [, :mapping=>_m_]
         | 
| 12 13 | 
             
                #
         | 
| 13 14 | 
             
                # Node that maps an XML node's text (the element's first child
         | 
| 14 15 | 
             
                # text node resp. the attribute's value) to a (string) attribute
         | 
| 15 | 
            -
                # of the mapped object.  | 
| 16 | 
            -
                #  | 
| 17 | 
            -
                #  | 
| 18 | 
            -
                #  | 
| 19 | 
            -
                #  | 
| 20 | 
            -
                #  | 
| 16 | 
            +
                # of the mapped object. _path_ (an XPath expression) locates the
         | 
| 17 | 
            +
                # XML node, _attrname_ (a symbol) names the
         | 
| 18 | 
            +
                # attribute. <tt>:default_value</tt> is the default value,
         | 
| 19 | 
            +
                # :optional=>true is equivalent to :default_value=>nil (see
         | 
| 20 | 
            +
                # superclass documentation for details). <tt>_m_</tt> is the
         | 
| 21 | 
            +
                # mapping; it defaults to the current default mapping
         | 
| 21 22 | 
             
                class TextNode < SingleAttributeNode
         | 
| 22 | 
            -
                   | 
| 23 | 
            -
             | 
| 24 | 
            -
                  # mapped node in the XML.
         | 
| 25 | 
            -
                  def initialize_impl(path)
         | 
| 23 | 
            +
                  def initialize(*args)
         | 
| 24 | 
            +
                    path,*args = super(*args)
         | 
| 26 25 | 
             
                    @path = XML::XXPath.new(path)
         | 
| 26 | 
            +
                    args
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 29 29 | 
             
                    default_when_xpath_err{ @path.first(xml).text }
         | 
| @@ -37,13 +37,16 @@ module XML | |
| 37 37 | 
             
                # 
         | 
| 38 38 | 
             
                #   numeric_node :_attrname_, _path_ [, :default_value=>_obj_]
         | 
| 39 39 | 
             
                #                                    [, :optional=>true]
         | 
| 40 | 
            +
                #                                    [, :mapping=>_m_]
         | 
| 40 41 | 
             
                #
         | 
| 41 42 | 
             
                # Like TextNode, but interprets the XML node's text as a number
         | 
| 42 43 | 
             
                # (Integer or Float, depending on the nodes's text) and maps it to
         | 
| 43 44 | 
             
                # an Integer or Float attribute.
         | 
| 44 45 | 
             
                class NumericNode < SingleAttributeNode
         | 
| 45 | 
            -
                  def  | 
| 46 | 
            +
                  def initialize(*args)
         | 
| 47 | 
            +
                    path,*args = super(*args)
         | 
| 46 48 | 
             
                    @path = XML::XXPath.new(path)
         | 
| 49 | 
            +
                    args
         | 
| 47 50 | 
             
                  end
         | 
| 48 51 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 49 52 | 
             
                    txt = default_when_xpath_err{ @path.first(xml).text }
         | 
| @@ -67,10 +70,9 @@ module XML | |
| 67 70 | 
             
                class SubObjectBaseNode < SingleAttributeNode
         | 
| 68 71 | 
             
                  # processes the keyword arguments :class, :marshaller, and
         | 
| 69 72 | 
             
                  # :unmarshaller (_args_ is ignored). When this initiaizer
         | 
| 70 | 
            -
                  # returns, @ | 
| 71 | 
            -
                  #  | 
| 72 | 
            -
                  #  | 
| 73 | 
            -
                  # to the initializer:
         | 
| 73 | 
            +
                  # returns, @marshaller and @unmarshaller are set to procs that
         | 
| 74 | 
            +
                  # marshal/unmarshal a Ruby object to/from an XML tree according
         | 
| 75 | 
            +
                  # to the keyword arguments that were passed to the initializer:
         | 
| 74 76 | 
             
                  #
         | 
| 75 77 | 
             
                  # You either supply a :class argument with a class implementing
         | 
| 76 78 | 
             
                  # XML::Mapping -- in that case, the subtree will be mapped to an
         | 
| @@ -89,37 +91,50 @@ module XML | |
| 89 91 | 
             
                  #
         | 
| 90 92 | 
             
                  # If both :class and :marshaller/:unmarshaller arguments are
         | 
| 91 93 | 
             
                  # supplied, the latter take precedence.
         | 
| 92 | 
            -
                  def  | 
| 94 | 
            +
                  def initialize(*args)
         | 
| 95 | 
            +
                    args = super(*args)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    @sub_mapping = @options[:sub_mapping] || @mapping
         | 
| 98 | 
            +
                    @marshaller, @unmarshaller = @options[:marshaller], @options[:unmarshaller]
         | 
| 99 | 
            +
             | 
| 93 100 | 
             
                    if @options[:class]
         | 
| 94 | 
            -
                      unless @ | 
| 95 | 
            -
                        @ | 
| 96 | 
            -
                          value.fill_into_xml | 
| 101 | 
            +
                      unless @marshaller
         | 
| 102 | 
            +
                        @marshaller = proc {|xml,value|
         | 
| 103 | 
            +
                          value.fill_into_xml xml, :mapping=>@sub_mapping
         | 
| 104 | 
            +
                          if xml.unspecified?
         | 
| 105 | 
            +
                            xml.name = value.class.root_element_name :mapping=>@sub_mapping
         | 
| 106 | 
            +
                            xml.unspecified = false
         | 
| 107 | 
            +
                          end
         | 
| 97 108 | 
             
                        }
         | 
| 98 109 | 
             
                      end
         | 
| 99 | 
            -
                      unless @ | 
| 100 | 
            -
                        @ | 
| 101 | 
            -
                          @options[:class].load_from_xml | 
| 110 | 
            +
                      unless @unmarshaller
         | 
| 111 | 
            +
                        @unmarshaller = proc {|xml|
         | 
| 112 | 
            +
                          @options[:class].load_from_xml xml, :mapping=>@sub_mapping
         | 
| 102 113 | 
             
                        }
         | 
| 103 114 | 
             
                      end
         | 
| 104 115 | 
             
                    end
         | 
| 105 116 |  | 
| 106 | 
            -
                    unless @ | 
| 107 | 
            -
                      @ | 
| 108 | 
            -
                        value.fill_into_xml | 
| 117 | 
            +
                    unless @marshaller
         | 
| 118 | 
            +
                      @marshaller = proc {|xml,value|
         | 
| 119 | 
            +
                        value.fill_into_xml xml, :mapping=>@sub_mapping
         | 
| 109 120 | 
             
                        if xml.unspecified?
         | 
| 110 | 
            -
                          xml.name = value.class.root_element_name
         | 
| 121 | 
            +
                          xml.name = value.class.root_element_name :mapping=>@sub_mapping
         | 
| 111 122 | 
             
                          xml.unspecified = false
         | 
| 112 123 | 
             
                        end
         | 
| 113 124 | 
             
                      }
         | 
| 114 125 | 
             
                    end
         | 
| 115 | 
            -
                    unless @ | 
| 116 | 
            -
                      @ | 
| 117 | 
            -
                        XML::Mapping.load_object_from_xml | 
| 126 | 
            +
                    unless @unmarshaller
         | 
| 127 | 
            +
                      @unmarshaller = proc {|xml|
         | 
| 128 | 
            +
                        XML::Mapping.load_object_from_xml xml, :mapping=>@sub_mapping
         | 
| 118 129 | 
             
                      }
         | 
| 119 130 | 
             
                    end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    args
         | 
| 120 133 | 
             
                  end
         | 
| 121 134 | 
             
                end
         | 
| 122 135 |  | 
| 136 | 
            +
                require 'xml/mapping/core_classes_mapping'
         | 
| 137 | 
            +
             | 
| 123 138 | 
             
                # Node factory function synopsis:
         | 
| 124 139 | 
             
                # 
         | 
| 125 140 | 
             
                #   object_node :_attrname_, _path_ [, :default_value=>_obj_]
         | 
| @@ -127,6 +142,8 @@ module XML | |
| 127 142 | 
             
                #                                   [, :class=>_c_]
         | 
| 128 143 | 
             
                #                                   [, :marshaller=>_proc_]
         | 
| 129 144 | 
             
                #                                   [, :unmarshaller=>_proc_]
         | 
| 145 | 
            +
                #                                   [, :mapping=>_m_]
         | 
| 146 | 
            +
                #                                   [, :sub_mapping=>_sm_]
         | 
| 130 147 | 
             
                #
         | 
| 131 148 | 
             
                # Node that maps a subtree in the source XML to a Ruby
         | 
| 132 149 | 
             
                # object. :_attrname_ and _path_ are again the attribute name
         | 
| @@ -140,15 +157,16 @@ module XML | |
| 140 157 | 
             
                class ObjectNode < SubObjectBaseNode
         | 
| 141 158 | 
             
                  # Initializer. _path_ (a string denoting an XPath expression) is
         | 
| 142 159 | 
             
                  # the location of the subtree.
         | 
| 143 | 
            -
                  def  | 
| 144 | 
            -
                    super
         | 
| 145 | 
            -
             | 
| 160 | 
            +
                  def initialize(*args)
         | 
| 161 | 
            +
                    path,*args = super(*args)
         | 
| 162 | 
            +
                    @path = XML::XXPath.new(path)
         | 
| 163 | 
            +
                    args
         | 
| 146 164 | 
             
                  end
         | 
| 147 165 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 148 | 
            -
                    @ | 
| 166 | 
            +
                    @unmarshaller.call(default_when_xpath_err{@path.first(xml)})
         | 
| 149 167 | 
             
                  end
         | 
| 150 168 | 
             
                  def set_attr_value(xml, value) # :nodoc:
         | 
| 151 | 
            -
                    @ | 
| 169 | 
            +
                    @marshaller.call(@path.first(xml,:ensure_created=>true), value)
         | 
| 152 170 | 
             
                  end
         | 
| 153 171 | 
             
                end
         | 
| 154 172 |  | 
| @@ -157,6 +175,7 @@ module XML | |
| 157 175 | 
             
                #   boolean_node :_attrname_, _path_,
         | 
| 158 176 | 
             
                #                _true_value_, _false_value_ [, :default_value=>_obj_]
         | 
| 159 177 | 
             
                #                                            [, :optional=>true]
         | 
| 178 | 
            +
                #                                            [, :mapping=>_m_]
         | 
| 160 179 | 
             
                #
         | 
| 161 180 | 
             
                # Node that maps an XML node's text (the element name resp. the
         | 
| 162 181 | 
             
                # attribute value) to a boolean attribute of the mapped
         | 
| @@ -168,9 +187,11 @@ module XML | |
| 168 187 | 
             
                # represent the +false+ boolean value.
         | 
| 169 188 | 
             
                class BooleanNode < SingleAttributeNode
         | 
| 170 189 | 
             
                  # Initializer.
         | 
| 171 | 
            -
                  def  | 
| 190 | 
            +
                  def initialize(*args)
         | 
| 191 | 
            +
                    path,true_value,false_value,*args = super(*args)
         | 
| 172 192 | 
             
                    @path = XML::XXPath.new(path)
         | 
| 173 193 | 
             
                    @true_value = true_value; @false_value = false_value
         | 
| 194 | 
            +
                    args
         | 
| 174 195 | 
             
                  end
         | 
| 175 196 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 176 197 | 
             
                    default_when_xpath_err{ @path.first(xml).text==@true_value }
         | 
| @@ -188,6 +209,8 @@ module XML | |
| 188 209 | 
             
                #                     [, :class=>_c_]
         | 
| 189 210 | 
             
                #                     [, :marshaller=>_proc_]
         | 
| 190 211 | 
             
                #                     [, :unmarshaller=>_proc_]
         | 
| 212 | 
            +
                #                     [, :mapping=>_m_]
         | 
| 213 | 
            +
                #                     [, :sub_mapping=>_sm_]
         | 
| 191 214 | 
             
                #
         | 
| 192 215 | 
             
                # -or-
         | 
| 193 216 | 
             
                #
         | 
| @@ -233,38 +256,35 @@ module XML | |
| 233 256 | 
             
                #    </bar>
         | 
| 234 257 | 
             
                #   </foo>
         | 
| 235 258 | 
             
                class ArrayNode < SubObjectBaseNode
         | 
| 236 | 
            -
                  # Initializer | 
| 237 | 
            -
                  #  | 
| 238 | 
            -
                  #  | 
| 239 | 
            -
                  #  | 
| 240 | 
            -
                   | 
| 241 | 
            -
             | 
| 242 | 
            -
                     | 
| 243 | 
            -
             | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 248 | 
            -
             | 
| 249 | 
            -
             | 
| 250 | 
            -
             | 
| 251 | 
            -
             | 
| 252 | 
            -
            	@base_path = XML::XXPath.new(base_path)
         | 
| 253 | 
            -
            	@per_arrelement_path = XML::XXPath.new(per_arrelement_path)
         | 
| 254 | 
            -
            	@reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path)
         | 
| 259 | 
            +
                  # Initializer. Called with keyword arguments and either 1 or 2
         | 
| 260 | 
            +
                  # paths; the hindmost path argument passed is delegated to
         | 
| 261 | 
            +
                  # _per_arrelement_path_; the preceding path argument (if
         | 
| 262 | 
            +
                  # present, "" by default) is delegated to _base_path_.
         | 
| 263 | 
            +
                  def initialize(*args)
         | 
| 264 | 
            +
                    path,path2,*args = super(*args)
         | 
| 265 | 
            +
                    base_path,per_arrelement_path = if path2
         | 
| 266 | 
            +
                                                      [path,path2]
         | 
| 267 | 
            +
                                                    else
         | 
| 268 | 
            +
                                                      [".",path]
         | 
| 269 | 
            +
                                                    end
         | 
| 270 | 
            +
                    per_arrelement_path=per_arrelement_path[1..-1] if per_arrelement_path[0]==?/
         | 
| 271 | 
            +
                    @base_path = XML::XXPath.new(base_path)
         | 
| 272 | 
            +
                    @per_arrelement_path = XML::XXPath.new(per_arrelement_path)
         | 
| 273 | 
            +
                    @reader_path = XML::XXPath.new(base_path+"/"+per_arrelement_path)
         | 
| 274 | 
            +
                    args
         | 
| 255 275 | 
             
                  end
         | 
| 256 276 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 257 277 | 
             
                    result = []
         | 
| 258 278 | 
             
                    default_when_xpath_err{@reader_path.all(xml)}.each do |elt|
         | 
| 259 | 
            -
                      result << @ | 
| 279 | 
            +
                      result << @unmarshaller.call(elt)
         | 
| 260 280 | 
             
                    end
         | 
| 261 281 | 
             
                    result
         | 
| 262 282 | 
             
                  end
         | 
| 263 283 | 
             
                  def set_attr_value(xml, value) # :nodoc:
         | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
                      @ | 
| 267 | 
            -
             | 
| 284 | 
            +
                    base_elt = @base_path.first(xml,:ensure_created=>true)
         | 
| 285 | 
            +
                    value.each do |arr_elt|
         | 
| 286 | 
            +
                      @marshaller.call(@per_arrelement_path.create_new(base_elt), arr_elt)
         | 
| 287 | 
            +
                    end
         | 
| 268 288 | 
             
                  end
         | 
| 269 289 | 
             
                end
         | 
| 270 290 |  | 
| @@ -277,6 +297,8 @@ module XML | |
| 277 297 | 
             
                #                     [, :class=>_c_]
         | 
| 278 298 | 
             
                #                     [, :marshaller=>_proc_]
         | 
| 279 299 | 
             
                #                     [, :unmarshaller=>_proc_]
         | 
| 300 | 
            +
                #                     [, :mapping=>_m_]
         | 
| 301 | 
            +
                #                     [, :sub_mapping=>_sm_]
         | 
| 280 302 | 
             
                #
         | 
| 281 303 | 
             
                # - or -
         | 
| 282 304 | 
             
                #
         | 
| @@ -296,45 +318,113 @@ module XML | |
| 296 318 | 
             
                # to such a node, key_path_ names the node whose text becomes the
         | 
| 297 319 | 
             
                # associated hash key.
         | 
| 298 320 | 
             
                class HashNode < SubObjectBaseNode
         | 
| 299 | 
            -
                  # Initializer | 
| 300 | 
            -
                  #  | 
| 301 | 
            -
                  #  | 
| 302 | 
            -
                  #  | 
| 303 | 
            -
                  #  | 
| 304 | 
            -
                  #  | 
| 305 | 
            -
                  #  | 
| 306 | 
            -
                  def  | 
| 307 | 
            -
                    super
         | 
| 308 | 
            -
                    if path3
         | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
            	@key_path = XML::XXPath.new(key_path)
         | 
| 320 | 
            -
            	@reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path)
         | 
| 321 | 
            +
                  # Initializer. Called with keyword arguments and either 2 or 3
         | 
| 322 | 
            +
                  # paths; the hindmost path argument passed is delegated to
         | 
| 323 | 
            +
                  # _key_path_, the preceding path argument is delegated to
         | 
| 324 | 
            +
                  # _per_arrelement_path_, the path preceding that argument (if
         | 
| 325 | 
            +
                  # present, "" by default) is delegated to _base_path_. The
         | 
| 326 | 
            +
                  # meaning of the keyword arguments is the same as for
         | 
| 327 | 
            +
                  # ObjectNode.
         | 
| 328 | 
            +
                  def initialize(*args)
         | 
| 329 | 
            +
                    path1,path2,path3,*args = super(*args)
         | 
| 330 | 
            +
                    base_path,per_hashelement_path,key_path = if path3
         | 
| 331 | 
            +
                                                                [path1,path2,path3]
         | 
| 332 | 
            +
                                                              else
         | 
| 333 | 
            +
                                                                ["",path1,path2]
         | 
| 334 | 
            +
                                                              end
         | 
| 335 | 
            +
                    per_hashelement_path=per_hashelement_path[1..-1] if per_hashelement_path[0]==?/
         | 
| 336 | 
            +
                    @base_path = XML::XXPath.new(base_path)
         | 
| 337 | 
            +
                    @per_hashelement_path = XML::XXPath.new(per_hashelement_path)
         | 
| 338 | 
            +
                    @key_path = XML::XXPath.new(key_path)
         | 
| 339 | 
            +
                    @reader_path = XML::XXPath.new(base_path+"/"+per_hashelement_path)
         | 
| 340 | 
            +
                    args
         | 
| 321 341 | 
             
                  end
         | 
| 322 342 | 
             
                  def extract_attr_value(xml) # :nodoc:
         | 
| 323 343 | 
             
                    result = {}
         | 
| 324 344 | 
             
                    default_when_xpath_err{@reader_path.all(xml)}.each do |elt|
         | 
| 325 345 | 
             
                      key = @key_path.first(elt).text
         | 
| 326 | 
            -
                      value = @ | 
| 346 | 
            +
                      value = @unmarshaller.call(elt)
         | 
| 327 347 | 
             
                      result[key] = value
         | 
| 328 348 | 
             
                    end
         | 
| 329 349 | 
             
                    result
         | 
| 330 350 | 
             
                  end
         | 
| 331 351 | 
             
                  def set_attr_value(xml, value) # :nodoc:
         | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 352 | 
            +
                    base_elt = @base_path.first(xml,:ensure_created=>true)
         | 
| 353 | 
            +
                    value.each_pair do |k,v|
         | 
| 334 354 | 
             
                      elt = @per_hashelement_path.create_new(base_elt)
         | 
| 335 | 
            -
                      @ | 
| 355 | 
            +
                      @marshaller.call(elt,v)
         | 
| 336 356 | 
             
                      @key_path.first(elt,:ensure_created=>true).text = k
         | 
| 337 | 
            -
             | 
| 357 | 
            +
                    end
         | 
| 358 | 
            +
                  end
         | 
| 359 | 
            +
                end
         | 
| 360 | 
            +
             | 
| 361 | 
            +
             | 
| 362 | 
            +
                class ChoiceNode < Node
         | 
| 363 | 
            +
             | 
| 364 | 
            +
                  def initialize(*args)
         | 
| 365 | 
            +
                    args = super(*args)
         | 
| 366 | 
            +
                    @choices = []
         | 
| 367 | 
            +
                    path=nil
         | 
| 368 | 
            +
                    args.each do |arg|
         | 
| 369 | 
            +
                      next if [:if,:then,:elsif].include? arg
         | 
| 370 | 
            +
                      if path.nil?
         | 
| 371 | 
            +
                        path = (if [:else,:default,:otherwise].include? arg
         | 
| 372 | 
            +
                                  :else
         | 
| 373 | 
            +
                                else
         | 
| 374 | 
            +
                                  XML::XXPath.new arg
         | 
| 375 | 
            +
                                end)
         | 
| 376 | 
            +
                      else
         | 
| 377 | 
            +
                        raise XML::MappingError, "node expected, found: #{arg.inspect}" unless Node===arg
         | 
| 378 | 
            +
                        @choices << [path,arg]
         | 
| 379 | 
            +
             | 
| 380 | 
            +
                        # undo what the node factory fcn did -- ugly ugly!  would
         | 
| 381 | 
            +
                        # need some way to lazy-evaluate arg (a proc would be
         | 
| 382 | 
            +
                        # simple but ugly for the user), and then use some
         | 
| 383 | 
            +
                        # mechanism (a flag with dynamic scope probably) to tell
         | 
| 384 | 
            +
                        # the node factory fcn not to add the node to the
         | 
| 385 | 
            +
                        # xml_mapping_nodes
         | 
| 386 | 
            +
                        @owner.xml_mapping_nodes(:mapping=>@mapping).delete arg 
         | 
| 387 | 
            +
                        path=nil
         | 
| 388 | 
            +
                      end
         | 
| 389 | 
            +
                    end
         | 
| 390 | 
            +
             | 
| 391 | 
            +
                    raise XML::MappingError, "node missing at end of argument list" unless path.nil?
         | 
| 392 | 
            +
                    raise XML::MappingError, "no choices were supplied" if @choices.empty?
         | 
| 393 | 
            +
                    
         | 
| 394 | 
            +
                    []
         | 
| 395 | 
            +
                  end
         | 
| 396 | 
            +
             | 
| 397 | 
            +
                  def xml_to_obj(obj,xml)
         | 
| 398 | 
            +
                    @choices.each do |path,node|
         | 
| 399 | 
            +
                      if path==:else or not(path.all(xml).empty?)
         | 
| 400 | 
            +
                        node.xml_to_obj(obj,xml)
         | 
| 401 | 
            +
                        return true
         | 
| 402 | 
            +
                      end
         | 
| 403 | 
            +
                    end
         | 
| 404 | 
            +
                    raise XML::MappingError, "xml_to_obj: no choice matched in: #{xml}"
         | 
| 405 | 
            +
                  end
         | 
| 406 | 
            +
             | 
| 407 | 
            +
                  def obj_to_xml(obj,xml)
         | 
| 408 | 
            +
                    @choices.each do |path,node|
         | 
| 409 | 
            +
                      if node.is_present_in? obj
         | 
| 410 | 
            +
                        node.obj_to_xml(obj,xml)
         | 
| 411 | 
            +
                        path.first(xml, :ensure_created=>true)
         | 
| 412 | 
            +
                        return true
         | 
| 413 | 
            +
                      end
         | 
| 414 | 
            +
                    end
         | 
| 415 | 
            +
                    # @choices[0][1].obj_to_xml(obj,xml)
         | 
| 416 | 
            +
                    raise XML::MappingError, "obj_to_xml: no choice present in object: #{obj.inspect}"
         | 
| 417 | 
            +
                  end
         | 
| 418 | 
            +
             | 
| 419 | 
            +
                  def obj_initializing(obj,mapping)
         | 
| 420 | 
            +
                    @choices[0][1].obj_initializing(obj,mapping)
         | 
| 421 | 
            +
                  end
         | 
| 422 | 
            +
             | 
| 423 | 
            +
                  # (overridden) true if at least one of our nodes is_present_in?
         | 
| 424 | 
            +
                  # obj.
         | 
| 425 | 
            +
                  def is_present_in? obj
         | 
| 426 | 
            +
                    # TODO: use Enumerable#any?
         | 
| 427 | 
            +
                    @choices.inject(false){|prev,(path,node)| prev or node.is_present_in?(obj)}
         | 
| 338 428 | 
             
                  end
         | 
| 339 429 | 
             
                end
         | 
| 340 430 |  | 
    
        data/lib/xml/mapping/version.rb
    CHANGED
    
    
| @@ -0,0 +1,186 @@ | |
| 1 | 
            +
            # xxpath -- XPath implementation for Ruby, including write access
         | 
| 2 | 
            +
            #  Copyright (C) 2004-2010 Olaf Klischat
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'rexml/document'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module XML
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              class XXPath
         | 
| 9 | 
            +
                module Accessors  #:nodoc:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # we need a boolean "unspecified?" attribute for XML nodes --
         | 
| 12 | 
            +
                  # paths like "*" oder (somewhen) "foo|bar" create "unspecified"
         | 
| 13 | 
            +
                  # nodes that the user must then "specify" by setting their text
         | 
| 14 | 
            +
                  # etc. (or manually setting unspecified=false)
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # This is mixed into the REXML::Element and
         | 
| 17 | 
            +
                  # XML::XXPath::Accessors::Attribute classes.
         | 
| 18 | 
            +
                  module UnspecifiednessSupport
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def unspecified?
         | 
| 21 | 
            +
                      @xml_xpath_unspecified ||= false
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    def unspecified=(x)
         | 
| 25 | 
            +
                      @xml_xpath_unspecified = x
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def self.append_features(base)
         | 
| 29 | 
            +
                      return if base.included_modules.include? self # avoid aliasing methods more than once
         | 
| 30 | 
            +
                                                                    # (would lead to infinite recursion)
         | 
| 31 | 
            +
                      super
         | 
| 32 | 
            +
                      base.module_eval <<-EOS
         | 
| 33 | 
            +
                        alias_method :_text_orig, :text
         | 
| 34 | 
            +
                        alias_method :_textis_orig, :text=
         | 
| 35 | 
            +
                        def text
         | 
| 36 | 
            +
                          # we're suffering from the "fragile base class"
         | 
| 37 | 
            +
                          # phenomenon here -- we don't know whether the
         | 
| 38 | 
            +
                          # implementation of the class we get mixed into always
         | 
| 39 | 
            +
                          # calls text (instead of just accessing @text or so)
         | 
| 40 | 
            +
                          if unspecified?
         | 
| 41 | 
            +
                            "[UNSPECIFIED]"
         | 
| 42 | 
            +
                          else
         | 
| 43 | 
            +
                            _text_orig
         | 
| 44 | 
            +
                          end
         | 
| 45 | 
            +
                        end
         | 
| 46 | 
            +
                        def text=(x)
         | 
| 47 | 
            +
                          _textis_orig(x)
         | 
| 48 | 
            +
                          self.unspecified=false
         | 
| 49 | 
            +
                        end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                        alias_method :_nameis_orig, :name=
         | 
| 52 | 
            +
                        def name=(x)
         | 
| 53 | 
            +
                          _nameis_orig(x)
         | 
| 54 | 
            +
                          self.unspecified=false
         | 
| 55 | 
            +
                        end
         | 
| 56 | 
            +
                      EOS
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  class REXML::Element              #:nodoc:
         | 
| 62 | 
            +
                    include UnspecifiednessSupport
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  # attribute node, more or less call-compatible with REXML's
         | 
| 66 | 
            +
                  # Element.  REXML's Attribute class doesn't provide this...
         | 
| 67 | 
            +
                  #
         | 
| 68 | 
            +
                  # The all/first calls return instances of this class if they
         | 
| 69 | 
            +
                  # matched an attribute node.
         | 
| 70 | 
            +
                  class Attribute
         | 
| 71 | 
            +
                    attr_reader :parent, :name
         | 
| 72 | 
            +
                    attr_writer :name
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    def initialize(parent,name)
         | 
| 75 | 
            +
                      @parent,@name = parent,name
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    def self.new(parent,name,create)
         | 
| 79 | 
            +
                      if parent.attributes[name]
         | 
| 80 | 
            +
                        super(parent,name)
         | 
| 81 | 
            +
                      else
         | 
| 82 | 
            +
                        if create
         | 
| 83 | 
            +
                          parent.attributes[name] = "[unset]"
         | 
| 84 | 
            +
                          super(parent,name)
         | 
| 85 | 
            +
                        else
         | 
| 86 | 
            +
                          nil
         | 
| 87 | 
            +
                        end
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    # the value of the attribute.
         | 
| 92 | 
            +
                    def text
         | 
| 93 | 
            +
                      parent.attributes[@name]
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    def text=(x)
         | 
| 97 | 
            +
                      parent.attributes[@name] = x
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    def ==(other)
         | 
| 101 | 
            +
                      other.kind_of?(Attribute) and other.parent==parent and other.name==name
         | 
| 102 | 
            +
                    end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    include UnspecifiednessSupport
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
             | 
| 113 | 
            +
             | 
| 114 | 
            +
             | 
| 115 | 
            +
             | 
| 116 | 
            +
             | 
| 117 | 
            +
            class REXML::Parent
         | 
| 118 | 
            +
              def each_on_axis_child
         | 
| 119 | 
            +
                if respond_to? :attributes
         | 
| 120 | 
            +
                  attributes.each_key do |name|
         | 
| 121 | 
            +
                    yield XML::XXPath::Accessors::Attribute.new(self, name, false)
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
                each_child do |c|
         | 
| 125 | 
            +
                  yield c
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
              end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              def each_on_axis_descendant(&block)
         | 
| 130 | 
            +
                each_on_axis_child do |c|
         | 
| 131 | 
            +
                  block.call c
         | 
| 132 | 
            +
                  if REXML::Parent===c
         | 
| 133 | 
            +
                    c.each_on_axis_descendant(&block)
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
              def each_on_axis_self
         | 
| 139 | 
            +
                yield self
         | 
| 140 | 
            +
              end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
              def each_on_axis(axis, &block)
         | 
| 143 | 
            +
                send :"each_on_axis_#{axis}", &block
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
            end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
             | 
| 148 | 
            +
            ## hotfix for REXML bug #128 -- see http://trac.germane-software.com/rexml/ticket/128
         | 
| 149 | 
            +
            #   a working Element#write is required by several tests and
         | 
| 150 | 
            +
            #   documentation code snippets
         | 
| 151 | 
            +
            begin
         | 
| 152 | 
            +
              # temporarily suppress warnings
         | 
| 153 | 
            +
              class <<Kernel
         | 
| 154 | 
            +
                alias_method :old_warn, :warn
         | 
| 155 | 
            +
                def warn(msg)
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
              end
         | 
| 158 | 
            +
              begin
         | 
| 159 | 
            +
                # detect bug
         | 
| 160 | 
            +
                REXML::Element.new.write("",2)
         | 
| 161 | 
            +
              ensure
         | 
| 162 | 
            +
                # unsuppress
         | 
| 163 | 
            +
                class <<Kernel
         | 
| 164 | 
            +
                  alias_method :warn, :old_warn
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
            rescue NameError
         | 
| 168 | 
            +
              # bug is present -- fix it. I use Element#write in numerous tests and rdoc
         | 
| 169 | 
            +
              #  inline code snippets. TODO: switch to REXML::Formatters there sometime.
         | 
| 170 | 
            +
              class REXML::Element
         | 
| 171 | 
            +
                def write(output=$stdout, indent=-1, transitive=false, ie_hack=false)
         | 
| 172 | 
            +
                  Kernel.warn("#{self.class.name}.write is deprecated.  See REXML::Formatters")
         | 
| 173 | 
            +
                  formatter = if indent > -1
         | 
| 174 | 
            +
                                if transitive
         | 
| 175 | 
            +
                                  require "rexml/formatters/transitive"
         | 
| 176 | 
            +
                                  REXML::Formatters::Transitive.new( indent, ie_hack )
         | 
| 177 | 
            +
                                else
         | 
| 178 | 
            +
                                  REXML::Formatters::Pretty.new( indent, ie_hack )
         | 
| 179 | 
            +
                                end
         | 
| 180 | 
            +
                              else
         | 
| 181 | 
            +
                                REXML::Formatters::Default.new( ie_hack )
         | 
| 182 | 
            +
                              end
         | 
| 183 | 
            +
                  formatter.write( self, output )
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
              end
         | 
| 186 | 
            +
            end
         |