【问题标题】:Get line number from xml node - java从 xml 节点获取行号 - java
【发布时间】:2011-06-22 08:57:40
【问题描述】:

我已经解析了一个 XML 文件并得到了一个我感兴趣的节点。我现在如何在源 XML 文件中找到该节点所在的行号?

编辑: 目前我正在使用 SAXParser 来解析我的 XML。但是,我会很高兴使用任何解析器的解决方案。

除了节点,我还有节点的 XPath 表达式。

我需要获取行号,因为我在文本框中显示 XML 文件,并且需要突出显示节点所在的行。假设 XML 文件的格式很好,有足够的换行符。

【问题讨论】:

    标签: java xml


    【解决方案1】:

    按照这个例子我已经得到了这个工作:

    http://eyalsch.wordpress.com/2010/11/30/xml-dom-2/

    此解决方案遵循 Michael Kay 建议的方法。以下是您的使用方法:

    // XmlTest.java
    
    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    
    public class XmlTest {
        public static void main(final String[] args) throws Exception {
    
            String xmlString = "<foo>\n"
                             + "    <bar>\n"
                             + "        <moo>Hello World!</moo>\n"
                             + "    </bar>\n"
                             + "</foo>";
    
            InputStream is = new ByteArrayInputStream(xmlString.getBytes());
            Document doc = PositionalXMLReader.readXML(is);
            is.close();
    
            Node node = doc.getElementsByTagName("moo").item(0);
    
            System.out.println("Line number: " + node.getUserData("lineNumber"));
        }
    }
    

    如果你运行这个程序,它会输出:"Line number: 3"

    PositionalXMLReader 是上面链接示例的略微修改版本。

    // PositionalXMLReader.java
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Stack;
    
    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 PositionalXMLReader {
        final static String LINE_NUMBER_KEY_NAME = "lineNumber";
    
        public static Document readXML(final InputStream is) throws IOException, SAXException {
            final Document doc;
            SAXParser parser;
            try {
                final SAXParserFactory factory = SAXParserFactory.newInstance();
                parser = factory.newSAXParser();
                final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
                final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
                doc = docBuilder.newDocument();
            } catch (final ParserConfigurationException e) {
                throw new RuntimeException("Can't create SAX parser / DOM builder.", e);
            }
    
            final Stack<Element> elementStack = new Stack<Element>();
            final StringBuilder textBuffer = new StringBuilder();
            final DefaultHandler handler = new DefaultHandler() {
                private Locator locator;
    
                @Override
                public void setDocumentLocator(final 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(final String uri, final String localName, final String qName, final Attributes attributes)
                        throws SAXException {
                    addTextIfNeeded();
                    final Element el = doc.createElement(qName);
                    for (int i = 0; i < attributes.getLength(); i++) {
                        el.setAttribute(attributes.getQName(i), attributes.getValue(i));
                    }
                    el.setUserData(LINE_NUMBER_KEY_NAME, String.valueOf(this.locator.getLineNumber()), null);
                    elementStack.push(el);
                }
    
                @Override
                public void endElement(final String uri, final String localName, final String qName) {
                    addTextIfNeeded();
                    final Element closedEl = elementStack.pop();
                    if (elementStack.isEmpty()) { // Is this the root element?
                        doc.appendChild(closedEl);
                    } else {
                        final Element parentEl = elementStack.peek();
                        parentEl.appendChild(closedEl);
                    }
                }
    
                @Override
                public void characters(final char ch[], final int start, final int length) throws SAXException {
                    textBuffer.append(ch, start, length);
                }
    
                // Outputs text accumulated under the current node
                private void addTextIfNeeded() {
                    if (textBuffer.length() > 0) {
                        final Element el = elementStack.peek();
                        final Node textNode = doc.createTextNode(textBuffer.toString());
                        el.appendChild(textNode);
                        textBuffer.delete(0, textBuffer.length());
                    }
                }
            };
            parser.parse(is, handler);
    
            return doc;
        }
    }
    

    【讨论】:

    • 请注意,此解决方案只注意元素,而忽略 cmets,可能还有 CDATA 和 DTD。您可以按照 javadoc 的指示实现 LexicalHandler 并调用 setProperty 来获得这些。
    【解决方案2】:

    如果您使用的是 SAX 解析器,则可以使用 Locator 对象获取事件的行号,该对象通过 setDocumentLocator() 回调通知 ContentHandler。这个在解析开始时调用,需要保存Locator;那么在任何事件之后(比如startElement()),就可以调用getLineNumber()等方法来获取源文件中的当前位置。 (在 startElement() 之后,回调被定义为给你开始标记的“>”出现的行号。)

    【讨论】:

    • 你好,我可以配置将它用作特定 xml 解析器的 saxon XSLT 处理器(任何版本)吗?我只发现参数 -x 使用了自己的 SAX 解析器。
    • Saxon 有一个配置选项 -l 或 FeatureKeys.LINE_NUMBERING,这将导致它收集 XML 解析器提供的行号信息并将其保留在构造的树中。然后可以使用 saxon:line-number() 扩展函数访问它。
    • 感谢您的回答。我知道 saxon:line-number 函数。对不起,我不够精确! priomsrb 的回答促使我修改他的 PositionalXMLReader 以向节点添加更多用户数据。我找到了 saxon:getUserData 函数(仅适用于
    • 我建议您在 saxonica.plan.io 的论坛上更详细地描述您正在尝试做的事情。在这里的评论线程中处理似乎有点太复杂了。
    【解决方案3】:

    请注意,根据规范(Locator.getLineNumber()),该方法返回 SAX 事件结束的行号!

    在“startElement()”的情况下,这意味着:

    这里 Element 的行号是 1

    <Element></Element>
    

    这里 Element 的行号是 3

    <Element
       attribute1="X"
       attribute2="Y">
    </Element>
    

    【讨论】:

    • 你好@hhaehle。欢迎来到 SO。这是一些有用的信息,但它可能应该放在评论中,因为它没有回答原始问题。您可以了解更多关于 cmets here.
    • 所以行号是结束行号,但是有什么办法可以得到起始行(事件开始的地方)?
    【解决方案4】:

    priomsrb's 答案很棒并且有效。对于我的用例,我需要将其集成到现有框架中,例如编码也包括在内。因此,应用以下重构以具有单独的 LineNumberHandler 类。

    然后代码也将与 Sax InputSource 一起使用,其中可以像这样修改编码:

                // read in the xml document
                org.xml.sax.InputSource is=new org.xml.sax.InputSource();
                is.setByteStream(instream);
                if (encoding!=null) {
                    is.setEncoding(encoding);
                    if (Debug.CORE)
                        Debug.log("setting XML encoding to - "+is.getEncoding());
                }   
    

    单独的 LineNumberHandler

    /**
     * LineNumber Handler
     * @author wf
     *
     */
    public static class LineNumberHandler extends DefaultHandler {
    
    final Stack<Element> elementStack = new Stack<Element>();
    final StringBuilder textBuffer = new StringBuilder();
    private Locator locator;
    private Document doc;
    
    /**
     * create a line number Handler for the given document
     * @param doc
     */
    public LineNumberHandler(Document doc) {
      this.doc=doc;
    }
    
    @Override
    public void setDocumentLocator(final 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(final String uri, final String localName,
        final String qName, final Attributes attributes) throws SAXException {
      addTextIfNeeded();
      final Element el = doc.createElement(qName);
      for (int i = 0; i < attributes.getLength(); i++) {
        el.setAttribute(attributes.getQName(i), attributes.getValue(i));
      }
      el.setUserData(LINE_NUMBER_KEY_NAME,
          String.valueOf(this.locator.getLineNumber()), null);
      elementStack.push(el);
    }
    
    @Override
    public void endElement(final String uri, final String localName,
        final String qName) {
      addTextIfNeeded();
      final Element closedEl = elementStack.pop();
      if (elementStack.isEmpty()) { // Is this the root element?
        doc.appendChild(closedEl);
      } else {
        final Element parentEl = elementStack.peek();
        parentEl.appendChild(closedEl);
      }
    }
    
    @Override
    public void characters(final char ch[], final int start, final int length)
        throws SAXException {
      textBuffer.append(ch, start, length);
    }
    
    // Outputs text accumulated under the current node
    private void addTextIfNeeded() {
      if (textBuffer.length() > 0) {
        final Element el = elementStack.peek();
        final Node textNode = doc.createTextNode(textBuffer.toString());
        el.appendChild(textNode);
        textBuffer.delete(0, textBuffer.length());
      }
    }
    

    };

    PositionalXMLReader

    public class PositionalXMLReader {
      final static String LINE_NUMBER_KEY_NAME = "lineNumber";
     /**
      * read a document from the given input strem
      * 
      * @param is
      *          - the input stream
      * @return - the Document
      * @throws IOException
      * @throws SAXException
      */
    public static Document readXML(final InputStream is)
      throws IOException, SAXException {
      final Document doc;
      SAXParser parser;
        try {
          final SAXParserFactory factory = SAXParserFactory.newInstance();
          parser = factory.newSAXParser();
          final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
          .newInstance();
          final DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
          doc = docBuilder.newDocument();
        } catch (final ParserConfigurationException e) {
          throw new RuntimeException("Can't create SAX parser / DOM builder.", e);
        }
        LineNumberHandler handler = new LineNumberHandler(doc);
        parser.parse(is, handler);
    
        return doc;
      }
    }
    

    JUnit 测试用例

    package com.bitplan.common.impl;
    
    import static org.junit.Assert.assertEquals;
    
    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    
    import org.junit.Test;
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    
    import com.bitplan.bobase.PositionalXMLReader;
    
    public class TestXMLWithLineNumbers {
    
      /**
       * get an Example XML Stream
       * @return the example stream
       */
      public InputStream getExampleXMLStream() {
        String xmlString = "<foo>\n" + "    <bar>\n"
            + "        <moo>Hello World!</moo>\n" + "    </bar>\n" + "</foo>";
    
        InputStream is = new ByteArrayInputStream(xmlString.getBytes());
        return is;
      }
    
      @Test
      public void testXMLWithLineNumbers() throws Exception {
        InputStream is = this.getExampleXMLStream();
        Document doc = PositionalXMLReader.readXML(is);
        is.close();
    
        Node node = doc.getElementsByTagName("moo").item(0);
        assertEquals("3", node.getUserData("lineNumber"));
      }  
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-12
      • 1970-01-01
      • 2020-06-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多