Как выполнить 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="' '"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Бежать с xsltproc --stringparam pattern //element/@attribute --stringparam value . arbitrary-xpath.xslt filename.xml
,
18 ответов
Вы должны попробовать эти инструменты:
xmlstarlet
: можно редактировать, выбирать, преобразовывать... Не установлено по умолчанию, xpath1xmllint
: часто устанавливается по умолчанию сlibxml2
, xpath1 (проверьте мою обертку, чтобы иметь вывод с разделителями новой строкиxpath
: устанавливается через модуль PerlXML::XPath
, xpath1xml_grep
: устанавливается через модуль PerlXML::Twig
, xpath1 (ограниченное использование xpath)xidel
: xpath3saxon-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
а также xmllint
xstlproc
предлагает.
Схема нумерации в именах приложений указывает на то, что "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.