Parse XML using Groovy: Override charset in declaration and add XML processing instruction - groovy

My initial question have been answered, but that did just open up for further issues.
Example code
Using Groovy 2.0.5 JVM 1.6.0_31
import groovy.xml.*
import groovy.xml.dom.DOMCategory
def xml = '''<?xml version="1.0" encoding="UTF-16"?>
| <?xml-stylesheet type="text/xsl" href="Bp8DefaultView.xsl"?>
|<root>
| <Settings>
| <Setting name="CASEID_SEQUENCE_SIZE">
| <HandlerURL>
| <![CDATA[ admin/MainWindow.jsp ]]>
| </HandlerURL>
| </Setting>
| <Setting name="SOMETHING_ELSE">
| <HandlerURL>
| <![CDATA[ admin/MainWindow.jsp ]]>
| </HandlerURL>
| </Setting>
| </Settings>
|</root>'''.stripMargin()
def document = DOMBuilder.parse( new StringReader( xml ) )
def root = document.documentElement
// Edit: Added the line below
def pi = document.createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="Bp8DefaultView.xsl"');
// Edit #2: Added line below
document.insertBefore(pi, root)
use(DOMCategory) {
root.Settings.Setting.each {
if( it.'#name' == 'CASEID_SEQUENCE_SIZE' ) {
it[ '#value' ] = 100
}
}
}
def outputfile = new File( 'c:/temp/output.xml' )
XmlUtil.serialize( root , new PrintWriter(outputfile))
// Edit #2: Changed from root to document.documentElement to see if that
// would make any difference
println XmlUtil.serialize(document.documentElement)
Problem description
I'm trying to parse a XML-file exported from a third party tool, and before promoting it to stage and production I need to replace certain attribute values. That is all ok, but in addition I must keep the encoding and ref. to the stylesheet.
Since this is pretty static it is totally ok to have both the encoding and the stylesheet ref. definition in a property-file, meaning: I do not need first to find the declarations in the original file.
Encoding in declaration issue
As shown in this answer found here on StackOverFlow you can do
new File('c:/data/myutf8.xml').write(f,'utf-8')
I have also tried
XmlUtil.serialize( root , new GroovyPrintStream('c:/temp/output.txt', 'utf-16'))
but it did not solve the problem for me either. So I have not understood how to override the UTF-value.
Processing instruction issue
Simply, how do I add
<?xml-stylesheet type="text/xsl" href="Bp8DefaultView.xsl"?>
to the output?
Update - This can be done like this
def pi = document.createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="Bp8DefaultView.xsl"');
The processing instruction is being added like this, this guideline showed me, but still I do not get the output.
document.insertBefore(pi, root) // Fails

All issues in this question has been answered in another question I raised, see Groovy and XML: Not able to insert processing instruction
The trick is that I expected
document.documentElement
to contain the processing instruction. But that is wrong, documentElement is:
...This is a convenience attribute that allows direct access to the child node that is the document element of the document...
Where the processing instruction is just another child node. So what I instead had to use was either the LSSerializer or the Transfomer. See Serialize XML processing instruction before root element for details.

Related

Python lxml.etree: how to add 'xml:lang="en-US"' as a namespace

I am trying to create a xml whose first element is:
<speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
</speak>
I am able to add the first attributes with...
from lxml.etree import Element, SubElement, QName, tostring
root = Element('speak', version="1.0",
xmlns="http://www.w3.org/2001/10/synthesis")
...but not the namespace xml:lang="en-US". Based on several tuto/question like this and this I tried many solutions but none worked.
For example, I tried this :
class XMLNamespaces:
xml = 'http://www.w3.org/2001/10/synthesis'
root.attrib[QName(XMLNamespaces.xml, 'lang')] = "en-US"
But the ouput is
<speak xmlns:ns0="http://www.w3.org/2001/10/synthesis" version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" ns0:lang="en-US">
How can I create the xml:lang="en-US" of my first xml element?
The special xml: prefix is associated with the http://www.w3.org/XML/1998/namespace URI.
The following code adds xml:lang="en-US" to the root element:
root.attrib[QName("http://www.w3.org/XML/1998/namespace", "lang")] = "en-US"

how to get child node value from a paren preferenced by another child value in Groovy readyAPI

I am trying to get values from a web service response in readyAPI, so i can pass it to another web service request, so i can create a automated test flow.
I have tried different code pieces most of them was a single line of code, which i prefer if it possible. I can take value from a node by typing the parent node by its attribute value. I also can get parent node by child nodes attribute value and use it to get another child value.
Here some examples:
First Format that I can use it to get childs value:
<webserviceResponse>
<documentslist>
<document #id="1">
<payment #currency="USD" >
<amount>1250.00</amount>
</payment>
</document>
<document #id="2">
<payment #currency="JPY" >
<amount>150.00</amount>
</payment>
</document>
<document #id="3">
<payment #currency="EUR" >
<amount>1170.00</amount>
</payment>
</document>
<!-- etc. -->
</documentslist>
-----> To get currency for a specific document
def webServiceResponse = "webservice#Response"
int index=2
def currency = context.expand('${'+webServiceResponse+'//*:document[#id="['+index+']"]//*:payment/#currency}')
-----> Result of this is "JPY"
<webserviceResponse>
<documentslist>
<document #id="1">
<payment #currency="USD" >
<amount>1250.00</amount>
</payment>
<refund>true</refund>
</document>
<document #id="2">
<payment #currency="JPY" >
<amount>150.00</amount>
</payment>
</document>
<document #id="3">
<payment #currency="EUR" >
<amount>1170.00</amount>
</payment>
<refund>false</refund>
</document>
<!-- etc. -->
</documentslist>
-------> To get a currency dependent on existence of a specific node
In this example we are looking the file from up to down and we are finding every refund nodes,
and taking currency value that is in the same block with the second time we see a refund node.
def webServiceResponse = "webservice#Response"
int index=2
def currrency= context.expand('${'+webServiceResponse+'(//*:refund)['+index+']//parent::*//*:payment/#currency}')
--------> Result for this is "EUR"
This one is that i cant take child value with the same way.
<webserviceResponse>
<documentslist>
<document>
<key>D_Computer</key>
<currency>USD</currency>
<amount>1250.00</amount>
<refund>true</refund>
</document>
<document>
<key>D_Keyboard</key>
<currency>JPY</currency>
<amount>150.00</amount>
</document>
<document>
<key>D_Monitor</key>
<currency>EUR</currency>
<amount>1170.00</amount>
<refund>false</refund>
</document>
<!-- etc. -->
</documentslist>
My problem with this one it doesn't have any attributes, has only values of the nodes. I know that it doesnt have an integer by the way but maybe i am doing wrong that i dont realize.
I want to get the amount value only dependent to the "key" nodes value which i am going to specify in the script.
result should show :150.00
Thank you for the very detailed and well written question.
You can use the below. Your problem is easy as there are no namespace in it.
Technique is same which you have dispalyed, its just that you need not to use # as its for attributes
def groovyUtils=new com.eviware.soapui.support.GroovyUtils(context)
def xml=groovyUtils.getXmlHolder("NameOfRequest#Response");
def currency=xml.getNodeValue("//*:documentslist/*:document[key='${key}']/*:amount");
log.info "Value of $key is " + currency
key="D_Monitor"
currency=xml.getNodeValue("//*:documentslist/*:document[key='${key}']/*:amount");
log.info "Value of $key is " + currency
Replace NameOfRequest with your Request's name
There is an alternative way too. I will post it as a separate answer so not to cause confusion. This one is still better than other one
There is an alternate way of doing things using Hashmap if the other answer is not working due to namespaces in your XML
Try this method
We are getting all values first by using getNodeValues and then since we have pair we are putting in hashmap.
Now you can retrieve anything.
def groovyUtils=new com.eviware.soapui.support.GroovyUtils(context)
def xml=groovyUtils.getXmlHolder("Request1#Response");
def keys=xml.getNodeValues("//*:documentslist/*:document/*:key")
def amounts=xml.getNodeValues("//*:documentslist/*:document/*:amount")
log.info keys.toString()
log.info amounts.toString()
HashMap h1=[:]
// Add the pair into hashmap and then retrieve
for(int i=0;i<keys.size();i++)
{
h1.put(keys[i],amounts[i])
}
def whichone="D_Computer"
log.info "Value for $whichone is " + h1.get(whichone)
Lets say you want to retrieve more than one value then you can use arrays.
i.e. take arrays as key,currency,amount,refund
so if you want to retrieve the refund for a key='Z' So using a for loop you can know that Z is present at 3 location in the array
then your refund should be refund[3]. Similarly currency[3] and amount[3]
Both the answers have their own relevance

Copy attribute values based on a preceding node attribute with Groovy

I'm trying to do something in SoapUi with Groovy and it's not working.
I have multiple nodes with many attributes and I need to extract the child node attribute based of a parent's attribute
For example:
<library id="82389389">
<book id="123" bookType="SF">
<price id="325" priceValue="5"/>
</book>
<book id="4741" bookType="History">
<price id="12388" priceValue="15"/>
</book>
<book id="2626" bookType="Drama">
<price id="12145" priceValue="40"/>
</book>
</library>
In this XML I need to extract priceValue based on the bookType and use it somewhere else (the order of the book nodes is changing)
I tried this but it doesn't work:
def response = .../library[1]
def i=0
def records = new XmlSlurper().parseText(response)
def size = records.book.size()
for (i=0,i<size,i++)
{
bookType1 = records.book[i].#bookType.first().value().text();
if (bookType1 == 'History')
{
def priceValueBook = records.book[i].price.#priceValue.first().value().text()
log.info priceValueBook
}
}
It's not clear exactly at what point you are trying to do this, but the following would work in SoapUI script step:
context.expand('${test_step#Response#//*:book[#bookType="History"]/*:price/#priceValue}')

Groovy MarkupBuilder

I have a list of things, each of which might be a foo or a bar. I want to build some xml that looks like this:
<rdf:RDF>
<foo id="1">
<foo id="2">
<bar id="3">
</rdf:RDF>
So I have gotten this far:
MarkupBuilder xml = new MarkupBuilder(writer)
xml.'rdf:RDF' (nsmap) { }
But now I am stuck. How do i - within that xml.'rdf:RDF' (nsmap) { } closure - iterate over my list of stuff? How do i - within that iterator - spit out a foo or a bar element as applicable?
Its simpler as you may think. Include a loop in the xml closure and in turn include markup in the loop. This script ...
import groovy.xml.MarkupBuilder
things = ['foo','foo','bar']
writer = new StringWriter()
xml = new MarkupBuilder(writer)
xml.'rdf:RDF' {
things.eachWithIndex {thing,index ->
"$thing" id:index+1
}
}
println writer
... will produce following output:
<rdf:RDF>
<foo id='1' />
<foo id='2' />
<bar id='3' />
</rdf:RDF>
Here You go:
import groovy.xml.MarkupBuilder
import org.custommonkey.xmlunit.*
def data = [foo:1,bar:2,baz:3]
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.'rdf:RDF' {
data.each { e ->
"$e.key"(id:e.value)
}
}
println writer
Ok.
The issue is that when a closure is run by a builder, it operates within the context of the builder. Now, it turns out that the builder context hides methods, but does not hide variables. This means that you can create a closure, assign it to a variable, and access it from within the builder.
NOw, I get the impression that this closure, too, runs in the context of the builder. But I suspect you can assign "this" to a variable outside the closures and then access that variable to get the context back.
It's ridiculous that you have to go to this sort of trouble. Im reminded of coding in Microsoft Access, back in the 90's. It works great, until the moment when you want to do something more than it was strictly designed to do.

Can an XML schema be specific by attribute value?

If I have the following XML:
<variables>
<variable name="age">92</variable>
<variable name="school">Fairview</variable>
<variable name="birthdate">11/13/2012</variable>
</variables>
Is there a way to create a schema where, based on the name attribute value, it then defines the node value (as decimal, string, date)? If so, how?
Update: This is for code in C# so I need to get this schema info using the >NET runtime.
thanks - dave
Here's a schema for your example in Relax NG
datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
element variable {
((
attribute name {'age'},
xsd:int) |
(
attribute name {'school'},
text) |
(
attribute name {'birthdate'},
xsd:date))
}

Resources