[c#] C #에서 XmlReader로 Xml 읽기

가능한 한 빨리 다음 Xml 문서를 읽고 추가 클래스가 각 하위 블록의 읽기를 관리하도록하려고합니다.

<ApplicationPool>
    <Accounts>
        <Account>
            <NameOfKin></NameOfKin>
            <StatementsAvailable>
                <Statement></Statement>
            </StatementsAvailable>
        </Account>
    </Accounts>
</ApplicationPool>

그러나 XmlReader 개체를 사용하여 각 계정을 읽은 후 “StatementsAvailable”을 읽으려고합니다. XmlReader.Read를 사용하여 각 요소를 확인하고 처리 할 것을 제안합니까?

각 노드를 올바르게 처리하기 위해 클래스를 분리하는 것을 생각했습니다. 따라서 NameOfKin 및 계정에 대한 여러 다른 속성을 읽는 XmlReader 인스턴스를 허용하는 AccountBase 클래스가 있습니다. 그런 다음 Statements를 통해 상호 작용하고 다른 클래스가 Statement에 대해 자체적으로 채우고 IList에 추가하고 싶었습니다.

지금까지 XmlReader.ReadElementString ()을 수행하여 “클래스 별”부분을 수행했지만 포인터가 StatementsAvailable 요소로 이동하도록 지시하고이를 반복하여 다른 클래스가 각 속성을 읽도록하는 방법을 연습 할 수 없습니다. .

쉽게 들리 네요!



답변

내 경험은 XmlReader실수로 너무 많이 읽는 것이 매우 쉽다는 것입니다. 난 당신이 가능한 한 빨리 그것을 읽고 싶은 말한 알아,하지만 당신은 시도 대신 DOM 모델을 사용하고 계십니까? LINQ to XML을 사용하면 XML 작업이 훨씬 쉬워진다 는 것을 알게되었습니다 .

문서가 특히 큰 경우에는 결합 할 수 XmlReader및 LINQ XML로 작성하여 XElement에서 XmlReader스트리밍 방식으로 “외부”각 요소를 들어 : 이것은 당신이 XML에 LINQ의 변환 작업의 대부분을 수행 할 수 있습니다,하지만 여전히 필요 한 번에 문서의 작은 부분을 메모리에 저장합니다. 다음은 몇 가지 샘플 코드입니다 ( 이 블로그 게시물 에서 약간 수정 됨 ).

static IEnumerable<XElement> SimpleStreamAxis(string inputUrl,
                                              string elementName)
{
  using (XmlReader reader = XmlReader.Create(inputUrl))
  {
    reader.MoveToContent();
    while (reader.Read())
    {
      if (reader.NodeType == XmlNodeType.Element)
      {
        if (reader.Name == elementName)
        {
          XElement el = XNode.ReadFrom(reader) as XElement;
          if (el != null)
          {
            yield return el;
          }
        }
      }
    }
  }
}

이전에 StackOverflow 사용자 데이터 (거대한)를 다른 형식으로 변환하는 데 사용했습니다. 매우 잘 작동합니다.

Radarbob에서 편집, Jon에 의해 재 포맷 됨- “너무 멀리 읽음”문제가 언급되고있는 것은 확실하지 않지만 …

이것은 중첩을 단순화하고 “너무 많이 읽음”문제를 처리해야합니다.

using (XmlReader reader = XmlReader.Create(inputUrl))
{
    reader.ReadStartElement("theRootElement");

    while (reader.Name == "TheNodeIWant")
    {
        XElement el = (XElement) XNode.ReadFrom(reader);
    }

    reader.ReadEndElement();
}

이것은 고전적인 while 루프 패턴을 구현하기 때문에 “너무 많이 읽음”문제를 처리합니다.

initial read;
(while "we're not at the end") {
    do stuff;
    read;
}


답변

3 년 후 아마도 WebApi와 xml 데이터에 대한 새로운 강조와 함께 저는이 질문을 발견했습니다. 코드 측면에서 나는 낙하산없이 비행기에서 Skeet을 따라 가고, 그의 초기 코드가 MS Xml 팀 기사와 BOL Streaming Transform of Large Xml Docs 의 예제에 의해 두 배로 통합되는 것을 보는 경향이 있기 때문에 다른 댓글을 매우 빠르게 간과했습니다. , 가장 구체적으로 말한 ‘pbz’는 이름별로 동일한 요소가 연속적으로 있으면 이중 읽기로 인해 다른 모든 요소는 건너 뜁니다. 실제로 BOL과 MS 블로그 기사는 둘 다 대상 요소가 2 단계보다 더 깊게 중첩 된 소스 문서를 구문 분석하여 이러한 부작용을 마스킹했습니다.

다른 답변은이 문제를 해결합니다. 지금까지 잘 작동하는 것처럼 보이는 약간 더 간단한 개정판을 제공하고 싶었고 xml이 uri가 아닌 다른 소스에서 올 수 있으므로 확장이 사용자 관리 XmlReader에서 작동한다는 점을 고려했습니다. 한 가지 가정은 독자가 초기 상태에 있다는 것입니다. 그렇지 않으면 첫 번째 ‘Read ()’가 원하는 노드를 지나갈 수 있습니다.

public static IEnumerable<XElement> ElementsNamed(this XmlReader reader, string elementName)
{
    reader.MoveToContent(); // will not advance reader if already on a content node; if successful, ReadState is Interactive
    reader.Read();          // this is needed, even with MoveToContent and ReadState.Interactive
    while(!reader.EOF && reader.ReadState == ReadState.Interactive)
    {
        // corrected for bug noted by Wes below...
        if(reader.NodeType == XmlNodeType.Element && reader.Name.Equals(elementName))
        {
             // this advances the reader...so it's either XNode.ReadFrom() or reader.Read(), but not both
             var matchedElement = XNode.ReadFrom(reader) as XElement;
             if(matchedElement != null)
                 yield return matchedElement;
        }
        else
            reader.Read();
    }
}


답변

우리는 항상 이런 종류의 XML 구문 분석을 수행합니다. 핵심은 구문 분석 방법이 종료시 판독기를 남겨 둘 위치를 정의하는 것입니다. 처음 읽은 요소 다음의 다음 요소에 항상 독자를두면 XML 스트림에서 안전하고 예측 가능하게 읽을 수 있습니다. 따라서 판독기가 현재 <Account>요소를 인덱싱하는 경우 파싱 ​​후 판독기는 </Accounts>닫는 태그를 인덱싱합니다 .

구문 분석 코드는 다음과 같습니다.

public class Account
{
    string _accountId;
    string _nameOfKin;
    Statements _statmentsAvailable;

    public void ReadFromXml( XmlReader reader )
    {
        reader.MoveToContent();

        // Read node attributes
        _accountId = reader.GetAttribute( "accountId" );
        ...

        if( reader.IsEmptyElement ) { reader.Read(); return; }

        reader.Read();
        while( ! reader.EOF )
        {
            if( reader.IsStartElement() )
            {
                switch( reader.Name )
                {
                    // Read element for a property of this class
                    case "NameOfKin":
                        _nameOfKin = reader.ReadElementContentAsString();
                        break;

                    // Starting sub-list
                case "StatementsAvailable":
                    _statementsAvailable = new Statements();
                    _statementsAvailable.Read( reader );
                    break;

                    default:
                        reader.Skip();
                }
            }
            else
            {
                reader.Read();
                break;
            }
        }
    }
}

Statements클래스는 단지 읽 <StatementsAvailable>노드

public class Statements
{
    List<Statement> _statements = new List<Statement>();

    public void ReadFromXml( XmlReader reader )
    {
        reader.MoveToContent();
        if( reader.IsEmptyElement ) { reader.Read(); return; }

        reader.Read();
        while( ! reader.EOF )
        {
            if( reader.IsStartElement() )
            {
                if( reader.Name == "Statement" )
                {
                    var statement = new Statement();
                    statement.ReadFromXml( reader );
                    _statements.Add( statement );
                }
                else
                {
                    reader.Skip();
                }
            }
            else
            {
                reader.Read();
                break;
            }
        }
    }
}

Statement클래스는 매우 같은 보일 것이다

public class Statement
{
    string _satementId;

    public void ReadFromXml( XmlReader reader )
    {
        reader.MoveToContent();

        // Read noe attributes
        _statementId = reader.GetAttribute( "statementId" );
        ...

        if( reader.IsEmptyElement ) { reader.Read(); return; }

        reader.Read();
        while( ! reader.EOF )
        {
            ....same basic loop
        }
    }
}


답변

하위 개체를 들어, ReadSubtree()당신에게 하위 개체로 제한하는 XML 판독기를 제공합니다,하지만 난 정말 이 어려운 방법을하고 있다고 생각합니다. 당신이하지 않는 매우 구체적인 특이한 / 예측 불가능한 XML을 처리하기위한 요구 사항, 사용은 XmlSerializer(아마도과 함께 sgen.exe당신이 정말로 원하는 경우).

XmlReader까다 롭습니다. 대비 :

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
public class ApplicationPool {
    private readonly List<Account> accounts = new List<Account>();
    public List<Account> Accounts {get{return accounts;}}
}
public class Account {
    public string NameOfKin {get;set;}
    private readonly List<Statement> statements = new List<Statement>();
    public List<Statement> StatementsAvailable {get{return statements;}}
}
public class Statement {}
static class Program {
    static void Main() {
        XmlSerializer ser = new XmlSerializer(typeof(ApplicationPool));
        ser.Serialize(Console.Out, new ApplicationPool {
            Accounts = { new Account { NameOfKin = "Fred",
                StatementsAvailable = { new Statement {}, new Statement {}}}}
        });
    }
}


답변

다음 예제에서는 스트림을 탐색하여 현재 노드 유형을 확인한 다음 XmlWriter를 사용하여 XmlReader 콘텐츠를 출력합니다.

    StringBuilder output = new StringBuilder();

    String xmlString =
            @"<?xml version='1.0'?>
            <!-- This is a sample XML document -->
            <Items>
              <Item>test with a child element <more/> stuff</Item>
            </Items>";
    // Create an XmlReader
    using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
    {
        XmlWriterSettings ws = new XmlWriterSettings();
        ws.Indent = true;
        using (XmlWriter writer = XmlWriter.Create(output, ws))
        {

            // Parse the file and display each of the nodes.
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        writer.WriteStartElement(reader.Name);
                        break;
                    case XmlNodeType.Text:
                        writer.WriteString(reader.Value);
                        break;
                    case XmlNodeType.XmlDeclaration:
                    case XmlNodeType.ProcessingInstruction:
                        writer.WriteProcessingInstruction(reader.Name, reader.Value);
                        break;
                    case XmlNodeType.Comment:
                        writer.WriteComment(reader.Value);
                        break;
                    case XmlNodeType.EndElement:
                        writer.WriteFullEndElement();
                        break;
                }
            }

        }
    }
    OutputTextBlock.Text = output.ToString();

다음 예제에서는 XmlReader 메서드를 사용하여 요소 및 특성의 내용을 읽습니다.

StringBuilder output = new StringBuilder();

String xmlString =
    @"<bookstore>
        <book genre='autobiography' publicationdate='1981-03-22' ISBN='1-861003-11-0'>
            <title>The Autobiography of Benjamin Franklin</title>
            <author>
                <first-name>Benjamin</first-name>
                <last-name>Franklin</last-name>
            </author>
            <price>8.99</price>
        </book>
    </bookstore>";

// Create an XmlReader
using (XmlReader reader = XmlReader.Create(new StringReader(xmlString)))
{
    reader.ReadToFollowing("book");
    reader.MoveToFirstAttribute();
    string genre = reader.Value;
    output.AppendLine("The genre value: " + genre);

    reader.ReadToFollowing("title");
    output.AppendLine("Content of the title element: " + reader.ReadElementContentAsString());
}

OutputTextBlock.Text = output.ToString();


답변

    XmlDataDocument xmldoc = new XmlDataDocument();
    XmlNodeList xmlnode ;
    int i = 0;
    string str = null;
    FileStream fs = new FileStream("product.xml", FileMode.Open, FileAccess.Read);
    xmldoc.Load(fs);
    xmlnode = xmldoc.GetElementsByTagName("Product");

당신은 xmlnode를 반복하고 데이터를 얻을 수 있습니다 … C # XML 리더


답변

나는 경험이 없지만 XmlReader가 불필요하다고 생각합니다. 사용하기 매우 어렵습니다.
XElement는 사용하기 매우 쉽습니다.
성능 (빠름)이 필요한 경우 파일 형식을 변경하고 StreamReader 및 StreamWriter 클래스를 사용해야합니다.