Как найти номер строки в xml файле

priomsrb’s answer is great and works. For my usecase i need to integrate it to an existing framework where e.g. the encoding is also covered. Therefore the following refactoring was applied to have a separate LineNumberHandler class.

Then the code will also work with a Sax InputSource where the encoding can be modified like this:

            // 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());
            }   

Separate 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 Testcase

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"));
  }  
}

Здравствуйте. Ситуация: есть модель(дисериализованный файл), довольно большая. В ней много строк и вложенных объектов. (Модель YML каталога).

[XmlRoot("yml_catalog")]
public class YMLCatalog
{
    /// <summary>
    /// Дата и время генерации YML файла на стороне магазина
    /// </summary>
    [Required]
    [XmlAttribute("date")]
    public string Date { get; set; }

    /// <summary>
    /// Магазин
    /// </summary>
    [Required]
    [ValidateObject]
    [XmlElement(ElementName = "shop")]
    public Shop Shop { get; set; }
}

/// <summary>
/// Точка продаж
/// https://yandex.ru/support/webmaster/goods-prices/technical-requirements.html
/// </summary>
public class Shop
{
    /// <summary>
    /// Название магазина.
    /// </summary>
    [Required]
    [XmlElement("name")]
    public string Name { get; set; }

    /// <summary>
    /// Название компании
    /// </summary>
    [Required]
    [XmlElement("company")]
    public string Company { get; set; }
    ... и тд.
}

При валидации модели, в случае ошибки, я получаю (при помощи магии. См. краткое описание в комментариях) путь к ошибке. Например для YML файла с ошибкой в теге price, 0го оффера, в виде отсутствия цены, я получаю путь: shop/offers/offer[‘0 или 1’, смотря как удобнее. Для XPath лучше 1]/price Value: Текст ошибки. (Value – поле со значением тега, аналогично может быть для атрибутов)

Нас интересует: shop/offers/offer[0]/price

По этому пути, мне нужно определить номер строки, где находиться этот тег. Чтобы указать пользователю где ошибка.

Что я пробовал:

1) Сделал через регулярные выражения. Сначала последовательно нахожу индекс элементов по пути, как получу индекс начала тега Price в файле. Затем собираю список строк в файле и нахожу номер строки для вывода, относительно всего файла. – Это работает, но меня не устраивает. Слишком медленно, а у меня могут быть очень большие файлы

2) Попробовал сделать через XPath. Т.е например можно легко получить элемент тега вот так:

XmlDocument document = new XmlDocument();
document.LoadXml(text);
var node = document.SelectSingleNode("//shop/offers/offer[1]/price");// это для примера

3) Есть вариант с созданием своего дессериализатора + атрибутов валидации для него, чтобы к каждому полю в модели приписывать в метаданных IXmlLineInfo – который в стандартном XmlSerializer к сожалению используется только при выдаче информации об ошибке – но это долго, а те готовые, что я нашел, не поддерживаются в net core… UPD: также в таком случае требуется поддержка не только составных объектов, но и простых + простых атрибутов (string, int, long и тп)

Вопрос 1: XPath как-нибудь позволяет получить кол-во элементов выше (или лучше их список) ? Вроде комбинировать preceding и ancestor нельзя (ссылка) – Но опять же, даже если это получиться, придется искать индекс, создавая список строк файла. – Не очень хорошо, хотелось бы узнать номер строки ошибки другим способом

Пробовал “//shop/offers/offer[ 1 ]/price/preceding::*”, но в таком случае игнорируются все элементы выше shop, почему не знаю. (когда использовал count(…), получал 27 элементов) – Думаю это всяко лучше только регулярных выражений

Знаете еще способ? Прошу в ответы:)

Главный вопрос: Так как лучше всего найти номер строки ошибки, имея XML и путь к тегу?

P.S Производительность решения приветствуется)

У меня это работает, следуя этому примеру:

Это решение следует методу, предложенному Майклом Кей. Вот как вы его используете:

// 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"));
    }
}

Если вы запустите эту программу, она выйдет: “Номер строки: 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;
    }
}
От:

ra88

 
Дата:  24.09.10 06:56
Оценка:

Доброго рабочего дня!

собственно имеется XML фаил, который считывается по средством XmlDocument.Load(filename)

Затем я бегаю по дереву с помощю SelectNodes() и ChildNodes. В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.

Как это сделать?

while true;


Re: номер строки в XML файле

От:

Arnx

Россия

 
Дата:  24.09.10 07:00
Оценка:

R>собственно имеется XML фаил, который считывается по средством XmlDocument.Load(filename)
R>Затем я бегаю по дереву с помощю SelectNodes() и ChildNodes. В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.
R>Как это сделать?
Вообще-то говоря, строк как таковых в хмл документе нету. Если хочется знать “как бы представление” то можешь держать текущую глубину элемента = собственно все пройденные узлы *(или как будешь справляться с закрывающими тегами) + дети.
Может тебе проще параллельно загнать построчно в хэш таблицу и вытаскивать оттуда по какому-либо ключу?


Re: номер строки в XML файле

От:

HowardLovekraft

 
Дата:  24.09.10 07:03
Оценка:

Здравствуйте, ra88, Вы писали:

R>В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.

А если XML хранится в файле в одну строку:

<root><child>123</child></root>

?


Re[2]: номер строки в XML файле

От:

ra88

 
Дата:  24.09.10 07:07
Оценка:

Здравствуйте, HowardLovekraft, Вы писали:

HL>Здравствуйте, ra88, Вы писали:


R>>В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.

HL>А если XML хранится в файле в одну строку:
HL>

HL><root><child>123</child></root>


HL>?

тогда номер строки 1.

while true;


Re: номер строки в XML файле

От:

Closer

 
Дата:  24.09.10 07:09
Оценка:

20 (4)

Здравствуйте, ra88, Вы писали:

[skipped]

R>Как это сделать?

Для начала научится пользоваться поиском.

Здесь

Автор: Closer
Дата: 17.10.06

решение.

Мы были здесь. Но пора идти дальше. (с) Дуглас Коупленд, Рабы “Микрософт”


Re[2]: номер строки в XML файле

От:

ra88

 
Дата:  24.09.10 07:15
Оценка:

Здравствуйте, Closer, Вы писали:

C>Здравствуйте, ra88, Вы писали:


C>[skipped]


R>>Как это сделать?


C>Для начала научится пользоваться поиском. Здесь

Автор: Closer
Дата: 17.10.06

решение.

while true;


Re: номер строки в XML файле

От:

_FRED_

Черногория

@ViIvanov
Дата:  24.09.10 10:30
Оценка:

8 (3)

Здравствуйте, ra88, Вы писали:

R>собственно имеется XML фаил, который считывается по средством XmlDocument.Load(filename)

R>Затем я бегаю по дереву с помощю SelectNodes() и ChildNodes. В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.
R>Как это сделать?

А использовать современное API (System.Xml.Linq) для работы с xml возможность есть? В нём уже каждый объект реализует System.Xml.IXmlLineInfo.

Help will always be given at Hogwarts to those who ask for it.


Re[2]: номер строки в XML файле

От:

ra88

 
Дата:  24.09.10 11:35
Оценка:

Здравствуйте, _FRED_, Вы писали:

_FR>Здравствуйте, ra88, Вы писали:


R>>собственно имеется XML фаил, который считывается по средством XmlDocument.Load(filename)

R>>Затем я бегаю по дереву с помощю SelectNodes() и ChildNodes. В определённый момент хочется узнать на какой строке в файле находится открывающий тэг выбранного XmlNode.
R>>Как это сделать?

_FR>А использовать современное API (System.Xml.Linq) для работы с xml возможность есть? В нём уже каждый объект реализует System.Xml.IXmlLineInfo.

пока нет. надо исходить из того, что имеется только .NET 2.0
Но спасибо за информацию. В будущем пригодится.

while true;

Подождите ...

Wait...

  • Переместить
  • Удалить
  • Выделить ветку

Пока на собственное сообщение не было ответов, его можно удалить.

Скажем, у меня есть этот XML-файл: https: // prod -c2g.s3.amazonaws.com/db/Winter2013/files/courses-noID.xml

Есть ли способ показать номера строк на веб-странице или вам нужно скопировать их на Eclipse, чтобы показать номера строк?

Я хочу избежать копирования в Eclipse, потому что тогда я не могу разворачивать или сворачивать теги.


1

committedandroider

30 Авг 2014 в 04:11

1

Ваш браузер полностью контролирует внешний вид документов или веб-страниц. Некоторые браузеры, безусловно, будут отображать XML-документы без таблиц стилей с индивидуально пронумерованными строками.

 – 

Sam Varshavchik

30 Авг 2014 в 04:12

Спасибо, знаете ли вы о браузере, который показывает номера строк с XML-документами?

 – 

committedandroider

30 Авг 2014 в 04:14

1

Если вы просматриваете исходный код в хроме, он покажет номера строк (при условии, что ваш исходный XML-файл разделен на разные строки).

 – 

terrywb

30 Авг 2014 в 04:15

«Просмотр исходного кода страницы» в Mozilla Firefox также отображает XML-документ с пронумерованными строками.

 – 

Sam Varshavchik

30 Авг 2014 в 04:18

1 ответ

Лучший ответ

Одна альтернатива – копирование в другой текстовый редактор, например Notepad ++, в котором есть возможность сворачивания бирки.

Это не идеально, но это решение, которое дает вам номера строк и сворачивание кода.


3

Peter Mortensen
12 Авг 2020 в 21:31

Похожие вопросы

Новые вопросы

xml

XML (Extensible Markup Language) – это формат структурированного документа, определяющий правила кодирования текста. При использовании этого тега включайте дополнительные теги, такие как язык программирования, наборы инструментов, используемые технологии XML и другие теги, описывающие среду, в которой опубликована проблема. Гибкость XML обеспечивает широкий спектр применений для передачи человеческих и машинных данных, в том числе конкретных инструментов и библиотек.

Подробнее про xml…

Добавить комментарий