2010-08-30 1 views
1

У меня есть следующий простой XML:Кратчайший XPath найти минимальное/максимальное одноранговых узлов DATETIME

<root> 
<item> 
    <d>2002-05-30T09:00:00</d> 
</item> 
<item> 
    <d>2005-05-30T09:00:00</d> 
</item> 
<item> 
    <d>2003-05-30T09:00:00</d> 
</item> 
</root> 

Теперь я хочу найти минимальный или максимальный узел DATETIME с использованием XPath.

Мое решение на данный момент:

/root/item[not(number(translate(./d, 'TZ:-', '.')) <= number(translate(following-sibling::item, 'TZ:-', '.')))][not(number(translate(./d, 'TZ:-', '.')) <= number(translate(preceding-sibling::item, 'TZ:-', '.')))][1]/d 

Это работает, но это некрасиво, как ад, и не очень эффективным. В основном он преобразует dateTime в число, а затем сравнивает их друг с другом. Я адаптировал это значение от here.

Каков наилучший способ сделать это?

Приветствие

нео

ответ

11

Вы не могли в XPath 1.0, если вы не будете знать заранее количество item, потому что каждая функция которым имеет не набор узлов аргумента не отбрасывает ее аргумент принимает первый узел в узел-набор и оператор сравнения заказов не работают со строками.

В XPath 2.0 вы можете использовать:

max(/root/item/d/xs:dateTime(.)) 
+0

Хорошо работает, мне очень нравится этот синтаксис, см. также мой комментарий к другому ответу. – letmaik

+0

@neo: Я рад, что это вам помогло. Теперь, когда вы знаете, что у вас XQuery 1.0 и XPath 2.0, вы найдете много улучшений от XPath 1.0: в данном случае не только 'fn: max', но и возможность использовать выражения в качестве последнего шага в пути. Также я поставил ваш вопрос. – 2010-08-31 13:22:09

2

@neo, выражение XPath вы перечислить не работает, когда я проверить его. Попробуйте другой набор данных, и вы увидите:

<root> 
    <item> 
     <d>2003-05-30T09:00:00</d> 
    </item> 
    <item> 
     <d>2002-05-30T09:00:00</d> 
    </item> 
    <item> 
     <d>2005-05-30T09:00:00</d> 
    </item> 
</root> 

Ваш XPath производит 2003-05-30T09:00:00, что, очевидно, не макс.

И имеет смысл, что это не сработает, потому что предыдущие оси sibling :: и follow-sibling :: внутри функций translate() будут давать только один родной друг. Вы пытаетесь перейти к общему (установленному) сравнению со всеми братьями и сестрами на каждой оси, но первый аргумент для перевода() должен быть преобразован в строку, прежде чем у оператора сравнения будет возможность выполнить свою задачу. Converting a nodeset to a string ignores all nodes except the first one in document order.

Кроме того, translate(./d, 'TZ:-', '.') дает вам результаты, такие как 2003.05.30.09.00.00. Это не действительное число, за пределами «5». Ваши тестовые данные работают только потому, что годы разные. Вы получите лучшие результаты с translate(./d, 'TZ:-', ''), который даст 20030530090000.

Алехандро говорит, что это невозможно в XPath 1.0, и он может быть прав. Попробуем, и, может быть, мы узнаем что-то, даже если нам это не удастся.

Далее я попытался бы использовать общее сравнение вне функции перевода, чтобы он мог сравнивать целые наборы узлов. Что-то вроде этой наивной попытки:

/root/item[ 
    not(following-sibling::item[ 
     translate($current/d, 'TZ:-', '') &lt;= translate(./d, 'TZ:-', '')]) 
    and not(preceding-sibling::item[ 
     translate($current/d, 'TZ:-', '') &lt;= translate(./d, 'TZ:-', '')])] 

Однако это является неполным, как показано на псевдо-переменной $ тока, который должен относиться к внешнему пункту, тот, который является узлом контекста вне всех предикатов. К сожалению, XPath 1.0 не дает нам способ ссылаться на этот внешний контекст, когда другой контекст был нажат на стек внутренним предикатом.

(Я, кажется, напоминаю, что \ некоторые реализации XSLT, например MSXML, позволяют сделать это с помощью расширенной функции, например current(1), но я не могу найти информацию об этом на данный момент. решение XPath и current() - не XPath.)

На данный момент я согласен с Алехандро, что это невозможно в чистом стандартном XSLT 1.0.

Если вы укажете среду, в которой вы используете XPath, например. XSLT, Javascript или XQuery, мы можем предложить эффективный способ получить то, что вам нужно. Если это XPath 2.0, у Алехандро есть свой ответ.

Если у вас есть XQuery 1.0, она должна поддерживать XPath 2.0, так что вы можете использовать решение Алехандро, с доком(), чтобы получить доступ к входной XML-документа:

max(doc("myInput.xml")/root/item/d/xs:dateTime(.)) 
+0

@larsH: общий максимум XPath 1.0 выражение '$ nodes [not ($ nodes>.)]' Не может использоваться здесь, потому что эти строковые значения не могут быть преобразованы в число. Если вы используете 'fn: translate', тогда вы должны выполнить вызов функции для каждого узла в наборе узлов (последовательность - это тип XSLT 2.0), поэтому вы должны заранее знать счетчик' item': '/ root/item [ , > = translate (../ item [1], 'TZ: -', '')] [. > = translate (../ item [2], 'TZ: -', '')] [. > = translate (../ item [3], 'TZ: -', '')] ' – 2010-08-30 22:04:26

+0

Должен сказать, мне очень нравится версия XPath 2.0, но у меня есть только XPath 1.0. Хотя это означает больше работы в моем случае, я также могу использовать XQuery здесь. Как это будет выглядеть? – letmaik

+0

Если вы можете использовать XQuery, в нем должен быть XPath 2.0. Таким образом, вы можете использовать решение Алехандро: 'max (doc (" myInput.xml ")/root/item/d/xs: dateTime (.))' Отказ от ответственности: я не пользователь XQuery. – LarsH

0

Он работал для меня, но я ищу теперь улучшаем xpath, т.е. для данных ниже:

<root> 
<items> 
    <item> 
    <d>2002-05-30T09:00:00</d> 
    </item> 
</items> 
<items> 
    <item> 
    <d>2005-05-30T09:00:00</d> 
    </item> 
</items> 
<items> 
    <item> 
    <d>2005-05-30T10:00:00</d> 
    </item> 
</items> 
</root>