WedX - журнал о программировании и компьютерных науках

Как (красиво) шаблон сопоставить несколько определенных дочерних элементов (объединение) в более широком XPATH

Я пытаюсь сопоставить набор определенных элементов, но только те, которые являются дочерними по отношению к другой структуре элементов (скажем, это ввод или выбор элементов только где-то внутри divs с классом «особый соус» на них). Обычно это было бы просто до XPATH: мы могли бы объединить в скобки целевые дочерние элементы, например:

 div[contains(@class, 'special-sauce')//(input | select)

Но именно здесь XSLT бросает вызов, когда мы пытаемся использовать это как сопоставление с шаблоном (по крайней мере, в Saxon):

 <xsl:template match="div[contains(@class, 'special-sauce')//(input | select)">

{"error": "Файл xsl (/section-settings.xsl) не может быть проанализирован. Не удалось скомпилировать таблицу стилей. Обнаружена 1 ошибка.", "code": "TRANSFORM_ERROR", "location": null, "cause" : ["Неустранимая ошибка: токен \" (\ "здесь не допускается в шаблоне XSLT"]}

По сути, круглые скобки не разрешены как часть соответствия шаблону на основном уровне пути (очевидно, они по-прежнему отлично работают внутри условных выражений / и т. Д.).

Так что делать?

Что ж, технически использование объединения все еще может работать, но нам придется каждый раз повторять предок XPATH, поскольку мы не можем заключить в скобки дочерние элементы:

 <xsl:template match="div[contains(@class, 'special-sauce')//input 
     | div[contains(@class, 'special-sauce')//select">

Это выполнимо (не очень красиво, но, конечно, мы справимся с этим! Разрывы строк могут работать здесь, чтобы помочь нашему здравомыслию) в нашем простом примере здесь, но это становится проблематичным с более сложным XPATH, особенно если бы объединение в скобках было в середине более длинного xpath или для множества элементов.

e.g.

div[contains(@class, 'major-mess')]/div[contains(@class, 'special-sauce')]//(dataset | optgroup | fieldset)//(button | option | label)

становится

a crazy mess.

Хорошо, это быстро становится менее подходящим вариантом в более сложных примерах. И хотя структурирование нашего XSLT по-другому может помочь (промежуточные совпадения, использование модальности и т. Д.), Остается вопрос:

Как мы можем изящно сопоставить шаблон, используя объединения отдельных дочерних элементов в более крупном шаблоне XPATH, когда скобки в скобках не работают?

Пример листа для первого примера:

 <div class="special-sauce">
    <input class="form-control" type="text" value="" placeholder="INHERITED:" />
    <select class="form-control">
        <option value="INHERITED: ">INHERIT: </option>
        <option value=""></option>
    </select>
    <div class="radio">
        <label>
            <input type="radio" name="param3vals" value="INHERITED: " />
            INHERIT:
        </label>
    </div>
 </div>
 <div class="not-special"><input type="text" id="contact-info-include-path" size="90">     
     <label>contact</label>
 </input></div>
 <div class="sad-panda"><input type="text" id="sidenav-include-path" size="90">
     <label>sidenav</label>
 </input></div>

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

Хотя есть и другие вопросы, на которые можно получить ответы, аналогичные, например, тому, который я даю ниже, я чувствовал, что контекст этих вопросов обычно был более общим (так что союз верхнего уровня был бы прекрасен в качестве их ответа без осложнений), более конкретными способами, которые не совпадают, или просто слишком разными. Отсюда и формат вопросов и ответов.

XSLT 1.0 против 2.0 против 3.0

Майкл Кей правильно отмечает в своем ответьте ниже, что, хотя исходный шаблон, использованный здесь, не работает в XSLT 1.0 или 2.0, он должен работать в (полностью) процессоре, совместимом с XSLT 3.0. В настоящее время я использую систему, использующую Saxon 9.3, которая технически является XSLT 2.0. Я просто хочу привлечь дополнительное внимание к этому ответу для тех, кто использует систему 3.0.

09.09.2015

Ответы:


1

Я просмотрел все, и большинство ответов на похожие проблемы включали копирование повторяющейся части XPATH в каждый элемент и объединение всего этого вместе. Но есть способ получше! Легко забыть, что сопоставление определенного элемента относительно эквивалентно сопоставлению имени этого элемента в XPATH.

Используйте name() или local-name() вместо сопоставления элемента непосредственно в шаблоне сопоставления *.

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

Например, первое совпадение выглядит следующим образом:

 <xsl:template match="div[contains(@class, 'special-sauce')//
     element()[local-name() = ('input', 'select')]">

Здесь нет большого выигрыша с точки зрения места или времени, чтобы написать это, но мы делаем сокращаем избыточность и связанные с этим ошибки согласованности данных, которые могут возникнуть (слишком часто, особенно если позже вносятся изменения).

Где это действительно хорошо видно, так это последний пример в вопросе (беспорядок):

 <xsl:template match="div[contains(@class, 'major-mess')]/
     div[contains(@class, 'special-sauce')]//
     element()[local-name() = ('dataset', 'optgroup', 'fieldset')]//
     element()[local-name() = ('button', 'option', 'label')]">

И поскольку я не могу вспомнить, полностью ли это XSLT / XPATH 1.0 совместимо путем создания фрагмента дерева элементов в скобках для сравнения, если вам действительно нужна обратная совместимость, «contains() с скобками разделителей» (уменьшая вероятность ложного срабатывания от другого элемента будучи подстрокой целевого полного имени) тоже всегда работает:

 <xsl:template match="div[contains(@class, 'major-mess')]/
     div[contains(@class, 'special-sauce')]//
     element()[contains('|dataset|optgroup|fieldset|'), concat('|', local-name(), '|'))]//
     element()[contains('|button|option|label|', concat('|', local-name(), '|'))]">

* = "шаблон соответствия" vs "XPath"

Если вам сложно понять, почему наивный подход (первое, что я попытался использовать в вопросе) терпит неудачу в XSLT, поможет понять, что правила шаблона, такие как «соответствие», должны следовать XSLT-шаблоны, которые, по сути, являются лишь подмножеством допустимых выражений XPath (что легко усложняет различение и запоминание, особенно когда многие источники просто притвориться, что это весь XPath целиком). Обратите внимание, что круглые скобки отображаются только как допустимый вариант для использования в качестве токенов выражений, которые находятся только в выражениях в пределах предикатов, а не в любой другой части пути к местоположению или расположение шагов.


Заключительные соображения

Производительность: я понятия не имею, есть ли заметные различия в производительности с этим подходом по сравнению с объединением каждого отдельного элемента в качестве полного пути к каждому из них, или есть даже реальная разница в производительности между обращением к элементу изначально и как предикат анонимного element() селектор. Я подозреваю, что, хотя большинство процессоров XSLT, вероятно, могут добиться более быстрого поиска в дереве DOM, когда одно совпадение записывается с использованием собственной структуры пути по сравнению с предикатом с функцией name () в анонимном селекторе, случаи объединения могут выполняться быстрее в зависимости от того, насколько хорошо процессор пытается предварительно скомпилировать и оптимизировать для логических шаблонов. Я оставлю эту задачу кому-то другому, чтобы он попробовал провести сравнительный анализ, потому что в конечном итоге реальным препятствием становится разумность разработчика и проблемы с обслуживанием (вероятность возникновения человеческих ошибок). Я считаю, что в сложных матчах любое небольшое снижение производительности, вероятно, будет легко компенсировано простой читаемостью и уменьшенной / устраненной избыточностью данных этого подхода.

09.09.2015
  • Я бы определенно не рекомендовал использовать сопоставление строк для значения name () или local-name () - в Saxon он превращает код сопоставления с образцом из поиска в хеш-таблице в последовательный поиск всех возможных шаблонов. 10.09.2015
  • Полагаю, ключевым моментом здесь становится максимально возможное сокращение области поиска с помощью окружающего шаблона? 10.09.2015
  • Вы можете найти интересную статью Джона Ламли XML London 2015 о сопоставлении с образцом XSLT. saxonica.com/papers/xmllondon-2015jl.pdf 10.09.2015
  • @MichaelKay Спасибо, похоже, это будет интересное чтение! В большинстве случаев мне не нужно как беспокоиться о производительности, как в живой динамической системе обработки, поскольку наша публикует статические html-страницы из обработки xslt и встраивает php / asp / cf (зависит от on target env) по мере необходимости для обработки любых динамических аспектов, но массовая повторная публикация целых сайтов сразу (тысячи страниц и более) начинает видеть, что производительность становится все более серьезной. Большинство подобных совпадений происходит с нашими шаблонами html, а не с содержимым самой страницы, поэтому, будем надеяться, область действия останется несколько меньшей. 11.09.2015
  • Почему бы не использовать полностью простое и хорошо работающее чистое выражение XPath 1.0 этого общего типа: HeadPathExpression/*[self::name1 or self::name2 or self::name3]? 17.09.2015
  • @DimitreNovatchev отчасти потому, что ... честно говоря ... я иногда забываю про ось from-self? Вы СОВЕРШЕННО правы, что это будет отлично работать в простом случае, подобном тому, что я изложил здесь, с предопределенными / неизменяемыми именами QNames. Это является виноватым в попытке упростить вопрос о том, что я делаю, что привело к тому, что я использовал это конкретное решение. Он ломается, когда вы имеете дело с переданными значениями либо из листа (xpath для атрибута элемента, внутреннего текста и т. Д.), Либо как параметры / переменные. Вы не возражаете, если я включу это в этот ответ вместе с различиями в гибкости? 17.09.2015

  • 2

    Я думаю, что ваш шаблон законен в XSLT 3.0, как написано. Но я думаю, вам нужно решение XSLT 2.0 ...

    Один отличный способ, который люди часто упускают из виду, - это использование шаблонов, поддерживающих схемы. Если вы хотите сопоставить выбор элементов, вполне вероятно, что они тесно связаны в схеме, например, имея общий тип T или будучи членами группы подстановки S. Затем вы можете написать

    div[contains(@class, 'special-sauce')//schema-element(S)
    

    or

    div[contains(@class, 'special-sauce')//element(*, T)
    

    Но я думаю, вам нужно решение, которое не поддерживает схему ...

    В таком случае я не думаю, что могу предложить что-то лучше, чем то, что есть у вас.

    Иногда ответом является несколько режимов: например, что-то вроде

    <xsl:template match="div[contains(@class, 'special-sauce')]">
      <xsl:apply-templates mode="special"/>
    </xsl:template>
    
    <xsl:template match="select|input" mode="special">
    

    В целом я считаю, что режимы используются недостаточно.

    10.09.2015
  • Как ни странно, исходный шаблон не работает в CMS, на которой мы его запускаем… и xsl: version сообщает 2.0, а xsl: vendor сообщает, что это только Saxon 9.3.0.5, так что я предполагаю, что именно поэтому. Это странно, поскольку некоторые функции XSLT 3.0, такие как xsl: Assessment, работают как таковые, а не как специфичные для saxon или fn: calls, поэтому я предположил (да, да, я знаю), что мы использовали версию, поддерживающую 3.0. Что, конечно, упоминается в документации saxon теперь, когда я снова ищу. Совершенно верно, что XSLT 3.0 должен работать: ParenthesizedExprP разрешен как синтаксис верхнего уровня согласно w3.org/TR/ xslt-30 10.09.2015
  • Однако я хотел сказать, что мне ДЕЙСТВИТЕЛЬНО нравится идея использования шаблонов, поддерживающих схему. Это не сработает для всех случаев, но было бы фантастически, где это работает. Хотя мне очень нравятся и используются режимы, они имеют свои собственные соображения, особенно когда идентификация преобразует весь документ (например, листы xhtml) вместо того, чтобы извлекать из него минимальные части. Для непосредственных потомков, где порядок не имел значения, это было бы хорошо (соответствие одному режиму, пустое совпадение по умолчанию, которое все еще применяется к нему), но для неизвестной глубины с другим сопоставлением по умолчанию и проблемами порядка это становится проблематичным. 10.09.2015

  • 3

    Почему бы не разделить этот шаблон на два или три (по одному для каждого уровня) с режимами? Что-то типа

    <xsl:template match="div[contains(@class, 'special-sauce')">
      <xsl:apply-templates select=".//select|input" mode="special-sauce"/>
    </xsl:template>
    
    <xsl:template match="select|input" mode="special-sauce">
      <!-- ... -->
    </xsl:template>
    

    На мой взгляд, так читается яснее.

    09.09.2015
  • Как я уже упоминал в вопросе, это определенно может работать в определенных случаях! Я предпочитаю по возможности избегать этого, поскольку теперь вы собираетесь жонглировать исключением в режиме по умолчанию (или иметь дело с несколькими вставленными копиями), вам нужно добавить соответствующее преобразование идентичности (копия и т. Д.) на сопоставленном div, если вы действительно хотите вставить его в результирующее дерево и / или продолжить сопоставление любых других его дочерних элементов, и теперь вы имеете дело с взаимозависимостями в отдельных сопоставлениях. Что наиболее важно, вы можете столкнуться с СЕРЬЕЗНЫМИ проблемами размещения в дереве результатов. 10.09.2015
  • Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]