How do I use XML Builder to create xml in rails? - ruby-on-rails

I am trying the following, but the output XML is badly formed:
xml = Builder::XmlMarkup.new({:target => display })
xml.instruct!
xml.mail {
xml.documents {
xml.document {
xml.template {
xml.source "gallery"
xml.name "Postcard: Image fill front"
}
xml.sections {
xml.section {
xml.name "Text"
xml.text "Hello, world"
}
xml.section {
xml.name "Image"
xml.attachment "1136946686-3425"
}
} #sections
} #document
} #documents
xml.addresses {
xml.addressee {
xml.name "Me"
xml.address "Street"
xml.city "San Francisco"
xml.state "CA"
}
}
}
#xml_display = xml
I need it to look more like this:
<?xml version="1.0" encoding="UTF-8"?>
<mail>
<documents>
<document>
<template>
<source>gallery</source>
<name>Postcard: Image fill front</name>
</template>
<sections>
<section>
<name>Text</name>
<text>Hello, World!</text>
</section>
<section>
<name>Image</name>
<attachment>...attachment id...</attachment>
</section>
</sections>
</document>
</documents>
<addressees>
<addressee>
<name>John Doe</name>
<address>123 Main St</address>
<city>Anytown</city>
<state>AZ</state>
<postal-code>10000</postal-code>
</addressee>
</addressees>
</mail>
My code ends up looking like this in the view (page source):
<?xml version="1.0" encoding="UTF-8"?><mail/><documents/><document/><template/><source>gallery</source><name>Postcard: Image fill front</name>
NOTE: The final XML needs to be POSTED to a URL, and I am using rest-client and so want everything to become an instance variable that is passed as the body.
The instance variable is just #xml_display = xml but it's not working... :(

You can pass a block to each of those to nest the items.
xml.mail do |mail|
mail.documents do |documents|
documents.document do |document|
document.template do |template|
template.source "gallery"
template.name "Postcard: Image fill front"
end
end
end
end
There's more information on the builder rubyforge page.

Related

XML generation using Nokogiri involving nested tags and namespace

Using gem Nokogiri I'm trying to generate XML like:
<?xml version='1.0'?>
<env:Envelope xmln:env = "http://abc.ca">
<env:Header>
<mm7:TransactionID xmlns:mm7="http://def.ca"> Some Text Here </mm7:TransactionID>
</env:Header>
</env:Envelope>
The code I have is:
env_ns = {
"xmlns:env" => "http://abc.ca"
}
mm7_ns = {
"xmlns:mm7" => "http://def.ca"
}
env_header = Nokogiri::XML::Builder.new do |xml|
xml['mm7'].TransactionID(mm7_ns) do
"Some Text Here"
end
end
builder = Nokogiri::XML::Builder.new { |xml|
xml['env'].Envelope(env_ns) do
xml.Header do
env_header
end
end
}
puts env_header.to_xml
puts "----------------------"
puts builder.to_xml
However, the output is not as desired because the value "Some Text Here" did not go inside the mm7:TranactionID tag. The mm7 tag did not go inside the header tag. Also, the header tag did not go inside the envelope tag.
<?xml version="1.0"?>
<mm7:TransactionID xmlns:mm7="http://def.ca"/>
-----------------------------------------------------------
<?xml version="1.0"?>
<env:Envelope xmlns:env="http://abc.ca">
<env:Header/>
</env:Envelope>
Thanks.
You only need 1 builder:
env_ns = {
"xmlns:env" => "http://abc.ca"
}
mm7_ns = {
"xmlns:mm7" => "http://def.ca"
}
builder = Nokogiri::XML::Builder.new do |xml|
xml['env'].Envelope(env_ns) do
xml.Header do
xml['mm7'].TransactionID(mm7_ns, "Some Text Here")
end
end
end
puts builder.to_xml
# will render the following:
# <?xml version="1.0"?>
# <env:Envelope xmlns:env="http://abc.ca">
# <env:Header>
# <mm7:TransactionID xmlns:mm7="http://def.ca">Some Text Here</mm7:TransactionID>
# </env:Header>
# </env:Envelope>

How to remove the namespace from few XML nodes

I need to read an XML file and generate a SOAP request body using Nokogiri in Ruby on Rails.
The request body that I need to generate is:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:c2="http://c2_0.customer.webservices.csx.dtv.com/">
<soapenv:Header>
<soapenv:Body>
<c2:getCustomer>
<customerId>10</customerId>
<UserId>adminUser</UserId>
</c2:getCustomer>
</soapenv:Body>
</soapenv:Header>
</soapenv:Envelope>
I am using this code:
require 'nokogiri'
doc = Nokogiri::XML(File.open('p_l_s.xml'))
wsID =doc.xpath('//transaction:WsID' , 'transaction' => 'http://www.nrf-arts.org/IXRetail/namespace/').inner_text
builder = Nokogiri::XML::Builder.new do |xml|
xml.Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/",
"xmlns:c2" => "http://c2_0.customer.webservices.csx.dtv.com/") do
xml.parent.namespace = xml.parent.namespace_definitions.first
xml['soapenv'].Header {
xml.Body {
xml['c2'].getCustomer{
#xml.remove_namespaces!
xml.customerId wsID
xml.UserId "adminUser"
}
}
}
end
end
puts builder.to_xml
And, when executing it from the terminal in Ubuntu, I get:
<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:c2="http://c2_0.customer.webservices.csx.dtv.com/">
<soapenv:Header>
<soapenv:Body>
<c2:getCustomer>
<c2:customerId>10</c2:customerId>
<c2:UserId>adminUser</c2:UserId>
</c2:getCustomer>
</soapenv:Body>
</soapenv:Header>
</soapenv:Envelope>
I get the c2 namespace for the XML elements customerId and UserId which is not required for the method I'm invoking in the WSDL file I will be calling.
I was able to generate the output you needed with the following code:
builder = Nokogiri::XML::Builder.new do |xml|
xml.Envelope("xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/",
"xmlns:c2" => "http://c2_0.customer.webservices.csx.dtv.com/") do
xml.parent.namespace = xml.parent.namespace_definitions.first
xml.Header {
xml.Body {
xml.getCustomer {
xml.customerId {
xml.parent.content=(wsID)
xml.parent.namespace = xml.parent.namespace_definitions.first
}
xml.UserId{
xml.parent.content=("adminUser")
xml.parent.namespace = xml.parent.namespace_definitions.first
}
xml.parent.namespace = xml.parent.namespace_scopes[1]
}
}
}
end
end
puts builder.to_xml
You don't have to explicitly set namespaces until you get to 'getCustomer'. With a combination of the 'content' and 'namespace_definition' methods, you can then specify the namespace and content of the child nodes - which should output what you were looking for.
The Nogokiri pages for the Builder: http://nokogiri.org/Nokogiri/HTML/Builder.html and Node: http://nokogiri.org/Nokogiri/XML/Node.html are super useful and will hopefully help shed more light on this for you.
I think this code in Mel T's answer:
xml.customerId {
xml.parent.content=(wsID)
xml.parent.namespace = xml.parent.namespace_definitions.first
}
Would output:
<soapenv:customerId>10</soapenv:customerId>
I did this instead:
xml.customerId {
xml.parent.content=(wsID)
xml.parent.namespace = nil
}

How do I create this element using Nokogiri builder?

I have an element in my XML but I am not sure how to get it generated in Nokogiri::XML::Builder.
<ns0:SearchCondition expressionLanguage='String'expressionType='PartyNumber'>31955854</ns0:SearchCondition>
I tried this:
def test_xml
builder = Nokogiri::XML::Builder.new do |xml|
xml.root {
xml.products {
xml.widget {
xml.id_ "10"
xml.name "Awesome widget"
xml.SearchCondition('expressionLanguage' => 'String', 'expressionType' => 'PartyNumber')
}
}
}
end
puts builder.to_xml
This produces the following
<?xml version="1.0"?>
<root>
<products>
<widget>
<id>10</id>
<name>Awesome widget</name>
<SearchCondition expressionLanguage="String" expressionType="PartyNumber"/>
</widget>
</products>
</root>
But I am not sure how I pass the value to the PartyNumber.
Not sure if this is what you are asking, but you can use the builder text method to create a text element inside another:
xml.SearchCondition('expressionLanguage' => 'String', 'expressionType' => 'PartyNumber') {
xml.text "31955854"
}
You're not using the namespace in your example, but you don't mention that, so I guess that is not an issue.

Namespace prefix and cdata tags in xml generation for rss feed in Ruby on Rails

I need to generate an xml for a feed which looks roughly like this :-
<?xml version="1.0" encoding="iso-8859-1" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<item>
<g:id><![CDATA[id]]></g:id>
<title><![CDATA[Product Name]]></title>
<description><![CDATA[This should be a relatively detailed description with as little formatting as possible.]]></description>
<g:brand>Brand X</g:brand>
<g:sale_id>new</g:sale_id>
</item>
<item>
Next product...
</item>
</channel>
</rss>
My code currently looks something like this :-
xml=Builder::XmlMarkup.new(:indent => 3)
xml.instruct!
xml.rss("version" => "2.0" , "xmlns:g" => "http://base.google.com/ns/1.0" , "xmlns:atom" => "http://www.w3.org/2005/Atom"){
xml.channel{
# remove xml.namespace = xml.namespace_definitions.find{|ns|ns.prefix=="atom"}
sale_products.each do |sp|
sid = (products_info[sp.product_id]["sale_id"]).to_s()
xml.item {
#xml.id{ |xml| xml.cdata!(products_info[sp.product_id].own_id) }
#xml.g :id,{ xml.cdata!("sdaf") }
xml.product_title{ |xml| xml.cdata!(products_info[sp.product_id].name) }
xml.description{ |xml| xml.cdata!(ActionController::Base.helpers.strip_tags(products_info[sp.product_id].description)) }
xml.item {
xml.brand { |xml| xml.cdata!(products_info[sp.product_id].designer_1) }
xml.sale_id{ |xml| xml.cdata!(sid) }
}
}
end
}
}
My problem is around getting both namespace prefixes and cdata tags working at the same time.
xml.g :id, "fdsafsad"
This gets the namesapce prefix.
xml.product_title{ |xml| xml.cdata!(products_info[sp.product_id].name) }
This gets cdata tags around the values.
xml.g :id,{ xml.cdata!("sdaf") }
This fails to do the trick.
How do i get both the namespace prefix as well as the cdata tags working at the same time for the same tag. What am I doing wrong?
Edit:- The output that I am currently getting is like:-
<g:id>
<![CDATA[10005-0003]]>
</g:id>
The output that I want should just have the value inside cdata tags (no newline etc) :-
<g:id><![CDATA[10005-0003]]></g:id>
Note that I do not want to remove the :indent => 3 while creating the markup, so that other tags are formatted as required.
xml.tag!("g:id") { xml.cdata!("sdaf") }

Cannot render form tag using 'render' in Grails Controller

Using markup with the render is not adding the form tag.
I tried this with contentType "text/html", "txt/xml" and it does not work.
I have this in my controller:
def myTest= {
render(contentType: "text/plain") {
div(id:"myDiv") {
p "somess text inside the div"
form (action:'get') {
p "inside form"
}
}
}
And I'm getting this:
<div id='myDiv'><p>somess text inside the div</p><p>inside form</p></div>
I want this:
<div id='myDiv'><p>somess text inside the div</p><form><p>inside form</p></form></div>
Does anybody know why is not adding the form and how to add it?
Thanks,
Federico
i found this problem earlier and the work around was to use the builder directly
def test = {
def sw = new StringWriter()
def b = new MarkupBuilder(sw)
b.html(contentType: "text/html") {
div(id: "myDiv") {
p "somess text inside the div"
b.form(action: 'get') {
p "inside form"
}
}
}
render sw
}
will render the following HTML
<html contentType='text/html'>
<div id='myDiv'>
<p>somess text inside the div</p>
<form action='get'>
<p>inside form</p>
</form>
</div>
</html>

Resources