Better way to detect XML?

2020-02-09 00:31发布

Currently, I have the following c# code to extract a value out of text. If its XML, I want the value within it - otherwise, if its not XML, it can just return the text itself.

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

I know exceptions are expensive in C#, so I was wondering if there was a better way to determine if the text I'm dealing with is xml or not?

I thought of regex testing, but I dont' see that as a cheaper alternative. Note, I'm asking for a less expensive method of doing this.

13条回答
再贱就再见
2楼-- · 2020-02-09 01:10

As noted by @JustEngland in the comment exceptions are not that expensive, a debugger intercepting them them might take time but in normally they are well performing and good practice. See How expensive are exceptions in C#?.

A better way would be to roll your own TryParse style function:

[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;
        }
    }
}

The DebuggerNonUserCode attribute makes the debugger skip the caught exception to streamline your debugging experience.

Used like this:

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

I'd have preferred to create an extension method for the TryParse, but you can't create a static one called on a type rather than an instance.

查看更多
Bombasti
3楼-- · 2020-02-09 01:14

The code given below will match all the following xml formats:

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

And here's the code:

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

}

Calling code:

if (!XmlExpresssion.TryParse(s, out result)) 
    result = s;
Console.WriteLine(result);
查看更多
The star\"
4楼-- · 2020-02-09 01:14

If you want to know if it's valid, why not use the built-in .NetFX object rather than write one from scratch?

Hope this helps,

Bill

查看更多
在下西门庆
5楼-- · 2020-02-09 01:16

You could do a preliminary check for a < since all XML has to start with one and the bulk of all non-XML will not start with one.

(Free-hand written.)

// 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;
}

I originally had the use of a regex but Trim()[0] is identical to what that regex would do.

查看更多
forever°为你锁心
6楼-- · 2020-02-09 01:19

The way you suggest will be expensive if you will use it in a loop where most of the xml s are not valied, In case of valied xml your code will work like there are not exception handling... so if in most of cases your xml is valied or you doesn't use it in loop, your code will work fine

查看更多
The star\"
7楼-- · 2020-02-09 01:21

I find that a perfectly acceptable way of handling your situation (it's probably the way I'd handle it as well). I couldn't find any sort of "XElement.TryParse(string)" in MSDN, so the way you have it would work just fine.

查看更多
登录 后发表回答