【问题标题】:Replacing XElement content with XElement用 XElement 替换 XElement 内容
【发布时间】:2016-01-06 20:18:51
【问题描述】:

有没有办法选择性地将 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 代码来说明我需要能够对已经填充了内容的整个文档执行此操作。

【问题讨论】:

    标签: c# .net xml xelement


    【解决方案1】:

    你可以使用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);
    

    【讨论】:

      【解决方案2】:

      所有这一切的关键是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>
      

      【讨论】:

        【解决方案3】:
        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());
            }
        }
        

        【讨论】:

          【解决方案4】:

          哦,天哪,你们比我预期的要快!所以,谢谢你,但与此同时,我用一种稍微不同的方式解决了它。这里的代码看起来比以前扩展了,因为一旦我让它工作,我就在这个特定的条件中添加了一些细节:

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

          【讨论】:

          • 有趣。我也有同样的想法。
          • 是的,亚历克斯,你做到了,如果我不是那么固执,我会转向其他事情并检查答案。为了善报,我会将您的回复标记为答案。
          【解决方案5】:

          哦,天哪,你们比我预期的要快!

          哇!还是发布我的解决方案吧!

          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+"))));
              }
          }
          

          对于每个提示元素,Regex 匹配所有暂停并将它们放入队列中。

          然后使用这些提示来分隔内部文本并抓取“其他”文本并将其放入队列中。

          使用 RemoveAll 清除元素中的所有数据,然后遍历分隔数据并将其重新添加为适当的数据类型。当您添加新属性时,您可以使用正则表达式从原始匹配中获取数值。

          【讨论】:

            猜你喜欢
            • 2011-06-27
            • 1970-01-01
            • 2016-04-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-10-05
            • 1970-01-01
            • 2012-05-15
            相关资源
            最近更新 更多