目前,我有以下c#代码从文本中提取值。如果是XML,我想要其中的值 - 否则,如果它不是XML,它只能返回文本本身。
String data = "..."
try
{
return XElement.Parse(data).Value;
}
catch (System.Xml.XmlException)
{
return data;
}
我知道C#中的异常是昂贵的,所以我想知道是否有更好的方法来确定我正在处理的文本是否是xml?
我想到了正则表达式测试,但我不认为这是一种更便宜的替代品。请注意,我要求使用更便宜的方法。
答案 0 :(得分:19)
您可以对<因为所有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;
}
我最初使用的是正则表达式,但Trim()[0]与正则表达式相同。
答案 1 :(得分:7)
下面给出的代码将匹配以下所有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);
答案 2 :(得分:5)
更新:(原帖如下) Colin有一个很棒的想法,即在调用之外移动正则表达式实例化,这样它们只能创建一次。继承了新计划:
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.
你有它。预编译的正则表达式是可行的方式,并且启动非常有效。
<小时/> <小时/> <小时/> (原帖)
我拼凑了以下程序来对为此答案提供的代码示例进行基准测试,以演示我的帖子的推理以及评估私有答案的速度。
没有进一步的麻烦,继承了该计划。
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;
}
}
}
}
以下是结果。每个人都执行了100万次。
<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分钟。
这是一个很好的例子,说明为什么复杂的正则表达式是一个坏主意,如果你正在寻找有效的代码。然而,它表明在某些情况下简单的正则表达式仍然可以是一个好的答案 - 即colinBurnett中的xml的小'预测试'创建了一个可能更昂贵的基本情况,(正则表达式创建于案例2中)但也更短的其他通过避免例外的情况。
答案 3 :(得分:3)
我发现处理你的情况是一种完全可以接受的方式(它可能也是我处理它的方式)。我在MSDN中找不到任何类型的“XElement.TryParse(string)”,所以你拥有它的方式就可以了。
答案 4 :(得分:2)
除了执行类似XElement.Parse的操作之外,无法验证文本是否为XML。例如,如果文本字段中缺少最后一个close-angle-bracket,则它不是有效的XML,并且您不太可能通过RegEx或文本解析发现它。 RegEx解析很可能会遗漏许多非法字符,非法序列等。
你所希望做的就是缩短你的失败案例。
所以,如果你希望看到很多非XML数据,那么XML的情况就越少,那么使用RegEx或子字符串搜索来检测尖括号可能会节省你一点时间,但我建议这是如果您在紧密循环中批量处理大量数据,则仅非常有用。
相反,如果这是从Web表单或winforms应用程序解析用户输入的数据,那么我认为支付Exception的成本可能比花费开发和测试工作更好,以确保您的快捷代码不会产生假阳性/阴性结果。
目前尚不清楚从哪里获取XML(文件,流,文本框或其他地方),但请记住,空格,注释,字节顺序标记和其他内容可能会妨碍简单的规则,例如“它必须启动用&lt;“。
答案 5 :(得分:1)
为什么正则表达式很贵?它是不是用1块石头杀死2只鸟(匹配和解析)?
解析所有元素的简单示例,如果它只是一个元素就更容易了!
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;
...
}
答案 6 :(得分:1)
正如@JustEngland在评论中指出的那样,异常并不昂贵,调试器拦截它们可能需要时间,但通常情况下它们表现良好且良好。请参阅How expensive are exceptions in 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();
}
}
我更倾向于为TryParse创建一个扩展方法,但是你不能创建一个在类型而不是实例上调用的静态方法。
答案 7 :(得分:0)
线索 - 所有有效的xml必须以"<?xml
开头
您可能需要处理字符集差异,但检查纯ASCII,utf-8和unicode将覆盖99.5%的xml。
答案 8 :(得分:0)
如果您将在大多数xml未被验证的循环中使用它,那么您建议的方式将是昂贵的。如果是有效的xml,您的代码将工作,就像没有异常处理...所以如果在大多数情况下如果您的xml已被保护,或者您没有在循环中使用它,您的代码将正常工作
答案 9 :(得分:0)
如果你想知道它是否有效,为什么不使用内置的.NetFX对象而不是从头开始写一个?
希望这有帮助,
比尔
答案 10 :(得分:0)
Colin Burnett技术的变体:你可以在开头做一个简单的正则表达式来查看文本是否以标记开头,然后尝试解析它。可能> 99%的字符串,您将处理以有效元素开头的字符串是XML。这样,您可以跳过正则表达式处理完整的有效XML,并且几乎在所有情况下都跳过基于异常的处理。
像^<[^>]+>
之类的东西可能就是这样。
答案 11 :(得分:0)
我不确定您的要求是否考虑了文件格式,因为这个问题需要很长一段时间才会被问到。我碰巧寻找类似的东西,我希望你知道什么对我有用,所以如果有人来这里这可能有帮助:)
我们可以使用Path.GetExtension(filePath)并检查它是否是XML然后以其他方式使用它做任何需要的事情
答案 12 :(得分:-2)
这个怎么样,把你的字符串或对象扔进一个新的XDocument或XElement。一切都使用ToString()解析。