C # XmlReader читает XML неправильно и отличается на основе того, как я вызываю методы читателя - PullRequest
2 голосов
/ 28 октября 2019

Итак, мое текущее понимание того, как работает C # XmlReader, заключается в том, что он берет данный XML-файл и читает его узел за узлом, когда я заключаю его в следующую конструкцию:

using System.Xml;
using System;
using System.Diagnostics;
...
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true;
using (XmlReader reader = XmlReader.Create(path, settings))
{
    while (reader.Read())
    {
        // All reader methods I call here will reference the current node
        // until I move the pointer to some further node by calling methods like
        // reader.Read(), reader.MoveToContent(), reader.MoveToElement() etc
    }
}

ПочемуБудут ли следующие два фрагмента (в приведенной выше конструкции) давать два очень разных результата, даже если они оба будут вызывать одни и те же методы?

Я использовал этот пример файла для тестирования.

Debug.WriteLine(new string(' ', reader.Depth * 2) + "<" + reader.NodeType.ToString() + "|" + reader.Name + ">" + reader.ReadString() + "</>");

(фрагмент 1)
против
(фрагмент 2)

string xmlcontent = reader.ReadString();
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Debug.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");

Вывод фрагмента 1:

<XmlDeclaration|xml></>
<Element|rss></>
    <Element|head></>
        <Text|>Test Xml File</>
      <Element|description>This will test my xml reader</>
    <EndElement|head></>
    <Element|body></>
        <Element|g:id>1QBX23</>
        <Element|g:title>Example Title</>
        <Element|g:description>Example Description</>
      <EndElement|item></>
      <Element|item></>
          <Text|>2QXB32</>
        <Element|g:title>Example Title</>
        <Element|g:description>Example Description</>
      <EndElement|item></>
    <EndElement|body></>
  <EndElement|xml></>
<EndElement|rss></>

Да, он отформатирован так, как это было в моем окне вывода. Как видно, он пропустил некоторые элементы и вывел неправильную глубину для нескольких других. Таким образом, NodeTypes верны, в отличие от Snippet Number 2, который выдает:

<XmlDeclaration|xml></>
  <Element|xml></>
      <Element|title></>
      <EndElement|title>Test Xml File</>
      <EndElement|description>This will test my xml reader</>
    <EndElement|head></>
      <Element|item></>
        <EndElement|g:id>1QBX23</>
        <EndElement|g:title>Example Title</>
        <EndElement|g:description>Example Description</>
      <EndElement|item></>
        <Element|g:id></>
        <EndElement|g:id>2QXB32</>
        <EndElement|g:title>Example Title</>
        <EndElement|g:description>Example Description</>
      <EndElement|item></>
    <EndElement|body></>
  <EndElement|xml></>
<EndElement|rss></>

Еще раз, глубина перепутана, но это не так критично, как с Snippet Number 1. Также пропущены некоторые элементы иназначил неправильный NodeTypes.

Почему он не может выдать ожидаемый результат? И почему эти два фрагмента дают два совершенно разных вывода с различной глубиной, NodeTypes и пропущенными узлами?
Буду признателен за любую помощь в этом. Я много искал ответы на этот вопрос, но мне кажется, что я единственный, кто испытывает эти проблемы. Я использую .NET Framework 4.6.2 с веб-формами Asp.net в Visual Studio 2017.

1 Ответ

2 голосов
/ 28 октября 2019

Во-первых, вы используете метод XmlReader.ReadString() , который устарел :

Метод XmlReader.ReadString

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

Однако, помимо предупреждения нас о методе,документация точно не определяет, что на самом деле делает. Чтобы определить это, нам нужно перейти к эталонному источнику :

public virtual  string  ReadString() {
    if (this.ReadState != ReadState.Interactive) {
        return string.Empty;
    }
    this.MoveToElement();
    if (this.NodeType == XmlNodeType.Element) {
        if (this.IsEmptyElement) {
            return string.Empty;
        }
        else if (!this.Read()) {
            throw new InvalidOperationException(Res.GetString(Res.Xml_InvalidOperation));
        }
        if (this.NodeType == XmlNodeType.EndElement) {
            return string.Empty;
        }
    }
    string result = string.Empty;
    while (IsTextualNode(this.NodeType)) {
        result += this.Value;
        if (!this.Read()) {
            break;
        }
    }
    return result;
}

Этот метод выполняет следующие действия:

  1. Если текущий узелявляется пустым элементом узла, возвращает пустую строку.

  2. Если текущий узел является непустым элементом, продвигает читателя .

    Если текущий текущий узел является концом элемента, верните пустую строку.

  3. Пока текущий узел является текстовым узлом, добавьте текст встрока и продвигают читателя . Как только текущий узел не является текстовым узлом, верните накопленную строку.

Таким образом, мы видим, что этот метод предназначен для продвижения читателя . Мы также можем видеть, что, учитывая смешанный контент XML, такой как <head>text <b>BOLD</b> more text</head>, ReadString() будет только частично читать элемент <head>, оставляя читателя в положении <b>. Вероятно, из-за этой странности Microsoft отказалась от этого метода.

Мы также видим, почему ваши два фрагмента функционируют по-разному. Во-первых, вы получаете reader.Depth и reader.NodeType перед вызовом ReadString() и продвижением читателя. Во втором вы получите эти свойства после продвижения читателя.

Поскольку ваша цель состоит в том, чтобы перебирать узлы и получать значение каждого, а не ReadString() или ReadElementContentAsString(), вам следует просто использовать XmlReader.Value:

получает текстовое значение текущего узла.

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

 string xmlcontent = reader.Value;
 string xmlname = reader.Name.ToString();
 string xmltype = reader.NodeType.ToString();
 int xmldepth = reader.Depth;
 Console.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");

XmlReader сложно работать. Вы всегда должны проверять документацию, чтобы точно определить, где данный метод позиционирует читателя. Например, XmlReader.ReadElementContentAsString() перемещает считыватель за концом элемента, тогда как XmlReader.ReadSubtree() перемещает считыватель к концуэлемента. Но, как правило, любой метод с именем Read продвигает читателя, поэтому вы должны быть осторожны, используя метод Read внутри внешнего цикла while (reader.Read()).

Демонстрационная скрипка здесь.

...