سؤال

ما هي أفضل طريقة للحصول على محتويات الخلطة 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()
                };
هل كانت مفيدة؟

المحلول

كنت أرغب في معرفة أي من هذه الحلول المقترحة يحقق أفضل أداء، لذلك أجريت بعض الاختبارات المقارنة.ومن باب الاهتمام، قمت أيضًا بمقارنة أساليب LINQ مع الأساليب القديمة System.Xml الطريقة التي اقترحها جريج.كان الاختلاف مثيرًا للاهتمام وليس ما كنت أتوقعه، مع وجود أبطأ الأساليب أكثر من 3 مرات أبطأ من الأسرع.

النتائج مرتبة حسب الأسرع إلى الأبطأ:

  1. CreateReader - صائد المثيلات (0.113 ثانية)
  2. System.Xml القديم البسيط - جريج هيرمان (0.134 ثانية)
  3. التجميع مع تسلسل السلسلة - مايك باول (0.324 ثانية)
  4. StringBuilder - فين (0.333 ثانية)
  5. String.Join on array - تيري (0.360 ثانية)
  6. String.Concat على المصفوفة - مارسين كوسيرادزكي (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 الداخلي" للعقد العشرين، 1000 مرة على التوالي، وأخذ المتوسط ​​(المتوسط) لـ 5 أشواط.لم أقم بتضمين الوقت الذي استغرقه تحميل ملف XML وتحليله في ملف XmlDocumentSystem.Xml الطريقة) أو XDocument (لجميع الآخرين).

خوارزميات LINQ التي استخدمتها كانت: (C# - كل شيء يأخذ XElement "الأصل" وإرجاع سلسلة XML الداخلية)

إنشاءقارئ:

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

return reader.ReadInnerXml();

التجميع مع تسلسل السلسلة:

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

منشئ السلسلة:

StringBuilder sb = new StringBuilder();

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

return sb.ToString();

String.Join على المصفوفة:

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

String.Concat على المصفوفة:

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

لم أعرض خوارزمية "System.Xml القديمة البسيطة" هنا لأنها تستدعي فقط .InnerXml على العقد.


خاتمة

إذا كان الأداء مهمًا (على سبيل المثال.الكثير من XML، والتي يتم تحليلها بشكل متكرر). استخدم دانيال CreateReader طريقة في كل مرة.إذا كنت تقوم ببعض الاستعلامات فقط، فقد ترغب في استخدام طريقة التجميع الأكثر إيجازًا من مايك.

إذا كنت تستخدم XML على عناصر كبيرة تحتوي على الكثير من العقد (ربما المئات)، فمن المحتمل أن تبدأ في رؤية فائدة الاستخدام StringBuilder عبر الأسلوب التجميعي، ولكن لم ينته بعد CreateReader.لا أعتقد أن Join و Concat ستكون الأساليب أكثر كفاءة في هذه الظروف بسبب عقوبة تحويل قائمة كبيرة إلى مصفوفة كبيرة (حتى هذا واضح هنا مع القوائم الأصغر).

نصائح أخرى

أعتقد أن هذه طريقة أفضل بكثير (في VB، لا ينبغي أن يكون من الصعب ترجمتها):

بالنظر إلى XElement x:

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

ماذا عن استخدام طريقة "الامتداد" هذه على 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 عنصر فقط.

مع كل التقدير لأولئك الذين اكتشفوا وأثبتوا أفضل نهج (شكرًا!)، تم تضمينه هنا في طريقة ملحقة:

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

اجعل الأمر بسيطًا وفعالًا:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • التجميع هو الذاكرة والأداء غير فعال عند تسلسل السلاسل
  • استخدام Join(""، sth) يستخدم مصفوفة سلسلة أكبر مرتين من Concat...ويبدو غريبًا جدًا في الكود.
  • يبدو استخدام += غريبًا جدًا، ولكن يبدو أنه ليس أسوأ بكثير من استخدام "+" - ربما سيتم تحسينه لنفس الكود، لأن نتيجة المهمة غير مستخدمة وقد تتم إزالتها بأمان بواسطة المترجم.
  • يعد StringBuilder أمرًا ضروريًا للغاية - والجميع يعلم أن "الحالة" غير الضرورية سيئة.

انتهى بي الأمر باستخدام هذا:

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

أنا شخصياً انتهيت من كتابة 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();

@ جريج:يبدو أنك قمت بتحرير إجابتك لتكون إجابة مختلفة تمامًا.وإجابتي هي نعم، يمكنني القيام بذلك باستخدام System.Xml ولكني كنت أتمنى أن أبدأ في استخدام LINQ to XML.

سأترك ردي الأصلي أدناه في حال تساءل أي شخص آخر عن سبب عدم قدرتي على استخدام خاصية .Value الخاصة بـ XElement للحصول على ما أحتاج إليه:

@ جريج:تقوم خاصية القيمة بتسلسل كافة محتويات النص لأي عقد فرعية.لذا، إذا كان عنصر النص يحتوي على نص فقط، فإنه يعمل، ولكن إذا كان يحتوي على XHTML، فإنني أحصل على كل النص متسلسلًا معًا ولكن لا شيء من العلامات.

// قد يكون استخدام 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);

doc.ToString() أو doc.ToString(SaveOptions) يقوم بالعمل.يرى http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

هل من الممكن استخدام كائنات مساحة الاسم System.Xml لإنجاز المهمة هنا بدلاً من استخدام LINQ؟كما ذكرت سابقًا، XmlNode.InnerXml هو بالضبط ما تحتاجه.

أتساءل عما إذا كان (لاحظ أنني تخلصت من 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.Join، هناك بعض الإشارات إلى FastStringAlllocation أو شيء من هذا القبيل، مما يجعلني أعتقد أن الأشخاص في Microsoft ربما وضعوا بعض التحسينات الإضافية في الأداء هناك.بالطبع فإن اتصالي .ToArray() ينفي ذلك، لكنني أردت فقط تقديم اقتراح آخر.

أنت تعرف؟أفضل ما يمكنك فعله هو العودة إلى CDATA :( أنا أبحث عن الحلول هنا ولكن أعتقد أن CDATA هو الأبسط والأرخص إلى حد بعيد، وليس الأكثر ملاءمة للتطوير معه

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

سوف تفعل هذه المهمة بالنسبة لك

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();
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top