5

I am trying to add child nodes under a root node. I tried it with the following XML but it doesn't work.

builder = Nokogiri::XML::Builder.with(@doc) do |xml|   
  nodes = Nokogiri::XML::NodeSet.new(@doc, [])   
  [].each {|nodes_one_by_one|  
    << nodes_one_by_one.Book  
    << nodes_one_by_one.Pen 
  }      
end  

I need to add nodes below a root node like this:

<Catalog>
    <Book>abc</Book>
    <Book_Author>Benjamin</Book_author>
</Catalog>

That works for me, but I to add these Nodes after a specific position in the document:

<Catalog>
    <!-- 
    <Book>abc</Book>
    <Book_Author>Benjamin</Book_author>
    -->
    <Interface></Interface>
    <Dialog></Dialog>
    <Manifest></Manifest>
</Catalog>

I tried it with at_xpath('//Catlog') but it is adding it at the end of the element:

 <Catalog>
    <Interface></Interface>
    <Dialog></Dialog>
    <Manifest></Manifest>
     <!-- 
      <Book>abc</Book>
      <Book_Author>Benjamin</Book_author>
     -->
 </Catalog>

and

book = Nokogiri::XML::Node.new('book', doc)
pen  = Nokogiri::XML::Node.new('pen', doc)     
.
.

Is there a way to loop using each instead of adding them one by one. I tried this way but that doesn't work:

builder = Nokogiri::XML::Builder.with(doc) do |xml|   
    nodes = Nokogiri::XML::Node.new(doc, [])   
    [].each {|child_list_element|  
        child_list_element.Book  "value"
        child_list_element.Pen  "value" 
        child_list_element.Diary  "value"
        child_list_element.Pen_stand  "value"
        child_list_element.Pencil  "value"
        .
        .
        .        
}      
end  
doc << nodes

The code might be wrong, but I want to do this way.

Also, can I add all the elements as a NodeSet instead of a Node.


Nested OpenStruct doesn't seem to be working. I tried:

Catalog collection of Store:

    require 'ostruct'
    require 'nokogiri'

    collection = [
    OpenStruct.new(:catalogStoreNumber => '657758',
    :catalogStoreId => 'CTH6536',
    :catalogStoreLocation => 'UnitedStates', 
    :catalogOwnerId => 'TYCT11190',
    :catalogOwner => 'McGrawHill Pub.',
    :catalogList => OpenStruct.new(
        :catalogProductInfo => OpenStruct.new(
      :productType => 'Book',
      :productName => 'The Client',
      :productId => 'CRSUS113246A',
      :productCategory => 'Crime And Suspense',
      :productSubcategory => 'Vintage Books',  
      :productPrice => '45.50 USD',
      :productAuthor => OpenStruct.new(
                :authorFirstName =>'John Grisham',
    :authorMiddleName=> 'Willburt',
    :authorContact => '19876648981')),
  :catalogProductInfo => OpenStruct.new(
      :productType => 'Pen',
      :productName => 'Reynolds',
      :productId => 'PRREY546647',
      :productCategory => 'Misc. Stationary',
      :productSubcategory => 'Stationery Items',  
      :productPrice => '3.00 USD',
      :productManufacturer => 'Reynolds Inc.',
      :productAuthor => OpenStruct.new(
          :authorFirstName => 'Ryan Reynolds',
    :authorMiddleName => 'William',
    :authorContact => '16577589898')),
    :catalogListType => 'ProductCollection',
    :catalogListSource => 'Web'
    ),
    :catalogVerificaitionLog => OpenStruct.new(
        :catalogVerificationStatus => 'Verified',
  :catalogVerificationDateTime => '2012-03-12T13:00:15+5:30',
  :catalogVerificationId => '64774A',
  :catalogVerificationRequestedBy => 'user_121')
    )]

I want to access the productType of the first catalogProductInfo and I used

collection.catalogList.catalogProductInfo.productType.content

and I got this error:

undefined method `productType' for #<Array:0x3057438> (NoMethodError)

Does OpenStruct have the support for the nested OpenStruct I want to construct XML in the following format using OpenStruct and Nokogiri?

<CatalogOrder>
  <CatalogStoreNumber>657758</CatalogStoreNumber>
  <CatalogStoreId>CTH6536</CatalogStoreId>
  <CatalogStoreLocation>UnitedStates</CatalogStoreLocation>
  <CatalogOwnerId>TYCT11190</CatalogOwnerId>
  <CatalogOwner>McGrawHill Pub.</CatalogOwner>
  <CatalogList>
    <CatalogProductInfo>
      <ProductType>Book</ProductType>
      <ProductName>The Client</ProductName>                
      <ProductId>CRSUS113246A</ProductId>
      <ProductCategory>Crime And Suspense</ProductCategory>
      <ProductSubCategory>Vintage Books</ProductSubCategory>
      <ProductPrice>45.50 USD</ProductPrice>
      <ProductAuthor>
        <AuthorFirstName>John Grisham</AuthorFirstName>
        <AuthorMiddleName>Willbur</AuthorMiddleName>
        <AuthorContact>19876648981</AuthorContact>
      </ProductAuthor>
    </CatalogProductInfo>
    <CatalogProductInfo>
      <ProductType>Pen</ProductType>
      <ProductName>Reynolds</ProductName>                
      <ProductId>PRREY546647</ProductId>
      <ProductCategory>Misc. Stationary</ProductCategory>
      <ProductSubCategory>Stationary Items</ProductSubCategory>
      <ProductPrice>3.00 USD</ProductPrice>
      <ProductAuthor>
        <AuthorFirstName>Ryan Reynolds</AuthorFirstName>
        <AuthorMiddleName>William</AuthorMiddleName>
        <AuthorContact>16577589898</AuthorContact>
      </ProductAuthor>
    </CatalogProductInfo>
    <CatalogListType>ProductCollection</CatalogListType>
    <CatalogListSource>Web</CatalogListSource>
  </CatalogList>
  <CatalogVerificationLog>
    <CatalogVerificationStatus>Verified</CatalogVerificationStatus>
    <CatalogVerificationDateTime>2012-03-12T13:00:15+5:30</CatalogVerificationDateTime>
    <CatalogVerificationId>64774A</CatalogVerificationId>
    <CatalogVerificationRequestedBy>User_121</CatalogVerificationRequestedBy>
  </CatalogVerificationLog>
</CatalogOrder>

I want to do this using Nokogiri and OpenStruct, but I am not sure whether it is possible with OpenStruct, as it lacks nesting capabilities. Is there any other way to use JSON to accomplish this without any limitations?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
user1023627
  • 183
  • 1
  • 3
  • 12
  • [Nokogiri::XML::Builder](http://nokogiri.rubyforge.org/nokogiri/Nokogiri/XML/Builder.html) is good for building an XML document from scratch, but you already have one to work with here, so as far as I am aware there is no reason to use it. – Jamie Apr 25 '12 at 19:40

1 Answers1

9

If I am understanding you correctly, the following should be roughly what you are looking for:

doc = Nokogiri::XML(original_xml_string) 

catalog = doc.at_css('Catalog') # #at_css will just grab the first node.
                                # use #css if you want to loop through several.
                                # alternatively just use doc.root

book = Nokogiri::XML::Node.new('Book', doc)
book_author = Nokogiri::XML::Node.new('Book_Author', doc)

book.content = 'abc'
book_author.content  = 'benjamin'

catalog << book
catalog << book_author

The << should append the nodes just before the end of the element.

After the updated question and simplified with @Phrogz's suggestions, this should meet your requirements:

require 'nokogiri'

xml = <<'XML'
<Catalog>
  <Interface></Interface>
  <Dialog></Dialog>
  <Manifest></Manifest>
</Catalog>
XML

doc = Nokogiri::XML(xml) 
catalog = doc.root

catalog.first_element_child.before("<Book_Author>abc</Book_Author>")
catalog.first_element_child.before("<Book>benjamin</Book>")

puts doc.to_xml

To iterate over a collection, add the nodes dynamically, and using a NodeSet, try the following:

require 'nokogiri'
require 'ostruct'

xml = <<-'XML'
<Catalog>
  <Interface></Interface>
  <Dialog></Dialog>
  <Manifest></Manifest>
</Catalog>
XML

collection = [
  OpenStruct.new(book: '1984', pen: 'George Orwell'),
  OpenStruct.new(book: 'Thinking, Fash and Slow', pen: 'Daniel Kahneman')
]

doc = Nokogiri::XML(xml) 
catalog = doc.root

node_set = Nokogiri::XML::NodeSet.new(doc)
collection.each do |object|
  book = Nokogiri::XML::Node.new('Book', doc)
  book_author = Nokogiri::XML::Node.new('Book_Author', doc)

  book.content = object.book
  book_author.content = object.pen

  node_set << book
  node_set << book_author
end

catalog.first_element_child.before(node_set)

puts doc.to_xml
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Jamie
  • 651
  • 2
  • 6
  • 10
  • Note that [`<<`](http://nokogiri.org/Nokogiri/XML/Node.html#method-i-3C-3C) can also accept a string, so you can simply do: `catalog << "abc"` – Phrogz Apr 24 '12 at 17:08
  • Thanks Phroz and Jamie that was really helpful. I will work on these and get back to you. – user1023627 Apr 24 '12 at 21:10
  • hi phroz and jamie. i updated my question. could you please look at it. – user1023627 Apr 24 '12 at 21:30
  • 1
    @user1023627 To insert a node as the new first child of an element insert it before the existing first child: [`catalog.first_element_child.before("abc")`](http://nokogiri.org/Nokogiri/XML/Node.html#method-i-before). – Phrogz Apr 24 '12 at 22:06
  • instead adding each and every element using .. book = Nokogiri::XML::Node.new('book', doc) pen = Nokogiri::XML::Node.new('pen', doc) .. can i use each.do and add elements using a loop and append them once at end eg:- doc << nodes – user1023627 Apr 25 '12 at 15:29
  • hi Phrogz and jamie .. could you please look at the my update on the question ? – user1023627 Apr 25 '12 at 18:40
  • Hi @Phrogz, ! The Nested OpenStruct doesn't seem to be working fine. I've below mentioned requirement, which isn't possible with the above mentioned solution. Could you please send me any corrections, in the above mentioned code. I also wanted to know which way storing the data is most scalable(I don't want to use YAML anymore bcoz i don't want multiple ".yml" files occupying my workspace for each and every specific scenario). Is there any way I can use JSon to store the data and Construct XML using JSon files.(If yes, give me the tips to store data in ".json" file and construct using Nokogiri – user1023627 Jun 23 '12 at 11:11
  • Hi @Jamie, Please help me out, I got stuck in here with OpenStruct and Nokogiri – user1023627 Jun 23 '12 at 13:46