Как выполнить XPath однострочно из оболочки?

Существует ли пакет для Ubuntu и / или CentOS, в котором есть инструмент командной строки, который может выполнять однострочную версию XPath, например foo //element@attribute filename.xml или же foo //element@attribute < filename.xml и возвращать результаты построчно?

Я ищу то, что позволило бы мне просто apt-get install foo или же yum install foo а затем просто работает "из коробки", никаких упаковок или другой адаптации не требуется.

Вот несколько примеров таких вещей:

Nokogiri. Если я напишу эту оболочку, я мог бы вызвать оболочку описанным выше способом:

#!/usr/bin/ruby

require 'nokogiri'

Nokogiri::XML(STDIN).xpath(ARGV[0]).each do |row|
  puts row
end

XML::XPath. Будет работать с этой оберткой:

#!/usr/bin/perl

use strict;
use warnings;
use XML::XPath;

my $root = XML::XPath->new(ioref => 'STDIN');
for my $node ($root->find($ARGV[0])->get_nodelist) {
  print($node->getData, "\n");
}

xpath из XML::XPath возвращает слишком много шума, -- NODE -- а также attribute = "value",

xml_grep из XML::Twig не может обрабатывать выражения, которые не возвращают элементы, поэтому не может использоваться для извлечения значений атрибутов без дальнейшей обработки.

РЕДАКТИРОВАТЬ:

echo cat //element/@attribute | xmllint --shell filename.xml возвращает шум, похожий на xpath,

xmllint --xpath //element/@attribute filename.xml возвращается attribute = "value",

xmllint --xpath 'string(//element/@attribute)' filename.xml возвращает то, что я хочу, но только для первого матча.

Для другого решения, почти удовлетворяющего этот вопрос, вот XSLT, который можно использовать для оценки произвольных выражений XPath (требуется поддержка dyn: define в процессоре XSLT):

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:dyn="http://exslt.org/dynamic" extension-element-prefixes="dyn">
  <xsl:output omit-xml-declaration="yes" indent="no" method="text"/>
  <xsl:template match="/">
    <xsl:for-each select="dyn:evaluate($pattern)">
      <xsl:value-of select="dyn:evaluate($value)"/>
      <xsl:value-of select="'&#10;'"/>
    </xsl:for-each> 
  </xsl:template>
</xsl:stylesheet>

Бежать с xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml,

18 ответов

Решение

Вы должны попробовать эти инструменты:

  • xmlstarlet: можно редактировать, выбирать, преобразовывать... Не установлено по умолчанию, xpath1
  • xmllint: часто устанавливается по умолчанию с libxml2, xpath1 (проверьте мою обертку, чтобы иметь вывод с разделителями новой строки
  • xpath: устанавливается через модуль Perl XML::XPath, xpath1
  • xml_grep: устанавливается через модуль Perl XML::Twig, xpath1 (ограниченное использование xpath)
  • xidel: xpath3
  • saxon-lint: мой собственный проект, обертка над Java-библиотекой Saxon-HE @Michael Kay, xpath3

xmllint приходит с libxml2-utils (может использоваться как интерактивная оболочка с --shell переключатель)

xmlstarlet является xmlstarlet,

xpath поставляется с модулем Perl XML::Xpath

xml_grep поставляется с модулем Perl XML::Twig

xidel является xidel

saxon-lint использование SaxonHE 9.6, XPath 3.x (+ ретро-совместимость)

Пример:

xmllint --xpath '//element/@attribute' file.xml
xmlstarlet sel -t -v "//element/@attribute" file.xml
xpath -q -e '//element/@attribute' file.xml
xidel -se '//element/@attribute' file.xml
saxon-lint --xpath '//element/@attribute' file.xml

,

Вы также можете попробовать мой Xidel. Его нет в пакете в хранилище, но вы можете просто загрузить его с веб-страницы (он не имеет зависимостей).

Он имеет простой синтаксис для этой задачи:

xidel filename.xml -e '//element/@attribute' 

И это один из редких инструментов, который поддерживает XPath 2.

Один пакет, который, вероятно, будет установлен в системе, уже python-lxml, Если это так, это возможно без установки какого-либо дополнительного пакета:

python -c "from lxml.etree import parse; from sys import stdin; print '\n'.join(parse(stdin).xpath('//element/@attribute'))"

В моем поиске по запросу файлов maven pom.xml я столкнулся с этим вопросом. Однако у меня были следующие ограничения:

  • должен работать кроссплатформенный.
  • должен существовать во всех основных дистрибутивах Linux без какой-либо дополнительной установки модуля
  • должен обрабатывать сложные xml-файлы, такие как файлы maven pom.xml
  • простой синтаксис

Я попробовал многие из вышеперечисленных без успеха:

  • Python lxml.etree не является частью стандартного дистрибутива Python
  • xml.etree есть, но плохо обрабатывает сложные файлы maven pom.xml, недостаточно глубоко копал
  • python xml.etree не обрабатывает файлы maven pom.xml по неизвестной причине
  • xmllint тоже не работает, ядро ​​часто выдает дамп на Ubuntu 12.04 "xmllint: используя libxml версию 20708"

Единственное решение, с которым я столкнулся, это стабильное, короткое и работающее на многих платформах, которое является зрелым, это встроенная в ruby ​​библиотека rexml lib:

ruby -r rexml/document -e 'include REXML; 
     p XPath.first(Document.new($stdin), "/project/version/text()")' < pom.xml

Что вдохновило меня на поиск этой статьи, так это следующие статьи:

Саксон сделает это не только для XPath 2.0, но и для XQuery 1.0 и (в коммерческой версии) 3.0. Это не пакет Linux, а файл JAR. Синтаксис (который вы можете легко обернуть в простой сценарий)

java net.sf.saxon.Query -s:source.xml -qs://element/attribute

Ответ Clacke отличный, но я думаю, что он работает только в том случае, если ваш источник - правильно сформированный XML, а не обычный HTML.

Таким образом, чтобы сделать то же самое для обычного веб-контента - документов HTML, которые не обязательно являются правильно сформированным XML:

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
from lxml import html; \
print '\n'.join(html.tostring(node) for node in html.parse(stdin).xpath('//p'))"

И вместо этого использовать html5lib (чтобы гарантировать, что вы получаете то же самое поведение при разборе, что и веб-браузеры - потому что, как и парсеры браузеров, html5lib соответствует требованиям синтаксического анализа в спецификации HTML).

echo "<p>foo<div>bar</div><p>baz" | python -c "from sys import stdin; \
import html5lib; from lxml import html; \
doc = html5lib.parse(stdin, treebuilder='lxml', namespaceHTMLElements=False); \
print '\n'.join(html.tostring(node) for node in doc.xpath('//p'))

Вы также можете быть заинтересованы в xsh. Он имеет интерактивный режим, в котором вы можете делать с документом все, что захотите:

open 1.xml ;
ls //element/@id ;
for //p[@class="first"] echo text() ;

Как и в ответах Майка и Клаки, здесь приведен однострочный код python (использующий python >= 2.5) для получения версии сборки из файла pom.xml, который позволяет обойти тот факт, что файлы pom.xml обычно не имеют dtd или пространство имен по умолчанию, так что не выглядите правильно в libxml:

python -c "import xml.etree.ElementTree as ET; \
  print(ET.parse(open('pom.xml')).getroot().find('\
  {http://maven.apache.org/POM/4.0.0}version').text)"

Протестировано на Mac и Linux и не требует установки дополнительных пакетов.

Я попробовал несколько утилит командной строки XPath и, когда понял, что слишком много времени гуглю и выясняю, как они работают, я написал простейший парсер XPath на Python, который сделал то, что мне было нужно.

Сценарий ниже показывает строковое значение, если выражение XPath вычисляется как строку, или показывает весь подузел XML, если результат является узлом:

#!/usr/bin/env python
import sys
from lxml import etree

tree = etree.parse(sys.argv[1])
xpath = sys.argv[2]

for e in tree.xpath(xpath):

    if isinstance(e, str):
        print(e)
    else:
        print((e.text and e.text.strip()) or etree.tostring(e))

Оно использует lxml - быстрый синтаксический анализатор XML, написанный на C, который не включен в стандартную библиотеку Python. Установите его с pip install lxml, В Linux/OSX может потребоваться префикс с sudo,

Использование:

python xmlcat.py file.xml "//mynode"

lxml также может принимать URL в качестве входных данных:

python xmlcat.py http://example.com/file.xml "//mynode" 

Извлеките атрибут url под узлом вложенности, т.е. <enclosure url="http:...""..>):

python xmlcat.py xmlcat.py file.xml "//enclosure/@url"

Xpath в Google Chrome

В качестве несвязанного примечания: если вы случайно захотите запустить выражение XPath для разметки веб-страницы, то вы можете сделать это прямо из Chrome devtools: щелкните правой кнопкой мыши страницу в Chrome> выберите Inspect, а затем в DevTools консоль вставьте ваше выражение XPath как $x("//spam/eggs"),

Получить всех авторов на этой странице:

$x("//*[@class='user-details']/a/text()")

Мой скрипт Python xgrep.py делает именно это. Для поиска всех атрибутовattribute элементов element в файлах filename.xml ..., вы запустите его следующим образом:

xgrep.py "//element/@attribute" filename.xml ...

Существуют различные переключатели для управления выходом, например: -c для подсчета матчей, -i для отступа совпадающих частей, и -l только для вывода имен файлов.

Скрипт недоступен в виде пакета Debian или Ubuntu, но есть все его зависимости.

Стоит упомянуть, что сам nokogiri поставляется с инструментом командной строки, который должен быть установлен с gem install nokogiri,

Вы можете найти этот пост полезным.

Установите базу данных BaseX, затем используйте ее "автономный режим командной строки" следующим образом:

basex -i - //element@attribute < filename.xml

или

basex -i filename.xml //element@attribute

На самом деле языком запросов является XQuery (3.0), а не XPath, но поскольку XQuery является надмножеством XPath, вы можете использовать запросы XPath, даже не замечая этого.

В дополнение к XML::XSH и XML::XSH2 есть несколько grep подобные утилиты сосут как App::xml_grep2 а также XML::Twig (который включает в себя xml_grep скорее, чем xml_grep2). Они могут быть весьма полезны при работе с большими или многочисленными XML-файлами для быстрых пользователей или Makefile цели. XML::Twig особенно приятно работать для perl подход сценариев, когда вы хотите немного больше обработки, чем ваш $SHELL а также xmllintxstlproc предлагает.

Схема нумерации в именах приложений указывает на то, что "2" версии являются более новой / более поздней версией, по сути, того же инструмента, для которого могут потребоваться более поздние версии других модулей (или perl сам).

Вот один пример использования xmlstarlet для извлечения данных из вложенных элементов elem1, elem2 в одну строку текста из этого типа XML (также показано, как обрабатывать пространства имен):

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<mydoctype xmlns="http://xml-namespace-uri" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xml-namespace-uri http://xsd-uri" format="20171221A" date="2018-05-15">

  <elem1 time="0.586" length="10.586">
      <elem2 value="cue-in" type="outro" />
  </elem1>

</mydoctype>

Выход будет

0.586 10.586 cue-in outro

В этом фрагменте -m соответствует вложенному элементу elem2, -v выводит значения атрибутов (с выражениями и относительной адресацией), -o литеральный текст, -n добавляет новую строку:

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2' \
 -v ../@time -o " " -v '../@time + ../@length' -o " " -v @value -o " " -v @type -n file.xml

Если от elem1 требуется больше атрибутов, это можно сделать так (также показывая функцию concat()):

xml sel -N ns="http://xml-namespace-uri" -t -m '//ns:elem1/ns:elem2/..' \
 -v 'concat(@time, " ", @time + @length, " ", ns:elem2/@value, " ", ns:elem2/@type)' -n file.xml

Обратите внимание на сложность (ненужную IMO) с пространствами имен (ns, объявленной с -N), из-за которой я почти разочаровался в xpath и xmlstarlet, и написание быстрого специального конвертера.

Мне не нравились однострочники Python для запросов HTML XPath, поэтому я написал свой собственный. Предполагается, что вы установилиpython-lxml пакет или запуск pip install --user lxml:

function htmlxpath() { python -c 'for x in __import__("lxml.html").html.fromstring(__import__("sys").stdin.read()).xpath(__import__("sys").argv[1]): print(x)' $1 }

Когда он у вас есть, вы можете использовать его, как в этом примере:

> curl -s https://slashdot.org | htmlxpath '//title/text()'
Slashdot: News for nerds, stuff that matters

Поскольку этот проект, по-видимому, довольно новый, ознакомьтесь с https://github.com/jeffbr13/xq lxml, но это все, что вам действительно нужно (и опубликовал специальные решения, использующие lxml и в других ответах)

Решение, которое работает, даже если объявления пространств имен существуют сверху:

Большинство команд, предложенных в ответах, не работают из коробки, если в xml объявлено пространство имен сверху. Учти это:

ввод xml:

      <elem1 xmlns="urn:x" xmlns:prefix="urn:y">
    <elem2 attr1="false" attr2="value2">
        elem2 value
    </elem2>
    <elem2 attr1="true" attr2="value2.1">
        elem2.1 value
    </elem2>    
    <prefix:elem3>
        elem3 value
    </prefix:elem3>        
</elem1>

Не работает:

      xmlstarlet sel -t -v "/elem1" input.xml
# nothing printed
xmllint -xpath "/elem1" input.xml
# XPath set is empty

Решение:

      # Requires >=java11 to run like below

# Prints the contents and self of "elem1"
java ExtractXpath.java "/elem1" input.xml

# Prints the value of the attribute: "value2.1"
java ExtractXpath.java "/elem1/elem2/@attr2" input.xml

# Prints the values inside elemt3: "elem3 value"
java ExtractXpath.java "/elem1/elem3/text()" input.xml

ИзвлечьXpath.java:

      import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ExtractXpath {

    public static void main(String[] args) throws Exception {
        assertThat(args.length==2, "Wrong number of args");
        String xpath = args[0];
        File file = new File(args[1]);
                
        assertThat(file.isFile(), file.getAbsolutePath()+" is not a file.");
        FileInputStream fileIS = new FileInputStream(file);
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = builderFactory.newDocumentBuilder();
        Document xmlDocument = builder.parse(fileIS);
        XPath xPath = XPathFactory.newInstance().newXPath();
        String expression = xpath;
        NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);
        
        for (int i = 0; i < nodeList.getLength(); i++) {
            System.out.println("==MATCH_START==\n" + convertNodeToString(nodeList.item(i)) + "\n==MATCH_END==");
        }        
    }

    private static String convertNodeToString(Node node) throws TransformerConfigurationException, TransformerException {
            short nType = node.getNodeType();
        switch (nType) {
            case Node.ATTRIBUTE_NODE , Node.TEXT_NODE -> {
                return node.getNodeValue();
            }
            case Node.ELEMENT_NODE -> {
                StringWriter writer = new StringWriter();
                Transformer trans = TransformerFactory.newInstance().newTransformer();
                trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                trans.setOutputProperty(OutputKeys.INDENT, "yes");
                trans.transform(new DOMSource(node), new StreamResult(writer));
                return writer.toString();
            }
            default -> {
                System.err.println("WARNING: FIXME: Node type:"+nType+" could possibly be handled in a better way.");
                return node.getNodeValue();
            }
                
        }
    }

    
    private static void assertThat(boolean b, String msg) {
        if(!b){
            System.err.println(msg+"\n\nUSAGE: program xpath xmlFile");
            System.exit(-1);
        }
    }
}

@SuppressWarnings("unchecked")
class NamespaceResolver implements NamespaceContext {
    //Store the source document to search the namespaces
    private final Document sourceDocument;
    public NamespaceResolver(Document document) {
        sourceDocument = document;
    }

    //The lookup for the namespace uris is delegated to the stored document.
    @Override
    public String getNamespaceURI(String prefix) {
        if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
            return sourceDocument.lookupNamespaceURI(null);
        } else {
            return sourceDocument.lookupNamespaceURI(prefix);
        }
    }

    @Override
    public String getPrefix(String namespaceURI) {
        return sourceDocument.lookupPrefix(namespaceURI);
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Iterator getPrefixes(String namespaceURI) {
        return null;
    }
}

и для простоты:

xpath-extractкоманда:

      #!/bin/bash
java ExtractXpath.java "$1" "$2"

Извините, что я еще один голос в драке. Я перепробовал все инструменты в этой ветке и не нашел ни одного из них удовлетворительным для моих нужд, поэтому написал свой собственный. Вы можете найти его здесь: https://github.com/charmparticle/xpe

Он был загружен в pypi, поэтому вы можете легко установить его с помощью pip3 следующим образом:

      sudo pip3 install xpe

После установки вы можете использовать его для выполнения выражений xpath для различных типов ввода с тем же уровнем гибкости, который вы получили бы от использования xpaths в селене или javascript. Да, с этим вы можете использовать xpaths против HTML.

Другие вопросы по тегам