I need to form such xml:
<jobs>
<job>
<title><![CDATA[cleaner]]></title>
<description><![CDATA[cleaner in af]]></description>
<text><![CDATA[cleaner weekly in af]]></text>
<referencenumber><![CDATA[518]]></referencenumber>
<company><![CDATA[we q.]]></company>
<country_code><![CDATA[NL]]></country_code>
<city><![CDATA[af]]></city>
<url><![CDATA[url]]></url>
</job>
</jobs>
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.jobs {
data.each do |data|
xml.job {
xml.title {
xml.cdata "..."
}
xml.text {
xml.cdata "..."
}
end
}
end
The above isn't working because text is an existing method on builder.
How do I create a <text>...</text> node?
From the docs:
The builder works by taking advantage of method_missing. Unfortunately some methods are defined in ruby that are difficult or dangerous to remove. You may want to create tags with the name “type”, “class”, and “id” for example. In that case, you can use an underscore to disambiguate your tag name from the method call.
Appending an underscore also works for “text”, i.e. use text_ instead:
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.job {
xml.text_ {
xml.cdata 'foo bar baz'
}
}
end
puts builder.to_xml
Output:
<?xml version="1.0" encoding="UTF-8"?>
<job>
<text><![CDATA[foo bar baz]]></text>
</job>
Related
in the below example the xpath had been in given in the code.
String xml = Recall.getXml()
def telephoneNumbers = new XmlSlurper().parseText(xml)
def outputBuilder = new groovy.xml.StreamingMarkupBuilder()
String telephoneXml = outputBuilder.bind { mkp.yield telephoneNumbers.telephone }
would like to know any possible solution to specify the same xpath through a variable. like below.
String telephoneXml = outputBuilder.bind { mkp.yield ${xpath} }
Thanks in advance.
to be clear this is not xpath. in groovy it's named gpath, and it's a groovy expression.
You can use Eval class to evaluate groovy expression from string:
def xml = '''<root>
<telephone>1234567899</telephone>
<cell>1234567890</cell>
</root>'''
def telephoneNumbers=new XmlSlurper().parseText(xml)
def outputBuilder = new groovy.xml.StreamingMarkupBuilder()
def gpath = "xml.telephone"
String telephoneXml = outputBuilder.bind { mkp.yield Eval.me('xml',telephoneNumbers,gpath) }
I want to make the following changes in the xml file
My test.xml
<root>
<display-name>Some Name</display-name>
....
<resource-ref id='change'>
<resource-name>Change this</resource-name>
</resource-ref>
<resource-ref id='modify'>
<resource-name>Change this too</resource-name>
</resource-ref>
</root>
I want to make changes in this xml file to look like this
<root>
<display-name>Final Name</display-name>
....
<resource-ref id='change'>
<resource-name>After Change</resource-name>
</resource-ref>
<resource-ref id='modify'>
<resource-name>After Modify</resource-name>
</resource-ref>
</root>
The first answer in this question nearly answers my question. But I need to make specific changes for elements with specific id as you can see.
This looks very simple. I tried looking answers and failed to find. Any help is appreciated.
And btw my gradle script looks like this
task ("replace")<<{
def xmlSource = file(path/to/test.xml)
def xmlDest = file(path/to/destination)
def xmlParser = new XmlParser()
xmlParser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
def xmlRoot = xmlParser.parse(xmlSource)
xmlRoot.'display-name'[0].value = 'RTM16'
//Looking for something like this
//xmlRoot.'resource-ref'[#id='change'].'resource-name'[0].value = 'After Change'
//xmlRoot.'resource-ref'[#id='modify'].'resource-name'[0].value = 'After Modify'
def nodePrinter = new XmlNodePrinter(new PrintWriter(new FileWriter(xmlDest)))
nodePrinter.preserveWhitespace = true
nodePrinter.print(xmlRoot)
}
After going through the Node (Groovy 2.4.6) I came up with this
task ("replace")<<{
xmlSource = file(path/to/xml source file)
xmlDest = file(path/to/destinationfile)
def parser = new XmlParser()
def xmlRoot = parser.parse(xmlSource)
xmlRoot.each{
if(it.name().equals("resource-ref")&& it.#id.equals("change")){
it.'resource-name'[0].value = 'After Change'
}
else if(it.name().equals("resource-ref")&& it.#id.equals("modify")){
it.'resource-name'[0].value = 'After Modify'
}
}
def b = new XmlNodePrinter(new PrintWriter(new FileWriter(xmlDest)))
b.preserveWhitespace = true
b.print(z)
}
Not sure if its the best way. But it works
I've been searching and searching for a couple of days on how to do this, but I can't seem to understand sax parsing in a way that will help me accomplish what I want to accomplish. I understand sax parsing on a basic level, but I can't wrap my mind around how to use it to extract the data I need to extract.
I'm currently using:
xml data
ruby
the saxerator gem (I'm not sold on this, it's just the easiest I've found so far that I'm able to understand clearly enough)
Here's a sample of the xml structure:
<result created="2015-08-26T09:42:35-05:00" host="testdata" status="
<items>
<client>
<clientid>00001</clientid>
<name>
<![CDATA[ ABC Company ]]>
</name>
<site>
<siteid>222222</siteid>
<name>
<![CDATA[ 123 Blvd ]]>
</name>
<workstations/>
<servers>
<server>
<id>333333</id>
<name>
<![CDATA[ 123BLVD-SRV ]]>
</name>
<failed_checks>
<check>
<checkid>4444444</checkid>
<check_type>0001</check_type>
<description>
<![CDATA[Critical Events Check - Application log]]>
</description>
<dsc_247>2</dsc_247>
<date>2015-08-26</date>
<time>06:03:44</time>
<consecutive_fails>2</consecutive_fails>
<startdate>2015-08-25</startdate>
<starttime>10:43:51</starttime>
<formatted_output>
<![CDATA[Event log issues[CLIENT:]]>
</formatted_output>
<checkstatus>
<![CDATA[ Status ]]>
</checkstatus>
</check>
</failed_checks>
</server>
</servers>
</site>
</client>
What I'm trying to extract is an array of clients. Each client will have a name, a clientid, an array of its workstations (and their properties), and an array of its servers (and their properties). Something like this:
clients_array = [
{
:name => 'ABC Company',
:clientid => '00001',
:workstations => [
{
:name => 'hostname',
:id => '00002',
:failed_checks => [
{
:description => 'description', :cause => 'cause'
}
]
},
{
:name => 'hostname2',
:id => '00003',
...
}
]
},
{
:name => 'Second Company',
:clientid => '...',
...
}
]
The problem I'm running into is I can extract the client node's information easily enough, but extracting the workstation and server information for each client node is difficult.
Side note: I would just use DOM parsing, which I've done in the past with great success, but the XML I'm working with is far too large and has crashed the server.
Here's what I've been working with so far. I keep getting stuck at the site/workstations/servers nodes because sometimes there will be one site (hash element) and sometimes there are multiple sites (array element). The same goes for workstations and servers.
Since this is sax parsing, I don't understand how I can point the workstations and servers back to each client. I don't need the site data, just the workstations and servers for each client:
require 'saxerator'
def parse_sax
clients_array = []
parser = Saxerator.parser(File.new("data.xml"))
parser.for_tag(:client).each do |client|
# Create a hash to store 'this' client's data in
client_hash = Hash.new
# Grab some data
client_hash[:name] = client['name']
client_hash[:clientid] = client['clientid']
# Here's where the workstation/server code would go
parser.for_tag(:site).each do |site|
# This just goes through and finds ALL sites
end
clients_array << client_hash
end
I thought I had figured it out when I thought about parsing clients, workstations, and servers separately:
parser.for_tag(:client).each do |client|
...
end
parser.for_tag(:workstation).each do |ws|
...
end
parser.for_tag(:server).each do |srv|
...
end
But then I end up with a bunch of separate client, workstation, and server objects with no way of relating the devices back to their respective clients.
It's very possible my grasp of sax parsing is such that I'm just missing something trivial that will accomplish what I want, but I can't seem to discover the solution.
I'm more than happy to provide clarification where needed and any help is more than appreciated.
Use XMLTextReader for huge xml files. Use code like this
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.txt";
static void Main(string[] args)
{
List<Client> clients = new List<Client>();
XmlTextReader reader = new XmlTextReader(FILENAME);
while (!reader.EOF)
{
if (reader.Name == "client")
{
string xmlClient = reader.ReadOuterXml();
XElement xClient = XElement.Parse(xmlClient);
Client newClient = new Client();
clients.Add(newClient);
newClient.name = xClient.Element("name").Value;
newClient.clientid = xClient.Element("clientid").Value;
newClient.workstations = xClient.Descendants("server").Select(x => new WorkStation
{
name = x.Element("name").Value,
id = x.Element("id").Value
}).ToList();
}
else
{
reader.ReadToFollowing("client");
}
}
}
}
public class Client
{
public string name { get; set;}
public string clientid { get; set; }
public List<WorkStation> workstations { get; set; }
}
public class WorkStation
{
public string name { get; set; }
public string id { get; set; }
}
}
I'm working on an XML export for a Ruby project and I'm looking for an elegant way to implement it. The goal of this XML file is to export all data inside a project container and there is several models (about 20) sharing some common properties (for example a name and a description).
Currently, the XML export looks like an awful thing like that:
def export_project(p)
#xml project {
#xml.name p.name
#xml.description p.description
#xml.itemAs {
p.item_as.each {|item_a|export_itemA(item_a)
}
#xml.itemBs {
p.item_Bs.each {|item_B|export_itemB(item_b)
}
#xml.itemCs {
p.item_cs.each {|item_c|export_itemC(item_c)
}
}
end
def export_itemA(a)
#xml.itemA {
#xml.name a.name
}
end
def export_itemB(b)
#xml.itemB {
#xml.description b.description
}
end
def export_itemC(c)
#xml.itemC {
#xml.name c.name
#xml.description c.description
}
end
Which is pretty ugly (well, it's bearrable with 4 types, but the reality is 480 lines of mess ...)
What I'd like would be something like that (considered there is a magic mapping between a model and an exporter):
module Named
def export
#xml.name #context.name
end
end
module Described
def export
#xml.description #context.description
end
end
class ProjectExporter < ModelExporter
include Named
include Described
def export
#xml.project {
super
#xml.itemAs {
export_items(p.item_as)
}
#xml.itemBs {
export_items(p.item_Bs)
}
#xml.itemCs {
export_items(p.item_cs)
}
}
end
class ItemAExporter < ModelExporter
include Named
def export
#xml.itemA {
super
}
end
end
class ItemBExporter < ModelExporter
include Described
def export
#xml.itemB {
super
}
end
end
class ItemCExporter < ModelExporter
include Named
include Described
def export
#xml.itemC {
super
}
end
end
The problem with this method is that "super" will only call the export method of one of the module, not all of them.
I'm pretty suer the module and super approach is not the correct one, but I'm unable to find something more suitable. Any idea ?
Cheers and thanks,
Vincent
I have a message class, which can be initialized by passing arguments into the constructor, or by passing no arguments and then setting the attributes later with accessors. There is some pre-processing going on in the setter methods of the attributes.
I've got tests which ensure the setter methods do what they're supposed to, but I can't seem to figure out a good way of testing that the initialize method actually calls the setters.
class Message
attr_accessor :body
attr_accessor :recipients
attr_accessor :options
def initialize(message=nil, recipients=nil, options=nil)
self.body = message if message
self.recipients = recipients if recipients
self.options = options if options
end
def body=(body)
#body = body.strip_html
end
def recipients=(recipients)
#recipients = []
[*recipients].each do |recipient|
self.add_recipient(recipient)
end
end
end
I would tend to test the behaviour of the initializer,
i.e. that its setup the variables how you would expect.
Not getting caught up in the actuality of how you do it, assume that the underlying accessors work, or alternatively you could set the instance variables if you wanted. Its almost a good old fashioned unit test.
e.g.
describe "initialize" do
let(:body) { "some text" }
let(:people) { ["Mr Bob","Mr Man"] }
let(:my_options) { { :opts => "are here" } }
subject { Message.new body, people, my_options }
its(:message) { should == body }
its(:recipients) { should == people }
its(:options) { should == my_options }
end