Cách tốt nhất để có được InternalXml của XEuity?


147

Cách tốt nhất để có được nội dung của bodyphần tử hỗn hợp trong mã dưới đây là gì? Phần tử có thể chứa XHTML hoặc văn bản, nhưng tôi chỉ muốn nội dung của nó ở dạng chuỗi. Các XmlElementloại có InnerXmltài sản đó là chính xác những gì tôi sau đó.

Mã như được viết gần như làm những gì tôi muốn, nhưng bao gồm phần tử <body>... xung quanh </body>, mà tôi không muốn.

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

Câu trả lời:


208

Tôi muốn xem những giải pháp được đề xuất nào hoạt động tốt nhất, vì vậy tôi đã chạy một số thử nghiệm so sánh. Không quan tâm, tôi cũng so sánh các phương thức LINQ với phương thức System.Xml cũ đơn giản được đề xuất bởi Greg. Biến thể này rất thú vị và không như tôi mong đợi, với các phương pháp chậm nhất chậm hơn 3 lần so với nhanh nhất .

Các kết quả được sắp xếp theo thứ tự nhanh nhất đến chậm nhất:

  1. CreatReader - Instance Hunter (0.113 giây)
  2. Hệ thống cũ đơn giản.Xml - Greg Hurlman (0.134 giây)
  3. Tổng hợp với nối chuỗi - Mike Powell (0,324 giây)
  4. StringBuilder - Vin (0,333 giây)
  5. String.Join trên mảng - Terry (0,360 giây)
  6. String.Concat trên mảng - Marcin Kosieradzki (0.364)

phương pháp

Tôi đã sử dụng một tài liệu XML duy nhất với 20 nút giống hệt nhau (được gọi là 'gợi ý'):

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

Các số được hiển thị như giây ở trên là kết quả của việc trích xuất "XML bên trong" của 20 nút, 1000 lần liên tiếp và lấy trung bình (trung bình) của 5 lần chạy. Tôi không bao gồm thời gian tải và phân tích cú pháp XML thành một XmlDocument(cho phương thức System.Xml ) hoặc XDocument(cho tất cả các phương thức khác).

Các thuật toán LINQ mà tôi đã sử dụng là: (C # - tất cả đều lấy XElement"cha mẹ" và trả về chuỗi XML bên trong)

Trình tạo:

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

return reader.ReadInnerXml();

Tổng hợp với nối chuỗi:

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

String.Join trên mảng:

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

String.Concat trên mảng:

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

Tôi chưa chỉ ra thuật toán "Plain old System.Xml" ở đây vì nó chỉ gọi .InnerXml trên các nút.


Phần kết luận

Nếu hiệu suất là quan trọng (ví dụ: nhiều XML, được phân tích cú pháp thường xuyên), tôi sẽ sử dụng CreateReaderphương pháp của Daniel mỗi lần . Nếu bạn chỉ đang thực hiện một vài truy vấn, bạn có thể muốn sử dụng phương pháp Tổng hợp ngắn gọn hơn của Mike.

Nếu bạn đang sử dụng XML trên các phần tử lớn có nhiều nút (có thể là 100), có lẽ bạn sẽ bắt đầu thấy lợi ích của việc sử dụng StringBuilderphương thức Tổng hợp, nhưng không kết thúc CreateReader. Tôi không nghĩ rằng các phương pháp JoinConcatsẽ trở nên hiệu quả hơn trong các điều kiện này vì hình phạt chuyển đổi một danh sách lớn thành một mảng lớn (thậm chí rõ ràng ở đây với các danh sách nhỏ hơn).


Phiên bản StringBuilder có thể được viết trên một dòng: var result = Parent.Elements (). Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ( ))
Softlion

7
Bạn đã bỏ lỡ parent.CreateNavigator().InnerXml(cần using System.Xml.XPathcho phương pháp mở rộng).
Richard

Tôi sẽ không nghĩ rằng bạn cần .ToArray()bên trong .Concat, nhưng dường như làm cho nó nhanh hơn
drzaus

Trong trường hợp bạn không cuộn xuống cuối câu trả lời sau: hãy xem xét việc tước container / root từ .ToString()mỗi câu trả lời này . Có vẻ còn nhanh hơn ...
drzaus

2
Bạn nên thực sự gói nó var reader = parent.CreateReader();trong một tuyên bố sử dụng.
BrainSlugs83 18/03/2015

70

Tôi nghĩ rằng đây là một phương pháp tốt hơn nhiều (trong VB, không nên khó dịch):

Đưa ra một XEuity x:

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

Đẹp! Điều này nhanh hơn rất nhiều so với một số phương pháp khác được đề xuất (tôi đã thử nghiệm tất cả - xem câu trả lời của tôi để biết chi tiết). Mặc dù tất cả đều thực hiện công việc, nhưng điều này thực hiện nhanh nhất - thậm chí còn nhanh hơn cả System.Xml.Node.InnerXml!
Luke Sampson

4
XmlReader là dùng một lần, vì vậy đừng quên bọc nó bằng cách sử dụng, xin vui lòng (tôi sẽ tự chỉnh sửa câu trả lời nếu tôi biết VB).
Dmitry Fedorkov

19

Làm thế nào về việc sử dụng phương pháp "mở rộng" này trên XEuity? đã làm cho tôi !

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

HOẶC sử dụng một chút Linq

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

    return innerXml.ToString();
}

Lưu ý : Mã ở trên phải sử dụng element.Nodes()trái ngược với element.Elements(). Điều rất quan trọng để nhớ sự khác biệt giữa hai. element.Nodes()cung cấp cho bạn mọi thứ như XText, XAttributev.v., nhưng XElementchỉ một phần tử.


15

Với tất cả các khoản tín dụng cho những người đã khám phá và chứng minh cách tiếp cận tốt nhất (cảm ơn!), Ở đây, nó được gói gọn trong một phương pháp mở rộng:

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

10

Giữ cho nó đơn giản và hiệu quả:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Uẩn là bộ nhớ và hiệu suất không hiệu quả khi nối chuỗi
  • Sử dụng Tham gia ("", sth) đang sử dụng mảng chuỗi lớn hơn hai lần so với Concat ... Và trông mã khá lạ.
  • Sử dụng + = trông rất kỳ quặc, nhưng rõ ràng không tệ hơn nhiều so với sử dụng '+' - có thể sẽ được tối ưu hóa cho cùng một mã, kết quả gán becase không được sử dụng và có thể được trình biên dịch xóa an toàn.
  • StringBuilder rất bắt buộc - và mọi người đều biết rằng "trạng thái" không cần thiết là hút.

7

Tôi đã kết thúc bằng cách sử dụng này:

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

Điều đó sẽ tạo ra nhiều kết nối chuỗi - bản thân tôi thích sử dụng StringBuilder của Vin hơn. Hướng dẫn sử dụng không phải là một tiêu cực.
Marc Gravell

Phương pháp này thực sự đã cứu tôi ngày hôm nay, cố gắng viết ra một XE đắp với nhà xây dựng mới và không có phương pháp nào khác được cho vay một cách cẩn thận, trong khi phương pháp này đã làm. Cảm ơn!
delliottg

3

Cá nhân, cuối cùng tôi đã viết một InnerXmlphương thức mở rộng bằng phương pháp Tổng hợp:

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

Mã máy khách của tôi sau đó cũng ngắn gọn như với không gian tên System.Xml cũ:

var innerXml = myXElement.InnerXml();

2

@Greg: Có vẻ như bạn đã chỉnh sửa câu trả lời của mình thành một câu trả lời hoàn toàn khác. Câu trả lời của tôi là có, tôi có thể làm điều này bằng System.Xml nhưng hy vọng sẽ làm ướt chân tôi với LINQ sang XML.

Tôi sẽ để lại câu trả lời ban đầu của mình bên dưới trong trường hợp bất kỳ ai khác tự hỏi tại sao tôi không thể sử dụng thuộc tính .Value của XEuity để có được thứ tôi cần:

@Greg: Thuộc tính Giá trị nối tất cả nội dung văn bản của bất kỳ nút con nào. Vì vậy, nếu phần tử cơ thể chỉ chứa văn bản, nó hoạt động, nhưng nếu nó chứa XHTML, tôi nhận được tất cả các văn bản được nối với nhau nhưng không có thẻ nào.


Tôi gặp phải vấn đề chính xác này và nghĩ rằng đó là một lỗi: Tôi có nội dung 'hỗn hợp' (nghĩa là <root>random text <sub1>child</sub1> <sub2>child</sub2></root>) đã trở thành random text childchildthông quaXElement.Parse(...).Value
drzaus

1

// sử dụng Regex có thể nhanh hơn để cắt thẻ phần tử bắt đầu và kết thúc

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
khéo léo. thậm chí nhanh hơn để chỉ sử dụng IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus


0

Có thể sử dụng các đối tượng không gian tên System.Xml để hoàn thành công việc ở đây thay vì sử dụng LINQ không? Như bạn đã đề cập, XmlNode.InnerXml chính xác là những gì bạn cần.


0

Tự hỏi nếu (chú ý tôi đã thoát khỏi b + = và chỉ có b +)

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

có thể kém hiệu quả hơn một chút

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

Không chắc chắn 100% ... nhưng liếc vào Aggregate () và chuỗi.Join () trong Reflector ... Tôi nghĩ rằng tôi đã đọc nó khi Aggregate chỉ nối thêm một giá trị trả về, vì vậy về cơ bản bạn sẽ nhận được:

chuỗi = chuỗi + chuỗi

so với chuỗi.Join, nó có một số đề cập trong đó có FastString Allocation hoặc một cái gì đó, điều này khiến tôi nghĩ rằng mọi người ở Microsoft có thể đã tăng thêm hiệu suất trong đó. Tất nhiên .ToArray () của tôi gọi tôi phủ nhận điều đó, nhưng tôi chỉ muốn đưa ra một đề nghị khác.


0

bạn biết? Điều tốt nhất để làm là quay lại CDATA :( tôi đang xem các giải pháp ở đây nhưng tôi nghĩ CDATA là đơn giản nhất và rẻ nhất, không phải là thuận tiện nhất để phát triển với tho


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

Sẽ làm việc cho bạn


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

Và nếu phần tử có bất kỳ thuộc tính nào hoặc thậm chí chỉ có một khoảng trắng quá nhiều thì logic sẽ thất bại.
Christoph
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.