如何在 Android 上将 Java XPath 用于 KML 文件和命名空间

How to use Java XPath with KML files and namespaces on Android

我正在为如何在包含新 gx:Track 和 gx:coord 标签的 KML 文件上使用 XPath 而苦恼。问题在于如何将 XPath 与 Android.

下的命名空间一起使用

我看过很多例子,包括这些

但我似乎连这些例子都无法发挥作用。

以下代码和输出说明了我的问题:

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() ) );
}

test()方法执行3次XPathstatements/patterns,对两个测试文档各调用一次。这两份文件是用不同的方法构建的,但内容应该是相同的。但是,我从 3 个 XPath 语句中得到的结果是不同的。

这些是文档 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

这些是文档 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

至少有两个问题:

  1. 既然2个文档是一样的(我觉得),为什么测试的结果不一样呢? (即,前 2 个 XPath 语句在文档 1 中成功,但在文档 2 中均不成功。)

  2. 为什么第三个 XPath 语句在文档 1 和文档 2 中都找不到 gx:Track 元素?

    更新:这个问题似乎与

    有关
    xmlns="http://www.opengis.net/kml/2.2"
    

    包含在文档 2 中。如果我删除它,前 2 个 XPath 测试的结果是正确的(对于两个文档)- 实际上 XPath 测试 3 现在可以在文档 2 上运行。不幸的是,我仍然没有无法处理此行为。

我可能遗漏了一些明显的东西,非常感谢任何帮助。

不同之处在于名称空间。 XML 的生成方式以及在 XPath 中 selecting 内容的时间。

不幸的是,很难看出差异,因为 XML 恰好被 toXmlString() 序列化为 testDoc1() 与 in- 的状态不完全匹配记忆文件。

当您构建 kml 元素时,使用 createElement() 它会创建一个绑定到 "no namespace" 的元素。然后,您添加了命名空间属性,这些属性在使用 toXmlString() 进行序列化时恰好出现,并使 kml 元素看起来位于 http://www.opengis.net/kml/2.2 命名空间中。

如果您要将 XML 编组回新的 Document 对象,kml 元素 将绑定 到该命名空间.但是,该元素的当前内存中对象不是。

您可以通过添加一些额外的诊断 println 消息来观察这一点:

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());

您可以往返您的 XML 并观察它在编组序列化 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)));

但是,这是作弊。您真正想要做的是确保首先正确创建元素。当您创建要绑定到命名空间的元素时,请使用 createElementNS() 方法,如 createElement():

的 JavaDoc 注释中所示

To create an element with a qualified name and namespace URI, use the createElementNS method.

因此,要创建绑定到 http://www.opengis.net/kml/2.2 命名空间的元素,您需要使用:

Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "kml"); 

和:

Element mKmlElement = mDocument.createElementNS("http://www.opengis.net/kml/2.2", "Placemark");

gx:Track 元素也是如此:

Element gxTrackElement = mDocument.createElementNS("http://www.google.com/kml/ext/2.2","gx:Track");

一旦您的文档对象真正相等且正确,您就需要调整您的 XPath。

使用 XPath,如果不应用名称空间前缀,它将 select 个元素绑定到 "no namespace"。因此,/kml 将仅 select kml 未绑定到命名空间的元素。但是由于您的 kml 元素绑定到 http://www.opengis.net/kml/2.2 命名空间,它不会 select 它们。

在覆盖 getNamespaceURI() 函数时,您可以为 Google KML 扩展命名空间保留 gx,然后默认任何其他命名空间前缀解析为 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";
}

然后,调整您的 XPath 语句以对这些 KML 元素使用前缀。如果你使用上面的代码,你使用什么前缀都没有关系。 gx 以外的任何内容都将 return http://www.opengis.net/kml/2.2 命名空间。

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()));

综合起来:

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()));

}