Get element by attribute value variables (xpath) [duplicate] - xpath

I want to pass a parameter into an XPath expression.
(//a/b/c[x=?],myParamForXAttribute)
Can I do this with XPath 1.0 ? (I tried string-join but it is not there in XPath 1.0)
Then how can I do this ?
My XML looks like
<a>
<b>
<c>
<x>val1</x>
<y>abc</y>
</c>
<c>
<x>val2</x>
<y>abcd</y>
</c>
</b>
</a>
I want to get <y> element value where x element value is val1
I tried //a/b/c[x='val1']/y but it did not work.

Given that you're using the Axiom XPath library, which in turn uses Jaxen, you'll need to follow the following three steps to do this in a thoroughly robust manner:
Create a SimpleVariableContext, and call context.setVariableValue("val", "value1") to assign a value to that variable.
On your BaseXPath object, call .setVariableContext() to pass in the context you assigned.
Inside your expression, use /a/b/c[x=$val]/y to refer to that value.
Consider the following:
package com.example;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.common.AxiomText;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axiom.om.xpath.DocumentNavigator;
import org.jaxen.*;
import javax.xml.stream.XMLStreamException;
public class Main {
public static void main(String[] args) throws XMLStreamException, JaxenException {
String xmlPayload="<parent><a><b><c><x>val1</x><y>abc</y></c>" +
"<c><x>val2</x><y>abcd</y></c>" +
"</b></a></parent>";
OMElement xmlOMOBject = AXIOMUtil.stringToOM(xmlPayload);
SimpleVariableContext svc = new SimpleVariableContext();
svc.setVariableValue("val", "val2");
String xpartString = "//c[x=$val]/y/text()";
BaseXPath contextpath = new BaseXPath(xpartString, new DocumentNavigator());
contextpath.setVariableContext(svc);
AxiomText selectedNode = (AxiomText) contextpath.selectSingleNode(xmlOMOBject);
System.out.println(selectedNode.getText());
}
}
...which emits as output:
abcd

It depends on the language in which you're using XPath.
In XSLT:
"//a/b/c[x=$myParamForXAttribute]"
Note that, unlike the approach above, the three below are open to XPath injection attacks and should never be used with uncontrolled or untrusted inputs; to avoid this, use a mechanism provided by your language or library to pass in variables out-of-band. [Credit: Charles Duffy]
In C#:
String.Format("//a/b/c[x={0}]", myParamForXAttribute);
In Java:
String.format("//a/b/c[x=%s]", myParamForXAttribute);
In Python:
"//a/b/c[x={}]".format(myParamForXAttribute)

Related

VTD fails to evaluate a "find all empty nodes with no attributes" xpath

I found a bug (I think) using the 2.13.4 version of vtd-xml. Well, in short I have the following snippet code:
String test = "<catalog><description></description></catalog>";
VTDGen vg = new VTDGen();
vg.setDoc(test.getBytes("UTF-8"));
vg.parse(true);
VTDNav vn = vg.getNav();
//get nodes with no childs, text and attributes
String xpath = "/catalog//*[not(child::node()) and not(child::text()) and count(#*)=0]";
AutoPilot ap = new AutoPilot(vn);
ap.selectXPath(xpath);
//block inside while is never executed
while(ap.evalXPath()!=-1) {
System.out.println("current node "+vn.toRawString(vn.getCurrentIndex()));
}
and this doesn't work (=do not find any node, while it should find "description" instead). The code above works if I use the self closed tag:
String test = "<catalog><description/></catalog>";
The point is every xpath evaluator works with both version of the xml. Sadly I receive the xml from an external source, so I have no power over it...
Breaking the xpath I noticed that evaluating both
/catalog//*[not(child::node())]
and
/catalog//*[not(child::text())]
give false as result. As additional bit I tried something like:
String xpath = "/catalog/description/text()";
ap.selectXpath(xpath);
if(ap.evalXPath()!=-1)
System.out.println(vn.toRawString(vn.getCurrentIndex()));
And this print empty space, so in some way VTD "thinks" the node has text, even empty but still, while I expect a no match. Any hint?
TL;DR
When I faced this issue, I was left mainly with three options (see below). I went for the second option : Use XMLModifier to fix the VTDNav. At the bottom of my answser, you'll find an implementation of this option and a sample output.
The long story ...
I faced the same issue. Here are the main three options I first thought of (by order of difficulty) :
1. Turn empty elements into self closed tags in the XML source.
This option isn't always possible (like in OP case). Moreover, it may be difficult to "pre-process" the xml before hand.
2. Use XMLModifier to fix the VTDNav.
Find the empty elements with an xpath expression, replace them with self closed tags and rebuild the VTDNav.
2.bis Use XMLModifier#removeToken
A lower level variant of the preceding solution would consist in looping over the tokens in VTDNav and remove unecessary tokens thanks to XMLModifier#removeToken.
3. Patch the vtd-xml code directly.
Taking this path may require more effort and more time. IMO, the optimized vtd-xml code isn't easy to grasp at first sight.
Option 1 wasn't feasible in my case. I failed implementing Option 2bis. The "unecessary" tokens still remained. I didn't look at Option 3 because I didn't want to fix some (rather complex) third party code.
I was left with Option 2. Here is an implementation:
Code
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XMLModifier;
#Test
public void turnEmptyElementsIntoSelfClosedTags() throws VTDException, IOException {
// STEP 1 : Load XML into VTDNav
// * Convert the initial xml code into a byte array
String xml = "<root><empty-element></empty-element><self-closed/><empty-element2 foo='bar'></empty-element2></root>";
byte[] ba = xml.getBytes(StandardCharsets.UTF_8);
// * Build VTDNav and dump it to screen
VTDGen vg = new VTDGen();
vg.setDoc(ba);
vg.parse(false); // Use `true' to activate namespace support
VTDNav nav = vg.getNav();
dump("BEFORE", nav);
// STEP 2 : Prepare to fix the VTDNAv
// * Prepare an autopilot to find empty elements
AutoPilot ap = new AutoPilot(nav);
ap.selectXPath("//*[count(child::node())=1][text()='']");
// * Prepare a simple regex matcher to create self closed tags
Matcher elementReducer = Pattern.compile("^<(.+)></.+>$").matcher("");
// STEP 3 : Fix the VTDNAv
// * Instanciate an XMLModifier on the VTDNav
XMLModifier xm = new XMLModifier(nav);
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // baos will hold the elements to fix
String utf8 = StandardCharsets.UTF_8.name();
// * Find all empty elements and replace them
while (ap.evalXPath() != -1) {
nav.dumpFragment(baos);
String emptyElementXml = baos.toString(utf8);
String selfClosingTagXml = elementReducer.reset(emptyElementXml).replaceFirst("<$1/>");
xm.remove();
xm.insertAfterElement(selfClosingTagXml);
baos.reset();
}
// * Rebuild VTDNav and dump it to screen
nav = xm.outputAndReparse(); // You MUST call this method to save all your changes
dump("AFTER", nav);
}
private void dump(String msg,VTDNav nav) throws NavException, IOException {
System.out.print(msg + ":\n ");
nav.dumpFragment(System.out);
System.out.print("\n\n");
}
Output
BEFORE:
<root><empty-element></empty-element><self-closed/><empty-element2 foo='bar'></empty-element2></root>
AFTER:
<root><empty-element/><self-closed/><empty-element2 foo='bar'/></root>

Spring DSL: Use message header in transformer

I have xml like below, in the runtime i want to dynamically process . First time first then etc
<tag1>
<tag2>
.
.
.
</tag2>
<tag2>
.
.
.
</tag2>
</tag1>
CASE1: Working code sending static xslt parameter:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters(1 or 2)))
public Tuple2[] xsltParameters(int number)
{
final SpelExpressionParser parser = new SpelExpressionParser();
final List<Tuple2<String,Expression>> parameters = new ArrayList<>();
parameters.add(Tuples.of("Id1", parser.parseRaw(String.format("headers['%s']",ID1))));
parameters.add(Tuples.of("Id2", parser.parseRaw(String.format("headers['%s']",ID2))));
parameters.add(Tuples.of("tagNumber", parser.parseRaw(String.format("%d",number))));
return parameters.toArray(new Tuple2[]{});
}
Above is working and transformer can trasnform xslt.
CASE2: Getting error when i want to pass dynamic number:
.transform(Message.class, m-> Transformers.xslt(this.config.getSome().getXslt(), xsltParameters())
xsltparametr(){
parameters.add(Tuples.of("tagNumber",
parser.parseRaw(String.format("headers['%s']",COUNT))));
}
ERROR while transforming:
Error on line 158 of test.xslt(Prior to line 158 is working fine)
XPTY0004: A sequence of more than one item is not allowed as the first argument of starts-with() ("xxmmyty", "xxmmyty")
My xslt at line 158
<xsl:if test="starts-with(xc:tag1/xc:tag2[$count]/xc:trade/xcml:header/xcml:Identifier/xcml:tradeId[#IdScheme='urn:abcd.something'], 'XYZ')" >
<xsl:value-of select="tag" />
</xsl:if>
In CASE1 with same xslt file i can pass above code but in case of dynamic passing(CASE2) it is failing.
First of all it looks like you use old enough Spring version.
Would be grate to upgrade at least to 5.0.x.
Secondly it isn't clear what is your xsltParameters(). You hide it from us and we fully don't know what you do over there. Although we can assume that you are fully based on this method:
#SafeVarargs
public static XsltPayloadTransformer xslt(Resource xsltTemplate,
Tuple2<String, Expression>... xslParameterMappings) {
So, you build in that xsltParameters() an array of Tuple2. OK. So, get access to the header in the param you need to build an appropriate expression.
I'm rally sure that it can be a FunctionExpression:
.transform(Transformers.xslt(this.config.getSome().getXslt(),
Tuples.of("foo", new FunctionExpression<Message<?>>(m -> m.getHeader.get("int_val"))))).
What you do with the lambda can't be combined with a transformer from that factory.
UPDATE
You need to modify your xsltParameters() to accept a String instead. Which is going to be just raw expression representation.
So, your CASE1 is going to be like this:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters("'1'")))
With meaning as literal expression.
The CASE2 is going to be like this:
.transform(Transformers.xslt(this.config.getSome().getXslt(), xsltParameters("headers.int_val")))

Use Datasource Properties in XPath Expression of SoapUI

I need to know whether it is possible to use a datasource property in XPath Expression panel of XPath Match Configuration. For instance, if we have the following XML document:
<ns1:Ions>
<ns1:Ion>UI</ns1:Ion>
<ns1:IonType>X</ns1:IonType>
<ns1:StartDate>2010-05-10</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>HH</ns1:Ion>
<ns1:IonType>RI</ns1:IonType>
<ns1:StartDate>1998-11-23</ns1:StartDate>
</ns1:Ions>
<ns1:Ions>
<ns1:Ion>CF</ns1:Ion>
<ns1:IonType>A</ns1:IonType>
<ns1:StartDate>2000-06-10</ns1:StartDate>
</ns1:Ions>
I need to evaluate to see whether a content of IonType is 'A' only if its sibling node, Ion, has a value of 'CF'. I was hoping to accomplish this by setting XPath Match Configuration as following:
XPath Expression (DataSourceInput#ION is 'CF')
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()=${DataSourceInput#ION}]]/ns1:IonType/text()
Expected Results (DataSourceInput#ION_TYPE is 'A')
${DataSourceInput#ION_TYPE}
Running the test would result in SoapUI [Pro] to error the following, Missing content for xpath declare. If I replace ${DataSourceInput#ION} with an actual value, i.e. 'CF', the test works accordingly (I even tried place single quotes around ${DataSourceInput#ION}, but it didn't work).
Is there another way of accomplish this in SoapUI?
I try what you do and it works for me if I put single quotes around the property:
declare namespace ns1='http://my.namespace.com';
//ns1:Ions[ns1:Ion[text()='${DataSourceInput#ION}']]/ns1:IonType/text()
Did you check that testStep name is exactly DataSourceInput? If there are spaces in the TestStep name (i.e your testStep name is Data Source Input you have to put ${Data Source Input#ION}).
Anyway I give you another way to do so, you can add a testStep of type groovy script after the testStep where you are getting the <Ions>response, and check the assert here like follows:
// get xml holder
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context);
def ionsHolder = groovyUtils.getXmlHolder("IonsTestStepName#response");
// generate xpath expression
def xpathExpression = "//*:Ions[*:Ion[text()='" + context.expand('${DataSourceInput#ION}') + "']]/*:IonType/text()";
log.info xpathExpression;
// get the node value
def nodeValue = ionsHolder.getNodeValue(xpathExpression);
// check expected value
assert nodeValue == context.expand('${DataSourceInput#ION_TYPE}'),'ERROR IONS VALUE';
Hope this helps,

xpath: check if current elements position is second in order

Background:
I have an XML document with the following structure:
<body>
<section>content</section>
<section>content</section>
<section>content</section>
<section>content</section>
</body>
Using xpath I want to check if a <section> element is the second element and if so apply some function.
Question:
How do I check if a <section> element is the second element in the body element?
../section[position()=2]
If you want to know if the second element in the body is named section then you can do this:
local-name(/body/child::element()[2]) eq "section"
That will return either true or false.
However, you then asked how can you check this and if it is true, then apply some function. In XPath you cannot author your own functions you can only do that in XQuery or XSLT. So let me for a moment assume you are wishing to call a different XPath function on the value of the second element if it is a section. Here is an example of applying the lower-case function:
if(local-name(/body/child::element()[2]) eq "section")then
lower-case(/body/child::element()[2])
else()
However, this can simplified as lower-case and many other functions take a value with a minimum cardinality of zero. This means that you can just apply the function to a path expression, and if the path did not match anything then the function typically returns an empty sequence, in the same way as a path that did not match will. So, this is semantically equivalent to the above:
lower-case(/body/child::element()[2][local-name(.) eq "section"])
If you are in XQuery or XSLT and are writing your own functions, I would encourage you to write functions that will accept a minimum cardinality of zero, just like lower-case does. By doing this you can chain functions together, and if there is no input data (i.e. from a path expression that does not match anything), these is no output data. This leads to a very nice functional programming style.
Question: How do I check if a element is the second element
in the body element?
Using C#, you can utilize theXPathNodeIterator class in order to traverse the nodes data, and use its CurrentPosition property to investigate the current node position:
XPathNodeIterator.CurrentPosition
Example:
const string xmlStr = #"<body>
<section>1</section>
<section>2</section>
<section>3</section>
<section>4</section>
</body>";
using (var stream = new StringReader(xmlStr))
{
var document = new XPathDocument(stream);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("/body/section");
if (nodes.MoveNext())
{
XPathNavigator nodesNavigator = nodes.Current;
XPathNodeIterator nodesText =
nodesNavigator.SelectDescendants(XPathNodeType.Text, false);
while (nodesText.MoveNext())
{
if (nodesText.CurrentPosition == 2)
{
//DO SOMETHING WITH THE VALUE AT THIS POSITION
var currentValue = nodesText.Current.Value;
}
}
}
}

Freemarker - replace & with &

If i have the & symbol in some field (from a db, cannot be changed), and i want to display this via freemarker... but have the display (from freemarker) read &, what is the way to do so?
To reiterate, I cannot change the value before hand (or at least, I don't want to), i'd like freemarker to "unmark" &.
To double re-iterate, this is a value that is being placed with a lot of other xml. The value itself is displayed on its own, surrouded by tags... so something like
<someTag>${wheeeMyValueWithAnAmpersand}<someTag>
As a result, i don't want all ampersands escaped, or the xml will look funny... just that one in the interpolation.
Oh goodness.
I see the problem: the code was written like this:
<#escape x as x?xml>
<#import "small.ftl" as my>
<#my.macro1/>
</#escape>
and at which i'd assumed that the excape would excape all the calls within it - it is certainly what the documentation sort of implies
http://freemarker.org/docs/ref_directive_escape.html
<#assign x = "<test>"> m1>
m1: ${x}
</#macro>
<#escape x as x?html>
<#macro m2>m2: ${x}</#macro>
${x}
<#m1/>
</#escape>
${x}
<#m2/>
the output will be:
<test>
m1: <test>
<test>
m2: <test>
However it appears that when you import the file, then this isn't the case, and the escape... escapes!
SOLUTION:
http://watchitlater.com/blog/2011/10/default-html-escape-using-freemarker/
the above link details how to solve the problem. In effect, it comes down to loading a different FreemakerLoader, one that wraps all templates with an escape tag.
class SomeCoolClass implements TemplateLoader {
//other functions here
#Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
Reader reader = delegate.getReader(templateSource, encoding);
try {
String templateText = IOUtils.toString(reader);
return new StringReader(ESCAPE_PREFIX + templateText + ESCAPE_SUFFIX);
} finally {
IOUtils.closeQuietly(reader);
}
}
which is a snippet from the link above. You create the class with the existing templateLoader, and just defer all the required methods to that.
Starting from FreeMarker 2.3.24 no TemplateLoader "hack" is needed anymore. There's a setting called output_format, which specifies if and what escaping is needed. This can be configured both globally, and/or per-template-name-pattern utilizing the template_configurations setting. The recommend way of doing this is even simpler (from the manual):
[...] if the
recognize_standard_file_extensions setting is true (which is the
default with the incompatible_improvements setting set to 2.3.24 or
higher), templates whose source name ends with ".ftlh" gets "HTML"
output format, and those with ".ftlx" get "XML" output format

Resources