How to use Java XPath with KML files and namespaces on Android
I'm struggling with how to use XPath on KML files that contain the new gx:Track and gx:coord tags. The problem is with how to use XPath with namespaces under Android.
I've looked at a number of examples, including these
https://www.ibm.com/developerworks/library/x-nmspccontext/index.html
https://howtodoinjava.com/xml/xpath-namespace-resolution-example/
XPath with namespace in Java
NamespaceContext and using namespaces with XPath
but I can't seem to get even those examples to work.
The following code and output illustrates my problem:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString( Document document ) throws TransformerException {
DOMSource domSource = new DOMSource( document );
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult( writer );
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform( domSource, result );
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
Element mKmlElement = mDocument.createElement( "kml" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2" );
mDocument.appendChild( mKmlElement );
Element mPlacemarkElement = mDocument.createElement( "Placemark" );
mKmlElement.appendChild( mPlacemarkElement );
Element gxTrackElement = mDocument.createElement( "gx:Track" );
mPlacemarkElement.appendChild( gxTrackElement );
Element gxCoordElement = mDocument.createElement( "gx:coord" );
gxCoordElement.setTextContent( "-122.207881 37.371915 156.000000" );
gxTrackElement.appendChild( gxCoordElement );
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse( new InputSource( new StringReader( kmlString ) ) );
return mDocument;
}
private void test( Document mDocument ) throws Exception {
String xml = toXmlString( mDocument );
Log.d( "TEST-XML", xml );
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext( new NamespaceContext() {
@Override
public String getNamespaceURI( String prefix ) {
switch( prefix ) {
case XMLConstants.DEFAULT_NS_PREFIX:
return "http://www.opengis.net/kml/2.2";
case "gx":
return "http://www.google.com/kml/ext/2.2";
}
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix( String namespaceURI ) {
return null;
}
@Override
public Iterator getPrefixes( String namespaceURI ) {
return null;
}
} );
NodeList result1 = (NodeList) xPath.evaluate( "/kml", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT1", String.valueOf( result1.getLength() ) );
NodeList result2 = (NodeList) xPath.evaluate( "/kml/Placemark", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT2", String.valueOf( result2.getLength() ) );
NodeList result3 = (NodeList) xPath.evaluate( "/kml/Placemark/gx:Track", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT3", String.valueOf( result3.getLength() ) );
}
The test()
method executes 3 XPath statements/patterns and is called once for each of two test documents. The 2 documents are constructed using different methods but the contents should be identical. However, the results I get from the 3 XPath statements are different.
These are the results with document 1:
2018-11-17 17:51:28.289 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.324 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 1
2018-11-17 17:51:28.334 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 1
2018-11-17 17:51:28.343 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
and these are the results with document 2:
2018-11-17 17:51:28.348 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.358 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 0
2018-11-17 17:51:28.363 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 0
2018-11-17 17:51:28.372 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
There are at least 2 problems:
since the 2 documents are identical (I think), why are the results of the tests different? (i.e., the first 2 XPath statements succeed with document 1 but neither succeeds with document 2.)
and why does the 3rd XPath statement fail to find the gx:Track element in both document 1 and document 2?
UPDATE: This problem seems to have something to do with having
xmlns="http://www.opengis.net/kml/2.2"
included in document 2. If I remove it, the results of the first 2 XPath tests are the correct (for both documents) - and in fact XPath test 3 now works on document 2. Unfortunately, I still don't have a handle on this behavior.
I'm probably missing something obvious and would appreciate any help.
java android xpath xml-namespaces
add a comment |
I'm struggling with how to use XPath on KML files that contain the new gx:Track and gx:coord tags. The problem is with how to use XPath with namespaces under Android.
I've looked at a number of examples, including these
https://www.ibm.com/developerworks/library/x-nmspccontext/index.html
https://howtodoinjava.com/xml/xpath-namespace-resolution-example/
XPath with namespace in Java
NamespaceContext and using namespaces with XPath
but I can't seem to get even those examples to work.
The following code and output illustrates my problem:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString( Document document ) throws TransformerException {
DOMSource domSource = new DOMSource( document );
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult( writer );
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform( domSource, result );
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
Element mKmlElement = mDocument.createElement( "kml" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2" );
mDocument.appendChild( mKmlElement );
Element mPlacemarkElement = mDocument.createElement( "Placemark" );
mKmlElement.appendChild( mPlacemarkElement );
Element gxTrackElement = mDocument.createElement( "gx:Track" );
mPlacemarkElement.appendChild( gxTrackElement );
Element gxCoordElement = mDocument.createElement( "gx:coord" );
gxCoordElement.setTextContent( "-122.207881 37.371915 156.000000" );
gxTrackElement.appendChild( gxCoordElement );
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse( new InputSource( new StringReader( kmlString ) ) );
return mDocument;
}
private void test( Document mDocument ) throws Exception {
String xml = toXmlString( mDocument );
Log.d( "TEST-XML", xml );
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext( new NamespaceContext() {
@Override
public String getNamespaceURI( String prefix ) {
switch( prefix ) {
case XMLConstants.DEFAULT_NS_PREFIX:
return "http://www.opengis.net/kml/2.2";
case "gx":
return "http://www.google.com/kml/ext/2.2";
}
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix( String namespaceURI ) {
return null;
}
@Override
public Iterator getPrefixes( String namespaceURI ) {
return null;
}
} );
NodeList result1 = (NodeList) xPath.evaluate( "/kml", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT1", String.valueOf( result1.getLength() ) );
NodeList result2 = (NodeList) xPath.evaluate( "/kml/Placemark", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT2", String.valueOf( result2.getLength() ) );
NodeList result3 = (NodeList) xPath.evaluate( "/kml/Placemark/gx:Track", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT3", String.valueOf( result3.getLength() ) );
}
The test()
method executes 3 XPath statements/patterns and is called once for each of two test documents. The 2 documents are constructed using different methods but the contents should be identical. However, the results I get from the 3 XPath statements are different.
These are the results with document 1:
2018-11-17 17:51:28.289 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.324 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 1
2018-11-17 17:51:28.334 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 1
2018-11-17 17:51:28.343 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
and these are the results with document 2:
2018-11-17 17:51:28.348 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.358 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 0
2018-11-17 17:51:28.363 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 0
2018-11-17 17:51:28.372 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
There are at least 2 problems:
since the 2 documents are identical (I think), why are the results of the tests different? (i.e., the first 2 XPath statements succeed with document 1 but neither succeeds with document 2.)
and why does the 3rd XPath statement fail to find the gx:Track element in both document 1 and document 2?
UPDATE: This problem seems to have something to do with having
xmlns="http://www.opengis.net/kml/2.2"
included in document 2. If I remove it, the results of the first 2 XPath tests are the correct (for both documents) - and in fact XPath test 3 now works on document 2. Unfortunately, I still don't have a handle on this behavior.
I'm probably missing something obvious and would appreciate any help.
java android xpath xml-namespaces
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29
add a comment |
I'm struggling with how to use XPath on KML files that contain the new gx:Track and gx:coord tags. The problem is with how to use XPath with namespaces under Android.
I've looked at a number of examples, including these
https://www.ibm.com/developerworks/library/x-nmspccontext/index.html
https://howtodoinjava.com/xml/xpath-namespace-resolution-example/
XPath with namespace in Java
NamespaceContext and using namespaces with XPath
but I can't seem to get even those examples to work.
The following code and output illustrates my problem:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString( Document document ) throws TransformerException {
DOMSource domSource = new DOMSource( document );
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult( writer );
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform( domSource, result );
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
Element mKmlElement = mDocument.createElement( "kml" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2" );
mDocument.appendChild( mKmlElement );
Element mPlacemarkElement = mDocument.createElement( "Placemark" );
mKmlElement.appendChild( mPlacemarkElement );
Element gxTrackElement = mDocument.createElement( "gx:Track" );
mPlacemarkElement.appendChild( gxTrackElement );
Element gxCoordElement = mDocument.createElement( "gx:coord" );
gxCoordElement.setTextContent( "-122.207881 37.371915 156.000000" );
gxTrackElement.appendChild( gxCoordElement );
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse( new InputSource( new StringReader( kmlString ) ) );
return mDocument;
}
private void test( Document mDocument ) throws Exception {
String xml = toXmlString( mDocument );
Log.d( "TEST-XML", xml );
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext( new NamespaceContext() {
@Override
public String getNamespaceURI( String prefix ) {
switch( prefix ) {
case XMLConstants.DEFAULT_NS_PREFIX:
return "http://www.opengis.net/kml/2.2";
case "gx":
return "http://www.google.com/kml/ext/2.2";
}
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix( String namespaceURI ) {
return null;
}
@Override
public Iterator getPrefixes( String namespaceURI ) {
return null;
}
} );
NodeList result1 = (NodeList) xPath.evaluate( "/kml", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT1", String.valueOf( result1.getLength() ) );
NodeList result2 = (NodeList) xPath.evaluate( "/kml/Placemark", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT2", String.valueOf( result2.getLength() ) );
NodeList result3 = (NodeList) xPath.evaluate( "/kml/Placemark/gx:Track", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT3", String.valueOf( result3.getLength() ) );
}
The test()
method executes 3 XPath statements/patterns and is called once for each of two test documents. The 2 documents are constructed using different methods but the contents should be identical. However, the results I get from the 3 XPath statements are different.
These are the results with document 1:
2018-11-17 17:51:28.289 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.324 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 1
2018-11-17 17:51:28.334 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 1
2018-11-17 17:51:28.343 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
and these are the results with document 2:
2018-11-17 17:51:28.348 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.358 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 0
2018-11-17 17:51:28.363 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 0
2018-11-17 17:51:28.372 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
There are at least 2 problems:
since the 2 documents are identical (I think), why are the results of the tests different? (i.e., the first 2 XPath statements succeed with document 1 but neither succeeds with document 2.)
and why does the 3rd XPath statement fail to find the gx:Track element in both document 1 and document 2?
UPDATE: This problem seems to have something to do with having
xmlns="http://www.opengis.net/kml/2.2"
included in document 2. If I remove it, the results of the first 2 XPath tests are the correct (for both documents) - and in fact XPath test 3 now works on document 2. Unfortunately, I still don't have a handle on this behavior.
I'm probably missing something obvious and would appreciate any help.
java android xpath xml-namespaces
I'm struggling with how to use XPath on KML files that contain the new gx:Track and gx:coord tags. The problem is with how to use XPath with namespaces under Android.
I've looked at a number of examples, including these
https://www.ibm.com/developerworks/library/x-nmspccontext/index.html
https://howtodoinjava.com/xml/xpath-namespace-resolution-example/
XPath with namespace in Java
NamespaceContext and using namespaces with XPath
but I can't seem to get even those examples to work.
The following code and output illustrates my problem:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString( Document document ) throws TransformerException {
DOMSource domSource = new DOMSource( document );
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult( writer );
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform( domSource, result );
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
Element mKmlElement = mDocument.createElement( "kml" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2" );
mKmlElement.setAttributeNS( XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2" );
mDocument.appendChild( mKmlElement );
Element mPlacemarkElement = mDocument.createElement( "Placemark" );
mKmlElement.appendChild( mPlacemarkElement );
Element gxTrackElement = mDocument.createElement( "gx:Track" );
mPlacemarkElement.appendChild( gxTrackElement );
Element gxCoordElement = mDocument.createElement( "gx:coord" );
gxCoordElement.setTextContent( "-122.207881 37.371915 156.000000" );
gxTrackElement.appendChild( gxCoordElement );
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware( true );
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse( new InputSource( new StringReader( kmlString ) ) );
return mDocument;
}
private void test( Document mDocument ) throws Exception {
String xml = toXmlString( mDocument );
Log.d( "TEST-XML", xml );
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext( new NamespaceContext() {
@Override
public String getNamespaceURI( String prefix ) {
switch( prefix ) {
case XMLConstants.DEFAULT_NS_PREFIX:
return "http://www.opengis.net/kml/2.2";
case "gx":
return "http://www.google.com/kml/ext/2.2";
}
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix( String namespaceURI ) {
return null;
}
@Override
public Iterator getPrefixes( String namespaceURI ) {
return null;
}
} );
NodeList result1 = (NodeList) xPath.evaluate( "/kml", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT1", String.valueOf( result1.getLength() ) );
NodeList result2 = (NodeList) xPath.evaluate( "/kml/Placemark", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT2", String.valueOf( result2.getLength() ) );
NodeList result3 = (NodeList) xPath.evaluate( "/kml/Placemark/gx:Track", mDocument, XPathConstants.NODESET );
Log.d( "TEST-RESULT3", String.valueOf( result3.getLength() ) );
}
The test()
method executes 3 XPath statements/patterns and is called once for each of two test documents. The 2 documents are constructed using different methods but the contents should be identical. However, the results I get from the 3 XPath statements are different.
These are the results with document 1:
2018-11-17 17:51:28.289 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.324 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 1
2018-11-17 17:51:28.334 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 1
2018-11-17 17:51:28.343 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
and these are the results with document 2:
2018-11-17 17:51:28.348 22837-22837/ca.csdesigninc.offroadtracker D/TEST-XML: <?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>
2018-11-17 17:51:28.358 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT1: 0
2018-11-17 17:51:28.363 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT2: 0
2018-11-17 17:51:28.372 22837-22837/ca.csdesigninc.offroadtracker D/TEST-RESULT3: 0
There are at least 2 problems:
since the 2 documents are identical (I think), why are the results of the tests different? (i.e., the first 2 XPath statements succeed with document 1 but neither succeeds with document 2.)
and why does the 3rd XPath statement fail to find the gx:Track element in both document 1 and document 2?
UPDATE: This problem seems to have something to do with having
xmlns="http://www.opengis.net/kml/2.2"
included in document 2. If I remove it, the results of the first 2 XPath tests are the correct (for both documents) - and in fact XPath test 3 now works on document 2. Unfortunately, I still don't have a handle on this behavior.
I'm probably missing something obvious and would appreciate any help.
java android xpath xml-namespaces
java android xpath xml-namespaces
edited Nov 18 '18 at 23:28
Terry Wilkinson
asked Nov 17 '18 at 23:45
Terry WilkinsonTerry Wilkinson
10017
10017
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29
add a comment |
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29
add a comment |
1 Answer
1
active
oldest
votes
The differences are due to namespaces. Both in how the XML is being produced, and when you are selecting content in the XPath.
Unfortunately, it is difficult to see the difference because the XML that happens to be serialized by the toXmlString()
for testDoc1()
doesn't exactly match the state of the in-memory document.
When you construct the kml
element, using createElement()
it creates an element that is bound to the "no namespace". Then, you added namespace attributes, which happen to come out when serializing with toXmlString()
and make the kml
element appear to be in the http://www.opengis.net/kml/2.2
namespace.
If you were to marshal that XML back to a new Document
object, the kml
element would be bound to that namespace. However, the current in-memory object for that element is not.
You can observe this by adding some additional diagnostics println
messages:
NodeList result1 = (NodeList) xPath.evaluate("/kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
You can round-trip your XML and observe that it behaves different when you marshall the serialized XML:
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
mDocument = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
However, that's cheating. What you really want to do is ensure that the elements are created properly in the first place. When you create an element that you want to be bound to a namespace, use the createElementNS()
method, as indicated in the JavaDoc comments for createElement()
:
To create an element with a qualified name and namespace URI, use the
createElementNS
method.
So, to create an element that is bound to the http://www.opengis.net/kml/2.2
namespace, you would want to use:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
and:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
and the same goes for the gx:Track
element:
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
Once you get your Document objects truly equal and correct, you then need to adjust your XPath.
With XPath, if you don't apply a namespace prefix, it will select elements bound to the "no namespace". So, /kml
will only select kml
elements that are not bound to a namespace. But since your kml
elements are bound to the http://www.opengis.net/kml/2.2
namespace, it won't select them.
In your override of the getNamespaceURI()
function, you could reserve gx
for the Google KML Extension namespace, and then default any other namespace-prefix to resolve to http://www.opengis.net/kml/2.2
:
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
Then, adjust your XPath statements to use a prefix for those KML elements. If you use the above code, it doesn't matter what prefix you use. Anything other than gx
will return the http://www.opengis.net/kml/2.2
namespace.
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
Putting it all together:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
//Element mKmlElement = mDocument.createElement("kml");
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
//mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2");
mDocument.appendChild(mKmlElement);
//Element mPlacemarkElement = mDocument.createElement("Placemark");
Element mPlacemarkElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
//mPlacemarkElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.appendChild(mPlacemarkElement);
//Element gxTrackElement = mDocument.createElement("gx:Track");
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
mPlacemarkElement.appendChild(gxTrackElement);
//Element gxCoordElement = mDocument.createElement("gx:coord");
Element gxCoordElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2", "gx:coord");
gxCoordElement.setTextContent("-122.207881 37.371915 156.000000");
gxTrackElement.appendChild(gxCoordElement);
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse(new
InputSource(new StringReader(kmlString)));
return mDocument;
}
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
@Override
public String getPrefix(String namespaceURI) {
if ("http://www.google.com/kml/ext/2.2".equals(namespaceURI)) {
return "gx";
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List<String> ns = new ArrayList<>();
ns.add("gx");
return ns.iterator();
}
});
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
}
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53356645%2fhow-to-use-java-xpath-with-kml-files-and-namespaces-on-android%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
The differences are due to namespaces. Both in how the XML is being produced, and when you are selecting content in the XPath.
Unfortunately, it is difficult to see the difference because the XML that happens to be serialized by the toXmlString()
for testDoc1()
doesn't exactly match the state of the in-memory document.
When you construct the kml
element, using createElement()
it creates an element that is bound to the "no namespace". Then, you added namespace attributes, which happen to come out when serializing with toXmlString()
and make the kml
element appear to be in the http://www.opengis.net/kml/2.2
namespace.
If you were to marshal that XML back to a new Document
object, the kml
element would be bound to that namespace. However, the current in-memory object for that element is not.
You can observe this by adding some additional diagnostics println
messages:
NodeList result1 = (NodeList) xPath.evaluate("/kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
You can round-trip your XML and observe that it behaves different when you marshall the serialized XML:
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
mDocument = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
However, that's cheating. What you really want to do is ensure that the elements are created properly in the first place. When you create an element that you want to be bound to a namespace, use the createElementNS()
method, as indicated in the JavaDoc comments for createElement()
:
To create an element with a qualified name and namespace URI, use the
createElementNS
method.
So, to create an element that is bound to the http://www.opengis.net/kml/2.2
namespace, you would want to use:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
and:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
and the same goes for the gx:Track
element:
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
Once you get your Document objects truly equal and correct, you then need to adjust your XPath.
With XPath, if you don't apply a namespace prefix, it will select elements bound to the "no namespace". So, /kml
will only select kml
elements that are not bound to a namespace. But since your kml
elements are bound to the http://www.opengis.net/kml/2.2
namespace, it won't select them.
In your override of the getNamespaceURI()
function, you could reserve gx
for the Google KML Extension namespace, and then default any other namespace-prefix to resolve to http://www.opengis.net/kml/2.2
:
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
Then, adjust your XPath statements to use a prefix for those KML elements. If you use the above code, it doesn't matter what prefix you use. Anything other than gx
will return the http://www.opengis.net/kml/2.2
namespace.
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
Putting it all together:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
//Element mKmlElement = mDocument.createElement("kml");
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
//mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2");
mDocument.appendChild(mKmlElement);
//Element mPlacemarkElement = mDocument.createElement("Placemark");
Element mPlacemarkElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
//mPlacemarkElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.appendChild(mPlacemarkElement);
//Element gxTrackElement = mDocument.createElement("gx:Track");
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
mPlacemarkElement.appendChild(gxTrackElement);
//Element gxCoordElement = mDocument.createElement("gx:coord");
Element gxCoordElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2", "gx:coord");
gxCoordElement.setTextContent("-122.207881 37.371915 156.000000");
gxTrackElement.appendChild(gxCoordElement);
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse(new
InputSource(new StringReader(kmlString)));
return mDocument;
}
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
@Override
public String getPrefix(String namespaceURI) {
if ("http://www.google.com/kml/ext/2.2".equals(namespaceURI)) {
return "gx";
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List<String> ns = new ArrayList<>();
ns.add("gx");
return ns.iterator();
}
});
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
}
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
add a comment |
The differences are due to namespaces. Both in how the XML is being produced, and when you are selecting content in the XPath.
Unfortunately, it is difficult to see the difference because the XML that happens to be serialized by the toXmlString()
for testDoc1()
doesn't exactly match the state of the in-memory document.
When you construct the kml
element, using createElement()
it creates an element that is bound to the "no namespace". Then, you added namespace attributes, which happen to come out when serializing with toXmlString()
and make the kml
element appear to be in the http://www.opengis.net/kml/2.2
namespace.
If you were to marshal that XML back to a new Document
object, the kml
element would be bound to that namespace. However, the current in-memory object for that element is not.
You can observe this by adding some additional diagnostics println
messages:
NodeList result1 = (NodeList) xPath.evaluate("/kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
You can round-trip your XML and observe that it behaves different when you marshall the serialized XML:
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
mDocument = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
However, that's cheating. What you really want to do is ensure that the elements are created properly in the first place. When you create an element that you want to be bound to a namespace, use the createElementNS()
method, as indicated in the JavaDoc comments for createElement()
:
To create an element with a qualified name and namespace URI, use the
createElementNS
method.
So, to create an element that is bound to the http://www.opengis.net/kml/2.2
namespace, you would want to use:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
and:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
and the same goes for the gx:Track
element:
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
Once you get your Document objects truly equal and correct, you then need to adjust your XPath.
With XPath, if you don't apply a namespace prefix, it will select elements bound to the "no namespace". So, /kml
will only select kml
elements that are not bound to a namespace. But since your kml
elements are bound to the http://www.opengis.net/kml/2.2
namespace, it won't select them.
In your override of the getNamespaceURI()
function, you could reserve gx
for the Google KML Extension namespace, and then default any other namespace-prefix to resolve to http://www.opengis.net/kml/2.2
:
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
Then, adjust your XPath statements to use a prefix for those KML elements. If you use the above code, it doesn't matter what prefix you use. Anything other than gx
will return the http://www.opengis.net/kml/2.2
namespace.
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
Putting it all together:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
//Element mKmlElement = mDocument.createElement("kml");
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
//mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2");
mDocument.appendChild(mKmlElement);
//Element mPlacemarkElement = mDocument.createElement("Placemark");
Element mPlacemarkElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
//mPlacemarkElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.appendChild(mPlacemarkElement);
//Element gxTrackElement = mDocument.createElement("gx:Track");
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
mPlacemarkElement.appendChild(gxTrackElement);
//Element gxCoordElement = mDocument.createElement("gx:coord");
Element gxCoordElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2", "gx:coord");
gxCoordElement.setTextContent("-122.207881 37.371915 156.000000");
gxTrackElement.appendChild(gxCoordElement);
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse(new
InputSource(new StringReader(kmlString)));
return mDocument;
}
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
@Override
public String getPrefix(String namespaceURI) {
if ("http://www.google.com/kml/ext/2.2".equals(namespaceURI)) {
return "gx";
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List<String> ns = new ArrayList<>();
ns.add("gx");
return ns.iterator();
}
});
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
}
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
add a comment |
The differences are due to namespaces. Both in how the XML is being produced, and when you are selecting content in the XPath.
Unfortunately, it is difficult to see the difference because the XML that happens to be serialized by the toXmlString()
for testDoc1()
doesn't exactly match the state of the in-memory document.
When you construct the kml
element, using createElement()
it creates an element that is bound to the "no namespace". Then, you added namespace attributes, which happen to come out when serializing with toXmlString()
and make the kml
element appear to be in the http://www.opengis.net/kml/2.2
namespace.
If you were to marshal that XML back to a new Document
object, the kml
element would be bound to that namespace. However, the current in-memory object for that element is not.
You can observe this by adding some additional diagnostics println
messages:
NodeList result1 = (NodeList) xPath.evaluate("/kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
You can round-trip your XML and observe that it behaves different when you marshall the serialized XML:
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
mDocument = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
However, that's cheating. What you really want to do is ensure that the elements are created properly in the first place. When you create an element that you want to be bound to a namespace, use the createElementNS()
method, as indicated in the JavaDoc comments for createElement()
:
To create an element with a qualified name and namespace URI, use the
createElementNS
method.
So, to create an element that is bound to the http://www.opengis.net/kml/2.2
namespace, you would want to use:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
and:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
and the same goes for the gx:Track
element:
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
Once you get your Document objects truly equal and correct, you then need to adjust your XPath.
With XPath, if you don't apply a namespace prefix, it will select elements bound to the "no namespace". So, /kml
will only select kml
elements that are not bound to a namespace. But since your kml
elements are bound to the http://www.opengis.net/kml/2.2
namespace, it won't select them.
In your override of the getNamespaceURI()
function, you could reserve gx
for the Google KML Extension namespace, and then default any other namespace-prefix to resolve to http://www.opengis.net/kml/2.2
:
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
Then, adjust your XPath statements to use a prefix for those KML elements. If you use the above code, it doesn't matter what prefix you use. Anything other than gx
will return the http://www.opengis.net/kml/2.2
namespace.
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
Putting it all together:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
//Element mKmlElement = mDocument.createElement("kml");
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
//mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2");
mDocument.appendChild(mKmlElement);
//Element mPlacemarkElement = mDocument.createElement("Placemark");
Element mPlacemarkElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
//mPlacemarkElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.appendChild(mPlacemarkElement);
//Element gxTrackElement = mDocument.createElement("gx:Track");
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
mPlacemarkElement.appendChild(gxTrackElement);
//Element gxCoordElement = mDocument.createElement("gx:coord");
Element gxCoordElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2", "gx:coord");
gxCoordElement.setTextContent("-122.207881 37.371915 156.000000");
gxTrackElement.appendChild(gxCoordElement);
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse(new
InputSource(new StringReader(kmlString)));
return mDocument;
}
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
@Override
public String getPrefix(String namespaceURI) {
if ("http://www.google.com/kml/ext/2.2".equals(namespaceURI)) {
return "gx";
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List<String> ns = new ArrayList<>();
ns.add("gx");
return ns.iterator();
}
});
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
}
The differences are due to namespaces. Both in how the XML is being produced, and when you are selecting content in the XPath.
Unfortunately, it is difficult to see the difference because the XML that happens to be serialized by the toXmlString()
for testDoc1()
doesn't exactly match the state of the in-memory document.
When you construct the kml
element, using createElement()
it creates an element that is bound to the "no namespace". Then, you added namespace attributes, which happen to come out when serializing with toXmlString()
and make the kml
element appear to be in the http://www.opengis.net/kml/2.2
namespace.
If you were to marshal that XML back to a new Document
object, the kml
element would be bound to that namespace. However, the current in-memory object for that element is not.
You can observe this by adding some additional diagnostics println
messages:
NodeList result1 = (NodeList) xPath.evaluate("/kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
You can round-trip your XML and observe that it behaves different when you marshall the serialized XML:
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
mDocument = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
However, that's cheating. What you really want to do is ensure that the elements are created properly in the first place. When you create an element that you want to be bound to a namespace, use the createElementNS()
method, as indicated in the JavaDoc comments for createElement()
:
To create an element with a qualified name and namespace URI, use the
createElementNS
method.
So, to create an element that is bound to the http://www.opengis.net/kml/2.2
namespace, you would want to use:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
and:
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
and the same goes for the gx:Track
element:
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
Once you get your Document objects truly equal and correct, you then need to adjust your XPath.
With XPath, if you don't apply a namespace prefix, it will select elements bound to the "no namespace". So, /kml
will only select kml
elements that are not bound to a namespace. But since your kml
elements are bound to the http://www.opengis.net/kml/2.2
namespace, it won't select them.
In your override of the getNamespaceURI()
function, you could reserve gx
for the Google KML Extension namespace, and then default any other namespace-prefix to resolve to http://www.opengis.net/kml/2.2
:
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
Then, adjust your XPath statements to use a prefix for those KML elements. If you use the above code, it doesn't matter what prefix you use. Anything other than gx
will return the http://www.opengis.net/kml/2.2
namespace.
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
Putting it all together:
public App() {
super();
try {
test( testDoc1() );
test( testDoc2() );
} catch( Exception e ) {
e.printStackTrace();
} finally {
Log.d( "TEST-FINISHED", "test is finished" );
}
}
private String toXmlString(Document document) throws TransformerException {
DOMSource domSource = new DOMSource(document);
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(domSource, result);
return writer.toString();
}
private Document testDoc1() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().newDocument();
String XMLNS_NAMESPACE_URI = "http://www.w3.org/2000/xmlns/";
//Element mKmlElement = mDocument.createElement("kml");
Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml");
//mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns:gx", "http://www.google.com/kml/ext/2.2");
mDocument.appendChild(mKmlElement);
//Element mPlacemarkElement = mDocument.createElement("Placemark");
Element mPlacemarkElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");
//mPlacemarkElement.setAttributeNS(XMLNS_NAMESPACE_URI, "xmlns", "http://www.opengis.net/kml/2.2");
mKmlElement.appendChild(mPlacemarkElement);
//Element gxTrackElement = mDocument.createElement("gx:Track");
Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");
mPlacemarkElement.appendChild(gxTrackElement);
//Element gxCoordElement = mDocument.createElement("gx:coord");
Element gxCoordElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2", "gx:coord");
gxCoordElement.setTextContent("-122.207881 37.371915 156.000000");
gxTrackElement.appendChild(gxCoordElement);
return mDocument;
}
private Document testDoc2() throws ParserConfigurationException, IOException, SAXException {
String kmlString = "<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"><Placemark><gx:Track><gx:coord>-122.207881 37.371915 156.000000</gx:coord></gx:Track></Placemark></kml>";
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document mDocument = documentBuilderFactory.newDocumentBuilder().parse(new
InputSource(new StringReader(kmlString)));
return mDocument;
}
private void test(Document mDocument) throws Exception {
String xml = toXmlString(mDocument);
System.out.println( xml);
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
return "gx".equals(prefix) ? "http://www.google.com/kml/ext/2.2" : "http://www.opengis.net/kml/2.2";
}
@Override
public String getPrefix(String namespaceURI) {
if ("http://www.google.com/kml/ext/2.2".equals(namespaceURI)) {
return "gx";
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
List<String> ns = new ArrayList<>();
ns.add("gx");
return ns.iterator();
}
});
NodeList result1 = (NodeList) xPath.evaluate("/k:kml", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result1.getLength()));
System.out.println("Namespace URI: " + result1.item(0).getNamespaceURI());
System.out.println("Prefix: " + result1.item(0).getPrefix());
NodeList result2 = (NodeList) xPath.evaluate("/k:kml/k:Placemark", mDocument, XPathConstants.NODESET);
System.out.println( String.valueOf(result2.getLength()));
NodeList result3 = (NodeList) xPath.evaluate("/k:kml/k:Placemark/gx:Track", mDocument, XPathConstants.NODESET);
System.out.println(String.valueOf(result3.getLength()));
}
answered Nov 18 '18 at 20:07
Mads HansenMads Hansen
44.4k1094121
44.4k1094121
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
add a comment |
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
Mads, thanks so much for the most detailed explanation on the web! I now think I understand how to use namespaces! If I could up-vote you more than once, I would.
– Terry Wilkinson
Nov 19 '18 at 5:32
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53356645%2fhow-to-use-java-xpath-with-kml-files-and-namespaces-on-android%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
I found some further info on this problem and updated the OP.
– Terry Wilkinson
Nov 18 '18 at 23:29