用 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 作为适当的数据类型。当您添加新属性时,您可以使用正则表达式从原始匹配中获取数字值。