c# - Linq-to-XML XElement.Remove() 留下不需要的空白

标签 c# xml linq-to-xml xelement

我有一个从字节数组创建的 XDocument(通过 tcp/ip 接收)。

然后我搜索特定的 xml 节点 (XElements),并在检索到值后通过调用 XElement.Remove() 将其从 Xdocument 中“弹出”。完成所有解析后,我希望能够记录未解析的 xml(XDocument 中剩余的 xml)。问题是调用 XElement.Remove() 时会保留额外的空格。我想知道在保留剩余 xml 中的其余格式的同时删除此额外空格的最佳方法。

示例/示例代码

如果我通过套接字收到以下 xml:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

然后我使用以下代码解析此 xml 并删除一些 XElements:

private void socket_messageReceived(object sender, MessageReceivedEventArgs e)
{
     XDocument xDoc;
     try
     {
         using (MemoryStream xmlStream = new MemoryStream(e.XmlAsBytes))
         using (XmlTextReader reader = new XmlTextReader(xmlStream))
         {
             xDoc = XDocument.Load(reader);
         }

         XElement Author = xDoc.Root.Descendants("author").FirstOrDefault();
         XElement Title  = xDoc.Root.Descendants("title").FirstOrDefault();
         XElement Genre  = xDoc.Root.Descendants("genre").FirstOrDefault();

         // Do something with Author, Title, and Genre here...

         if (Author != null) Author.Remove();
         if (Title  != null) Title.Remove();
         if (Genre  != null) Genre.Remove();

         LogUnparsedXML(xDoc.ToString());

     }
     catch (Exception ex)
     {
         // Exception Handling here...
     }
}

然后发送到 LogUnparsedXML 消息的 xml 的结果字符串将是:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">



      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

在这个人为设计的示例中,它可能看起来没什么大不了的,但在我的实际应用程序中,剩余的 xml 看起来很草率。我尝试使用采用 SaveOptions 枚举的 XDocument.ToString 重载无济于事。我还尝试调用 xDoc.Save 以使用 SaveOptions 枚举保存到文件。我确实尝试过一些使用 XElement.Nodes().OfType<XText>() 的不同 linq 查询。尝试删除空格,但通常我最终会同时使用我希望保留的空格和我试图摆脱的空格。

在此先感谢您的帮助。

最佳答案

以可移植的方式回答并不容易,因为解决方案在很大程度上取决于 XDocument.Load() 的方式。生成空白文本节点(LINQ to XML 的几种实现可能不同意这个微妙的细节)。

也就是说,您似乎永远不会从 <description> 中删除最后一个 子级 ( <book>)元素。如果确实如此,那么我们就不必担心父元素的结束标记的缩进,我们可以只删除该元素及其后面的所有文本节点,直到我们到达另一个元素。 TakeWhile()将完成这项工作。

编辑:好吧,看来你毕竟需要删除最后一个 child 。因此,事情会变得更加复杂。下面的代码实现了以下算法:

  • If the element is not the last element of its parent:
    • Remove all following text nodes until we reach the next element.
  • Otherwise:
    • Remove all following text nodes until we find one containing a newline,
    • If that node only contains a newline:
      • Remove that node.
    • Otherwise:
      • Create a new node containing only the whitespace found after the newline,
      • Insert that node after the original node,
      • Remove the original node.
  • Remove the element itself.

结果代码是:

public static void RemoveWithNextWhitespace(this XElement element)
{
    IEnumerable<XText> textNodes
        = element.NodesAfterSelf()
                 .TakeWhile(node => node is XText).Cast<XText>();
    if (element.ElementsAfterSelf().Any()) {
        // Easy case, remove following text nodes.
        textNodes.ToList().ForEach(node => node.Remove());
    } else {
        // Remove trailing whitespace.
        textNodes.TakeWhile(text => !text.Value.Contains("\n"))
                 .ToList().ForEach(text => text.Remove());
        // Fetch text node containing newline, if any.
        XText newLineTextNode
            = element.NodesAfterSelf().OfType<XText>().FirstOrDefault();
        if (newLineTextNode != null) {
            string value = newLineTextNode.Value;
            if (value.Length > 1) {
                // Composite text node, trim until newline (inclusive).
                newLineTextNode.AddAfterSelf(
                    new XText(value.SubString(value.IndexOf('\n') + 1)));
            }
            // Remove original node.
            newLineTextNode.Remove();
        }
    }
    element.Remove();
}

从那里,您可以:

if (Author != null) Author.RemoveWithNextWhitespace();
if (Title  != null) Title.RemoveWithNextWhitespace();
if (Genre  != null) Genre.RemoveWithNextWhitespace();

虽然我建议您将上面的内容替换为从数组或 params 馈送的循环之类的东西方法调用,避免代码冗余。

关于c# - Linq-to-XML XElement.Remove() 留下不需要的空白,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6851247/

相关文章:

c# - Linq to XML 获取一对多元素名称和属性名称/值

c# - 生成非流利类的流利等价物的生产力工具?

c# - 使用 filecodemodel 添加属性

python - lxml XPath - 如何从节点获取连接文本

javascript - DTD 是否仍用于符合 xml?

java - 如何阻止在 svg createElement 步骤期间创建 xmlns 属性

c# - 需要在 C# 中构建文件夹树的字符串表示形式

c# - 删除应用程序的 DLL 依赖项

c# - C#异步套接字(* Async,SocketAsyncEventArgs)

c# - LINQ to XML 查询同级元素