如何确定发生无效XML的行号?

时间:2018-02-09 06:06:49

标签: java xml xml-parsing

我需要支持用户向我提交无效XML文件的情况,并向他们报告有关错误的信息。理想情况下,错误的位置(行号和列号)和错误的性质。

当标记丢失或类似错误时,我的示例代码(见下文)运行良好。在这种情况下,我得到一个大概的位置和一个有用的解释。但是,当XML文件包含非UTF-8字符时,我的代码会失败。在这种情况下,我得到一个无用的错误:

com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: Invalid byte 1 of 1-byte UTF-8 sequence.

我找不到确定无效字符可能的行号的方法,也找不到字符本身。有没有办法做到这一点?

如果正如一条评论所暗示的那样,可能无法实现解析步骤,是否有办法处理XML文件,而不是使用解析器,而只是逐行处理,寻找和报告非UTF-8字符?

示例代码如下。首先是一个基本的错误处理程序:

public class XmlErrorHandler implements ErrorHandler {
    @Override
    public void warning(SAXParseException e) throws SAXException {
        show("Warning", e); throw e;
    }

    @Override
    public void error(SAXParseException e) throws SAXException {
        show("Error", e); throw e;
    }

    @Override
    public void fatalError(SAXParseException e) throws SAXException {
        show("Fatal", e); throw e;
    }

    private void show(String type, SAXParseException e) {
        System.out.println("Line " + e.getLineNumber() + " Column " + e.getColumnNumber());
        System.out.println(type + ": " + e.getMessage());
    }
}

一个简单的测试程序:

public class XmlTest {
    public static void main(String[] args) {
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser parser = spf.newSAXParser();
            XMLReader reader = parser.getXMLReader();
            reader.setContentHandler(new DefaultHandler());
            reader.setErrorHandler(new XmlErrorHandler());
            InputSource is = new InputSource(args[0]);
            reader.parse(is);
        }
        catch (SAXException e) {      // Useful error case
            System.err.println(e);
            e.printStackTrace(System.err);
        }
        catch (Exception e) {         // Useless error case arrives here
            System.err.println(e);
            e.printStackTrace();
        }
    }
}

示例XML文件(使用来自(例如)Word文档的非UTF-8智能引号):

<?xml version="1.0" encoding="UTF-8"?>
<example>
    <![CDATA[Text with <91>smart quotes<92>.]]>
</example>

1 个答案:

答案 0 :(得分:0)

我在确定XML文件中的问题使用了几种方法方面取得了一些成功。

调整我的问题中的代码以使用带有ContentHandler的本地Locator(见下文),证明XML正在处理,直到遇到无效字符。特别是,正在跟踪行号。保留行号允许在发生有问题的异常时从ContentHandler检索它。

此时,我提出了两种可能性。第一种是在InputStream上使用不同的编码重新运行处理,例如。 Windows-1252。在这种情况下,解析完成且没有错误,我能够检测具有已知问题的行上的字符。这允许向用户提供合理有用的错误消息,即。行号和字符。

我的第二种方法是将最高等级答案的代码调整为this SO question。此代码允许您在字节流中查找第一个非UTF-8字符。如果您假设0x0A(换行)代表XML中的新行(这在实践中看起来效果很好),则可以轻松提取行号,列号和无效字符以获得精确错误信息。

// Modified test program
public class XmlTest {
    public static void main(String[] args) {
        ErrorFinder errorFinder = new ErrorFinder(0); // Create our own content handler
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser parser = spf.newSAXParser();
            XMLReader reader = parser.getXMLReader();
            reader.setContentHandler(errorFinder); // Use instead of the default handler
            reader.setErrorHandler(new XmlErrorHandler());
            InputSource is = new InputSource(args[0]);
            reader.parse(is);
        }
        catch (SAXException e) {      // Useful error case
            System.err.println(e);
            e.printStackTrace(System.err);
        }
        catch (Exception e) {         // Useless error case arrives here
            System.err.println(e);
            e.printStackTrace();
            // Option 1: repeat parsing (see above) with a new ErrorFinder initialised thus:
            ErrorFinder ef2 = new ErrorFinder(errorFinder.getCurrentLineNumber()); // and
            is.setEncoding("Windows-1252");
        }
    }
}

// Content handler with irrelevant method implementations elided.
public class ErrorFinder implements ContentHandler {
    private int lineNumber; // If non-zero, the line number to retrieve characters for.
    private int currentLineNumber;
    private char[] chars;
    private Locator locator;

    public ErrorFinder(int lineNumber) {
        super();
        this.lineNumber = lineNumber;
    }

    public void setDocumentLocator(Locator locator) {
        this.locator = locator;
    }

    @Override
    public void startDocument() throws SAXException {
        currentLineNumber = locator.getLineNumber();
    }

    ... // Skip other over-ridden methods as they have same code as startDocument().

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        currentLineNumber = locator.getLineNumber();
        if (currentLineNumber == lineNumber) {
            char[] c = new char[length];
            System.arraycopy(ch, start, c, 0, length);
            chars = c;
        }
    }

    public int getCurrentLineNumber() {
        return currentLineNumber;
    }

    public char[] getChars() {
        return chars;
    }
}