Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I not inherit namespace from parent element using Nokogiri and Builder?

First - check out this train wreck of code:

xml['soapenv'].Body {
          xml.Request {
            xml.version                   ("1.1") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.name          (@admin_name.name) {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.source_version       ("1.0") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.downloadmarked          ("0") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.from  (@dateFrom) {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.time_from  ("0000") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.to    (@dateTo) {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.time_to    ("2359") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.limit             ("100") {
              xml.parent.namespace = xml.parent.namespace_definitions.first
            }
            xml.parent.namespace = xml.parent.namespace_definitions.first
          }
        }

This creates XML like this:

  <soapenv:Body>
    <Request>
      <version>1.1</version>
      <name>COMPANY NAME HERE</name>
      <source_version>1.0</source_version>
      <downloadmarked>0</downloadmarked>
      <from>20140125</from>
      <time_from>0000</time_from>
      <to>20140125</to>
      <time_to>2359</time_to>
      <limit>100</limit>
    </Request>
  </soapenv:Body>

Without all my namespace_definitions hackery - the XML would come out like this:

  <soapenv:Body>
    <soapenv:Request>
      <soapenv:version>1.1</soapenv:version>
      <soapenv:name>COMPANY NAME HERE</soapenv:name>
      <soapenv:source_version>1.0</soapenv:source_version>
      <soapenv:downloadmarked>0</soapenv:downloadmarked>
      <soapenv:from>20140125</soapenv:from>
      <soapenv:time_from>0000</soapenv:time_from>
      <soapenv:to>20140125</soapenv:to>
      <soapenv:time_to>2359</soapenv:time_to>
      <soapenv:limit>100</soapenv:limit>
    </soapenv:Request>
  </soapenv:Body>

I've got this header portion with security elements that require the format with the namespace, but once we hit the Request portion (and any subsequent portion, or any other NodeSet which does different things with this particular API...) the documentation calls for non-namespaced elements.

The simple question is: How do I generate a NodeSet nested inside a parent element that has a namespace definition, without inheriting the namespace of the parent (without the disgusting hack that I've put together)?

I'm using the common:

builder = Nokogiri::XML::Builder.new do |xml|

And what I'm really interested in is how I can take 'builder' and do something like:

    el = builder.at_xpath('//Body')
    newEl = Nokogiri::XML::Node.new do |node|
    ...my node stuff here...
    end
el.add_child(newEl)

So that I could abstract this header portion (required for all messages) into it's own method and stitch on the different body portions for the functionalities exposed via the API.

Please help!

like image 865
notaceo Avatar asked Oct 21 '22 15:10

notaceo


1 Answers

You can accomplish that by using an additional builder.

xml['soapenv'].Body do
  xml << Nokogiri::XML::Builder.new do |request_xml|
    xml.Request do
      request_xml.version        "1.1"
      request_xml.name           @admin_name.name
      request_xml.source_version "1.0"
      request_xml.downloadmarked "0"
      request_xml.from           @dateFrom
      request_xml.time_from      "0000"
      request_xml.to             @dateTo
      request_xml.time_to        "2359"
      request_xml.limit          "100"
    end
  end.doc.root.to_xml
end

will result in:

<soapenv:Body>
  <Request>
    <version>1.1</version>
    <name>COMPANY NAME HERE</name>
    <source_version>1.0</source_version>
    <downloadmarked>0</downloadmarked>
    <from>20140125</from>
    <time_from>0000</time_from>
    <to>20140125</to>
    <time_to>2359</time_to>
    <limit>100</limit>
  </Request>
</soapenv:Body>

Using the << operator appends raw string to the document. Also note the use of #doc.root, if you just use #to_xml you'll get <?xml version="1.0"?> at the beginning of the string.

However if Request was desired to be namespaced but not it's children this approach wouldn't be ideal because you'd have to use a builder for each child (a builder can have only 1 root.) A solution for multiple "roots" is to use a DocumentFragment.

xml['soapenv'].Body do
  request = Nokogiri::XML::DocumentFragment.parse ""

  Nokogiri::XML::Builder.with(request) do |request_xml|
    request_xml.version        "1.1"
    request_xml.name           @admin_name.name
    request_xml.source_version "1.0"
    request_xml.downloadmarked "0"
    request_xml.from           @dateFrom
    request_xml.time_from      "0000"
    request_xml.to             @dateTo
    request_xml.time_to        "2359"
    request_xml.limit          "100"
  end

  xml.Request << request.to_xml
end

will result in:

<soapenv:Body>
  <soapenv:Request>
    <version>1.1</version>
    <name>COMPANY NAME HERE</name>
    <source_version>1.0</source_version>
    <downloadmarked>0</downloadmarked>
    <from>20140125</from>
    <time_from>0000</time_from>
    <to>20140125</to>
    <time_to>2359</time_to>
    <limit>100</limit>
  </soapenv:Request>
</soapenv:Body>
like image 88
Morgan Showman Avatar answered Oct 31 '22 20:10

Morgan Showman