Đọc Xml với XmlReader trong C #


97

Tôi đang cố đọc tài liệu Xml sau đây nhanh nhất có thể và để các lớp bổ sung quản lý việc đọc từng khối con.

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

Tuy nhiên, tôi đang cố gắng sử dụng đối tượng XmlReader để đọc từng Tài khoản và sau đó là "StatementsAvailable". Bạn có đề xuất sử dụng XmlReader.Read và kiểm tra từng phần tử và xử lý nó không?

Tôi đã nghĩ đến việc tách các lớp của mình để xử lý từng nút đúng cách. Vì vậy, có một lớp AccountBase chấp nhận một cá thể XmlReader đọc NameOfKin và một số thuộc tính khác về tài khoản. Sau đó, tôi muốn xem xét các Tuyên bố và để một lớp khác tự điền vào Tuyên bố (và sau đó thêm nó vào IList).

Cho đến nay, tôi đã thực hiện xong phần "per class" bằng cách thực hiện XmlReader.ReadElementString () nhưng tôi không thể tìm cách ra lệnh cho con trỏ di chuyển đến phần tử StatementsAvailable và hãy để tôi lặp lại chúng và để cho một lớp khác đọc từng lệnh đó .

Nghe có vẻ dễ dàng!


1
Nhấp vào dấu chấm hỏi màu cam ở góc trên bên phải của hộp chỉnh sửa để nhận trợ giúp chỉnh sửa. Có lẽ bạn muốn tạo một khối mã, được thực hiện trước tiên bằng một dòng trống và sau đó mỗi dòng được thụt lề với bốn khoảng trắng.
Anders Abel

hoặc chỉ cần chọn các dòng mã / XML của bạn và sau đó nhấp vào nút "mã" (101 010) trong thanh công cụ của trình soạn thảo - đơn giản như vậy!
marc_s

Câu trả lời:


163

Kinh nghiệm của tôi XmlReaderlà rất dễ vô tình đọc quá nhiều. Tôi biết bạn đã nói rằng bạn muốn đọc nó nhanh nhất có thể, nhưng bạn đã thử sử dụng mô hình DOM để thay thế chưa? Tôi nhận thấy rằng LINQ to XML làm cho XML hoạt động dễ dàng hơn nhiều .

Nếu tài liệu của bạn đặc biệt lớn, bạn có thể kết hợp XmlReadervà LINQ sang XML bằng cách tạo một XElementtừ an XmlReadercho mỗi phần tử "bên ngoài" của bạn theo cách trực tuyến: điều này cho phép bạn thực hiện hầu hết công việc chuyển đổi trong LINQ sang XML, nhưng vẫn chỉ cần một phần nhỏ của tài liệu trong bộ nhớ cùng một lúc. Đây là một số mã mẫu (được điều chỉnh một chút từ bài đăng trên blog này ):

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

Tôi đã sử dụng điều này để chuyển đổi dữ liệu người dùng StackOverflow (rất lớn) sang một định dạng khác trước đây - nó hoạt động rất tốt.

CHỈNH SỬA từ radarbob, được định dạng lại bởi Jon - mặc dù không rõ ràng vấn đề "đọc quá xa" nào đang được đề cập đến ...

Điều này sẽ đơn giản hóa việc lồng và giải quyết vấn đề "đọc quá xa".

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

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

    reader.ReadEndElement();
}

Điều này giải quyết vấn đề "đọc quá xa" vì nó triển khai mẫu vòng lặp while cổ điển:

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

17
Gọi XNode.ReadFrom đọc phần tử và chuyển đến phần tử tiếp theo, sau đó trình đọc sau.Read () đọc lại phần tử tiếp theo. Về cơ bản, bạn sẽ bỏ lỡ một phần tử nếu chúng trùng tên và liên tiếp.
pbz

3
@pbz: Cảm ơn. Tôi không chắc mình tin tưởng để chỉnh sửa nó một cách chính xác (đó là điều tôi không thích XmlReader :) Bạn có thể chỉnh sửa nó một cách chính xác không?
Jon Skeet,

1
@JonSkeet - Tôi có thể thiếu thứ gì đó nhưng sẽ không chỉ đơn giản là thay đổi if(reader.Name == elementName)để while(reader.Name == elementName)khắc phục sự cố do pbz chỉ ra?
David McLean

1
@pbz: Tôi đã thay đổi dòng: XElement el = XNode.ReadFrom (reader) thành XElement; trở thành: XElement el = XElement.Load (reader.ReadSubtree ()); vì điều này sẽ sửa lỗi bỏ qua các phần tử liên tiếp.
Dylan Hogg

1
Như đã đề cập trong các nhận xét khác, phiên bản hiện tại của SimpleStreamAxis()sẽ bỏ qua các phần tử khi XML không được thụt lề, bởi vì Node.ReadFrom()vị trí người đọc ở nút tiếp theo sau khi phần tử được tải - sẽ bị bỏ qua vô điều kiện tiếp theo Read(). Nếu nút tiếp theo là khoảng trắng thì tất cả đều ổn. Nếu không, không phải. Đối với các phiên bản không có vấn đề này, hãy xem tại đây , tại đây hoặc tại đây .
dbc

29

Ba năm sau, có lẽ với sự nhấn mạnh mới vào dữ liệu WebApi và xml, tôi đã bắt gặp câu hỏi này. Vì codewise, tôi có xu hướng theo dõi Skeet ra khỏi máy bay mà không có dù và thấy mã ban đầu của anh ấy được chứng thực gấp đôi bởi bài báo của nhóm MS Xml cũng như ví dụ trong BOL Streaming Transform của Large Xml Docs , tôi đã nhanh chóng bỏ qua các nhận xét khác , cụ thể nhất là từ 'pbz', người đã chỉ ra rằng nếu bạn có các phần tử giống nhau theo tên liên tiếp, mọi phần tử khác sẽ bị bỏ qua vì đọc kép. Và trên thực tế, cả hai bài blog BOL và MS đều đang phân tích cú pháp các tài liệu nguồn với các phần tử đích được lồng sâu hơn mức thứ hai, che đi tác dụng phụ này.

Các câu trả lời khác giải quyết vấn đề này. Tôi chỉ muốn cung cấp một bản sửa đổi đơn giản hơn một chút, có vẻ như hoạt động tốt cho đến nay và có tính đến việc xml có thể đến từ các nguồn khác nhau, không chỉ là một phần mở rộng và do đó, tiện ích mở rộng hoạt động trên XmlReader do người dùng quản lý. Một giả định là trình đọc đang ở trạng thái ban đầu, vì nếu không thì 'Read ()' đầu tiên có thể vượt qua một nút mong muốn:

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
Câu lệnh "if (reader.Name.Equals (elementName))" của bạn thiếu câu lệnh "else reader.Read ();" tương ứng tuyên bố. Nếu yếu tố không phải là những gì bạn muốn, bạn muốn tiếp tục đọc. Đó là những gì tôi phải thêm vào để làm cho nó hoạt động với tôi.
Wes

1
@Wes Đã khắc phục sự cố bằng cách thu gọn hai điều kiện (NodeType và Name) để else Read()áp dụng cho cả hai. Cảm ơn vì đã nắm bắt được điều đó.
mdisibio

1
Tôi ủng hộ bạn, nhưng tôi không hài lòng lắm khi thấy lệnh gọi phương thức Đọc được viết hai lần. Có thể bạn có thể sử dụng một vòng lặp do while ở đây? :)
nawfal

Một câu trả lời mà chú ý và giải quyết cùng một vấn đề với các tài liệu MSDN: stackoverflow.com/a/18282052/3744182
dbc

17

Chúng tôi thực hiện loại phân tích cú pháp XML này mọi lúc. Chìa khóa là xác định vị trí mà phương pháp phân tích cú pháp sẽ khiến người đọc thoát. Nếu bạn luôn để người đọc ở phần tử tiếp theo sau phần tử được đọc đầu tiên thì bạn có thể đọc một cách an toàn và có thể đoán được trong luồng XML. Vì vậy, nếu trình đọc hiện đang lập chỉ mục <Account>phần tử, sau khi phân tích cú pháp, trình đọc sẽ lập chỉ mục </Accounts>thẻ đóng.

Mã phân tích cú pháp trông giống như sau:

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

Các Statementslớp học chỉ đọc trong <StatementsAvailable>nút

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

Các Statementlớp học sẽ trông rất giống nhau

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

Đối với các đối tượng phụ, ReadSubtree()cung cấp cho bạn một trình đọc xml giới hạn đối với các đối tượng phụ, nhưng tôi thực sự nghĩ rằng bạn đang làm điều này một cách khó khăn. Trừ khi bạn có yêu cầu rất cụ thể để xử lý xml bất thường / không đáng tin cậy, hãy sử dụng XmlSerializer(có thể kết hợp với sgen.exenếu bạn thực sự muốn).

XmlReaderlà ... khôn lanh. Đôi nghịch vơi:

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

Ví dụ sau điều hướng thông qua luồng để xác định loại nút hiện tại, sau đó sử dụng XmlWriter để xuất nội dung 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();

Ví dụ sau sử dụng các phương thức XmlReader để đọc nội dung của các phần tử và thuộc tính.

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

Bạn có thể lặp qua xmlnode và lấy dữ liệu ...... C # XML Reader


4
Lớp này không được dùng nữa. Không được dùng.
nawfal

@Elvarism Có nhiều cách đọc xml khác trong trang web mà bạn chia sẻ và điều đó giúp ích cho tôi rất nhiều. Tôi sẽ bỏ phiếu cho bạn. Đây là một ví dụ dễ hiểu khác về XmlReader .
劉鎮 瑲

0

Tôi không có kinh nghiệm. Nhưng tôi nghĩ XmlReader là không cần thiết. Nó rất khó sử dụng.
XElement rất dễ sử dụng.
Nếu bạn cần hiệu suất (nhanh hơn), bạn phải thay đổi định dạng tệp và sử dụng các lớp StreamReader và StreamWriter.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.