Как отследить исходную строку (местоположение) элемента XML? - PullRequest
10 голосов
/ 15 декабря 2010

Я предполагаю, что, вероятно, нет удовлетворительного ответа на этот вопрос, но я все равно задаю его, если что-то пропустил.

По сути, я хочу выяснить строку в исходном документе, из которого происходит определенный элемент XML, с учетом экземпляра элемента. Я хочу это только для более качественных диагностических сообщений об ошибках - XML ​​является частью файла конфигурации, и если с ним что-то не так, я хочу иметь возможность указать читателю сообщения об ошибке точно на правильное место в документе XML чтобы он мог исправить ошибку.

Я понимаю, что стандартная поддержка Scala XML, вероятно, не имеет такой встроенной функции, как эта. В конце концов, было бы расточительно аннотировать каждый экземпляр NodeSeq такой информацией, и не каждый элемент XML даже имеет исходный документ, из которого он был проанализирован. Мне кажется, что стандартный синтаксический анализатор XML Scala отбрасывает информацию о строках, и в дальнейшем нет возможности извлечь ее.

Но переключение на другую структуру XML не вариант. Добавление другой библиотечной зависимости "только" для улучшения диагностических сообщений об ошибках кажется мне неуместным. Кроме того, несмотря на некоторые недостатки, мне действительно нравится встроенная поддержка сопоставления с образцом для XML.

Моя единственная надежда состоит в том, что вы можете показать мне способ изменения или подкласса стандартного синтаксического анализатора XML Scala, чтобы создаваемые им узлы были снабжены номером исходной строки. Возможно, для этого можно создать специальный подкласс NodeSeq. Или, может быть, только Atom может быть разделено на подклассы, потому что NodeSeq слишком динамичный? Я не знаю.

Во всяком случае, мои надежды близки к нулю. Я не думаю, что в парсере есть место, где мы можем подключиться, чтобы изменить способ создания узлов, и что в этом месте доступна информация о строках. Тем не менее, мне интересно, почему я не нашел этот вопрос раньше. Пожалуйста, укажите мне оригинал, если это дубликат.

Ответы [ 4 ]

11 голосов
/ 15 декабря 2010

Я понятия не имел, как это сделать, но Пангея показала мне путь .Сначала давайте создадим черту для обработки местоположения:

import org.xml.sax.{helpers, Locator, SAXParseException}
trait WithLocation extends helpers.DefaultHandler {
    var locator: org.xml.sax.Locator = _
    def printLocation(msg: String) {
        println("%s at line %d, column %d" format (msg, locator.getLineNumber, locator.getColumnNumber))
    }

    // Get location
    abstract override def setDocumentLocator(locator: Locator) {
        this.locator = locator
        super.setDocumentLocator(locator)
    }

    // Display location messages
    abstract override def warning(e: SAXParseException) {
        printLocation("warning")
        super.warning(e)
    }
    abstract override def error(e: SAXParseException) {
        printLocation("error")
        super.error(e)
    }
    abstract override def fatalError(e: SAXParseException) {
        printLocation("fatal error")
        super.fatalError(e)
    }
}

Далее, давайте создадим наш собственный загрузчик, переопределяющий XMLLoader adapter, чтобы включить нашу черту:

import scala.xml.{factory, parsing, Elem}
object MyLoader extends factory.XMLLoader[Elem] {
    override def adapter = new parsing.NoBindingFactoryAdapter with WithLocation
}

Иэто все, что нужно сделать!Объект XML добавляет немного к XMLLoader - в основном, к методам save.Возможно, вы захотите взглянуть на его исходный код, если вы чувствуете необходимость полной замены.Но это только в том случае, если вы хотите обработать все это самостоятельно, так как Scala уже имеет черту, приводящую к ошибкам:

object MyLoader extends factory.XMLLoader[Elem] {
    override def adapter = new parsing.NoBindingFactoryAdapter with parsing.ConsoleErrorHandler
}

Черта ConsoleErrorHandler извлекает информацию о строках и числах из исключения с помощьюпуть.Для наших целей нам нужно расположение и вне исключений (я предполагаю).

Теперь, чтобы изменить само создание узла, рассмотрим абстрактные методы scala.xml.factory.FactoryAdapter.Я остановился на createNode, но переопределяю на уровне NoBindingFactoryAdapter, потому что он возвращает Elem вместо Node, что позволяет мне добавлять атрибуты.Итак:

import org.xml.sax.Locator
import scala.xml._
import parsing.NoBindingFactoryAdapter
trait WithLocation extends NoBindingFactoryAdapter {
    var locator: org.xml.sax.Locator = _

    // Get location
    abstract override def setDocumentLocator(locator: Locator) {
        this.locator = locator
        super.setDocumentLocator(locator)
    }

    abstract override def createNode(pre: String, label: String, attrs: MetaData, scope: NamespaceBinding, children: List[Node]): Elem = (
        super.createNode(pre, label, attrs, scope, children) 
        % Attribute("line", Text(locator.getLineNumber.toString), Null) 
        % Attribute("column", Text(locator.getColumnNumber.toString), Null)
    )
}

object MyLoader extends factory.XMLLoader[Elem] {
    // Keeping ConsoleErrorHandler for good measure
    override def adapter = new parsing.NoBindingFactoryAdapter with parsing.ConsoleErrorHandler with WithLocation
}

Результат:

scala> MyLoader.loadString("<a><b/></a>")
res4: scala.xml.Elem = <a line="1" column="12"><b line="1" column="8"></b></a>

Обратите внимание, что он получил последнее местоположение, которое находится в закрывающем теге.Это одна вещь, которую можно улучшить, переопределив startElement, чтобы отслеживать, где каждый элемент начал в стеке, и endElement, чтобы выскочить из этого стека в var, используемый createNode.

Хороший вопросЯ многому научился!: -)

4 голосов
/ 15 декабря 2010

I вижу, что scala внутренне использует SAX для анализа.SAX позволяет вам установить Locator на ContentHandler , который может использоваться для получения текущего местоположения , в котором произошла ошибка .Я не уверен, как вы можете использовать внутреннюю работу Scala. Вот одна статья Я обнаружил, что это может помочь понять, выполнимо ли это.

2 голосов
/ 15 декабря 2010

Несмотря на то, что вы указали, что не хотите использовать другую библиотеку или среду, стоит отметить, что все хорошие потоковые парсеры Java (Xerces для Sax, Woodstox и Aalto для Stax) действительно предоставляют информацию о местоположении для всех событий / токенов, которые ониserve.

Хотя эта информация не всегда сохраняется в абстракциях более высокого уровня, таких как деревья DOM (из-за необходимости дополнительного хранилища; производительность не представляет большой проблемы, так как информация о местоположении всегда отслеживается, так как в любом случае необходима для сообщения об ошибках).) это может быть легко или, по крайней мере, возможно исправить.

2 голосов
/ 15 декабря 2010

Я ничего не знаю о Scala, но такая же проблема возникает в других средах.Например, преобразование XML отправляет свои результаты по конвейеру SAX в валидатор, и когда валидатор пытается найти номера строк для ошибок валидации, они исчезают.Или рассматриваемый XML никогда не сериализовался и не анализировался, и поэтому никогда не имел номеров строк.

Один из способов решения этой проблемы - генерирование (удобочитаемых) выражений XPath, указывающих, где произошла ошибка.Их не так просто использовать, как номера строк, но они намного лучше, чем ничего: они однозначно идентифицируют узел, и их часто довольно легко интерпретировать людям (особенно если у них есть редактор XML).

Например, этот шаблон XSLT Кена Холмана (я думаю), используемый Schematron, генерирует выражение XPath для описания местоположения / идентификатора узла контекста:

<xsl:template match="node() | @*" mode="schematron-get-full-path-2">
   <!--report the element hierarchy-->
   <xsl:for-each select="ancestor-or-self::*">
      <xsl:text>/</xsl:text>
      <xsl:value-of select="name(.)"/>
      <xsl:if test="preceding-sibling::*[name(.)=name(current())]">
         <xsl:text>[</xsl:text>
         <xsl:value-of
            select="count(preceding-sibling::*[name(.)=name(current())])+1"/>
         <xsl:text>]</xsl:text>
      </xsl:if>
   </xsl:for-each>
   <!--report the attribute-->
   <xsl:if test="not(self::*)">
      <xsl:text/>/@<xsl:value-of select="name(.)"/>
   </xsl:if>
</xsl:template>

Я не знаю, если выможет использовать XSLT в вашем сценарии, но вы можете применить тот же принцип к любым имеющимся инструментам.

...