Xmlstarlet

如何使用 xstarlet 從 XHTML 中刪除具有特定類的 div?

  • September 15, 2022

我在子目錄 (*) 中有數百個 .xhtml 文件,我想從中刪除所有具有特定類的 DIV(以及這些 DIV 的全部內容 - 包括其他 div、span、圖像和段落元素)。DIV 可能在每個 .xhtml 文件中的任意深度出現零次、一次或多次。

我要刪除的特定 DIV 是:

<div class="portlet solid author-note-portlet">.....</div>

使用xml_grepperl 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, 任何地方的直接子節點。


yqJSON 處理器周圍的包裝器(由 Andrey Kislyuk 編寫)jq帶有一個名為xq. 你也可以使用它:

xq -x 'del(.. | .div? | select(."@class"? == "portlet solid author-note-portlet"))' file

( -x)--xml-output選項為您提供 XML 輸出而不是 JSON 輸出。使用xqwith -i( --in-place) 將使其進行就地編輯。

這個 XML 解析器不關心名稱空間。

引用自:https://unix.stackexchange.com/questions/717339