[#4500] - Need a way to use namespaces in XPath queries

Category:
Feature request
Priority:
Normal
Status:
Closed
Project: Severity:
Major
Resolution:
Fixed
Component: Reproducibility:
Always
Assigned to:
0

The attached order.xml is an example of a source XML file I want to use. It includes namespace information.

I tried an XPath query with no namespace info:
/Openbravo/Order/summedLineAmount
This yields zero document pages, as if there was no data

I tried an XPath query with namespace info:
/ob:Openbravo/Order/summedLineAmount
This returns an error (I'll attach the full stack trace separately):
net.sf.jasperreports.engine.JRException: XPath selection failed. Expression: /ob:Openbravo/Order/summedLineAmount
at net.sf.jasperreports.engine.util.xml.JaxenXPathExecuter.selectNodeList(JaxenXPathExecuter.java:93)
...
at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:997) Caused by: org.jaxen.UnresolvableException: Cannot resolve namespace prefix 'ob'
at org.jaxen.expr.DefaultNameStep.matches(DefaultNameStep.java:358)
...

AttachmentSize
File Order.xml100.09 KB
Plain text icon stacktrace1.txt2.24 KB
mdahlman's picture
6120
Joined: Mar 13 2007 - 2:43am
Last seen: 3 years 11 months ago

12 Comments:

#1

The reason that the XPath expression /ob:Openbravo/Order/summedLineAmount does not work is because XPath requires that you register prefixes to namespace urns before you can use those prefixes in an XPath expression. Unfortunately the default JRXPathExecuter implementations do not give you the ability to register prefixes to namespace urns. We got around this by creating a custom implementation of the JRXPathExecuterFactory interface that allows a developer to register prefixes to namespace urns in a ThreadLocal variable and this JRXPathExecuterFactory then returns custom JRXPathExecuter instances that use this ThreadLocal registration information.

The only other way around this that I can think of is to use XPath expressions that ignore namespace information like this:

/*[local-name()='Openbravo']/*[local-name()='Order']/*[local-name()='summedLineAmount']

#2

I'm also experiencing problems refering to XML elements with XPath where the elements have namespaces. This is a huge problem and a showstopper for us, so it would be great to either get this fixed and/or get a workaround. My XML looks like this:
<QueryResults
xmlns:ns2="http://www.unece.org/cefact/namespaces/StandardBusinessDocumentHeader"
xmlns:ns3="urn:epcglobal:epcis-query:xsd:1" xmlns:ns4="urn:epcglobal:epcis-masterdata:xsd:1"
xmlns:ns5="urn:epcglobal:epcis:xsd:1">
<results>
<queryName>/query/SimpleEventQuery</queryName>
<resultsBody>
<EventList>
<ObjectEvent>
<eventTime>2010-07-02T13:24:00.000+02:00</eventTime>
<recordTime>2010-07-06T14:13:18.028+02:00</recordTime>
<eventTimeZoneOffset>+02:00</eventTimeZoneOffset>
<epcList>
<epc>Lot2</epc>
</epcList>
<action>OBSERVE</action>
<ns6:weight xmlns:ns6="http://www.tracefood.org/schema/epcis">1000</ns6:weight>
</ObjectEvent>
<ObjectEvent>
<eventTime>2010-07-02T13:24:00.000+02:00</eventTime>
<recordTime>2010-07-06T14:13:18.029+02:00</recordTime>
<eventTimeZoneOffset>+02:00</eventTimeZoneOffset>
<epcList>
<epc>Lot2</epc>
</epcList>
<action>OBSERVE</action>
<ns6:moisture xmlns:ns6="http://www.tracefood.org/schema/epcis">20</ns6:moisture>
</ObjectEvent>
</EventList>
</resultsBody>
</results>
</QueryResults>

and I want to access the weight and moisture elements. Ireport generates the following XPath's for me:
/QueryResults/results/resultsBody/EventList/ObjectEvent/ns6:weight
but I get this exception back when I try to preview it:
Error filling print... XPath selection failed. Expression: /QueryResults/results/resultsBody/EventList/ObjectEvent/ns6:weight
net.sf.jasperreports.engine.JRException: XPath selection failed. Expression: /QueryResults/results/resultsBody/EventList/ObjectEvent/ns6:weight at net.sf.jasperreports.engine.util.xml.JaxenXPathExecuter.selectObject(JaxenXPathExecuter.java:128) at net.sf.jasperreports.engine.data.JRXmlDataSource.getFieldValue(JRXmlDataSource.java:302) at net.sf.jasperreports.engine.fill.JRFillDataset.setOldValues(JRFillDataset.java:823) at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:787) at net.sf.jasperreports.engine.fill.JRBaseFiller.next(JRBaseFiller.java:1474) at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:125) at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:938) at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:118) at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:435) at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:271) at com.jaspersoft.ireport.designer.compiler.IReportCompiler.run(IReportCompiler.java:970) at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:572) at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:997) Caused by: org.jaxen.UnresolvableException: Cannot resolve namespace prefix 'ns6' at org.jaxen.expr.DefaultNameStep.matches(DefaultNameStep.java:358) at org.jaxen.expr.DefaultNameStep.evaluate(DefaultNameStep.java:285) at org.jaxen.expr.DefaultLocationPath.evaluate(DefaultLocationPath.java:140) at org.jaxen.expr.DefaultAbsoluteLocationPath.evaluate(DefaultAbsoluteLocationPath.java:113) at org.jaxen.expr.DefaultXPathExpr.asList(DefaultXPathExpr.java:102) at org.jaxen.BaseXPath.selectNodesForContext(BaseXPath.java:674) at org.jaxen.BaseXPath.selectNodes(BaseXPath.java:213) at org.jaxen.BaseXPath.evaluate(BaseXPath.java:172) at net.sf.jasperreports.engine.util.xml.JaxenXPathExecuter.selectObject(JaxenXPathExecuter.java:102) ... 12 more

#3

Hi Khill do you have a sample code for this work around?? I am still stuck at this problem.

Thanks.

#4

Good afternoon,

I want to use my favourite reporting tool to query a XML remote datasource, which is a geographic web service :
http://ws.carmencarto.fr/WFS/45/point_faune_2?SERVICE=WFS&VERSION=1.0.0&...

I would like to get the value of the <gml:coordinates> element
For this I use the syntax shown here by khill, because of the namespaces :

/*[local-name()='FeatureCollection']/*[local-name()='boundedBy']/*[local-name()='Box']

The report query assistant tells me that ther is one selected node but when I run the report, I don't get any page.
If I do the same with the same xml datasource without any namespace, the report is well populated.

Does anyone hhave the same problem?
Thank you for your help.

Regards,

Mathieu Bossaert

#5

This is the workaround and it works !,

for the moment I hardcode namespace mappings,

but this could be set by using a threadlocale set by the client java program

Cheers

Tony Nys

=================

import javax.xml.transform.TransformerException;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.util.xml.JRXPathExecuter;
import net.sf.jasperreports.engine.util.xml.JRXPathExecuterFactory;

import org.apache.xml.utils.PrefixResolver;
import org.apache.xpath.CachedXPathAPI;
import org.apache.xpath.objects.XObject;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* custom xpath exector factory to bypass the bug in jasper for namespace support in xml
* datasources; namespaces are not supported at all.
*
*
* run this to use a custom properties file:
* System.setProperty("net.sf.jasperreports.properties", "/tmp/tony.jasperreports.properties");

* set this line in the jasper report properties file:
* net.sf.jasperreports.xpath.executer.factory=MyXalanXPathExecuterFactory

*
* @author tony nys
*
*/
public class MyXalanXPathExecuterFactory implements JRXPathExecuterFactory{

public MyXalanXPathExecuterFactory(){
System.out.println("MyXalanXPathExecuterFactory.............");
}

@Override
public JRXPathExecuter getXPathExecuter() {
return new MyJRXPathExecuter();
}

/**
* source copied from XalanXPathExecuter from jasperreports
* and then tweeked for namespace support
*
*/
public class MyJRXPathExecuter implements JRXPathExecuter{
// XPath API facade
private CachedXPathAPI xpathAPI = new CachedXPathAPI();
PrefixResolver resolver=null;

public MyJRXPathExecuter(){
System.out.println("MyJRXPathExecuter.............");
//maybe link resolver through threadlocale from calling client through a map,
//so namespaces can be set dynamically by client program
resolver=new MyPrefixResolver();

}

public NodeList selectNodeList(Node contextNode, String expression) throws JRException
{
//System.out.println(",,,selectNodeList");
xpathAPI.getXPathContext().setNamespaceContext(resolver);

try {
return xpathAPI.selectNodeList(contextNode, expression);
} catch (TransformerException e) {
throw new JRException("XPath selection failed. Expression: "
+ expression, e);
}
}

public Object selectObject(Node contextNode, String expression) throws JRException {
try {
//System.out.println(",,,selectObject");
//xpathAPI.getXPathContext().setNamespaceContext(resolver);
Object value;
XObject object = xpathAPI.eval(contextNode, expression,resolver);
switch (object.getType()) {
case XObject.CLASS_NODESET:
value = object.nodeset().nextNode();
break;
case XObject.CLASS_BOOLEAN:
value = object.bool() ? Boolean.TRUE : Boolean.FALSE;
break;
case XObject.CLASS_NUMBER:
value = new Double(object.num());
break;
default:
value = object.str();
break;
}
return value;
} catch (TransformerException e) {
throw new JRException("XPath selection failed. Expression: "
+ expression, e);
}
}

}

//public class MyPrefixResolver extends JAXPPrefixResolver{
public class MyPrefixResolver implements PrefixResolver{
public MyPrefixResolver(){
//super();

}

@Override
public String getBaseIdentifier() {
//System.out.println("///////////////getBaseIdentifier");
return null;
}

@Override
public String getNamespaceForPrefix(String arg0) {
//System.out.println("///////////////getNamespaceForPrefix");

//TODO: use threadlocale here...
if(arg0.equalsIgnoreCase("v210")){
return "http://wpm.ac.com/solutions/fl/l/core/types/v210";
}else if(arg0.equalsIgnoreCase("wpm-core-common")){
return "http://wpm.ac.com/core/common/types/v210";
}else if(arg0.equalsIgnoreCase("wpm-business-common")){
return "http://wpm.ac.com/business/common/types/v210";
}else if(arg0.equalsIgnoreCase("v2101")){
return "http://wpm.ac.com/solutions/fl/l/configuration/types/v210";

}
return null;

}

@Override
public String getNamespaceForPrefix(String arg0, Node arg1) {
//System.out.println("///////////////getNamespaceForPrefix2");
//TODO: use threadlocale here...
if(arg0.equalsIgnoreCase("v210")){
return "http://wpm.ac.com/solutions/fl/l/core/types/v210";
}else if(arg0.equalsIgnoreCase("wpm-core-common")){
return "http://wpm.ac.com/core/common/types/v210";
}else if(arg0.equalsIgnoreCase("wpm-business-common")){
return "http://wpm.ac.com/business/common/types/v210";
}else if(arg0.equalsIgnoreCase("v2101")){
return "http://wpm.ac.com/solutions/fl/l/configuration/types/v210";

}
return null;
}

@Override
public boolean handlesNullPrefixes() {
return false;
}

}
}

#6

note, you need to instantiate the document using jaxp dom
if not it won't work, data will be null

here's the main client

===
public static void jasper(String[] args) throws Exception{

//this properties config contains custom xpath factory: MyXalanXPathExecuterFactory
System.setProperty("net.sf.jasperreports.properties", "/tmp/tony.jasperreports.properties");

System.out.println("factory:"+JRXPathExecuterUtils.PROPERTY_XPATH_EXECUTER_FACTORY);
System.out.println("executor:"+JRXPathExecuterUtils.getXPathExecuter().getClass().getName());
DocumentBuilderFactory xmlFact =DocumentBuilderFactory.newInstance();
xmlFact.setNamespaceAware(true);
DocumentBuilder builder = xmlFact.newDocumentBuilder();
Document doc = builder.parse(new FileInputStream(new File("/tmp/c.xml")));

//compile and execute
System.out.println("Compiling report");
JasperReport report=JasperCompileManager.compileReport(new FileInputStream("/tmp/report_xmltest.jrxml"));

System.out.println("generating report");
JasperPrint print=JasperFillManager.
fillReport(report,new HashMap(),
//new JRXmlDataSource(new File("/tmp/contract.xml")));
new JRXmlDataSource(doc));

System.out.println("Saving as pdf");
JasperExportManager.exportReportToPdfFile(print, "/tmp/report_xmltest.pdf");

System.out.println("Done");

#7

(1).The current implementation with the default configuration (in default.jasperreports.properties):

net.sf.jasperreports.query.executer.factory.xPath=net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory
net.sf.jasperreports.query.executer.factory.XPath=net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory
net.sf.jasperreports.xpath.executer.factory=net.sf.jasperreports.engine.util.xml.XalanXPathExecuterFactory

supports namespaces under the following conditions:

a. The namespaces must be declared in the root element. For namespaces used both in root and in child
nodes we have added new implementations. See below (2).

b. You must enable namespace support before parsing the XML in order to supply it via the
XML_DATA_DOCUMENT parameter. If you use JRXmlUtils.parse() to produce your document, note that it
internally uses a DocumentBuilderFactory that is not namespace aware. Instead you could parse your
document with a DocumentBuilderFactory that has that option turned on. Just call
setNamespaceAware(true) on the builder factory.

When using:

net.sf.jasperreports.xpath.executer.factory=net.sf.jasperreports.engine.util.xml.JaxenXPathExecuterFactory

there is no namespace support.

(2). New implementations for Jaxen and Xalan have been provided to solve the namespace problems. You can check out
the repository.

The namespace-aware Jaxen configuration is:

net.sf.jasperreports.query.executer.factory.xPath=net.sf.jasperreports.engine.query.JaxenXPathQueryExecuterFactory
net.sf.jasperreports.query.executer.factory.XPath=net.sf.jasperreports.engine.query.JaxenXPathQueryExecuterFactory

The namespace-aware Xalan configuration is:

net.sf.jasperreports.query.executer.factory.xPath=net.sf.jasperreports.engine.query.XalanXPathQueryExecuterFactory
net.sf.jasperreports.query.executer.factory.XPath=net.sf.jasperreports.engine.query.XalanXPathQueryExecuterFactory

For both you can now specify the following:

a. The XML_NAMESPACE_MAP parameter: a map with prefix/uri entries
b. Properties: net.sf.jasperreports.xml.namespace.{prefix}=uri for each namespace in the XML document
c. Boolean parameter or property: net.sf.jasperreports.xml.detect.namespaces

The namespace discovery algorithm is like this:

- if there is a non-null XML_NAMESPACE_MAP it will stop here;
- if the map is null and there are net.sf.jasperreports.xml.namespace.{prefix}=uri properties, an internal
map will be built based on those
- if none of the above and net.sf.jasperreports.xml.detect.namespaces=true AND the Xpath expression
contains prefixes, the XML document will be parsed and the namespace declarations extracted.

Important things to note:
- The default configuration for Xpath is still the one from point (1).
- For both new Xalan and Jaxen implementations you still have to provide the document produced with
namespace support.
- The new Xalan implementation behaves like the old one if you don't provide the namespace map or the
namespace properties or the auto detect parameter/property, meaning that it will be aware of the
namespace declarations present in the root element.
- If you have namespace declarations both in the root and child elements, you have to provide them all,
regardless of which implementation you choose.

#8

tested 4.01 jasper with namespaces in root element declared and also in subelement
and works fine

this is my xml:
<?xml version="1.0" encoding="UTF-8"?>
<n:Northwind xmlns:n="http://www.integrationarchitects.be/ns/Northwind/root" xmlns:c="http://www.integrationarchitects.be/ns/Northwind/customer">

<c:Customers>
<c:CustomerID>ALFKI</c:CustomerID>
<c:CompanyName>Alfredooooo Futterkiste</c:CompanyName>
<c:ContactName>Maria Anders</c:ContactName>
<c:ContactTitle>Sales Representative</c:ContactTitle>
<c:Address>Obere Str. 57</c:Address>
<c:City>Berlin</c:City>
<c:PostalCode>12209</c:PostalCode>
<c:Country>Germany</c:Country>
<c:Phone>030-0074321</c:Phone>
<c:Fax>030-0076545</c:Fax>

<o:Orders xmlns:o="http://www.integrationarchitects.be/ns/Northwind/order">
<o:OrderID>10954</o:OrderID>
<o:CustomerID>LINOD</o:CustomerID>
<o:EmployeeID>5</o:EmployeeID>
<o:OrderDate>1998-03-17</o:OrderDate>
<o:RequiredDate>1998-04-28</o:RequiredDate>
<o:ShippedDate>1998-03-20</o:ShippedDate>
<o:ShipVia>1</o:ShipVia>
<o:Freight>27.91</o:Freight>
<o:ShipName>LINO-Delicateses</o:ShipName>
<o:ShipAddress>Ave. 5 de Mayo Porlamar</o:ShipAddress>
<o:ShipCity>I. de Margarita</o:ShipCity>
<o:ShipRegion>Nueva Esparta</o:ShipRegion>
<o:ShipPostalCode>4980</o:ShipPostalCode>
<o:ShipCountry>Venezuela</o:ShipCountry>
</o:Orders>

in jasperreports.properties set:
net.sf.jasperreports.xpath.executer.factory=net.sf.jasperreports.engine.util.xml.XalanXPathExecuterFactory
net.sf.jasperreports.query.executer.factory.xPath=net.sf.jasperreports.engine.query.XalanXPathQueryExecuterFactory
net.sf.jasperreports.query.executer.factory.XPath=net.sf.jasperreports.engine.query.XalanXPathQueryExecuterFactory
net.sf.jasperreports.xml.detect.namespaces=true

in code:
long start = System.currentTimeMillis();
Map params = new HashMap();
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();

//need xml namespace enabled !!
dbf.setNamespaceAware(true);
DocumentBuilder db=dbf.newDocumentBuilder();

Document document = db.parse(new FileInputStream(XMLFILE));

params.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT, document);
params.put(JRXPathQueryExecuterFactory.XML_DATE_PATTERN, "yyyy-MM-dd");
params.put(JRXPathQueryExecuterFactory.XML_NUMBER_PATTERN, "#,##0.##");
params.put(JRXPathQueryExecuterFactory.XML_LOCALE, Locale.ENGLISH);
params.put(JRParameter.REPORT_LOCALE, Locale.US);

System.out.println("Compiling");
JasperReport compiledReport=JasperCompileManager.compileReport(REPORTDEF);

System.out.println("Generating");
JasperPrint jPrint=JasperFillManager.fillReport(compiledReport, params);
JRPdfExporter exporter=new JRPdfExporter();

exporter.setParameter(JRExporterParameter.JASPER_PRINT, jPrint);

exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, new FileOutputStream(XMLFILE+".pdf"));

System.out.println("EXporting");
exporter.exportReport();
System.out.println("Done");

#9

Trying to run it inside IReport 4.01. Set the 4 properties ok, but cannot see how to set the documentbuilderfactory to namespaceaware

#10

Finally got it working in IReport 4.01
Problem there is that
a) you cannot use xalen, u have to use jaxen (apparently due to Netbeans issues). Allways classnotfoundexception
b)XPath doesn't work since IReport constructs a DOcument object without namespace information.
This is because the DOcumentBuilderFactory is not set .setNamespaceAware(true).

TO fix this, I changed the JRXmlUtils class (inside the jasper reports jar file in modules/ext folder) added line at :180
dbf.setNamespaceAware(true);
and patched the jar file, and restarted IReport
Then make sure you have these jasper properties set in IREPORT:

net.sf.jasperreports.query.executer.factory.xPath=net.sf.jasperreports.engine.query.JaxenXPathQueryExecuterFactory
net.sf.jasperreports.query.executer.factory.XPath=net.sf.jasperreports.engine.query.JaxenXPathQueryExecuterFactory
net.sf.jasperreports.xml.detect.namespaces=true

Then restart IReport, and it should work...

=> for me this is a bug in JRXmlutils.
This class must check the proeprty "net.sf.jasperreports.xml.detect.namespaces" . If it is true,
it shouls set the namespaceaware option on the Documentbuilderfactory

Thiw will makes sure the

#11

Hello,

I can confirm this issue also occurs in jasperreports 4.0.2.
I patched the JRXmlUtils class in the same manner to fix the issue.

However, I found an issue when perfoming a XPath query on an element having an xmlns="<namespace_here>" attribute, such as <report xmlns="mynamespace">

Having no prefix, I cannot use one in the query. But using /report does not return a value if the attribute is present.

Am I doing it wrong or is it a known bug ?

#12
  • Resolution:Open» Fixed
  • Status:Resolved» Closed

Hi,

For new issues, please create new trackers. I'm closing this one now.

Thanks,
Teodor

Feedback