การอ่าน Xml ด้วย XmlReader ใน C #


98

ฉันพยายามอ่านเอกสาร Xml ต่อไปนี้ให้เร็วที่สุดและปล่อยให้ชั้นเรียนเพิ่มเติมจัดการการอ่านของแต่ละบล็อกย่อย

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

อย่างไรก็ตามฉันกำลังพยายามใช้ออบเจ็กต์ XmlReader เพื่ออ่านแต่ละบัญชีและต่อมา "StatementsAvailable" คุณแนะนำให้ใช้ XmlReader อ่านและตรวจสอบแต่ละองค์ประกอบและจัดการหรือไม่

ฉันคิดจะแยกชั้นเรียนเพื่อจัดการกับแต่ละโหนดอย่างเหมาะสม ดังนั้นจึงเป็นคลาส AccountBase ที่ยอมรับอินสแตนซ์ XmlReader ที่อ่าน NameOfKin และคุณสมบัติอื่น ๆ เกี่ยวกับบัญชี จากนั้นฉันก็ต้องการที่จะโต้ตอบผ่านคำสั่งและให้ชั้นเรียนอื่นกรอกข้อมูลเกี่ยวกับคำชี้แจง (และต่อมาเพิ่มไปยัง IList)

จนถึงตอนนี้ฉันมีส่วน "ต่อคลาส" ที่ทำโดยการทำ XmlReader.ReadElementString () แต่ฉันไม่สามารถออกกำลังกายได้ว่าจะบอกให้ตัวชี้ย้ายไปยังองค์ประกอบ StatementsAvailable ได้อย่างไรและให้ฉันทำซ้ำผ่านพวกเขาและให้ชั้นเรียนอื่นอ่านแต่ละส่วนขององค์ประกอบเหล่านั้น .

ฟังดูง่าย!


1
คลิกเครื่องหมายคำถามสีส้มที่มุมขวาบนของช่องแก้ไขเพื่อรับความช่วยเหลือในการแก้ไข คุณอาจต้องการสร้างบล็อกโค้ดซึ่งทำได้โดยการเว้นบรรทัดว่างก่อนแล้วแต่ละบรรทัดเยื้องด้วยช่องว่างสี่ช่อง
Anders Abel

หรือเพียงแค่เลือกบรรทัดของโค้ด / XML จากนั้นคลิกที่ปุ่ม "รหัส" (101 010) ในแถบเครื่องมือตัวแก้ไข - ง่ายๆแค่นั้น
marc_s

คำตอบ:


164

ประสบการณ์ของฉันXmlReaderคือมันง่ายมากที่จะเผลออ่านมากเกินไป ฉันรู้ว่าคุณบอกว่าคุณต้องการอ่านโดยเร็วที่สุด แต่คุณได้ลองใช้โมเดล DOM แทนหรือไม่? ฉันพบว่า LINQ เป็น XML ทำให้ XML ทำงานได้มากได้ง่ายขึ้น

หากเอกสารของคุณมีขนาดใหญ่เป็นพิเศษคุณสามารถรวมXmlReaderและ LINQ เป็น XML ได้โดยการสร้างXElementจากXmlReaderองค์ประกอบ "ภายนอก" ของคุณในลักษณะการสตรีมซึ่งจะช่วยให้คุณสามารถแปลงงานส่วนใหญ่ใน LINQ เป็น XML ได้ แต่ก็ยังต้องการเพียง ส่วนเล็ก ๆ ของเอกสารในหน่วยความจำในคราวเดียว นี่คือโค้ดตัวอย่าง (ดัดแปลงเล็กน้อยจากบทความในบล็อกนี้ ):

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 (ซึ่งมีขนาดใหญ่มาก) เป็นรูปแบบอื่นมาก่อน - ใช้งานได้ดีมาก

แก้ไขจากเรดาร์บ็อบจัดรูปแบบใหม่โดยจอน - แม้ว่าจะไม่ชัดเจนนักว่าปัญหา "อ่านไกลเกินไป" กำลังถูกอ้างถึง ...

สิ่งนี้จะทำให้การซ้อนง่ายขึ้นและดูแลปัญหา "อ่านไกลเกินไป"

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

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

    reader.ReadEndElement();
}

สิ่งนี้จะดูแลปัญหา "อ่านไกลเกินไป" เนื่องจากใช้รูปแบบ classic while loop:

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

17
การเรียกใช้ XNode.ReadFrom จะอ่านองค์ประกอบและไปที่รายการถัดไปจากนั้นโปรแกรมอ่านต่อไปนี้ Read () จะอ่านรายการถัดไปอีกครั้ง คุณจะพลาดองค์ประกอบเป็นหลักหากพวกเขามีชื่อเดียวกันและอยู่ติดกัน
pbz

3
@pbz: ขอบคุณ ฉันไม่แน่ใจว่าฉันเชื่อใจตัวเองว่าจะแก้ไขได้อย่างถูกต้อง (นั่นคือฉันไม่ชอบ XmlReader มากแค่ไหน :) คุณสามารถแก้ไขได้ถูกต้องหรือไม่?
Jon Skeet

1
@JonSkeet - ฉันอาจจะขาดอะไรไป แต่จะไม่เปลี่ยนเพียงif(reader.Name == elementName)เพื่อwhile(reader.Name == elementName)แก้ไขปัญหาที่ pbz ชี้?
David McLean

1
@pbz: ฉันเปลี่ยนบรรทัด: XElement el = XNode.ReadFrom (ผู้อ่าน) เป็น XElement; ที่จะเป็น: XElement el = XElement.Load (reader.ReadSubtree ()); เนื่องจากสิ่งนี้จะแก้ไขข้อบกพร่องของการข้ามองค์ประกอบที่ติดต่อกัน
Dylan Hogg

1
ตามที่ระบุไว้ในความคิดเห็นอื่น ๆ รุ่นปัจจุบันSimpleStreamAxis()จะข้ามองค์ประกอบเมื่อ XML จะไม่เยื้องเพราะNode.ReadFrom()ตำแหน่งผู้อ่านที่โหนดถัดไปหลังจากองค์ประกอบโหลด - Read()ซึ่งจะได้รับการข้ามโดยไม่มีเงื่อนไขต่อไป ถ้าโหนดถัดไปเป็นช่องว่างแสดงว่าทุกอย่างดี มิฉะนั้นไม่ สำหรับรุ่นโดยไม่มีปัญหานี้ดูที่นี่ , ที่นี่หรือที่นี่
dbc

29

สามปีต่อมาบางทีด้วยการเน้นย้ำเกี่ยวกับข้อมูล WebApi และ xml ฉันก็เจอคำถามนี้ เนื่องจากการเข้ารหัสฉันมีแนวโน้มที่จะติดตาม Skeet ออกจากเครื่องบินโดยไม่ใช้ร่มชูชีพและเมื่อเห็นรหัสเริ่มต้นของเขาได้รับการยืนยันเป็นสองเท่าจากบทความของทีม MS Xml รวมถึงตัวอย่างใน BOL Streaming Transform ของ Large Xml Docsฉันจึงมองข้ามความคิดเห็นอื่น ๆ ไปอย่างรวดเร็ว โดยเฉพาะอย่างยิ่งจาก 'pbz' ซึ่งชี้ให้เห็นว่าหากคุณมีองค์ประกอบที่เหมือนกันตามชื่ออย่างต่อเนื่ององค์ประกอบอื่น ๆ จะถูกข้ามไปเนื่องจากการอ่านซ้ำซ้อน และในความเป็นจริงบทความในบล็อก BOL และ MS ทั้งสองกำลังแยกวิเคราะห์เอกสารต้นฉบับที่มีองค์ประกอบเป้าหมายซ้อนอยู่ลึกกว่าระดับที่สองโดยปิดบังผลข้างเคียง

คำตอบอื่น ๆ ช่วยแก้ปัญหานี้ ฉันแค่อยากจะเสนอการแก้ไขที่ง่ายกว่าเล็กน้อยซึ่งดูเหมือนว่าจะทำงานได้ดีและคำนึงถึงว่า 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
ฉันโหวตให้คุณ แต่ฉันไม่พอใจมากที่เห็นการเรียกวิธีการอ่านเขียนสองครั้ง คุณสามารถใช้ do while loop ที่นี่ได้ไหม :)
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
        }       
    }
}

7

สำหรับย่อยวัตถุ, ReadSubtree()ช่วยให้คุณ XML-reader จำกัด การย่อยวัตถุ แต่ฉันจริงๆคิดว่าคุณกำลังทำวิธีการนี้ยาก เว้นแต่คุณจะมีข้อกำหนดที่เฉพาะเจาะจงมากสำหรับการจัดการ 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 {}}}}
        });
    }
}

4

ตัวอย่างต่อไปนี้นำทางผ่านสตรีมเพื่อกำหนดชนิดโหนดปัจจุบันจากนั้นใช้ 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 Reader


4
คลาสนี้เลิกใช้แล้ว ไม่ได้ใช้.
nawfal

@Elvarism มีวิธีอ่าน xml อื่น ๆ อีกมากมายในเว็บไซต์ที่คุณแบ่งปันและนั่นช่วยฉันได้มาก ฉันจะโหวตให้คุณ นี่คือตัวอย่างXmlReader ที่เข้าใจง่ายอีกตัวอย่างหนึ่ง
劉鎮瑲

0

ฉันไม่ได้รับประสบการณ์ แต่ฉันคิดว่า XmlReader ไม่จำเป็น ใช้งานยากมาก
XElement ใช้งานง่ายมาก
หากคุณต้องการประสิทธิภาพ (เร็วกว่า) คุณต้องเปลี่ยนรูปแบบไฟล์และใช้คลาส StreamReader และ StreamWriter

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.