Как найти максимальный атрибут из XML-документа с помощью Xpath 1.0

Есть ли способ запросить XML-документ, чтобы вернуть максимум данного атрибута, используя Xpath 1.0?

Например, есть ли способ получить максимальный идентификатор?

<?xml version="1.0" encoding="utf-8"?>
<library>
        <book id="2" name="Dragon Tatoo"/>
        <book id="7" name="Ender's Game"/>
        <book id="3" name="Catch 22"/>
        <book id="1" name="Lord of the rings"/>
</library>

person HerbSpiral    schedule 02.01.2012    source источник
comment
Какой у вас основной язык для выполнения XPath? Если вы используете XPath 1.0 (в котором нет функции max), то, вероятно, быстрее сначала выбрать все элементы и найти максимум в вашем PL.   -  person Wayne    schedule 03.01.2012


Ответы (7)


В XPath 2.0 используйте функцию max. Чтобы найти книгу с наивысшим id, выполните

/library/book[@id = max(/library/book/@id)]
person Fred Foo    schedule 02.01.2012
comment
Похоже, функция max не является частью Xpath 1.0 :( - person HerbSpiral; 02.01.2012
comment
@HerbSpiral: хм. Попробовал это в режиме совместимости XQilla XPath 1.0, и это работает, но, возможно, это не совсем XPath 1.0. - person Fred Foo; 02.01.2012

Следующий XPath выбирает книгу с наивысшим идентификатором:

/library/book[not(@id <= preceding-sibling::book/@id) and not(@id <=following-sibling::book/@id)]
person dertkw    schedule 02.01.2012
comment
Это действительно работает, однако производительность не так хороша (когда в документе присутствуют тысячи идентификаторов). - person HerbSpiral; 02.01.2012
comment
+1 - Я повторил суть вашего ответа, но я просто хотел предоставить дополнительную информацию в своем ответе, включая часть того, что было распространено в комментариях. - person Wayne; 03.01.2012
comment
Не работает, если все элементы имеют одинаковое значение - person Anonymoose; 27.02.2018

Если вы хотите использовать внешние инструменты (что зависит от вашей реализации с реализацией этих инструментов), попробуйте EXSLT:Math функция highest().

Тот факт, что EXSLT реализует это, подразумевает, что такая функция, конечно, недоступна напрямую в простом xpath. Если вы не используете Transforms или хотите придерживаться только разметки, соответствующей стандартам, предложения других авторов будут лучшим выбором.

person Tom W    schedule 02.01.2012

Примечание. Следующая информация предполагает использование XPath 1.0.

Следующее выражение возвращает элемент(ы) с наибольшим значением id:

/*/book[not(@id < preceding-sibling::book/@id) and 
        not(@id < following-sibling::book/@id)]

Обратите внимание, что это немного отличается от ответа @timbooo тем, что он вернет более одного элемента, если есть дубликаты с одинаковым максимальным значением (@timbooo не вернет ни одного). Если в этом случае вам нужен только один элемент, вам нужна стратегия разрешения. Чтобы выбрать первый такой элемент в порядке документа, используйте это:

/*/book[not(@id < preceding-sibling::book/@id) and 
        not(@id < following-sibling::book/@id)][1]

Чтобы выбрать последний, используйте это:

/*/book[not(@id < preceding-sibling::book/@id) and 
        not(@id < following-sibling::book/@id)][last()]

Этот подход очень неэффективен (O(n^2)), потому что он требует, чтобы вы сравнивали каждый элемент с каждым другим потенциальным максимумом. По этой причине, вероятно, лучше всего использовать ваш основной язык программирования для выбора максимального элемента. Просто сначала выберите все элементы book, а затем выберите максимум из этого списка. Это (скорее всего) линейная операция (O(n)), которая будет заметно быстрее на очень больших документах. Например, в Java (JAXP) это можно сделать так:

XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList) xpath.evaluate("/*/book", doc,
        XPathConstants.NODESET);
Node max = nodes.item(0);
for (int i = 0; i < nodes.getLength(); i++) {
    int maxval = Integer.parseInt(max.getAttributes()
            .getNamedItem("id").getNodeValue());
    int curval = Integer.parseInt(nodes.item(i).getAttributes()
            .getNamedItem("id").getNodeValue());
    if (curval >= maxval)
        max = nodes.item(i);
}
System.out.println(max.getAttributes().getNamedItem("name"));

Обратите внимание, что это всего лишь демонстрация; не забудьте включить нулевые проверки, где это уместно.

person Wayne    schedule 03.01.2012

Я обнаружил, что такие ответы, как lwburk или timbooo, отлично подходят для атрибутов, представляющих числа, имеющие только одну цифру. Однако, если атрибут представляет собой число, состоящее из более чем одной цифры, при сравнении значений атрибутов могут возникать посторонние вещи. Например, попробуйте изменить исходные данные XML примерно так:

<?xml version="1.0" encoding="utf-8"?>
<library>
        <book id="250" name="Dragon Tatoo"/>
        <book id="700123" name="Ender's Game"/>
        <book id="305" name="Catch 22"/>
        <book id="1070" name="Lord of the rings"/>
</library>

Запуск предлагаемых фрагментов не будет работать. Я получил решение с использованием оператора приведения xs:int(), примененного к атрибуту id, например:

/library/book[not(xs:int(@id) <= preceding-sibling::book/@id) and not(xs:int(@id) <=following-sibling::book/@id)]

Это даст правильный ответ!

person Ricardo    schedule 23.01.2014

Этот пример можно использовать для нахождения макс.

XmlDocument doc = new XmlDocument();                    
doc.Load("../../Employees.xml");
XmlNode node = doc.SelectSingleNode("//Employees/Employee/@Id[not(. <=../preceding-sibling::Employee/@id) and not(. <=../following-sibling::Employee/@Id)]");
int maxId = Convert.ToInt32(node.Value);

Другие похожие темы по xpath и linq см. на странице http://rmanimaran.wordpress.com/2011/03/20/xml-find-max-and-min-value-in-a-attribute-using.-xpath-and-linq/

person Jpepper    schedule 02.01.2012

XPath 1.0

/library/book[not(@id < /library/book/@id)]

Этот стиль запроса является более общим и работает, даже если книги сгруппированы, т.е.

<?xml version="1.0" encoding="utf-8"?>
<library>
    <genre id="1">
        <book id="2" name="Dragon Tatoo"/>
        <book id="7" name="Ender's Game"/>
    </genre>
    <genre id="2">
        <book id="3" name="Catch 22"/>
        <book id="1" name="Lord of the rings"/>
    </genre>
</library>

Тот же запрос все еще работает (путь должен быть изменен)

/library/genre/book[not(@id < /library/genre/book/@id)]

или даже

//book[not(@id < //book/@id)]

Чтобы избежать проблем с производительностью, используйте вместо этого XPath 2 max()

person serge    schedule 11.10.2019