Как я могу получить дополнительную информацию о недопустимом элементе DOM через валидатор?

Я проверяю объект DOM в памяти, используя класс javax.xml.validation.Validator по схеме XSD. Я получаю SAXParseException во время проверки всякий раз, когда есть какое-то повреждение данных в информации, из которой я заполняю свой DOM.

Пример ошибки:

org.xml.SAXParseException: cvc-datatype-valid.1.2.1: '???"??[?????G?>???p~tn??~0?1]' не является допустимым значение для 'hexBinary'.

Я надеюсь, что есть способ найти местоположение этой ошибки в моем DOM в памяти и распечатать элемент-нарушитель и его родительский элемент. Мой текущий код:

public void writeDocumentToFile(Document document) throws XMLWriteException {
  try {
    // Validate the document against the schema
    Validator validator = getSchema(xmlSchema).newValidator();
    validator.validate(new DOMSource(document));

    // Serialisation logic here.

  } catch(SAXException e) {
    throw new XMLWriteException(e); // This is being thrown
  } // Some other exceptions caught here.
}

private Schema getSchema(URL schema) throws SAXException {
  SchemaFactory schemaFactory = 
    SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

  // Some logic here to specify a ResourceResolver

  return schemaFactory.newSchema(schema);
}

Я изучил метод Validator#setErrorHandler(ErrorHandler handler), но интерфейс ErrorHandler дает мне доступ только к SAXParseException, который показывает только номер строки и номер столбца ошибки. Поскольку я использую DOM в памяти, это возвращает -1 для номера строки и столбца.

Есть лучший способ сделать это? Я действительно не хочу вручную проверять строки, прежде чем добавлять их в DOM, если библиотеки предоставляют мне функцию, которую я ищу.

Я использую JDK 6, обновление 26 и JDK 6, обновление 7, в зависимости от того, где работает этот код.

РЕДАКТИРОВАТЬ: с добавлением этого кода -

validator.setErrorHandler(new ErrorHandler() {
  @Override
  public void warning(SAXParseException exception) throws SAXException {
    printException(exception);
    throw exception;
  }

  @Override
  public void error(SAXParseException exception) throws SAXException {
    printException(exception);
    throw exception;
  }

  @Override
  public void fatalError(SAXParseException exception) throws SAXException {
    printException(exception);
    throw exception;
  }

  private void printException(SAXParseException exception) {
    System.out.println("exception.getPublicId() = " + exception.getPublicId());
    System.out.println("exception.getSystemId() = " + exception.getSystemId());
    System.out.println("exception.getColumnNumber() = " + exception.getColumnNumber());
    System.out.println("exception.getLineNumber() = " + exception.getLineNumber());
  }
});

Я получаю вывод:

exception.getPublicId() = null
exception.getSystemId() = null
exception.getColumnNumber() = -1
exception.getLineNumber() = -1

person Bringer128    schedule 10.11.2011    source источник


Ответы (2)


Если вы используете Xerces (по умолчанию Sun JDK), вы можете получить элемент, не прошедший проверку, через http://xerces.apache.org/xerces2-j/properties.html#dom.current-element-node:

...
catch (SAXParseException e)
{
    Element curElement = (Element)validator.getProperty("http://apache.org/xml/properties/dom/current-element-node");

    System.out.println("Validation error: " + e.getMessage());
    System.out.println("Element: " + curElement);
}   

Пример:

String xml = "<root xmlns=\"http://www.myschema.org\">\n" +
             "<text>This is text</text>\n" +
             "<number>32</number>\n" +
             "<number>abc</number>\n" +
             "</root>";

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
Schema schema = getSchema(getClass().getResource("myschema.xsd"));

Validator validator = schema.newValidator();
try
{
    validator.validate(new DOMSource(doc));
}
catch (SAXParseException e)
{
    Element curElement = (Element)validator.getProperty("http://apache.org/xml/properties/dom/current-element-node");

    System.out.println("Validation error: " + e.getMessage());
    System.out.println(curElement.getLocalName() + ": " + curElement.getTextContent());

    //Use curElement.getParentNode() or whatever you need here
}         

Если вам нужно получить номера строк/столбцов из DOM, этот ответ есть решение этой проблемы.

person prunge    schedule 17.11.2011
comment
Хм, спасибо за подробный ответ. Я получаю org.xml.sax.SAXNotRecognizedException: Property 'http://apache.org/xml/properties/dom/current-element-node' is not recognized. в обновлении 7 JDK 1.6. Я предполагаю, что для доступа к этому свойству не требуется доступ в Интернет? - person Bringer128; 17.11.2011
comment
@ Bringer128 Я использую Java 1.6.0u25, и это работает. Возможно, это свойство Xerces было добавлено недавно. Это может быть возможно, если вам нужно работать под старым JDK 1.6, чтобы связать более позднюю версию Xerces с вашим приложением и использовать реализации DocumentBuilderFactory/Validator вместо JDK по умолчанию. - person prunge; 17.11.2011
comment
Вы правы, это работает и под Java 1.6.0u26. Кажется, это то, что я ищу, спасибо! - person Bringer128; 17.11.2011
comment
Можно ли это сделать, когда файл DTD используется для проверки XML? - person Troyseph; 24.10.2014

SaxParseException раскрывает SystemId и PublicId. Разве это не дает вам достаточно информации?

person Pavan    schedule 16.11.2011
comment
Я обновил свой вопрос. Похоже, что валидатор возвращает null для этих двух значений, поэтому они не очень полезны. - person Bringer128; 17.11.2011