Как использовать Java XPath с файлами KML и пространствами имен на Android - PullRequest
0 голосов
/ 18 ноября 2018

Я борюсь с тем, как использовать XPath для файлов KML, которые содержат новые теги gx: Track и gx :ordin. Проблема в том, как использовать 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 оператора / шаблона XPath и вызывается один раз для каждого из двух тестовых документов. 2 документа составлены с использованием разных методов, но их содержание должно быть идентичным. Однако результаты, которые я получаю из 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

Есть как минимум 2 проблемы:

  1. Поскольку 2 документа идентичны (я думаю), почему результаты тестов отличаются? (т. е. первые 2 оператора XPath успешно выполняются с документом 1, но не с документом 2).

  2. и почему третьему оператору XPath не удается найти элемент gx: Track как в документе 1, так и в документе 2?

    ОБНОВЛЕНИЕ: Эта проблема, кажется, как-то связана с

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

    включено в документ 2. Если я удалю его, результаты первых 2-х тестов XPath будут правильными (для обоих документов) - и фактически тест XPath 3 теперь работает с документом 2. К сожалению, у меня все еще нет справиться с этим поведением.

Я, вероятно, упускаю что-то очевидное и буду признателен за любую помощь.

1 Ответ

0 голосов
/ 18 ноября 2018

Различия связаны с пространствами имен. Как в том, как создается XML, так и при выборе содержимого в XPath.

К сожалению, трудно увидеть разницу, потому что XML, который оказывается сериализованным toXmlString() для testDoc1(), не совсем соответствует состоянию документа в памяти.

Когда вы создаете элемент kml, используя createElement(), он создает элемент, связанный с «пространством имен без». Затем вы добавили атрибуты пространства имен, которые появляются при сериализации с 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(), как указано в комментариях JavaDoc для createElement():

Чтобы создать элемент с определенным именем и URI пространства имен, используйте метод createElementNS.

Итак, чтобы создать элемент, который связан с пространством имен 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");

Как только вы добьетесь правильности и корректности объектов Document, вам необходимо настроить XPath.

В XPath, если вы не примените префикс пространства имен, он выберет элементы, связанные с "пространством имен". Таким образом, /kml будет выбирать только kml элементы, которые не связаны с пространством имен. Но так как ваши kml элементы связаны с пространством имен http://www.opengis.net/kml/2.2, он не будет выбирать их.

В переопределении функции getNamespaceURI() вы можете зарезервировать gx для пространства имен Расширения Google KML, а затем по умолчанию любой другой префикс пространства имен разрешить до 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, вернет пространство имен 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()));

}
...