سؤال

حاليا، لدي رمز C # التالية لاستخراج قيمة من النص. إذا كانت XML الخاصة بها، فأريد القيمة داخلها - وإلا، إذا لم تكن XML، فيمكن فقط إرجاع النص نفسه.

String data = "..."
try
{
    return XElement.Parse(data).Value;
}
catch (System.Xml.XmlException)
{
    return data;
}

أعرف استثناءات مكلفة في C #، لذلك كنت أتساءل عما إذا كانت هناك طريقة أفضل لتحديد ما إذا كان النص الذي أتعامل معه هو XML أم لا؟

فكرت في اختبار Regex، لكنني لا أرى ذلك كديل أرخص. ملاحظة، أنا أسأل عن أقل غلاء طريقة القيام بذلك.

هل كانت مفيدة؟

المحلول

يمكنك إجراء فحص أولي ل A <لأن All XML يجب أن يبدأ بأحد واحد ولم يبدأ الجزء الأكبر من جميع غير XML مع واحد.

(حر اليد المكتوبة.)

// Has to have length to be XML
if (!string.IsNullOrEmpty(data))
{
    // If it starts with a < after trimming then it probably is XML
    // Need to do an empty check again in case the string is all white space.
    var trimmedData = data.TrimStart();
    if (string.IsNullOrEmpty(trimmedData))
    {
       return data;
    }

    if (trimmedData[0] == '<')
    {
        try
        {
            return XElement.Parse(data).Value;
        }
        catch (System.Xml.XmlException)
        {
            return data;
        }
    }
}
else
{
    return data;
}

لقد كان لدي في الأصل استخدام Regex ولكن تقليم () [0] مطابق لما سيفعله Regex.

نصائح أخرى

سيتطابق الرمز المعطى أدناه جميع تنسيقات XML التالية:

<text />                             
<text/>                              
<text   />                           
<text>xml data1</text>               
<text attr='2'>data2</text>");
<text attr='2' attr='4' >data3 </text>
<text attr>data4</text>              
<text attr1 attr2>data5</text>

وهنا رمز:

public class XmlExpresssion
{
    // EXPLANATION OF EXPRESSION
    // <        :   \<{1}
    // text     :   (?<xmlTag>\w+)  : xmlTag is a backreference so that the start and end tags match
    // >        :   >{1}
    // xml data :   (?<data>.*)     : data is a backreference used for the regex to return the element data      
    // </       :   <{1}/{1}
    // text     :   \k<xmlTag>
    // >        :   >{1}
    // (\w|\W)* :   Matches attributes if any

    // Sample match and pattern egs
    // Just to show how I incrementally made the patterns so that the final pattern is well-understood
    // <text>data</text>
    // @"^\<{1}(?<xmlTag>\w+)\>{1}.*\<{1}/{1}\k<xmlTag>\>{1}$";

    //<text />
    // @"^\<{1}(?<xmlTag>\w+)\s*/{1}\>{1}$";

    //<text>data</text> or <text />
    // @"^\<{1}(?<xmlTag>\w+)((\>{1}.*\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

    //<text>data</text> or <text /> or <text attr='2'>xml data</text> or <text attr='2' attr2 >data</text>
    // @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

    private const string XML_PATTERN = @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

    // Checks if the string is in xml format
    private static bool IsXml(string value)
    {
        return Regex.IsMatch(value, XML_PATTERN);
    }

    /// <summary>
    /// Assigns the element value to result if the string is xml
    /// </summary>
    /// <returns>true if success, false otherwise</returns>
    public static bool TryParse(string s, out string result)
    {
        if (XmlExpresssion.IsXml(s))
        {
            Regex r = new Regex(XML_PATTERN, RegexOptions.Compiled);
            result = r.Match(s).Result("${data}");
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

}

رمز الاتصال:

if (!XmlExpresssion.TryParse(s, out result)) 
    result = s;
Console.WriteLine(result);

تحديث: (المشاركة الأصلية أدناه) يحتوي Colin على الفكرة الرائعة لتحريك إنشاء مثيل Regex خارج المكالمات بحيث يتم إنشاؤها مرة واحدة فقط. هيريس البرنامج الجديد:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace ConsoleApplication3
{
    delegate String xmltestFunc(String data);

    class Program
    {
        static readonly int iterations = 1000000;

        private static void benchmark(xmltestFunc func, String data, String expectedResult)
        {
            if (!func(data).Equals(expectedResult))
            {
                Console.WriteLine(data + ": fail");
                return;
            }
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)
                func(data);
            sw.Stop();
            Console.WriteLine(data + ": " + (float)((float)sw.ElapsedMilliseconds / 1000));
        }

        static void Main(string[] args)
        {
            benchmark(xmltest1, "<tag>base</tag>", "base");
            benchmark(xmltest1, " <tag>base</tag> ", "base");
            benchmark(xmltest1, "base", "base");
            benchmark(xmltest2, "<tag>ColinBurnett</tag>", "ColinBurnett");
            benchmark(xmltest2, " <tag>ColinBurnett</tag> ", "ColinBurnett");
            benchmark(xmltest2, "ColinBurnett", "ColinBurnett");
            benchmark(xmltest3, "<tag>Si</tag>", "Si");
            benchmark(xmltest3, " <tag>Si</tag> ", "Si" );
            benchmark(xmltest3, "Si", "Si");
            benchmark(xmltest4, "<tag>RashmiPandit</tag>", "RashmiPandit");
            benchmark(xmltest4, " <tag>RashmiPandit</tag> ", "RashmiPandit");
            benchmark(xmltest4, "RashmiPandit", "RashmiPandit");
            benchmark(xmltest5, "<tag>Custom</tag>", "Custom");
            benchmark(xmltest5, " <tag>Custom</tag> ", "Custom");
            benchmark(xmltest5, "Custom", "Custom");

            // "press any key to continue"
            Console.WriteLine("Done.");
            Console.ReadLine();
        }

        public static String xmltest1(String data)
        {
            try
            {
                return XElement.Parse(data).Value;
            }
            catch (System.Xml.XmlException)
            {
                return data;
            }
        }

        static Regex xmltest2regex = new Regex("^[ \t\r\n]*<");
        public static String xmltest2(String data)
        {
            // Has to have length to be XML
            if (!string.IsNullOrEmpty(data))
            {
                // If it starts with a < then it probably is XML
                // But also cover the case where there is indeterminate whitespace before the <
                if (data[0] == '<' || xmltest2regex.Match(data).Success)
                {
                    try
                    {
                        return XElement.Parse(data).Value;
                    }
                    catch (System.Xml.XmlException)
                    {
                        return data;
                    }
                }
            }
           return data;
        }

        static Regex xmltest3regex = new Regex(@"<(?<tag>\w*)>(?<text>.*)</\k<tag>>");
        public static String xmltest3(String data)
        {
            Match m = xmltest3regex.Match(data);
            if (m.Success)
            {
                GroupCollection gc = m.Groups;
                if (gc.Count > 0)
                {
                    return gc["text"].Value;
                }
            }
            return data;
        }

        public static String xmltest4(String data)
        {
            String result;
            if (!XmlExpresssion.TryParse(data, out result))
                result = data;

            return result;
        }

        static Regex xmltest5regex = new Regex("^[ \t\r\n]*<");
        public static String xmltest5(String data)
        {
            // Has to have length to be XML
            if (!string.IsNullOrEmpty(data))
            {
                // If it starts with a < then it probably is XML
                // But also cover the case where there is indeterminate whitespace before the <
                if (data[0] == '<' || data.Trim()[0] == '<' || xmltest5regex.Match(data).Success)
                {
                    try
                    {
                        return XElement.Parse(data).Value;
                    }
                    catch (System.Xml.XmlException)
                    {
                        return data;
                    }
                }
            }
            return data;
        }
    }

    public class XmlExpresssion
    {
        // EXPLANATION OF EXPRESSION
        // <        :   \<{1}
        // text     :   (?<xmlTag>\w+)  : xmlTag is a backreference so that the start and end tags match
        // >        :   >{1}
        // xml data :   (?<data>.*)     : data is a backreference used for the regex to return the element data      
        // </       :   <{1}/{1}
        // text     :   \k<xmlTag>
        // >        :   >{1}
        // (\w|\W)* :   Matches attributes if any

        // Sample match and pattern egs
        // Just to show how I incrementally made the patterns so that the final pattern is well-understood
        // <text>data</text>
        // @"^\<{1}(?<xmlTag>\w+)\>{1}.*\<{1}/{1}\k<xmlTag>\>{1}$";

        //<text />
        // @"^\<{1}(?<xmlTag>\w+)\s*/{1}\>{1}$";

        //<text>data</text> or <text />
        // @"^\<{1}(?<xmlTag>\w+)((\>{1}.*\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

        //<text>data</text> or <text /> or <text attr='2'>xml data</text> or <text attr='2' attr2 >data</text>
        // @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

        private static string XML_PATTERN = @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";
        private static Regex regex = new Regex(XML_PATTERN, RegexOptions.Compiled);

        // Checks if the string is in xml format
        private static bool IsXml(string value)
        {
            return regex.IsMatch(value);
        }

        /// <summary>
        /// Assigns the element value to result if the string is xml
        /// </summary>
        /// <returns>true if success, false otherwise</returns>
        public static bool TryParse(string s, out string result)
        {
            if (XmlExpresssion.IsXml(s))
            {
                result = regex.Match(s).Result("${data}");
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

    }


}

وهنا النتائج الجديدة:

<tag>base</tag>: 3.667
 <tag>base</tag> : 3.707
base: 40.737
<tag>ColinBurnett</tag>: 3.707
 <tag>ColinBurnett</tag> : 4.784
ColinBurnett: 0.413
<tag>Si</tag>: 2.016
 <tag>Si</tag> : 2.141
Si: 0.087
<tag>RashmiPandit</tag>: 12.305
 <tag>RashmiPandit</tag> : fail
RashmiPandit: 0.131
<tag>Custom</tag>: 3.761
 <tag>Custom</tag> : 3.866
Custom: 0.329
Done.

هناك لديك. Regex المعبأة هي الطريقة للذهاب، وفعالة جدا للتمهيد.




(المشاركة الأصلية)

لقد تم صوحيها معا البرنامج التالي لقياس عينات التعليمات البرمجية التي تم توفيرها لهذه الإجابة، لإظهار التفكير في رسالتي بالإضافة إلى تقييم سرعة الإجابات المميزة.

دون مزيد من ADO، هيريس البرنامج.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace ConsoleApplication3
{
    delegate String xmltestFunc(String data);

    class Program
    {
        static readonly int iterations = 1000000;

        private static void benchmark(xmltestFunc func, String data, String expectedResult)
        {
            if (!func(data).Equals(expectedResult))
            {
                Console.WriteLine(data + ": fail");
                return;
            }
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)
                func(data);
            sw.Stop();
            Console.WriteLine(data + ": " + (float)((float)sw.ElapsedMilliseconds / 1000));
        }

        static void Main(string[] args)
        {
            benchmark(xmltest1, "<tag>base</tag>", "base");
            benchmark(xmltest1, " <tag>base</tag> ", "base");
            benchmark(xmltest1, "base", "base");
            benchmark(xmltest2, "<tag>ColinBurnett</tag>", "ColinBurnett");
            benchmark(xmltest2, " <tag>ColinBurnett</tag> ", "ColinBurnett");
            benchmark(xmltest2, "ColinBurnett", "ColinBurnett");
            benchmark(xmltest3, "<tag>Si</tag>", "Si");
            benchmark(xmltest3, " <tag>Si</tag> ", "Si" );
            benchmark(xmltest3, "Si", "Si");
            benchmark(xmltest4, "<tag>RashmiPandit</tag>", "RashmiPandit");
            benchmark(xmltest4, " <tag>RashmiPandit</tag> ", "RashmiPandit");
            benchmark(xmltest4, "RashmiPandit", "RashmiPandit");

            // "press any key to continue"
            Console.WriteLine("Done.");
            Console.ReadLine();
        }

        public static String xmltest1(String data)
        {
            try
            {
                return XElement.Parse(data).Value;
            }
            catch (System.Xml.XmlException)
            {
                return data;
            }
        }

        public static String xmltest2(String data)
        {
            // Has to have length to be XML
            if (!string.IsNullOrEmpty(data))
            {
                // If it starts with a < then it probably is XML
                // But also cover the case where there is indeterminate whitespace before the <
                if (data[0] == '<' || new Regex("^[ \t\r\n]*<").Match(data).Success)
                {
                    try
                    {
                        return XElement.Parse(data).Value;
                    }
                    catch (System.Xml.XmlException)
                    {
                        return data;
                    }
                }
            }
           return data;
        }

        public static String xmltest3(String data)
        {
            Regex regex = new Regex(@"<(?<tag>\w*)>(?<text>.*)</\k<tag>>");
            Match m = regex.Match(data);
            if (m.Success)
            {
                GroupCollection gc = m.Groups;
                if (gc.Count > 0)
                {
                    return gc["text"].Value;
                }
            }
            return data;
        }

        public static String xmltest4(String data)
        {
            String result;
            if (!XmlExpresssion.TryParse(data, out result))
                result = data;

            return result;
        }

    }

    public class XmlExpresssion
    {
        // EXPLANATION OF EXPRESSION
        // <        :   \<{1}
        // text     :   (?<xmlTag>\w+)  : xmlTag is a backreference so that the start and end tags match
        // >        :   >{1}
        // xml data :   (?<data>.*)     : data is a backreference used for the regex to return the element data      
        // </       :   <{1}/{1}
        // text     :   \k<xmlTag>
        // >        :   >{1}
        // (\w|\W)* :   Matches attributes if any

        // Sample match and pattern egs
        // Just to show how I incrementally made the patterns so that the final pattern is well-understood
        // <text>data</text>
        // @"^\<{1}(?<xmlTag>\w+)\>{1}.*\<{1}/{1}\k<xmlTag>\>{1}$";

        //<text />
        // @"^\<{1}(?<xmlTag>\w+)\s*/{1}\>{1}$";

        //<text>data</text> or <text />
        // @"^\<{1}(?<xmlTag>\w+)((\>{1}.*\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

        //<text>data</text> or <text /> or <text attr='2'>xml data</text> or <text attr='2' attr2 >data</text>
        // @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

        private const string XML_PATTERN = @"^\<{1}(?<xmlTag>\w+)(((\w|\W)*\>{1}(?<data>.*)\<{1}/{1}\k<xmlTag>)|(\s*/{1}))\>{1}$";

        // Checks if the string is in xml format
        private static bool IsXml(string value)
        {
            return Regex.IsMatch(value, XML_PATTERN);
        }

        /// <summary>
        /// Assigns the element value to result if the string is xml
        /// </summary>
        /// <returns>true if success, false otherwise</returns>
        public static bool TryParse(string s, out string result)
        {
            if (XmlExpresssion.IsXml(s))
            {
                Regex r = new Regex(XML_PATTERN, RegexOptions.Compiled);
                result = r.Match(s).Result("${data}");
                return true;
            }
            else
            {
                result = null;
                return false;
            }
        }

    }


}

وهنا النتائج. تم تنفيذ كل واحد 1 مليون مرة.

<tag>base</tag>: 3.531
 <tag>base</tag> : 3.624
base: 41.422
<tag>ColinBurnett</tag>: 3.622
 <tag>ColinBurnett</tag> : 16.467
ColinBurnett: 7.995
<tag>Si</tag>: 19.014
 <tag>Si</tag> : 19.201
Si: 15.567

استغرق اختبار 4 وقتا طويلا، حيث تم اعتباره 30 دقيقة بطيئة للغاية. لإظهار مقدار أبطأ كان، هنا هو نفس الاختبار تشغيل 1000 مرة فقط.

<tag>base</tag>: 0.004
 <tag>base</tag> : 0.004
base: 0.047
<tag>ColinBurnett</tag>: 0.003
 <tag>ColinBurnett</tag> : 0.016
ColinBurnett: 0.008
<tag>Si</tag>: 0.021
 <tag>Si</tag> : 0.017
Si: 0.014
<tag>RashmiPandit</tag>: 3.456
 <tag>RashmiPandit</tag> : fail
RashmiPandit: 0
Done.

الاستقراء إلى مليون عملية إعدام، كان قد اتخذت 3456 ثانية، أو أكثر من 57 دقيقة فقط.

هذا مثال جيد لماذا تعد Regex المعقدة فكرة سيئة إذا كنت تبحث عن رمز فعال. ومع ذلك فقد أظهر أن Regex البسيط لا يزال بإمكانه إجابة جيدة في بعض الحالات - أي "اختبار ما قبل الاختبار" الصغير XML في إجابة Colinburnett إنشاء حالة قاعدة أساسية محتملة، (تم إنشاء Regex في حالة 2) ولكن أيضا أقصر بكثير حالة عن طريق تجنب الاستثناء.

أجد أن طريقة مقبولة تماما للتعامل مع وضعك (ربما هي الطريقة التي كنت أستخدمها أيضا). لم أستطع العثور على أي نوع من "Xelement.tryparse (سلسلة)" في MSDN، وبالتالي فإن الطريقة التي لديك ستعمل على ما يرام.

لا توجد طريقة للتحقق من أن النص هو XML غير القيام بشيء مثل Xelement.Parse. إذا كان، على سبيل المثال، فإن آخر قوس زاوية وثيق مفقود من حقل النص، فهذا غير صحيح XML، ومن غير المرجح أن تكتشف هذا مع تحليل Regex أو النص. هناك عدد من الأحرف غير القانونية، والتسلسلات غير القانونية وغيرها التي من المرجح أن تفوتها Cregex Parsiing.

كل ما يمكنك أن تأمل في القيام به هو قطع حالات الفشل قصيرة.

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

إذا كان هذا، بدلا من ذلك، يقوم هذا بتحليل المستخدم الذي تم إدخاله بيانات من نموذج ويب أو تطبيق WinForms، فأنا أعتقد أن تكلفة الاستثناء قد يكون أفضل من قضاء جهد التطوير والاختبار التأكد من أن التعليمات البرمجية القصيرة القصيرة لا تولد إيجابية كاذبة / النتائج السلبية.

ليس من الواضح أين تحصل على XML الخاص بك من (ملف أو دفق أو مربع النص أو في مكان آخر) ولكن تذكر أن علامات المسافة البيضاء والتعليقات وعلامات طلب البايت وغيرها من الأشياء يمكن أن تحصل في طريقة القواعد البسيطة مثل "يجب أن تبدأ مع < ".

لماذا regex باهظة الثمن؟ لا تقتل طائرتين مع 1 حجر (مباراة وتحليل)؟

مثال بسيط تحليل جميع العناصر، حتى أسهل إذا كان عنصر واحد فقط!

Regex regex = new Regex(@"<(?<tag>\w*)>(?<text>.*)</\k<tag>>");
MatchCollection matches = regex.Matches(data);
foreach (Match match in matches)
{
    GroupCollection groups = match.Groups;
    string name = groups["tag"].Value;
    string value = groups["text"].Value;
    ...
}

كما لوحظت justengland في التعليق استثناءات ليست باهظة الثمن، فإن مصحح الأخطاء يعترض لهم قد يستغرق الأمر وقتا ولكنهم عادة ما يكونون أداء جيدا وممارسة جيدة. يرى كم تكلف استثناءات في C #؟.

طريقة أفضل ستكون لفة وظيفة أسلوب tryparse الخاص بك:

[System.Diagnostics.DebuggerNonUserCode]
static class MyXElement
{
    public static bool TryParse(string data, out XElement result)
    {
        try
        {
            result = XElement.Parse(data);
            return true;
        }
        catch (System.Xml.XmlException)
        {
            result = default(XElement);
            return false;
        }
    }
}

سمة debuggernonusercode تجعل المصحح تخطي استثناء معين لتبسيط تجربة التصحيح الخاصة بك.

تستخدم مثل هذا:

    static void Main()
    {
        var addressList = "line one~line two~line three~postcode";

        var address = new XElement("Address");
        var addressHtml = "<span>" + addressList.Replace("~", "<br />") + "</span>";

        XElement content;
        if (MyXElement.TryParse(addressHtml, out content))
            address.ReplaceAll(content);
        else
            address.SetValue(addressHtml);

        Console.WriteLine(address.ToString());
        Console.ReadKey();
    }
}

لقد تفضل إنشاء طريقة تمديد ل TRYPARESE، لكن لا يمكنك إنشاء واحدة ثابتة يسمى نوع بدلا من مثيل.

فكرة - يجب أن تبدأ كل XML صالحة "<?xml "

قد تضطر إلى التعامل مع الاختلافات في مجموعة الأحرف ولكن التحقق من ASCII عادي، سوف تغطي UTF-8 و Unicode 99.5٪ من XML هناك.

الطريقة التي تقترح بها ستكون مكلفة إذا كنت ستستخدمها في حلقة حيث لا تتعثر معظم أجهزة XML ST، في حالة وجود رمز XML Valied الخاص بك، سيعمل على عدم وجود معالجة استثناء ... لذلك إذا كنت في معظم الحالات XML صمام أو لا تستخدمه في حلقة، وسوف يعمل الكود الخاص بك بشكل جيد

إذا كنت ترغب في معرفة ما إذا كان صالحا، فلماذا لا تستخدم كائن .NETFX المدمج بدلا من كتابة واحدة من نقطة الصفر؟

أتمنى أن يساعدك هذا،

مشروع قانون

اختلاف في تقنية Colin Burnett: يمكنك القيام ب Regex بسيطة في البداية لمعرفة ما إذا كان النص يبدأ في علامة، ثم حاول تحليله. ربما> 99٪ من السلاسل التي ستعامل معها مع عنصر صالح هي XML. وبهذه الطريقة يمكنك تخطي معالجة Regex للحصول على XML صالح بالكامل XML وتتخطي المعالجة المستندة إلى الاستثناء في كل حالة تقريبا.

شيء مثل ^<[^>]+> ربما تفعل الخدعة.

أنا لست متأكدا تماما إذا كان متطلباتك يعتبر تنسيق الملف وكما سئل هذا السؤال وقتا طويلا ويحدث للبحث عن شيء مشابه، أود منك أن تعرف ما الذي يعمل بالنسبة لي، لذلك إذا كان أي شخص يأتي هنا هذا قد تساعد :)

يمكننا استخدام Path.getextension (FilePath) وتحقق مما إذا كان XML، فاستخدمه من الحكمة الأخرى التي لا يلزم بها

ماذا عن هذا، خذ سلسلة أو كائنك وإرمها في XDocument جديد أو Xelement. كل شيء يحل استخدام Tostring ().

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top