我有一个从字节数组创建的XDocument(通过tcp / ip接收)。
然后,我搜索特定的xml节点(XElements),并在获取值'pop'之后,通过调用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并删除许多XElement:
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看起来很草率。我试过使用XDocument.ToString重载,它会使SaveOptions枚举无效。我也曾尝试调用xDoc.Save使用SaveOptions枚举将其保存到文件中。我确实尝试过使用XElement.Nodes().OfType<XText>()
来删除空白的一些不同的linq查询,但是通常我最终还是希望保留要保留的空白以及我要摆脱的空白。
感谢您的帮助。
乔
以可移植的方式回答并不容易,因为该解决方案在很大程度上取决于XDocument.Load()
如何生成空白文本节点(并且围绕LINQ to XML的几种实现可能对此微妙的细节意见不一致)。
也就是说,您似乎永远不会从<description>
元素中删除last子级(<book>
)。如果确实如此,那么我们就不必担心父元素的结束标记的缩进,我们可以删除该元素及其所有后续文本节点,直到到达另一个元素为止。 TakeWhile()将完成工作。
EDIT:好吧,看来您毕竟需要删除最后一个孩子。因此,事情将变得更加复杂。下面的代码实现以下算法:
- 如果元素不是其父元素的最后一个元素:
- 删除所有随后的文本节点,直到我们到达下一个元素。
- 否则:
- 删除以下所有文本节点,直到找到包含换行符的文本节点,
- 如果该节点仅包含换行符:
- 删除该节点。
- 否则:
- 创建仅包含在换行符之后找到的空白的新节点,
- 在原始节点之后插入该节点,
- 删除原始节点。
- 删除元素本身。
结果代码为:
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
方法调用,以避免代码冗余。
我有一个比被接受的答案更简单的解决方案,该解决方案适用于我的情况,并且似乎也适用于您的情况。我不确定,也许有些更复杂的情况无法解决。
这里是代码:
public static void RemoveWithNextWhitespace(this XElement element)
{
if (element.PreviousNode is XText textNode)
{
textNode.Remove();
}
element
.Remove();
}
这是我的LINQPad查询以及您的用例:
void Main()
{
var xDoc = XDocument.Parse(@"<?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>", LoadOptions.PreserveWhitespace);
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.RemoveWithNextWhitespace();
if (Title != null) Title.RemoveWithNextWhitespace();
if (Genre != null) Genre.RemoveWithNextWhitespace();
xDoc.ToString().Dump();
}
static class Ext
{
public static void RemoveWithNextWhitespace(this XElement element)
{
if (element.PreviousNode is XText textNode)
{
textNode.Remove();
}
element
.Remove();
}
}
我不只是自己使用接受的答案的主要原因是,在某些情况下,它没有使我的XML格式正确。例如在您的用例中,如果我删除了“ description”元素,它将留下如下所示的内容:
<catalog>
<book id="bk101">
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
</book>
</catalog>