View Category
XML

Process an XML document

Given the XML Document:

<shopping>
  <item name="bread" quantity="3" price="2.50"/>
  <item name="milk" quantity="2" price="3.50"/>
</shopping>

Print out the total cost of the items, e.g. $14.50
ruby
#!/usr/bin/env ruby

# needed to parse xml
require 'rexml/document'

# grab the file
file = File.new('shop.xml')

# load it as an xml document
doc = REXML::Document.new(file)

# initialize the total to 0 as a float
total = 0.0

# cycle through the items
doc.elements.each('shopping/item') do |item|

# add the price to the total
total += item.attributes['price'].to_f
end

# round the total to the nearest 0.01
total = (total*100.0).round/100.0

# pad the output with the proper number of trailing 0's
printf "$%.2f\n", total
csharp
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(
@"<shopping>
<item name='bread' quantity='3' price='2.50'/>
<item name='milk' quantity='2' price='3.50'/>
</shopping>");

string decimalSeparator= System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator;

double sum=0;

foreach(System.Xml.XmlNode nodo in doc.SelectNodes("/shopping/item")){
sum += int.Parse(nodo.Attributes["quantity"].InnerText) * double.Parse(nodo.Attributes["price"].InnerText.Replace(".",decimalSeparator));
}
Console.WriteLine("{0:#.00}",sum);
clojure
(println (format "Total cost of items are $%#.2f"
(->> (xml-seq (parse *xml-input-stream*))
(filter #(= :item (:tag %))) ; Remove all but the item tags
(map :attrs) ; Keep the attributes
(map (fn [e] (str "(* " (:quantity e) " " (:price e) ")"))) ; Get the total price as a sexp
(map read-string) ; "(* quantity price)" -> (* quantity price)
(map eval) ; (* quantity price) -> quantity*price
(apply +)))) ; Sum all elements
fsharp
#r @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.5\System.Xml.Linq.dll"

open System
open System.Xml.Linq
//XElement Helper
let xname sname = XName.Get sname

let xmlsnippet =
let snippet = new XElement(xname "shopping")
//create bread
let bread = new XElement(xname "item")
bread.SetAttributeValue(xname "name","bread")
bread.SetAttributeValue(xname "quantity",3)
bread.SetAttributeValue(xname "price",2.50)
//add bread to snippet
snippet.Add(bread)
//create milk
let milk = new XElement(xname "item")
milk.SetAttributeValue(xname "name","milk")
milk.SetAttributeValue(xname "quantity",2)
milk.SetAttributeValue(xname "price",3.50)
//add milk to snippet
snippet.Add(milk)
snippet

let totalprice (xe: XElement) =
xe.Descendants(xname "item")
|> Seq.map(fun i -> Double.Parse(i.Attribute(xname "price").Value))
|> Seq.fold(fun acc x -> acc + x) 0.0


let xname sname = XName.Get sname
let xattr (elem: XElement) sname = elem.Attribute(xname sname).Value
let xml = XDocument.Load("xml.txt")

let shoppingCost =
xml.Descendants(xname "item")
|> Seq.map (fun i -> Double.Parse(xattr i "quantity"), Double.Parse(xattr i "price"))
|> Seq.sumBy (fun (quantity, price) -> quantity * price)
// Alternative solution that uses XML Navigation, and XPath expressions to ensure that
// the items have the required attributes
let xname sname = XName.Get sname
let xattr (elem: XElement) sname = elem.Attribute(xname sname).Value

let navigator = XPathDocument("xml.txt").CreateNavigator()
let path = XPathExpression.Compile("/shopping/item[@price][@quantity]")
let names = XmlNamespaceManager(navigator.NameTable)
path.SetContext(names)
let shoppingCost =
match path.ReturnType with
| XPathResultType.NodeSet ->
navigator.Select(path)
|> Seq.cast
|> Seq.map (fun (i: XPathNavigator) ->
if i.IsNode then
let elem = XElement.Parse(i.OuterXml)
Double.Parse(xattr elem "quantity"), Double.Parse(xattr elem "price")
else
failwith "Error in expression, expecting to see a node"
)
|> Seq.sumBy (fun (quantity, price) -> quantity * price)
| _ -> failwith "Error in expression, expecting to see a node set"
groovy
printf '$%.2f\n', new XmlSlurper().parseText(xml).item.collect{
it.@quantity.toInteger() * it.@price.toFloat()
}.sum()

create some XML programmatically

Given the following CSV:

bread,3,2.50
milk,2,3.50

Produce the equivalent information in XML, e.g.:

<shopping>
  <item name="bread" quantity="3" price="2.50" />
  <item name="milk" quantity="2" price="3.50" />
</shopping>
ruby
# gem install builder
require 'builder'

xml = Builder::XmlMarkup.new
xml.shopping do
xml.item(:name => "bread", :quantity => 3, :price => "2.50")
xml.item(:name => "milk", :quantity => 2, :price => "3.50")
end
xml
csharp
string cvs ="bread,3,2.50\nmilk,2,3.50";
IList<string> rows = cvs.Split('\n');

System.Text.StringBuilder sb = new System.Text.StringBuilder("<shopping>");
foreach(string row in rows){
IList<string> data = row.Split(',');
sb.AppendFormat("<item name='{0}' quantity='{1}' price='{2}' />",data[0],data[1],data[2]);
}
sb.Append("</shopping>");
clojure
(defn list->xml-item [lst]
(let [[name quantity price] (map str lst)]
{:tag :item
:attrs {:name name
:quantity quantity
:price price}}))

(defn cvs->xml [r]
(->> (map #(read-string (str "(" % ")")) (line-seq r))
(map list->xml-item)
(assoc {:tag :shopping} :content)
(emit)
(with-out-str)))

(println (cvs->xml *cvs-reader*))
fsharp
#r "System.Xml.dll"
#r "System.Xml.Linq.dll"

open System
open System.Xml
open System.Xml.Linq

let data = "bread,3,2.50
milk,2,3.50"

let X name =
XName.Get(name)

let lines = data.Split( [|"\n" |], StringSplitOptions.RemoveEmptyEntries)

let document = new XDocument()
let element = new XElement(X "shopping")
document.Add(element)

lines
|> Seq.iter (fun line ->
let items = line.Split([|','|])
let item = new XElement(X "item",
new XAttribute(X "name", items.[0]),
new XAttribute(X "quantity", items.[1]),
new XAttribute(X "price", items.[2]))
element.Add(item))

let output = document.ToString();;
groovy
b = new groovy.xml.MarkupBuilder()
b.shopping {
csv.eachLine { line ->
(n, q, p) = line.split(',')
item(name:n, quantity:q, price:p)
}
}
// Groovy equivalent of Java JAXB solution
@XmlAccessorType(NONE)
class Item {
@XmlAttribute String name
@XmlAttribute Integer quantity
@XmlAttribute Double price
}

@XmlAccessorType(NONE)
@XmlRootElement
class Shopping {
@XmlElement Set<Item> items = []
}

Shopping shopping = new Shopping()
csvtext.eachLine{ line ->
(n, q, p) = line.split(',')
shopping.items << new Item(name:n, quantity:q.toInteger(), price:p.toDouble())
}
JAXB.marshal shopping, System.out