Bash - Удалить узлы XML, если значение атрибута дочернего узла не равно конкретному значению? - PullRequest
0 голосов
/ 05 ноября 2019

У меня есть RSS-лента, например:

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>my feed</title>
  <link rel="self" href="http://myhomesite.com/articles/feed/"/>
  <updated>2019-11-04T12:45:00Z</updated>
  <id>http://myhomesite.com/articles/feed/?dt=2019-11-04T12:45:00Z</id>
  <entry>
    <id>id0</id>
    <link rel="alternate" type="text/html" href="https://yandex.ru/link123"/>
    <author>
      <name/>
    </author>
    <published>2019-11-04T12:45:00Z</published>
    <updated>2019-11-04T12:45:00Z</updated>
    <title type="html"><![CDATA[foo bar foo bar]]></title>
    <content type="html"><![CDATA[]]></content>
  </entry>
  <entry>
    <id>id2</id>
    <link rel="alternate" type="text/html" href="https://myhomesite.com"/>
    <author>
      <name/>
    </author>
    <published>2019-11-04T09:45:00Z</published>
    <updated>2019-11-04T09:45:00Z</updated>
    <title type="html"><![CDATA[foo bar foo bar]]></title>
    <content type="html"><![CDATA[]]></content>
  </entry>
....

Я хочу удалить все узлы (/feed/entry), где ссылка href ! = http://myhomesite.com.

Как удалить узел XML, где значение начинается с указанных символов, используя Bash?

1 Ответ

3 голосов
/ 06 ноября 2019

Функции Bash сами по себе не очень хорошо подходят для анализа XML.

Этот известный FAQ по Bash гласит следующее:

Не попытка [извлечь данные из файла XML] с помощью , , и т. Д. (Приводит к нежелательные результаты ).

Рассмотрите возможность использования специального инструмента командной строки XML, такого как XMLStarlet . См. Информацию о загрузке здесь , если у вас еще не установлен XML Starlet.


Решение:

Используя XML Starlet, вы можете запустить следующую команду для выводажелаемые результаты для вашего терминала:

xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss

Примечание: Часть /path/to/file.rss в конце команды, показанной выше, должна быть заменена реальным путем к фактическому .rss файл.

Объяснение:

Части вышеупомянутой разбивки команд следующие:

  • xml - вызов команды XML Starlet.
  • ed - редактирование / обновление документа XML.
  • -N x="http://www.w3.org/2005/Atom"- Параметр -N связывает пространство имен, то есть http://www.w3.org/2005/Atom, с префиксом, который мы произвольно назвали x.
  • -d -удалите совпадающие узлы.
  • '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' Выражение , используемое для поиска / сопоставления соответствующих узлов, как указано в вашем вопросе.

    все узлы (/ feed / entry), где ссылка href! = http://myhomesite.com.

    Как вы можете видеть, в выражении XPath мыдобавьте префикс x к именам узлов элемента, то есть x:entry и x:link, чтобы убедиться, что мы обращаемся к элементам в правильном пространстве имен.

  • /path/to/file.rss - Путь к исходному файлу .rss.

Сохранение результирующего XML (RSS)

Для сохранения результирующего XML вы можете:

  1. Добавьте параметр --inplace к вышеупомянутой команде - это заменит исходный .rss с желаемым результатом. Например:

    xml ed --inplace -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss
    
  2. Или используйте оператор перенаправления (>) и укажите путь к месту, в котором следует сохранить выходные данные. Например, следующая составная команда сохранит результаты в новом файле:

    xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss > /path/to/results.rss
    

    Примечание: /path/to/results.rss в конце вышеупомянутой составной команды следует заменитьс реальным путем, куда вы хотите сохранить новый файл.

XPath с local-name():

Учитывая, что ваш пример исходного XML (RSS)не включает в себя QNames , также возможно использовать функцию XPath local-name(). Это избавит от необходимости связывать пространство имен, используя опцию XMLStarlet -N. Например:

xml ed -d '//*[local-name() = "entry" and not(child::*[local-name() = "link"][@href="https://myhomesite.com"])]' /path/to/file.rss

ВАЖНО: Вам может необходимо заменить ведущую часть xml во всех примерах команд, показанных в этом посте, наxmlstarlet вместо. Например:

xmlstarlet ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[@href="https://myhomesite.com"])]' /path/to/file.rss.
^^^^^^^^^^

Редактировать:

Учитывая ваш пример XML, также возможно использовать упрощенный синтаксис для пространства имен по умолчанию, которое заключается в использовании_: вместо x:. Используя подчеркивание (_), вам не нужно использовать опцию -N, чтобы связать пространство имен с префиксом. Обратитесь к разделу 1.3. Более удобное решение в документации XMLStarlet для получения дополнительной информации об этой функции.

Например:

xml ed -d '//_:entry[not(child::_:link[@href="https://myhomesite.com"])]' /path/to/file.rss

Для дальнейшего понимания использования XMLStarlet, когда ваш исходный XML использует пространства имен, я предлагаю также прочитать Пространства имен и пространство имен по умолчанию в документации.


Редактировать 2:

Автор ОП впоследствии написал следующее в комментариях:

Еще один вопрос. Состояние [not(child::_:link[@href="myhomesite.com"])] строгое. Я хочу начать что-то вроде myhomesite.com, но URI не важен, т.е. myhomesite.com**anything**. Это возможно? [sic]

как-то так .. xmlstarlet ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[matches(@href, '^https://myhomesite.com/' )]/@href)]' feed.rs

Рассмотрите возможность использования функции starts-with() Xpath с любым из ранее приведенных примеров. Например:

  • Использование опции -N и starts-with():

    xml ed -N x="http://www.w3.org/2005/Atom" -d '//x:entry[not(child::x:link[starts-with(@href, "https://myhomesite.com")])]' file.rss
    
  • Использование local-name() и starts-with():

    xml ed -d '//*[local-name() = "entry" and not(child::*[local-name() = "link"][starts-with(@href, "https://myhomesite.com")])]' file.rss
    
  • Использование упрощенного синтаксиса для пространства имен по умолчанию, то есть подчеркивания, и starts-with():

    xml ed -d '//_:entry[not(child::_:link[starts-with(@href, "https://myhomesite.com")])]' file.rss
    
...