[英]Linq-to-XML XElement.Remove() leaves unwanted whitespace
我有一個從字節數組(通過 tcp/ip 接收)創建的 XDocument。
然后我搜索特定的 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 看起來很草率。 我曾嘗試使用 XDocument.ToString 重載,它采用 SaveOptions 枚舉無濟於事。 我還嘗試調用 xDoc.Save 以使用 SaveOptions 枚舉保存到文件中。 我確實嘗試嘗試使用XElement.Nodes().OfType<XText>()
嘗試刪除空格的幾個不同的 linq 查詢,但通常我最終會使用我希望保留的空格以及我的空格我想擺脫。
預先感謝您的幫助。
喬
以可移植的方式回答並不容易,因為該解決方案在很大程度上取決於XDocument.Load()
如何生成空白文本節點(並且有幾個 LINQ to XML 的實現可能在這個微妙的細節上存在分歧)。
也就是說,看起來您永遠不會從<book>
元素中刪除最后一個子元素 ( <description>
)。 如果確實如此,那么我們就不必擔心父元素的結束標記的縮進,我們只需刪除該元素及其所有后續文本節點,直到到達另一個元素。 TakeWhile()將完成這項工作。
編輯:好吧,看來您畢竟需要刪除最后一個孩子。 因此,事情會變得更加復雜。 下面的代碼實現了以下算法:
- 如果元素不是其父元素的最后一個元素:
- 刪除所有后續文本節點,直到我們到達下一個元素。
- 否則:
- 刪除所有后面的文本節點,直到我們找到一個包含換行符的節點,
- 如果該節點僅包含換行符:
- 刪除該節點。
- 否則:
- 創建一個僅包含在換行符之后找到的空格的新節點,
- 在原始節點之后插入該節點,
- 刪除原始節點。
- 刪除元素本身。
結果代碼是:
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 格式正確。 例如,在您的用例中,如果我刪除了“描述”元素,它將留下如下所示的內容:
<catalog>
<book id="bk101">
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
</book>
</catalog>
默認情況下,通過XmlReader
讀取 xml 將保留空格,包括您在此處看到的無關緊要的空格。
您應該通過設置適當的 xml 閱讀器設置來忽略空格來閱讀它:
using (var reader = XmlReader.Create(xmlStream, new XmlReaderSettings { IgnoreWhitespace = true }))
請注意,這不會刪除重要的空格(例如混合內容中的空格或保留空格的范圍內的空格),因此您的格式將保留。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.