Eu tenho várias centenas de arquivos .xhtml em um subdiretório(*) e quero excluir todos os DIVs com uma classe específica (e todo o conteúdo desses DIVs - incluindo outros divs, spans, elementos de imagem e parágrafo) deles. O DIV pode aparecer zero, uma ou mais vezes em qualquer profundidade arbitrária dentro de cada arquivo .xhtml.
Os DIVs específicos que quero excluir são:
<div class="portlet solid author-note-portlet">.....</div>
Usando o xml_grep
utilitário do módulo perl XML::Twig , posso executar xml_grep -v 'div[@class="portlet solid author-note-portlet"]' file*.xhtml
e ele removerá todas as instâncias dessa div dos arquivos .xhtml e exibirá o resultado em stdout. Exatamente o que eu quero, exceto por "exibir no stdout".
Se xml_grep
tivesse algum tipo de opção de edição no local, tudo bem, eu apenas usaria isso .... mas não, então eu teria que escrever um script wrapper que usasse um arquivo temporário ou sponge
e executar xml_grep em cada arquivo .xhtml individualmente, o que seria lento e tedioso. Ou eu poderia hackear uma cópia do xml_grep para que ele pudesse editar seu(s) arquivo(s) de entrada.
Mas não quero fazer nenhuma dessas coisas, quero usar a ferramenta existente que já pode fazer isso, quero usar xmlstarlet
- será mais rápido, tem edição no local e não precisarei execute-o uma vez por nome de arquivo.
O problema é que não importa o que eu tente (e já tentei dezenas de variações), não consigo descobrir a especificação xpath correta para excluir um div com essa classe. por exemplo, eu tentei:
xmlstarlet ed -d "div[@class='portlet solid author-note-portlet']" file.xhtml
e (com cotação diferente)
xmlstarlet ed -d 'div[@class="portlet solid author-note-portlet"]' file.xhtml
e
xmlstarlet ed -d '//html/body/div/div/div[@class="portlet solid author-note-portlet"]'
e dezenas de outras variações. Nenhum deles resultou em qualquer alteração na saída xhtml. Este é o ponto em que eu costumo desistir de xmlstarlet e escrever um script perl, mas desta vez estou determinado a fazê-lo com xmlstarlet.
Então, qual é a maneira correta de especificar essa classe div para xmlstarlet?
BTW, para um exemplo de arquivo .xhtml (com duas instâncias deste div, que estão na mesma profundidade ... o que é bastante típico, mas não universal), xmlstarlet el -v
diz:
$ 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']
(*) Não que isso importe, mas esses arquivos .xhtml estão dentro de um arquivo .epub(**) gerado pelo plugin FanFicFare para Caliber - que baixa todos os capítulos de livros em vários sites de ficção e os transforma em um arquivo epub ( que é basicamente um arquivo zip contendo arquivos XHTML e CSS e talvez arquivos jpeg ou gif, junto com um monte de arquivos de metadados).
<div class="portlet solid author-note-portlet">
é usado por um site (Royal Road) para que os autores incluam uma nota com um capítulo. Alguns autores o usam com moderação e inserem notas curtas sobre o capítulo ou o livro ou anúncios breves sobre coisas aleatórias, talvez com um link para sua página do patreon... tudo bem, não é grande coisa.
Outros o usam para adicionar uma nota de meia página com links para 10 de seus outros livros no início de cada capítulo e novamente para adicionar três páginas e meia de links (com imagens de capa) a esses livros no final de cada capítulo. O que é meio bom se você estiver lendo em forma de série capítulo por capítulo no site, mas não se estiver lendo como um livro - ~ 4 páginas de autopromoção para cada 6-10 ou então as páginas da história são excessivas e distrativas. E, BTW, são 4 "páginas" no meu tablet Android de 10 polegadas - é mais que o dobro no meu telefone.
Eu posso facilmente adicionar display: none
à folha de estilo do epub para esta classe, mas eu quero realmente excluir os divs dos arquivos .xhtml. Eles aumentam notavelmente o tamanho do arquivo .epub.
(**) extrair o conteúdo do .epub com descompactação e reconstruí-lo posteriormente está fora do escopo desta questão, portanto, não se distraia com detalhes irrelevantes. Já tratado.
Exemplo de arquivo .xhtml, editado ao mínimo (e história/capítulo/nome do autor anonimizado para proteger o "culpado :-):
<?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>
A maneira correta de fazer isso
xmlstarlet
éou, usando opções curtas,
Como o documento usa um namespace padrão, precisamos informar
xmlstarlet
que todos os nós pertencem a esse namespace e também prefixar o nome do nó com o espaço reservado para namespace na expressão XPath.De acordo com a documentação,
-N
deve ser a última "opção global", ou seja, deve vir depois-L
(outra opção global). A-d
é a "operação de exclusão" paraxmlstarlet ed
, portanto, não é uma das opções globais.O XPath
//xmlns:div
procurará recursivamente por um nó chamadodiv
noxmlns
namespace.Na pergunta, além de não manipular o namespace, você especificou isso de forma insuficiente ou excessiva. Usar
div
, que é o mesmo que/div
, corresponderia a um nó raiz e//html/body/div/div/div
corresponderia a um nó filho imediato dehtml/body/div/div
, em qualquer lugar.O
yq
wrapper (por Andrey Kislyuk) em torno do processador JSONjq
vem com um wrapper do analisador XML chamadoxq
. Você pode usar isso também:A opção
-x
(--xml-output
) fornece saída XML em vez de saída JSON. Usarxq
com-i
(--in-place
) fará com que ele faça a edição no local.Este analisador XML não se importa com namespaces.
Uma nota separada seria, dado que você pode obter a filtragem desejada com
xml_grep
, você teria resolvido o problema em muito menos tempo do que levaria para escrever sua pergunta para usar algo como os seguintes comandos bashPor outro lado, há mérito e satisfação em aprender a usar outras ferramentas.