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?
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
Related
Given I have the following code:
ENDPOINT = 'http://api.eventful.com'
API_KEY = 'PbFVZfjTXJQWrnJp'
def get_xml(url, options={})
compiled_url = "#{ENDPOINT}/rest#{url}" << "?app_key=#{API_KEY}&sort_order=popularity"
options.each { |k, v| compiled_url << "&#{k.to_s}=#{v.to_s}" }
REXML::Document.new((Net::HTTP.get(URI.parse(URI.escape(compiled_url)))))
end
def event_search(location, date)
get_xml('/events/search',
:location => "#{location}, United Kingdom",
:date => date
)
end
And we access the XML data formatted by REXML::Document like this:
events = event_search('London', 'Today').elements
And we can access these elements like this (this prints all the titles in the events):
events.each('search/events/event/title') do |title|
puts title.text
end
The XML I'm using can be found here. I would like this construct a Hash like so:
{"Title1" => {:title => 'Title1', :date => 'Date1', :post_code => 'PostCode1'},
"Title2" => {:title => 'Title2', :date => 'Date2', :post_code => 'PostCode2'}}
When using events.each('search/events/event/title'), events.each('search/events/event/date'), and events.each('search/events/event/post_code').
So I want to create a Hash from the XML provided by the URL I have included above. Thanks!
You should loop over the events themselves, not the titles. Something like this
events_by_title = {}
elements.each('search/events/event') do |event|
title = event.get_elements('title').first.text
events_by_title[title] = {
:title => title,
:date => event.get_elements('start_time').first.text
:post_code => event.get_elements('postal_code').first.text,
}
end
Get the root element using root() on the REXML:Document object then use each_element("search/events/event") to iterate over "event" node. You can then extract the different values out of it using the different methods on element: http://ruby-doc.org/stdlib-1.9.3/libdoc/rexml/rdoc/REXML/Element.html
I'd like to map a hash to a CSV line.
I have a couple of objects in a hash:
person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
I want to transform this into a CSV file with the keys as columns and the values for each row.
I can easily create the headers by creating a CSV::Row like this:
h = CSV::Row.new(all_keys_as_array,[],true)
I cannot rely on the order and more important, not all values are filled all the time.
But when I now try to add rows to the table via << and array, the mapping of the headers is ignored. It has to be in the right order.
To demonstrate this, I wrote this little script:
require 'csv'
person1 = {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
person2 = {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
persons = [person1, person2]
all_keys_as_array = %w{first_name second_name favorite_color favorite_band}
h = CSV::Row.new(all_keys_as_array,[],true)
t = CSV::Table.new([h])
persons.each do |p|
r = CSV::Row.new([],[],false)
p.each do |k, v|
r << {k => v}
end
t << r
end
puts t.to_csv
I would expect this output:
first_name,last_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,Smith,green,
Instead the values in the order as they appear. So the output is this:
first_name,second_name,favorite_color,favorite_band
John,Doe,blue,Backstreet Boys
Susan,green,Smith
The strangest part is, when I do a lookup via ['key'] I get the correct values:
puts "favorite_bands: #{t['favorite_band']}"
> favorite_bands: [nil, "Backstreet Boys", nil]
So is there any way to write to a CSV file as I expect?
You can just iterate over the column names
persons.each do |person|
r = CSV::Row.new([],[],false)
all_keys_as_array.each do |key|
r << person[key]
end
t << r
end
The current suggestions both work, so thank you for this.
However I stumbled upon a more elegant solution by using CSV::Row#fields.
Then I can just convert the CSV row to the correct array before adding it to the table:
t << r.fields(*all_keys_as_array)
You may be able to just use #to_csv and dispense with all the CSV::Row and CSV::Table stuff:
headers = %w{first_name second_name favorite_color favorite_band} # fyi if you have commas in here you'll get messed up
people = []
people << {'first_name' => 'John', 'second_name' => 'Doe', 'favorite_color' => 'blue', 'favorite_band' => 'Backstreet Boys'}
people << {'first_name' => 'Susan', 'favorite_color' => 'green', 'second_name' => 'Smith'}
require 'csv'
File.open('output.csv', 'w') do |f|
f.puts headers.to_csv
people.each do |person|
f.puts headers.map { |h| person[h] }.to_csv
end
end
I have a collection of OpenStruct elements using which I need to build an XML with help of 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',
:productAuthorized => 'Y',
:productId => 'BKSUS113246A',
:productVerificationCode => '4546747',
:productPurcTransactionTime => '2012-05-21T13:36:38+05:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'StoreUserS14',
:productAuditTime => '2012-05-21T13:36:38+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '50.14 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
),
:productAuditDetails => OpenStruct.new(
:productAuditNo => '2',
:prodHandledByUser => 'OnlineUserOn008',
:productAuditTime => '2012-05-23T16:16:08+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '84.86 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
)
),
:catalogProductInfo => OpenStruct.new(
:productType => 'Pen',
:productName => 'Reynolds'
:productAuthorized => 'N',
:productId => 'PNSUS228886B',
:productVerificationCode => '2330076',
:productPurcTransactionTime => '2012-04-22T15:06:18+04:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'CCUserA14',
:productAuditTime => '2012-04-26T13:36:38+05:30',
:productAuditAdminId => 'ReyGr1132',
:productPurchaseRate => '20.19 Prcnt',
:productSystemLoggerId => 'WIN-NETW4536'
)
)
)
)]
I tried with below code .. as per your answer (handpick of elements)
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
collection.each do |ctlg|
xml.CatalogStoreNumber ctlg.catalogStoreNumber
xml CatalogStoreId ctlg.catalogStoreId
xml.CatalogOwnerId ctlg.catalogOwnerid
xml.CatalogOwner ctlg.catalogOwner
xml.CatalogList do
prod_count = 0
aud_list_count = 0
collection.each do |prod|
info = prod.catalogList[0].catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId info.productId
xml.ProductVerificationCode info.ProductVerificationCode
xml.ProductPurcTransactionTime info.productPurcTransactionTime
xml.ProductAuditDetails do
collection.each do |aud_dtl|
aud_info = aud_dtl.catalogList[0].catalogProductinfo[0].productAuditDetails
xml.ProductAuditNo aud_info.productAuditNo
xml.ProdHandledByUser aud_info.prodHandledByUser
xml.ProductAuditTime aud_info.productAuditTime
xml.ProductAuditAdminId aud_info.productAuditAdminId
xml.ProductPurchaseRate aud_info.productPurchaseRate
xml.ProductSystemLoggerId aud_info.productSystemLoggerId
# Do whatever you must above to concoct your ProductId
end
aud_list_count = aud_list_count + 1
end
prod_count = prod_count + 1
end
end
end
puts builder.to_xml
I need the Output as below...
<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>
<ProductAuthorized>Y</ProductAuthorized>
<ProductId>BKSUS113246A</ProductId>
<ProductVerificationCode>4546747</ProductVerificationCode>
<ProductPurcTransactionTime>2012-05-21T13:36:38+05:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
<ProductAuditTime>2012-05-21T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>50.14 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
<ProductAuditTime>2012-05-23T16:16:08+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>84.86 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>UNX-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuthorized>N</ProductAuthorized>
<ProductId>PNSUS228886B</ProductId>
<ProductVerificationCode>2330076</ProductVerificationCode>
<ProductPurcTransactionTime>2012-04-22T15:06:18+04:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
<ProductAuditTime>2012-04-26T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>ReyGr1132</ProductAuditAdminId>
<ProductPurchaseRate>20.19 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>
I tried to loop in the nested Array of OpenStruct of elements, but couldn't land on right logic for that ..
Ref.. How to add child nodes in NodeSet using Nokogiri
Setup Code
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'
)
)
)
]
If You Want To Hand-Pick Your Elements and Data
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod|
info = prod.catalogList.catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId "#{prod.catalogOwnerId}-#{prod.catalogStoreNumber}"
# Do whatever you must above to concoct your ProductId
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductId>TYCT11190-657758</ProductId>
</ProductInfo>
</CatalogList>
</CatalogOrder>
If you want a more generic conversion (an XML representation of your OpenStruct hierarchy) see either of the two solutions below:
One Way to Perform Generic Conversion
# Add all entries of an OpenStruct to an XML builder
# Recursively creates sub-nodes for OpenStruct instances
def ostruct_each(ostruct,xml)
ostruct.instance_variable_get(:#table).each do |field,value|
if value.is_a?(OpenStruct)
xml.send(field) do
ostruct_each(value,xml)
end
else
xml.send(field,value)
end
end
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
ostruct_each(prod_info,xml)
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<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>
</catalogProductInfo>
</catalogList>
</ProductInfo>
</CatalogList>
</CatalogOrder>
A Different Way to Generically Convert It
# Create a NodeSet of elements for all attributes in an OpenStruct
# Recursively creates child elements for any value that is an OpenStruct
def ostruct_to_elements(xml_doc,ostruct)
Nokogiri::XML::NodeSet.new(
xml_doc,
ostruct.instance_variable_get(:#table).map do |name,val|
xml_doc.create_element(name.to_s).tap do |el|
el << (val.is_a?(OpenStruct) ? ostruct_to_elements(xml_doc,val) : val)
end
end
)
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
xml.parent << ostruct_to_elements(xml.doc,prod_info)
end
end
end
end
end
puts builder.to_xml
Answering your changed question and data.
Here's your source data, summarized:
collection = [
OpenStruct.new(
:foo => 'bar',
:list => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(...)
:catalogProductInfo => OpenStruct.new(...)
)
)
]
The first thing we notice is that collection is an array, but it only has one item. That doesn't seem very useful.
The second—far more important—thing to notice is that you are attempting to use an OpenStruct (the inner one) as an array. This fails quite fully, as the second value completely overwrites the first:
require 'ostruct'
p OpenStruct.new( a:1, a:2 )
#=> #<OpenStruct a=2>
You can't have two values for the same key in a struct. Instead, you probably wanted an array value inside your object. For example:
root = OpenStruct.new(
:foo => 'bar',
:list => [ # this is an array of two distinct objects
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) ),
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) )
]
)
Thirdly, as I noted earlier, I see no good reason for you to be using an OpenStruct here. Instead, just use Hash literals. Combined with the code I provided in my other answer, this is what your data (simplified) and a working solution looks like:
Source Data
MyOrder = {
catalogStoreNumber: '657758', # Ruby 1.9 Hash syntax;
catalogStoreId: 'CTH6536', # same as :catalogStoreId => 'CTH536'
catalogList: {
catalogProductInfo: [
{
productType: 'Book',
productName: 'The Client',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'StoreUserS14' },
{ productAuditNo: '2', prodHandledByUser: 'OnlineUserOn008' }
]
},
{
productType: 'Pen',
productName: 'Reynolds',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'CCUserA14' }
]
}
]
}
}
Generic Conversion
# Adds key/value pairs from a Hash to a Nokogiri::XML::Builder
def hash2xml(hash,xml)
hash.each do |field,value|
name = field.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar"
case value
when Hash then xml.send(name){ hash2xml(value,xml) }
when Array then value.each{ |o| xml.send(name){ hash2xml(o,xml) } }
else xml.send(name,value)
end
end
end
Working Code
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
hash2xml(MyOrder,xml)
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>
I have two dads going into my YAML file, but only one family comes out. What happened to Sam? How do I get both out?
## dads.rb
require 'yaml'
require 'pp'
dad=[]
dad[0] = {:name => "Joe", :kids => ["Mary", "John"]}
dad[1] = {:name => "Sam", :kids => ["Sam Jr", "Samantha", "Samizdat"]}
open('dads.yml' , 'w') do |f|
dad.each do |d|
f.write YAML::dump(d)
end
end
family = []
open('dads.yml') do |f|
family << YAML::load(f.read)
end
pp fams
You dump multiple YAML documents but only read back one. Instead, you can just dump and read the whole array:
require 'yaml'
dads = []
dads << {:name => "Joe", :kids => ["Mary", "John"]}
dads << {:name => "Sam", :kids => ["Sam Jr", "Samantha", "Samizdat"]}
open('dads.yml', 'w') { |f| YAML::dump(dads, f) }
family = YAML::load(File.read('dads.yml'))
p family
Your code currently creates separate "documents" within the YAML output. By default, YAML::load will just read in the first document. Niklas' answer is definitely the way you should go, but if you absolutely had to deal with multiple documents, you could use the load_documents method:
family = YAML.load_documents(File.read("dads.yml"))
# => [{:name=>"Joe", :kids=>["Mary", "John"]}, {:name=>"Sam", :kids=>["Sam Jr", "Samantha", "Samizdat"]}]
I have a YAML file of groups that I would like to get into a MongoDB collection called groups with documents like {"name" => "golf", "parent" => "sports"} (Top level groups, like sports, would just be {"name" => "sports"} without a parent.)
We are trying to traverse the nested hash, but I'm not sure if it's working correctly. I'd prefer to use a recursive method than a lambda proc. What should we change to make it work?
Thanks!
Matt
Here's the working code:
require 'mongo'
require 'yaml'
conn = Mongo::Connection.new
db = conn.db("acani")
interests = db.collection("interests")
##interest_id = 0
interests_hash = YAML::load_file('interests.yml')
def interests.insert_interest(interest, parent=nil)
interest_id = ##interest_id.to_s(36)
if interest.is_a? String # base case
insert({:_id => interest_id, :n => interest, :p => parent})
##interest_id += 1
else # it's a hash
interest = interest.first # get key-value pair in hash
interest_name = interest[0]
insert({:_id => interest_id, :n => interest_name, :p => parent})
##interest_id += 1
interest[1].each do |i|
insert_interest(i, interest_name)
end
end
end
interests.insert_interest interests_hash
View the Interests YAML.
View the acani source.
Your question is just how to convert this code:
insert_enumerable = lambda {|obj, collection|
# obj = {:value => obj} if !obj.kind_of? Enumerable
if(obj.kind_of? Array or obj.kind_of? Hash)
obj.each do |k, v|
v = (v.nil?) ? k : v
insert_enumerable.call({:value => v, :parent => obj}, collection)
end
else
obj = {:value => obj}
end
# collection.insert({name => obj[:value], :parent => obj[:parent]})
pp({name => obj[:value], :parent => obj[:parent]})
}
...to use a method rather than a lambda? If so, then:
def insert_enumerable( obj, collection )
# obj = {:value => obj} if !obj.kind_of? Enumerable
if(obj.kind_of? Array or obj.kind_of? Hash)
obj.each do |k, v|
v = (v.nil?) ? k : v
insert_enumerable({:value => v, :parent => obj}, collection)
end
else
obj = {:value => obj}
end
# collection.insert({name => obj[:value], :parent => obj[:parent]})
pp({name => obj[:value], :parent => obj[:parent]})
end
If that's not what you're asking, please help clarify.