C #에서 XmlReader로 Xml 읽기


97

가능한 한 빨리 다음 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 요소로 이동하도록 지시하고이를 반복하여 다른 클래스가 각 속성을 읽도록하는 방법을 연습 할 수 없습니다. .

쉽게 들리 네요!


1
편집 도움말을 보려면 편집 상자의 오른쪽 상단 모서리에있는 주황색 물음표를 클릭하십시오. 아마도 당신은 코드 블록을 만들고 싶을 것입니다. 이것은 먼저 빈 줄로 수행되고 각 줄은 4 개의 공백으로 들여 쓰기됩니다.
Anders Abel

또는 코드 / XML 줄을 선택한 다음 편집기 도구 모음에서 "code"버튼 (101 010)을 클릭하면됩니다.
marc_s

답변:


163

내 경험은 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;
}

17
XNode.ReadFrom을 호출하면 요소를 읽고 다음 요소로 이동 한 다음 다음 reader.Read ()가 다음 요소를 다시 읽습니다. 이름이 같고 연속적이면 본질적으로 요소를 놓칠 것입니다.
pbz 2011-07-29

3
@pbz : 감사합니다. 올바르게 편집 할 수 있다고 확신 할 수 없습니다 (XmlReader를 싫어합니다. :) 올바르게 편집 할 수 있습니까?
Jon Skeet

1
@ JonSkeet-나는 뭔가를 놓치고 있을지도 모르지만 단순히 pbz가 지적한 문제 if(reader.Name == elementName)while(reader.Name == elementName)해결 하기 위해 변경하지 않을 것 입니까?
David McLean

1
@pbz : 다음 줄을 변경했습니다. XElement el = XNode.ReadFrom (reader) as XElement; 될 : XElement el = XElement.Load (reader.ReadSubtree ()); 이것은 연속 요소 건너 뛰기 버그를 수정하기 때문입니다.
딜런 호그

1
다른 주석에서 언급했듯이, 현재 버전은 SimpleStreamAxis()XML이 들여 쓰기되지 않은 경우 요소를 건너 뜁니다. 왜냐하면 요소가로드 된 Node.ReadFrom() 다음 노드에 리더를 배치 하기 때문 입니다. 다음 unconditional . 다음 노드가 공백이면 모든 것이 정상입니다. 그렇지 않으면 아닙니다. 이 문제가없는 버전은 여기 , 여기 또는 여기를 참조 하십시오 . Read()
dbc

29

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

1
"if (reader.Name.Equals (elementName))"문에 해당 "else reader.Read ();"가 없습니다. 성명서. 요소가 원하는 것이 아닌 경우 계속 읽으십시오. 그것이 나를 위해 작동하도록 추가해야했던 것입니다.
Wes

1
@Wes 두 조건 (NodeType 및 Name)을 축소하여 두 조건 else Read()모두에 적용 되도록 문제를 해결했습니다 . 잡아 주셔서 감사합니다.
mdisibio

1
나는 당신을 찬성했지만 Read 메서드 호출이 두 번 작성되는 것을별로 좋아하지 않습니다. 여기서 do while 루프를 사용할 수 있습니까? :)
nawfal

MSDN 문서에서 동일한 문제를 발견하고 해결 한 또 다른 답변 : stackoverflow.com/a/18282052/3744182
dbc

17

우리는 항상 이런 종류의 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
        }       
    }
}

6

하위 개체를 들어, 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 {}}}}
        });
    }
}

3

다음 예제에서는 스트림을 탐색하여 현재 노드 유형을 확인한 다음 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();

0
    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 리더


4
이 클래스는 더 이상 사용되지 않습니다. 사용하지 마세요.
nawfal

@Elvarism 당신이 공유하는 웹 사이트에는 다른 많은 읽기 xml 방법이 있으며, 그것은 저에게 많은 도움이됩니다. 투표하겠습니다. 쉽게 이해할 수있는 또 다른 XmlReader 예제입니다.
劉鎮瑲

0

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.