Двухэтапное преобразование XSLT XML в HTML - должен быть лучший способ

Мы получаем XML-пакет с изменением цены, а затем хотим обновить конкретный раздел HTML-документа. Проблема в том, что единственный способ увидеть, как это работает, — это двухэтапное преобразование, которое сначала преобразует пакет XML в правильно сформированный фрагмент HTML, а затем второй XSLT для чтения в файле HTML и перезаписывания этого конкретного раздела.

HTML-файл для обновления (он правильно сформирован):

<html>
  <head>
    <title>Mini-me Amazon</title>
  </head>
  <body>
    <p>This is our Product Price Sheet</p>
    <table style="width:100%">
      <tr>
        <th>Product</th>
        <th>Price</th>
      </tr>
      <tr data-key="1">
        <td>Whiz-bang widget</td>
        <td name="price1">$19.99</td>
      </tr>
      <tr data-key="3">
        <td>Unreal widget</td>
        <td name="price3">$99.99</td>
      </tr>
      ...
    </table>
  </body>
</html>

Входящий XML:

<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="xml-price.xsl"?>
<supplier>
  <product>
    <key>3</key>
    <pprice uptype="1">
      <price>$22.34</price>
    </pprice>
  </product>
</supplier>

1-й XSL:

<xsl:stylesheet ...>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:template match="/supplier">
    <xsl:apply-templates select="product"/>
  </xsl:template>
  <xsl:template match="product">
    <xsl:variable name="PKey">
      <xsl:value-of select="key"/>
    </xsl:variable>
    <xsl:for-each select="pprice">  <!-- could be more than 1 -->
      <xsl:choose>
        <xsl:when test="@uptype=0">
        </xsl:when>
        <xsl:when test="@uptype=1">
          <xsl:apply-templates select="price"/>
        </xsl:when>
        <xsl:otherwise>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="price">
      <td name="rate$PKey"><xsl:value-of select="."/></td>
  </xsl:template>
</xsl:stylesheet>

Итак, Saxon-js возвращает <td name="price3">$22.34</td>. Все хорошо. Итак, мы хотим взять этот фрагмент HTML и обновить HTML.

2-й XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output omit-xml-declaration="yes"/>
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
  <xsl:template match="td[@name='price3']">   <!-- Problem 1 -->
    <td name="price3">$22.34</td>             <!-- Problem 2 --> 
  </xsl:template>
  <xsl:template match="/">
    <xsl:apply-templates select="document('/home/tireduser/node/bigstuff/public/update-html.html')/node()"/>
  </xsl:template>
</xsl:stylesheet>

Проблема:

Как получить динамические значения price3 и <td name="price3">$22.34</td> (которые изменяют каждый новый поступающий XML) во второй XSL без повторной компиляции XSL в .sef.json, который требует Saxon-js, и без использования параметров для передачи этих значения (так как мы прочитали, что использовать параметры не рекомендуется? Или все это можно сделать за 1 преобразование?

2-й вопрос: состояние документов Saxon-js:

Using fn:transform()
If a source XSLT stylesheet is supplied as input to the fn:transform() function in XPath, the XX compiler will be invoked to compile the stylesheet before it is executed. However, there is no way of capturing the intermediate SEF stylesheet for subsequent re-use.

Мы обнаружили, что это неправда (или мы делаем это неправильно). Если мы просто передаем XSL в функцию Transform (stylesheetFileName:), возникает ошибка.


person ImTalkingCode    schedule 11.03.2021    source источник
comment
Не могли бы вы уточнить, используете ли вы Saxon-JS в браузере или под Node.js?   -  person Martin Honnen    schedule 11.03.2021
comment
Где вы прочитали, что параметры не рекомендуются? Мне это кажется очень плохим советом. Написание таблиц стилей без параметров — это работа со связанными за спиной руками.   -  person Michael Kay    schedule 11.03.2021
comment
@MichaelKay Использование его под node.js. Нужно купить вашу книгу. Приведенное ниже решение Мартина показывает мне отсутствие у нас навыков XSL.   -  person Michael Kay    schedule 11.03.2021
comment
В качестве альтернативы, если это более удобно, вы можете загрузить вторичные данные по запросу, используя функцию document().   -  person ImTalkingCode    schedule 11.03.2021


Ответы (1)


Я думаю, вам в основном нужна одна таблица стилей в соответствии с

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:param name="price-data">
<supplier>
  <product>
    <key>3</key>
    <pprice uptype="1">
      <price>$22.34</price>
    </pprice>
  </product>
</supplier>
  </xsl:param>
  
  <xsl:key name="price" match="product/pprice[@uptype = 1]/price" use="'price' || ancestor::product/key"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="td[@name][key('price', @name, $price-data)]/text()">{key('price', ../@name, $price-data)}</xsl:template>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:next-match/>
    <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
  </xsl:template>
  
</xsl:stylesheet>

Для компактности я встроил вторичные данные в параметр, для Saxon-JS 2 и под Node.js вы бы в основном объявили, что параметр привязывает значение, например. <xsl:param name="price-data" select="doc('sample2.xml')"/> или вы можете предварительно загрузить документ с помощью getResource, а затем установить параметр для предварительно загруженного документа перед запуском преобразования; см. раздел примеров на https://www.saxonica.com/saxon-js/documentation/index.html#!api/getResource.

В своем комментарии вы говорите, что передаете данные XML в виде строки из Node.js в Saxon-JS, в этом случае вам нужно использовать parse-xml:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:param name="price-data">
<supplier>
  <product>
    <key>3</key>
    <pprice uptype="1">
      <price>$22.34</price>
    </pprice>
  </product>
</supplier>
  </xsl:param>
  
  <xsl:key name="price" match="product/pprice[@uptype = 1]/price" use="'price' || ancestor::product/key"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="td[@name][key('price', @name, $price-data)]/text()">{key('price', ../@name, $price-data)}</xsl:template>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:next-match/>
    <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
  </xsl:template>
  
</xsl:stylesheet>
означает функцию XPath 3.1, а не метод Saxon API

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:param name="price-data" as="xs:string"><![CDATA[
<supplier>
  <product>
    <key>3</key>
    <pprice uptype="1">
      <price>$22.34</price>
    </pprice>
  </product>
</supplier>
  ]]></xsl:param>
  
  <xsl:param name="price-doc" select="parse-xml($price-data)"/>
  
  <xsl:key name="price" match="product/pprice[@uptype = 1]/price" use="'price' || ancestor::product/key"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="td[@name][key('price', @name, $price-doc)]/text()">{key('price', ../@name, $price-doc)}</xsl:template>

  <xsl:template match="/" name="xsl:initial-template">
    <xsl:next-match/>
    <xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
  </xsl:template>
  
</xsl:stylesheet>
person Martin Honnen    schedule 11.03.2021
comment
@MichaelKay, верно, да, я как бы думал, что асинхронная обработка в Saxon-JS требует предварительной загрузки, и поэтому не упомянул функцию _1_ или _2_, но я отредактировал ответ, упомянув этот более простой способ, и проверил, что он работает с Saxon -JS 2.1 и простая, относительная загрузка файлов без проблем. - person Michael Kay; 11.03.2021
comment
Конечно, если вы хотите, чтобы все работало красиво и асинхронно, лучше использовать предварительную загрузку. - person Martin Honnen; 11.03.2021
comment
@MartinHonnen Спасибо за пример кода. Но у него нет xsl для загрузки файла HTML (/home/tireduser/node/bigstuff/public/update-html.html). Должен ли я использовать для этого свой существующий код? - person Michael Kay; 11.03.2021
comment
@ImTalkingCode, насколько я понимаю сценарий, вы должны передать этот правильно сформированный HTML-файл в качестве основного ввода в приведенную выше таблицу стилей. - person ImTalkingCode; 23.03.2021
comment
@MartinHonnen Работал как шарм. До сих пор не понимаю соответствия основного шаблона ключевой строке, но я пойму. - person Martin Honnen; 23.03.2021
comment
@MartinHonnen Спасибо за скрипку и спасибо, что указали мне на сервис xslt3fiddle. Единственная проблема сейчас заключается в том, что когда я беру введенные вручную данные о ценах и использую stylesheetParams в преобразовании node.js, преобразование не обновляет HTML. Я использую: stylesheetParams = {ценовые данные: '‹поставщик›...‹/поставщик›'} - person ImTalkingCode; 23.03.2021
comment
Если вы передаете строковые данные, то в XSLT используйте для них _1_, чтобы преобразовать их в документ. - person ImTalkingCode; 24.03.2021
comment
@MartinHonnen Я попробую это. Я также думал, что as=node() в xsl:param также используется для входящих строк XML. Но это не так. - person Martin Honnen; 24.03.2021
comment
Вот онлайн-пример использования Saxon-JS 2 в браузере - person ImTalkingCode; 24.03.2021