如何使用 xstarlet 從 XHTML 中刪除具有特定類的 div?
我在子目錄 (*) 中有數百個 .xhtml 文件,我想從中刪除所有具有特定類的 DIV(以及這些 DIV 的全部內容 - 包括其他 div、span、圖像和段落元素)。DIV 可能在每個 .xhtml 文件中的任意深度出現零次、一次或多次。
我要刪除的特定 DIV 是:
<div class="portlet solid author-note-portlet">.....</div>
使用
xml_grep
perl XML::Twig模組中的實用程序,我可以執行xml_grep -v 'div[@class="portlet solid author-note-portlet"]' file*.xhtml
它,它將從 .xhtml 文件中刪除該 div 的所有實例並在標準輸出上顯示結果。正是我想要的,除了“在標準輸出上顯示”。如果
xml_grep
有某種就地編輯選項,那很好,我會使用它….但它沒有,所以我必須編寫一個使用臨時文件或sponge
執行的包裝腳本xml_grep 分別針對每個 .xhtml 文件,這將是緩慢而乏味的。或者我可以破解 xml_grep 的副本,以便它可以編輯其輸入文件。但我不想做這兩件事,我想使用已經可以做到這一點的現有工具,我想使用
xmlstarlet
- 它會更快,有就地編輯,我不必每個文件名執行一次。問題是,無論我嘗試什麼(我已經嘗試了幾十種變體),我都無法找出正確的 xpath 規範來刪除這個類的 div。例如,我嘗試過:
xmlstarlet ed -d "div[@class='portlet solid author-note-portlet']" file.xhtml
和(不同的引用)
xmlstarlet ed -d 'div[@class="portlet solid author-note-portlet"]' file.xhtml
和
xmlstarlet ed -d '//html/body/div/div/div[@class="portlet solid author-note-portlet"]'
以及數十種其他變體。它們都沒有導致 xhtml 輸出發生任何變化。這是我通常放棄 xmlstarlet 並編寫 perl 腳本的點,但這次我決心用 xmlstarlet 來做。
那麼,為 xmlstarlet 指定這個 div 類的正確方法是什麼?
順便說一句,舉個例子 .xhtml 文件(這個 div 的兩個實例,恰好處於相同的深度……這是相當典型但不普遍),
xmlstarlet el -v
說:$ xmlstarlet el -v OEBPS/file0007.xhtml | grep author-note-portlet html/body/div/div[@class='portlet solid author-note-portlet'] html/body/div/div[@class='portlet solid author-note-portlet']
(*) 沒關係,但這些 .xhtml 文件位於Calibre的FanFicFare外掛生成的 .epub 文件中 (**) - 該外掛從各種小說網站上的書籍下載所有章節並將它們轉換為 epub 文件(它基本上是一個包含 XHTML 和 CSS 文件,可能還有 jpeg 或 gif 文件,以及一堆元數據文件的 zip 存檔)。
<div class="portlet solid author-note-portlet">
由一個站點(皇家路)使用,供作者在章節中包含註釋。一些作者很少使用它,並插入關於章節或書籍的簡短註釋或關於隨機內容的簡短公告,可能還有指向他們的 patreon 頁面的連結……好吧,沒什麼大不了的。其他人使用它在每章開頭添加半頁註釋,其中包含指向其他 10 本書的連結,並再次在每章末尾添加三頁半連結(帶有封面圖片)到這些書籍。如果您在網站上逐章閱讀它,這有點不錯,但如果您將其作為一本書閱讀,則不是 - 每 6 到 10 頁自我推銷約 4 頁大約幾頁的故事過多且分散注意力。而且,順便說一句,這是我 10 英寸安卓平板電腦上的 4 個“頁面”——它是我手機上的兩倍多。
我可以很容易地
display: none
為這個類添加到 epub 的樣式表中,但我想真正從 .xhtml 文件中刪除 div。它們明顯增加了 .epub 文件的大小。(**) 解壓縮 .epub 的內容並在之後重建它超出了這個問題的範圍,所以請不要被無關的細節分心。已經處理好了。
範例 .xhtml 文件,編輯到最低限度(故事/章節/作者姓名匿名以保護“有罪:-):
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Chapter Five - Chapter Name</title> <link href="stylesheet.css" type="text/css" rel="stylesheet"/> <meta name="chapterurl" content="https://www.royalroad.com/fiction/URL"/> <meta name="chapterorigtitle" content="Chapter Five - Chapter Name"/> <meta name="chaptertoctitle" content="Chapter Five - Chapter Name"/> <meta name="chaptertitle" content="Chapter Five - Chapter Name"/> </head> <body class="fff_chapter"> <h3 class="fff_chapter_title">Chapter Five - Chapter Name</h3> <div class="chapter-inner chapter-content"><div class="portlet solid author-note-portlet"> <div class="portlet-title"> <div class="caption"> <i class="fa fa-sticky-note"></i> <span class="caption-subject bold uppercase">A note from Author Name</span> </div> </div> <div class="portlet-body author-note"><p><span>About a dozen or so p, span, img, and br tags here</span></p> </div> </div> <p> story text here. a few hundreds p, br, etc tags </p> <div class="portlet solid author-note-portlet"> <div class="portlet-title"> <div class="caption"> <i class="fa fa-sticky-note"></i> <span class="caption-subject bold uppercase">A note from Author Name</span> </div> </div> <div class="portlet-body author-note"><p>several dozen more p, span, br, img, etc tags here</p> </div> </div> </div> </body> </html>
正確的做法
xmlstarlet
是xmlstarlet ed --inplace -N xmlns="http://www.w3.org/1999/xhtml" \ --delete '//xmlns:div[@class="portlet solid author-note-portlet"]' file
或者,使用空頭期權,
xmlstarlet ed -L -N xmlns="http://www.w3.org/1999/xhtml" \ -d '//xmlns:div[@class="portlet solid author-note-portlet"]' file
由於文件使用預設命名空間,我們需要
xmlstarlet
知道所有節點都屬於這個命名空間,然後還要在 XPath 表達式中使用命名空間佔位符作為節點名稱的前綴。根據文件,
-N
必須是最後一個“全域選項”,即它必須在-L
(另一個全域選項)之後。是對的-d
“刪除操作”xmlstarlet ed
,因此它不是全域選項之一。XPath
//xmlns:div
將遞歸查找命名空間中呼叫的div
節點xmlns
。在這個問題中,除了不處理名稱空間之外,您還沒有指定或過度指定它。使用
div
,與 相同/div
,將匹配根節點,並且//html/body/div/div/div
將匹配html/body/div/div
, 任何地方的直接子節點。
yq
JSON 處理器周圍的包裝器(由 Andrey Kislyuk 編寫)jq
帶有一個名為xq
. 你也可以使用它:xq -x 'del(.. | .div? | select(."@class"? == "portlet solid author-note-portlet"))' file
(
-x
)--xml-output
選項為您提供 XML 輸出而不是 JSON 輸出。使用xq
with-i
(--in-place
) 將使其進行就地編輯。這個 XML 解析器不關心名稱空間。