Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add child nodes in NodeSet using Nokogiri

Tags:

xml

ruby

nokogiri

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?

like image 732
user1023627 Avatar asked Apr 24 '12 14:04

user1023627


1 Answers

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
like image 104
Jamie Avatar answered Oct 23 '22 05:10

Jamie