วิธีที่ดีที่สุดในการรับ InnerXml ของ XElement


147

วิธีที่ดีที่สุดในการรับเนื้อหาของbodyองค์ประกอบผสมในรหัสด้านล่างคืออะไร องค์ประกอบอาจมี XHTML หรือข้อความ แต่ฉันต้องการเนื้อหาในรูปแบบสตริง XmlElementประเภทมีInnerXmlคุณสมบัติซึ่งเป็นสิ่งที่ฉันหลังจาก

รหัสตามที่เขียนเกือบจะเป็นสิ่งที่ฉันต้องการ แต่รวมถึงองค์ประกอบ<body>... โดยรอบ</body>ซึ่งฉันไม่ต้องการ

XDocument doc = XDocument.Load(new StreamReader(s));
var templates = from t in doc.Descendants("template")
                where t.Attribute("name").Value == templateName
                select new
                {
                   Subject = t.Element("subject").Value,
                   Body = t.Element("body").ToString()
                };

คำตอบ:


208

ฉันต้องการดูว่าโซลูชันใดที่แนะนำเหล่านี้ทำงานได้ดีที่สุดดังนั้นฉันจึงทำการทดสอบเปรียบเทียบ ฉันยังได้เปรียบเทียบวิธี LINQ กับวิธีการแบบเก่าSystem.Xml ที่ Greg แนะนำ รูปแบบที่เป็นที่น่าสนใจและไม่ใช่สิ่งที่ผมคาดว่าจะด้วยวิธีการที่ช้าที่สุดเป็นมากกว่า 3 ครั้งช้ากว่าที่เร็วที่สุด

ผลลัพธ์เรียงลำดับโดยเร็วที่สุดไปช้าที่สุด:

  1. CreateReader - อินสแตนซ์ฮันเตอร์ (0.113 วินาที)
  2. ระบบเก่าแบบธรรมดา Xml - Greg Hurlman (0.134 วินาที)
  3. รวมกับการต่อสตริง - Mike Powell (0.324 วินาที)
  4. StringBuilder - Vin (0.333 วินาที)
  5. สตริงเข้าร่วมอาร์เรย์ - เทอร์รี่ (0.360 วินาที)
  6. String.Concat ในอาร์เรย์ - Marcin Kosieradzki (0.364)

วิธี

ฉันใช้เอกสาร XML เดียวที่มี 20 โหนดเหมือนกัน (เรียกว่า 'คำใบ้'):

<hint>
  <strong>Thinking of using a fake address?</strong>
  <br />
  Please don't. If we can't verify your address we might just
  have to reject your application.
</hint>

ตัวเลขที่แสดงเป็นวินาทีข้างต้นเป็นผลลัพธ์ของการแยก "XML ภายใน" ของ 20 โหนด 1000 ครั้งในแถวและรับค่าเฉลี่ย (หมายถึง) จำนวน 5 การทำงาน ฉันไม่ได้รวมเวลาที่ใช้ในการโหลดและแยกวิเคราะห์ XML เป็นXmlDocument(สำหรับวิธีSystem.Xml ) หรือXDocument(สำหรับส่วนอื่น ๆ ทั้งหมด)

ขั้นตอนวิธี LINQ ที่ฉันใช้คือ: (C # - ทั้งหมดใช้XElement"parent" และส่งคืนสตริง XML ภายใน)

CreateReader:

var reader = parent.CreateReader();
reader.MoveToContent();

return reader.ReadInnerXml();

รวมกับการต่อสตริง:

return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

StringBuilder:

StringBuilder sb = new StringBuilder();

foreach(var node in parent.Nodes()) {
    sb.Append(node.ToString());
}

return sb.ToString();

สตริงเข้าร่วมในอาร์เรย์:

return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray());

String.Concat ในอาร์เรย์:

return String.Concat(parent.Nodes().Select(x => x.ToString()).ToArray());

ฉันไม่ได้แสดงอัลกอริทึม "Plain old System.Xml" ที่นี่เพราะมันเพิ่งเรียกว่า. InnerXml บนโหนด


ข้อสรุป

ถ้าประสิทธิภาพเป็นสิ่งสำคัญ (เช่นจำนวนมาก XML แจงบ่อย) ผมใช้ของแดเนียลCreateReaderวิธีการทุกครั้ง หากคุณเพียงแค่ทำแบบสอบถามสองสามข้อคุณอาจต้องการใช้วิธีการรวมที่กระชับยิ่งขึ้นของไมค์

หากคุณกำลังใช้ XML ในองค์ประกอบที่มีขนาดใหญ่ที่มีจำนวนมากของโหนด (อาจจะ 100), คุณอาจจะเริ่มต้นที่จะเห็นประโยชน์ของการใช้StringBuilderมากกว่าวิธีการรวม CreateReaderแต่ไม่เกิน ฉันไม่คิดว่าJoinและConcatวิธีการจะมีประสิทธิภาพมากขึ้นในเงื่อนไขเหล่านี้เนื่องจากโทษของการแปลงรายการขนาดใหญ่เป็นอาร์เรย์ขนาดใหญ่


รุ่น StringBuilder สามารถเขียนได้ในหนึ่งบรรทัด: var result = parent.Elements () รวม (StringBuilder ใหม่ (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString () ))
Softlion

7
คุณพลาดparent.CreateNavigator().InnerXml(จำเป็นต้องusing System.Xml.XPathใช้วิธีการขยาย)
Richard

ฉันไม่คิดว่าคุณต้องการสิ่งที่.ToArray()อยู่ข้างใน.Concatแต่ดูเหมือนจะทำให้เร็วขึ้น
drzaus

ในกรณีที่คุณไม่ได้เลื่อนไปด้านล่างของคำตอบเหล่านี้: พิจารณาเพียงแค่ปอกภาชนะ / รากจาก.ToString()ต่อคำตอบนี้ ดูเหมือนจะเร็วยิ่งขึ้น ...
drzaus

2
คุณควรห่อสิ่งนั้นvar reader = parent.CreateReader();ไว้ในคำแถลงที่ใช้
BrainSlugs83

70

ฉันคิดว่านี่เป็นวิธีที่ดีกว่ามาก (ใน VB ไม่ควรแปลยาก):

รับ XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml

ดี! นี่เร็วกว่าวิธีอื่น ๆ ที่เสนอ (ฉันทดสอบพวกเขาทั้งหมด - ดูคำตอบของฉันสำหรับรายละเอียด) แม้ว่าพวกเขาทั้งหมดจะทำงานได้ แต่อันนี้ก็ทำงานได้เร็วที่สุด - แม้จะเห็นเร็วกว่า System.Xml.Node.InnerXml เอง!
ลุค Sampson

4
XmlReader เป็นแบบใช้แล้วทิ้งดังนั้นอย่าลืมห่อด้วยการใช้โปรด (ฉันจะแก้ไขคำตอบด้วยตัวเองถ้าฉันรู้ว่า VB)
Dmitry Fedorkov

19

วิธีการเกี่ยวกับการใช้ "ส่วนขยาย" วิธีนี้ใน XElement ทำงานให้ฉัน!

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();

    foreach (XNode node in element.Nodes())
    {
        // append node's xml string to innerXml
        innerXml.Append(node.ToString());
    }

    return innerXml.ToString();
}

หรือใช้ Linq เล็กน้อย

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();
    doc.Nodes().ToList().ForEach( node => innerXml.Append(node.ToString()));

    return innerXml.ToString();
}

หมายเหตุ : รหัสดังกล่าวมีการใช้งานเมื่อเทียบกับelement.Nodes() element.Elements()สิ่งสำคัญมากที่ต้องจดจำความแตกต่างระหว่างสอง element.Nodes()ช่วยให้คุณมีทุกอย่างเหมือนXText, XAttributeฯลฯ แต่XElementเพียงองค์ประกอบ


15

ด้วยเครดิตที่ครบกำหนดให้กับผู้ที่ค้นพบและพิสูจน์แล้วว่าเป็นวิธีที่ดีที่สุด (ขอบคุณ!) นี่คือสรุปในวิธีการขยาย:

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}

10

ทำให้มันง่ายและมีประสิทธิภาพ:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • ผลรวมคือหน่วยความจำและประสิทธิภาพไม่มีประสิทธิภาพเมื่อต่อสตริงเข้าด้วยกัน
  • ใช้เข้าร่วม ("", sth) กำลังใช้อาร์เรย์สตริงที่ใหญ่กว่าสองเท่าของ Concat ... และดูแปลกในรหัส
  • การใช้ + = ดูแปลกมาก แต่เห็นได้ชัดว่าไม่เลวร้ายไปกว่าการใช้ '+' - อาจจะได้รับการปรับให้เหมาะกับรหัสเดียวกันเนื่องจากผลการมอบหมายไม่ได้ถูกใช้และอาจถูกลบออกอย่างปลอดภัยโดยคอมไพเลอร์
  • StringBuilder มีความจำเป็นอย่างยิ่ง - และทุกคนรู้ว่า "สถานะ" ไม่จำเป็นครับ

7

ฉันลงเอยด้วยการใช้สิ่งนี้:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());

นั่นจะทำให้การเรียงสตริงเป็นจำนวนมาก - ฉันต้องการใช้ StringBuilder ของ Vin เอง foreach แบบแมนนวลไม่ได้เป็นเชิงลบ
Marc Gravell

วิธีนี้ช่วยฉันได้จริงๆในวันนี้พยายามเขียน XElement กับนวกรรมิกตัวใหม่และไม่มีวิธีการอื่นใดที่ยืมตัวมาใช้อย่างคล่องแคล่วในขณะที่วิธีนี้ทำได้ ขอบคุณ!
delliottg

3

ส่วนตัวฉันลงเอยด้วยการเขียนInnerXmlวิธีการขยายโดยใช้วิธีการรวม:

public static string InnerXml(this XElement thiz)
{
   return thiz.Nodes().Aggregate( string.Empty, ( element, node ) => element += node.ToString() );
}

รหัสไคลเอนต์ของฉันเป็นเช่นนั้นสั้นตามที่ควรจะเป็นกับเนมสเปซ System.Xml เก่า:

var innerXml = myXElement.InnerXml();

2

@Greg: ดูเหมือนว่าคุณได้แก้ไขคำตอบของคุณให้เป็นคำตอบที่ต่างออกไปโดยสิ้นเชิง คำตอบของฉันคือใช่ฉันทำได้โดยใช้ System.Xml แต่หวังว่าจะทำให้เท้าฉันเปียกด้วย LINQ ถึง XML

ฉันจะทิ้งคำตอบดั้งเดิมไว้ด้านล่างในกรณีที่คนอื่นสงสัยว่าทำไมฉันไม่สามารถใช้คุณสมบัติ. VALUE ของ XElement เพื่อรับสิ่งที่ฉันต้องการ:

@Greg: คุณสมบัติค่าเชื่อมเนื้อหาข้อความทั้งหมดของโหนดลูกใด ๆ ดังนั้นหากองค์ประกอบของร่างกายมีเพียงข้อความก็ใช้งานได้ แต่ถ้ามันมี XHTML ฉันจะได้ข้อความทั้งหมดที่ต่อกันเข้าด้วยกัน แต่ไม่มีแท็กเลย


ฉันพบปัญหาเดียวกันนี้และคิดว่ามันเป็นข้อผิดพลาด: ฉันมีเนื้อหา 'ผสม' (เช่น<root>random text <sub1>child</sub1> <sub2>child</sub2></root>) ซึ่งกลายเป็นrandom text childchildผ่านXElement.Parse(...).Value
drzaus

1

// การใช้ Regex อาจเร็วกว่าการตัดแต่งแท็กองค์ประกอบเริ่มต้นและแท็กสิ้นสุด

var content = element.ToString();
var matchBegin = Regex.Match(content, @"<.+?>");
content = content.Substring(matchBegin.Index + matchBegin.Length);          
var matchEnd = Regex.Match(content, @"</.+?>", RegexOptions.RightToLeft);
content = content.Substring(0, matchEnd.Index);

1
เรียบร้อย เร็วยิ่งขึ้นเพียงแค่ใช้IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus

1

doc.ToString () หรือ doc.ToString (SaveOptions) ทำงานได้ ดูhttp://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx


ไม่มันไม่ นอกจากนี้ยังมีองค์ประกอบที่มีคุณสมบัติทั้งหมด ต้องการเฉพาะเนื้อหาระหว่างแท็กเริ่มต้นและแท็กสิ้นสุด
Christoph

0

เป็นไปได้หรือไม่ที่จะใช้ออบเจ็กต์ System.Xml เพื่อให้งานเสร็จที่นี่แทนที่จะใช้ LINQ ตามที่คุณพูดไปแล้ว XmlNode.InnerXml เป็นสิ่งที่คุณต้องการ


0

หากสงสัย (สังเกตว่าฉันกำจัด b + = และเพิ่งมี b +)

t.Element( "body" ).Nodes()
 .Aggregate( "", ( b, node ) => b + node.ToString() );

อาจมีประสิทธิภาพน้อยกว่าเล็กน้อย

string.Join( "", t.Element.Nodes()
                  .Select( n => n.ToString() ).ToArray() );

ไม่แน่ใจ 100% ... แต่ดูที่ Aggregate () และ string.Join () ใน Reflector ... ฉันคิดว่าฉันอ่านว่า Aggregate เพิ่งต่อท้ายค่าที่ส่งคืนดังนั้นคุณจะได้รับ:

string = string + string

เมื่อเทียบกับสตริงเข้าร่วมมันมีการกล่าวถึงใน FastStringAllocation หรือบางสิ่งบางอย่างซึ่งทำให้ฉันสิ่งที่คนที่ Microsoft อาจมีการเพิ่มประสิทธิภาพการทำงานบางอย่างที่นั่น แน่นอน. ToArray ของฉัน () โทรหาฉันปฏิเสธว่า แต่ฉันแค่อยากจะเสนอข้อเสนอแนะอื่น


0

คุณรู้? สิ่งที่ดีที่สุดที่ต้องทำคือกลับไปที่ CDATA :( ฉันกำลังมองหาวิธีแก้ปัญหาที่นี่ แต่ฉันคิดว่า CDATA นั้นง่ายและถูกที่สุดไม่ใช่วิธีที่สะดวกที่สุดในการพัฒนาด้วยสรรพสิ่ง


0
var innerXmlAsText= XElement.Parse(xmlContent)
                    .Descendants()
                    .Where(n => n.Name.LocalName == "template")
                    .Elements()
                    .Single()
                    .ToString();

จะทำงานให้คุณ


-2
public static string InnerXml(this XElement xElement)
{
    //remove start tag
    string innerXml = xElement.ToString().Trim().Replace(string.Format("<{0}>", xElement.Name), "");
    ////remove end tag
    innerXml = innerXml.Trim().Replace(string.Format("</{0}>", xElement.Name), "");
    return innerXml.Trim();
}

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