有没有一种方法可以通过 SAX/DOM 解析 XML,并且每个节点都有可用的行号

问题描述 投票:0回答:3

我已经为大型 XML 文档格式编写了一个 DOM 解析器,其中包含许多可用于自动生成 Java 代码的项目。这仅限于随后合并到动态生成的 Java 源文件中的小表达式。

到目前为止 - 很好。一切正常。

但是 - 我希望能够嵌入包含 Java 代码的 XML 节点的行号(这样,如果配置包含无法编译的代码,每个方法都会有一个指向源 XML 文档的指针和行号)方便调试)。我不需要在解析时提供行号,也不需要验证 XML 源文档并在特定行号处引发错误。我需要能够访问 DOM 或每个 SAX 事件中每个节点和属性的行号。

关于我如何实现这一目标有什么建议吗?

附注 另外,我读到 StAX 有一种在解析时获取行号的方法,但理想情况下我希望通过 Java 4/5 中的常规 SAX/DOM 处理获得相同的结果,而不是成为 Java 6+ 应用程序或承担额外的 . jar 文件。

java xml dom sax
3个回答
11
投票

我知道这个线程有点旧(抱歉),但我花了很长时间才解决这个问题,我不得不与某人分享解决方案......

您似乎只能使用不构建 DOM 的 SAX 来获取行号。 DOM 解析器不会给出行号,也不会让您接近它正在使用的 SAX 解析器。 我的解决方案是使用 SAX 源和 DOM 结果执行空的 XSLT 转换,但即使如此,也有人尽力隐藏这一点。 请参阅下面的代码。

我将位置信息作为带有我自己的命名空间的属性添加到每个元素,这样我就可以使用 XPath 查找元素并报告数据的来源。

希望这有帮助:

// The file to parse.
String systemId = "myxml.xml";

/*
 * Create transformer SAX source that adds current element position to
 * the element as attributes.
 */
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
LocationFilter locationFilter = new LocationFilter(xmlReader);

InputSource inputSource = new InputSource(new FileReader(systemId));
// Do this so that XPath function document() can take relative URI.
inputSource.setSystemId(systemId);
SAXSource saxSource = new SAXSource(locationFilter, inputSource);

/*
 * Perform an empty transformation from SAX source to DOM result.
 */
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMResult domResult = new DOMResult();
transformer.transform(saxSource, domResult);
Node root = domResult.getNode();

...
class LocationFilter extends XMLFilterImpl {

    LocationFilter(XMLReader xmlReader) {
        super(xmlReader);
    }

    private Locator locator = null;

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

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

        // Add extra attribute to elements to hold location
        String location = locator.getSystemId() + ':' + locator.getLineNumber() + ':' + locator.getColumnNumber();
        Attributes2Impl attrs = new Attributes2Impl(attributes);
        attrs.addAttribute("http://myNamespace", "location", "myns:location", "CDATA", location);
        super.startElement(uri, localName, qName, attrs);
    }
}

1
投票

我最近遇到了这个问题,我想我应该分享一个现成的实用程序类来处理它。适用于 Java 11,而 Reg Whitton 的一些代码使用了一些现已弃用的类。

主要基于这篇文章,并进行了一些调整。值得注意的是,将行号存储为节点的用户数据,而不是将其设置为属性。

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.Deque;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class XmlDom {

    public static Document readXML(InputStream is, final String lineNumAttribName) throws IOException, SAXException {
        final Document doc;
        SAXParser parser;
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            parser = factory.newSAXParser();
            DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
            doc = docBuilder.newDocument();           
        } catch(ParserConfigurationException e){
            throw new RuntimeException("Can't create SAX parser / DOM builder.", e);
        }

        final Deque<Element> elementStack = new ArrayDeque<>();
        final StringBuilder textBuffer = new StringBuilder();
        DefaultHandler handler = new DefaultHandler() {
            private Locator locator;

            @Override
            public void setDocumentLocator(Locator locator) {
                this.locator = locator; //Save the locator, so that it can be used later for line tracking when traversing nodes.
            }

            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {               
                addTextIfNeeded();
                Element el = doc.createElement(qName);
                for(int i = 0;i < attributes.getLength(); i++)
                    el.setAttribute(attributes.getQName(i), attributes.getValue(i));
                el.setUserData(lineNumAttribName, String.valueOf(locator.getLineNumber()), null);
                elementStack.push(el);               
            }

            @Override
            public void endElement(String uri, String localName, String qName){
                addTextIfNeeded();
                Element closedEl = elementStack.pop();
                if (elementStack.isEmpty()) { // Is this the root element?
                    doc.appendChild(closedEl);
                } else {
                    Element parentEl = elementStack.peek();
                    parentEl.appendChild(closedEl);                   
                }
            }

            @Override
            public void characters (char ch[], int start, int length) throws SAXException {
                textBuffer.append(ch, start, length);
            }

            // Outputs text accumulated under the current node
            private void addTextIfNeeded() {
                if (textBuffer.length() > 0) {
                    Element el = elementStack.peek();
                    Node textNode = doc.createTextNode(textBuffer.toString());
                    el.appendChild(textNode);
                    textBuffer.delete(0, textBuffer.length());
                }
            }           
        };
        parser.parse(is, handler);

        return doc;
    }   

}

使用

访问行号
node.getUserData(lineNumAttribName);

0
投票

这是 Kotlin 中的一个变体。

package oracle.xml.parser.v2

import org.w3c.dom.Node

class MyDOMParser: DOMParser() {
    private class MyDocumentBuilder: DocumentBuilder() {
        override fun addChild(node: XMLNode?) {
            super.addChild(node)
            node?.setUserData(LINE_NUMBER_ATTRIB, locator?.lineNumber ?: -1, null)
        }
    }

    override fun init() {
        hndl = MyDocumentBuilder()
        hndl.xmlParser = this
        hndl.err = parser.err;
        parser.cntHandler = hndl
        parser.lexHandler = hndl;
        parser.declHandler = hndl;
        super.init()
    }

    companion object {
        private const val LINE_NUMBER_ATTRIB = "_lineNumber"

        // Use this to access the line number
        val Node.customLineNumber: Int
            get() = getUserData(LINE_NUMBER_ATTRIB) as? Int ?: -1
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.