Если у меня есть файл xml, содержащий такие записи

<root>
  <d:entry d:title="OYSTER">
    <span class="foot">
      <span role="text">
      foo</span>
    </span>
    <span class="sg">
      <span id="004">
    <span role="text">
      <span class="pos">
        <span class="baz">tart</span>
        <d:pos></d:pos>
      </span>
    </span>
    <span id="005" class="star">
      <span class="NAME">GUYBRUSH THREEPWOOD
      <d:def></d:def></span>
      <span role="text" class="bar">:</span>
      <span role="text" class="grog">
        <span class="ex">pirate
        </span>
        <span class="parrot">.</span>
      </span>
    </span>
      </span>
    </span>
  </d:entry>
</root>

Как я могу извлечь текст «GUYBRUSH THREEPWOOD», указав (d :) title «OYSTER» и класс «NAME»?

0
humbug 16 Май 2021 в 22:57
1
Лучше всего использовать парсер HTML, но сначала добавьте ожидаемый результат к вашему вопросу. Не пишите это в комментариях.
 – 
Nasir Riley
16 Май 2021 в 19:20
Это настоящий XML? Существует пространство имен d, которое вы не объявили.
 – 
Kusalananda
16 Май 2021 в 19:59

1 ответ

Использование xq (часть yq, jq-подобной коллекции синтаксических анализаторов для YAML, XML и TOML, из https://kislyuk.github.io/yq/), потому что xmlstarlet слишком строго относится к вашему отсутствующему объявлению пространства имен (см. конец вопроса для xmlstarlet решение в любом случае).

xq -r --arg title "OYSTER" --arg class "NAME" '
    (.. | select(."@d:title"? == $title)) |
    (.. | select(."@class"?   == $class))."#text"' file.xml

Это рекурсивно выбирает любой узел документа, который имеет атрибут d:title (начальный @, используемый в выражении, обозначает атрибут узла, а не имя узла), который имеет значение OYSTER.

Учитывая эти узлы (только один в примере), они рекурсивно ищут любой узел, который имеет атрибут class со значением NAME.

Для каждого такого узла извлекается значение узла.

Две строки OYSTER и NAME привязаны к внутренним переменным в командной строке с опцией --arg.

Вывод, учитывая документ в вопросе:

GUYBRUSH THREEPWOOD

Если другие узлы, кроме d:entry, могут иметь атрибут d:title, и/или другие узлы, кроме span, могут иметь атрибут class, и вы хотите избежать неправильного сопоставления этих атрибутов. тип узла, затем убедитесь, что вы смотрите только на соответствующие узлы:

xq -r --arg title "OYSTER" --arg class "NAME" '
    (.. | ."d:entry"? | select(."@d:title"? == $title)) |
    (.. | .span?[]?   | select(."@class"?   == $class))."#text"' file.xml

В качестве справки, поскольку xq фактически вызывает jq с документом JSON за кулисами, ниже приведен документ JSON, в который переведен ваш XML-документ:

{
  "root": {
    "d:entry": {
      "@d:title": "OYSTER",
      "span": [
        {
          "@class": "foot",
          "span": {
            "@role": "text",
            "#text": "foo"
          }
        },
        {
          "@class": "sg",
          "span": {
            "@id": "004",
            "span": [
              {
                "@role": "text",
                "span": {
                  "@class": "pos",
                  "span": {
                    "@class": "baz",
                    "#text": "tart"
                  },
                  "d:pos": null
                }
              },
              {
                "@id": "005",
                "@class": "star",
                "span": [
                  {
                    "@class": "NAME",
                    "d:def": null,
                    "#text": "GUYBRUSH THREEPWOOD"
                  },
                  {
                    "@role": "text",
                    "@class": "bar",
                    "#text": ":"
                  },
                  {
                    "@role": "text",
                    "@class": "grog",
                    "span": [
                      {
                        "@class": "ex",
                        "#text": "pirate"
                      },
                      {
                        "@class": "parrot",
                        "#text": "."
                      }
                    ]
                  }
                ]
              }
            ]
          }
        }
      ]
    }
  }
}

Предполагая, что документ имеет правильное объявление пространства имен d, xmlstarlet можно использовать для извлечения нужного текста следующим образом:

xmlstarlet sel -t \
    -m '//d:entry[@d:title = "OYSTER"]' \
    -v '//span[@class = "NAME"]' -nl file.xml

Или с внутренними переменными, установленными в командной строке с помощью --var (обратите внимание на включение кавычек в значения),

xmlstarlet sel -t --var title='"OYSTER"' --var class='"NAME"' \
    -m '//d:entry[@d:title = $title]' \
    -v '//span[@class = $class]' -nl file

Оба они начинаются с сопоставления любого узла d:entry, чей атрибут d:title равен OYSTER. Для каждого такого совпадающего узла он рекурсивно ищет span узлов с атрибутом class со значением NAME. Выводится значение каждого такого узла.

1
Kusalananda 16 Май 2021 в 23:27
Спасибо. было бы альтернативой добавить объявление? (я на 100% ничего не знаю о xml.)
 – 
humbug
16 Май 2021 в 21:38
Я предполагаю, что ваш реальный файл XML имеет корневой элемент, говорящий что-то вроде <root xmlns:d="http://some.url.here">, верно?
 – 
Kusalananda
16 Май 2021 в 21:43
Да, извините, я не понял, что это важно
 – 
humbug
16 Май 2021 в 21:44
2
Возможно, вы захотите обновить XML в своем вопросе.
 – 
Kusalananda
16 Май 2021 в 21:45