用 XElement 替换 XElement 内容
Replacing XElement content with XElement
有没有办法用其他 XElement 有选择地替换 XElement 内容?
我有这个XML:
<prompt>
There is something I want to tell you.[pause=3]
You are my favorite caller today.[pause=1]
Have a great day!
</prompt>
我想把它渲染成这样:
<prompt>
There is something I want to tell you.<break time="3s"/>
You are my favorite caller today.<break time="1s"/>
Have a great day!
</prompt>
我需要用实际的 XElement 替换占位符,但是当我尝试更改 XElement 的内容时,.NET 当然会转义所有尖括号。我理解为什么内容通常需要正确转义,但我需要绕过该行为并将 XML 直接注入内容。
这是我的代码,否则可以正常工作。
MatchCollection matches = Regex.Matches(content, @"\[(\w+)=(\d+)]");
foreach (XElement element in voiceXmlDocument.Descendants("prompt"))
{
if (matches[0] == null)
continue;
element.Value = element.Value.Replace(matches[0].Value, @"<break time=""5s""/>");
}
这是一项正在进行的工作,所以不要太担心 RegEx 模式的有效性,因为我稍后会解决这个问题以匹配多个条件。这是概念代码的证明,重点是按照描述替换占位符。我在此处仅包含迭代和 RegEx 代码,以说明我需要能够对已填充内容的整个文档执行此操作。
所有这一切的关键是 XText
,它允许您将文本作为一个元素来处理。
这是循环:
foreach (XElement prompt in voiceXmlDocument.Descendants("prompt"))
{
string text = prompt.Value;
prompt.RemoveAll();
foreach (string phrase in text.Split('['))
{
string[] parts = phrase.Split(']');
if (parts.Length > 1)
{
string[] pause = parts[0].Split('=');
prompt.Add(new XElement("break", new XAttribute("time", pause[1])));
// add a + "s" if you REALLY want it, but then you have to get rid
// of it later in some other code.
}
prompt.Add(new XText(parts[parts.Length - 1]));
}
}
这是最终结果
<prompt>
There is something I want to tell you.<break time="3" />
You are my favorite caller today.<break time="1" />
Have a great day!
</prompt>
可以使用XElement.Parse()
方法:
首先,获取 XElement 的外部 xml,例如
string outerXml = element.ToString();
您正好可以使用此字符串:
<prompt>
There is something I want to tell you.[pause=3]
You are my favorite caller today.[pause=1]
Have a great day!
</prompt>
然后你可以做你的替换
outerXml = outerXml.Replace(matches[0].Value, @"<break time=""5s""/>");
然后你可以解析回来:
XElement repElement = XElement.Parse(outerXml);
最后,替换原来的 XElement:
element.ReplaceWith(repElement);
class Program
{
static void Main(string[] args)
{
var xml =
@"<prompt>There is something I want to tell you.[pause=3] You are my favorite caller today.[pause=1] Have a great day!</prompt>";
var voiceXmlDocument = XElement.Parse(xml);
var pattern = new Regex(@"\[(\w+)=(\d+)]");
foreach (var element in voiceXmlDocument.DescendantsAndSelf("prompt"))
{
var matches = pattern.Matches(element.Value);
foreach (var match in matches)
{
var matchValue = match.ToString();
var number = Regex.Match(matchValue, @"\d+").Value;
var newValue = string.Format(@"<break time=""{0}""/>", number);
element.Value = element.Value.Replace(matchValue, newValue);
}
}
Console.WriteLine(voiceXmlDocument.ToString());
}
}
哦,天哪,你们比我想象的要快!所以,谢谢你,但与此同时,我用一种稍微不同的方式解决了它。这里的代码看起来比以前扩展了,因为一旦我让它工作,我就在这个特定条件下添加了一些细节:
foreach (XElement element in voiceXmlDocument.Descendants("prompt").ToArray())
{
// convert the element to a string and see to see if there are any instances
// of pause placeholders in it
string elementAsString = element.ToString();
MatchCollection matches = Regex.Matches(elementAsString, @"\[pause=(\d+)]");
if (matches == null || matches.Count == 0)
continue;
// if there were no matches or an empty set, move on to the next one
// iterate through the indexed matches
for (int i = 0; i < matches.Count; i++)
{
int pauseValue = 0; // capture the original pause value specified by the user
int pauseMilliSeconds = 1000; // if things go wrong, use a 1 second default
if (matches[i].Groups.Count == 2) // the value is expected to be in the second group
{
// if the value could be parsed to an integer, convert it from 1/8 seconds to milliseconds
if (int.TryParse(matches[i].Groups[1].Value, out pauseValue))
pauseMilliSeconds = pauseValue * 125;
}
// replace the specific match with the new <break> tag content
elementAsString = elementAsString.Replace(matches[i].Value, string.Format(@"<break time=""{0}ms""/>", pauseMilliSeconds));
}
// finally replace the element by parsing
element.ReplaceWith(XElement.Parse(elementAsString));
}
Oh, my goodness, you guys were quicker than I expected!
呸!还不如post我的解决方案呢!
foreach (var element in xml.Descendants("prompt"))
{
Queue<string> pauses = new Queue<string>(Regex.Matches(element.Value, @"\[pause *= *\d+\]")
.Cast<Match>()
.Select(m => m.Value));
Queue<string> text = new Queue<string>(element.Value.Split(pauses.ToArray(), StringSplitOptions.None));
element.RemoveAll();
while (text.Any())
{
element.Add(new XText(text.Dequeue()));
if (pauses.Any())
element.Add(new XElement("break", new XAttribute("time", Regex.Match(pauses.Dequeue(), @"\d+"))));
}
}
对于每个提示元素,正则表达式匹配您的所有停顿并将它们放入队列中。
然后使用这些提示分隔内部文本并抓取 'other' 文本并将其放入队列中。
使用 RemoveAll 清除元素中的所有数据,然后遍历您的分隔数据并将其 re-add 作为适当的数据类型。当您添加新属性时,您可以使用正则表达式从原始匹配中获取数字值。
有没有办法用其他 XElement 有选择地替换 XElement 内容?
我有这个XML:
<prompt>
There is something I want to tell you.[pause=3]
You are my favorite caller today.[pause=1]
Have a great day!
</prompt>
我想把它渲染成这样:
<prompt>
There is something I want to tell you.<break time="3s"/>
You are my favorite caller today.<break time="1s"/>
Have a great day!
</prompt>
我需要用实际的 XElement 替换占位符,但是当我尝试更改 XElement 的内容时,.NET 当然会转义所有尖括号。我理解为什么内容通常需要正确转义,但我需要绕过该行为并将 XML 直接注入内容。
这是我的代码,否则可以正常工作。
MatchCollection matches = Regex.Matches(content, @"\[(\w+)=(\d+)]");
foreach (XElement element in voiceXmlDocument.Descendants("prompt"))
{
if (matches[0] == null)
continue;
element.Value = element.Value.Replace(matches[0].Value, @"<break time=""5s""/>");
}
这是一项正在进行的工作,所以不要太担心 RegEx 模式的有效性,因为我稍后会解决这个问题以匹配多个条件。这是概念代码的证明,重点是按照描述替换占位符。我在此处仅包含迭代和 RegEx 代码,以说明我需要能够对已填充内容的整个文档执行此操作。
所有这一切的关键是 XText
,它允许您将文本作为一个元素来处理。
这是循环:
foreach (XElement prompt in voiceXmlDocument.Descendants("prompt"))
{
string text = prompt.Value;
prompt.RemoveAll();
foreach (string phrase in text.Split('['))
{
string[] parts = phrase.Split(']');
if (parts.Length > 1)
{
string[] pause = parts[0].Split('=');
prompt.Add(new XElement("break", new XAttribute("time", pause[1])));
// add a + "s" if you REALLY want it, but then you have to get rid
// of it later in some other code.
}
prompt.Add(new XText(parts[parts.Length - 1]));
}
}
这是最终结果
<prompt>
There is something I want to tell you.<break time="3" />
You are my favorite caller today.<break time="1" />
Have a great day!
</prompt>
可以使用XElement.Parse()
方法:
首先,获取 XElement 的外部 xml,例如
string outerXml = element.ToString();
您正好可以使用此字符串:
<prompt>
There is something I want to tell you.[pause=3]
You are my favorite caller today.[pause=1]
Have a great day!
</prompt>
然后你可以做你的替换
outerXml = outerXml.Replace(matches[0].Value, @"<break time=""5s""/>");
然后你可以解析回来:
XElement repElement = XElement.Parse(outerXml);
最后,替换原来的 XElement:
element.ReplaceWith(repElement);
class Program
{
static void Main(string[] args)
{
var xml =
@"<prompt>There is something I want to tell you.[pause=3] You are my favorite caller today.[pause=1] Have a great day!</prompt>";
var voiceXmlDocument = XElement.Parse(xml);
var pattern = new Regex(@"\[(\w+)=(\d+)]");
foreach (var element in voiceXmlDocument.DescendantsAndSelf("prompt"))
{
var matches = pattern.Matches(element.Value);
foreach (var match in matches)
{
var matchValue = match.ToString();
var number = Regex.Match(matchValue, @"\d+").Value;
var newValue = string.Format(@"<break time=""{0}""/>", number);
element.Value = element.Value.Replace(matchValue, newValue);
}
}
Console.WriteLine(voiceXmlDocument.ToString());
}
}
哦,天哪,你们比我想象的要快!所以,谢谢你,但与此同时,我用一种稍微不同的方式解决了它。这里的代码看起来比以前扩展了,因为一旦我让它工作,我就在这个特定条件下添加了一些细节:
foreach (XElement element in voiceXmlDocument.Descendants("prompt").ToArray())
{
// convert the element to a string and see to see if there are any instances
// of pause placeholders in it
string elementAsString = element.ToString();
MatchCollection matches = Regex.Matches(elementAsString, @"\[pause=(\d+)]");
if (matches == null || matches.Count == 0)
continue;
// if there were no matches or an empty set, move on to the next one
// iterate through the indexed matches
for (int i = 0; i < matches.Count; i++)
{
int pauseValue = 0; // capture the original pause value specified by the user
int pauseMilliSeconds = 1000; // if things go wrong, use a 1 second default
if (matches[i].Groups.Count == 2) // the value is expected to be in the second group
{
// if the value could be parsed to an integer, convert it from 1/8 seconds to milliseconds
if (int.TryParse(matches[i].Groups[1].Value, out pauseValue))
pauseMilliSeconds = pauseValue * 125;
}
// replace the specific match with the new <break> tag content
elementAsString = elementAsString.Replace(matches[i].Value, string.Format(@"<break time=""{0}ms""/>", pauseMilliSeconds));
}
// finally replace the element by parsing
element.ReplaceWith(XElement.Parse(elementAsString));
}
Oh, my goodness, you guys were quicker than I expected!
呸!还不如post我的解决方案呢!
foreach (var element in xml.Descendants("prompt"))
{
Queue<string> pauses = new Queue<string>(Regex.Matches(element.Value, @"\[pause *= *\d+\]")
.Cast<Match>()
.Select(m => m.Value));
Queue<string> text = new Queue<string>(element.Value.Split(pauses.ToArray(), StringSplitOptions.None));
element.RemoveAll();
while (text.Any())
{
element.Add(new XText(text.Dequeue()));
if (pauses.Any())
element.Add(new XElement("break", new XAttribute("time", Regex.Match(pauses.Dequeue(), @"\d+"))));
}
}
对于每个提示元素,正则表达式匹配您的所有停顿并将它们放入队列中。
然后使用这些提示分隔内部文本并抓取 'other' 文本并将其放入队列中。
使用 RemoveAll 清除元素中的所有数据,然后遍历您的分隔数据并将其 re-add 作为适当的数据类型。当您添加新属性时,您可以使用正则表达式从原始匹配中获取数字值。