XSLT 3 | Хэш-функция

Мы пытались сгенерировать хэш определенного текста из данного документа и придумали следующую версию XSLT:

<xsl:stylesheet version = "2.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:iway = "http://iway.company.com/saxon-extension">
    <xsl:output method = "xml" version = "1.0" encoding = "UTF-8" indent = "yes" exclude-result-prefixes = "iway"/>
    <xsl:strip-space elements = "*"/>

    <xsl:template match = "*[not(descendant::text()[normalize-space()])]"/>

    <xsl:template match = "@*|node()">
        <xsl:copy>
            <xsl:apply-templates select = "@*|node()"/>
        </xsl:copy>     
    </xsl:template>
    
    <xsl:template match = "row" exclude-result-prefixes = "iway">
    <xsl:variable name = "jsonForHash" select = "JSON_Output/text()"/>
    <xsl:variable name = "iflExpression" select = "concat('_sha1(''', $jsonForHash, ''')')"/>
        <xsl:copy>
            <xsl:apply-templates select = "@*|node()"/>
            <CurrentDataHash type = "12" typename = "varchar"><xsl:value-of select = "iway:ifl($iflExpression)"/></CurrentDataHash>   
            <Duplicity type = "12" typename = "varchar"><xsl:value-of select = "$jsonForHash = LastDataHash/text()"/></Duplicity>     
        </xsl:copy>     
    </xsl:template>

</xsl:stylesheet>

... который выполняет свою работу. Недостатком является то, что его нельзя было протестировать локально (в Altova/Stylus Studio) без модификации, а мы хотели бы иметь возможность это сделать. Это работает только в среде выполнения, основанной на Saxon-HE-9*. Пытаясь исправить это, мы попробовали приведенную ниже версию (вдохновленная ЗДЕСЬ):

<xsl:transform version = "3.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:digest = "java?path=jar:file:///C:/libs/commons-codec-1.13.jar!/">
    <xsl:output method = "xml" indent = "yes" omit-xml-declaration = "yes"/>

    <xsl:template match = "/">
        <Output>
            <xsl:apply-templates mode = "hash"/>
        </Output>
    </xsl:template>
    
    <xsl:template match = "SKU_SEG" mode = "hash">
        <Group>
            <xsl:variable name = "val" select = "."/>
            <xsl:copy-of select = "$val"/>
            <xsl:variable name = "hash-val" select = "digest:org.apache.commons.codec.digest.DigestUtils.md5Hex($val)"/>
            <HashValue>
                <xsl:value-of select = "$hash-val"/>
            </HashValue>
        </Group>
    </xsl:template>
    
</xsl:transform>

...который работает только локально на Altova, но не работает во время выполнения, поскольку мы используем Saxon-HE, но функция поддерживается только на Saxon-PE/EE. Чтобы преодолеть это, мы придумали эту версию:

<xsl:transform version = "3.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" xmlns:digest = "java?path=jar:file:///C:/libs/commons-codec-1.13.jar!/" xmlns:iway = "http://iway.company.com/saxon-extension" exclude-result-prefixes = "digest iway">
    <xsl:output method = "xml" indent = "yes" omit-xml-declaration = "yes" exclude-result-prefixes = "digest iway"/>
    <xsl:template match = "/">
        <Output>
            <xsl:apply-templates mode = "hash"/>
        </Output>
    </xsl:template>
    <xsl:template match = "SKU_SEG" mode = "hash">
        <xsl:variable name = "parserInfo" select = "system-property('xsl:vendor')"/>
        <Group>
            <xsl:variable name = "textForHash" select = "."/>
            <xsl:variable name = "iflExpression" select = "concat('_sha1(''', $textForHash, ''')')"/>
            <xsl:copy-of select = "$textForHash"/>
            <xsl:variable name = "hashedVal">
                <xsl:choose>
                    <xsl:when test = "contains(lower-case($parserInfo), 'saxon')">
                        <xsl:value-of select = "iway:ifl($textForHash)"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select = "digest:org.apache.commons.codec.digest.DigestUtils.md5Hex($textForHash)"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <HashValue>
                <xsl:value-of select = "$hashedVal"/>
            </HashValue>
        </Group>
    </xsl:template>
</xsl:transform>

... который работает локально на Altova XMLSpy, но не во время выполнения, поскольку Saxon жалуется на следующее:

net.sf.saxon.trans.XPathException: 
Cannot find a 1-argument function named 
Q{java?path=jar:file:///C:/libs/commons-codec-1.13.jar!/}
org.apache.commons.codec.digest.DigestUtils.md5Hex(). 
Reflexive calls to Java methods are not available under Saxon-HE

Теперь вопрос: можно ли вообще выполнить требование? Заранее спасибо.

Setup Info: 
Runtime: Java Application relying on Saxon-HE
XSLT Versions Supported: 1/2/3
Standalone Tool for local tests: Altova XMLSpy

PS: приведенная ниже версия (вдохновленная ЗДЕСЬ), по-видимому, работает как локально, так и удаленно, если текст, который нужно хешировать, не слишком длинный, но текст, который хэшируется здесь, слишком длинный, длиннее, чем разрешено на URL-адрес HTTP, поэтому это не вариант:

<xsl:transform version = "3.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
    <xsl:output method = "xml" indent = "yes" omit-xml-declaration = "yes"/>
    
    <xsl:template match = "/">
        <Output>
            <arg0>
                <xsl:text disable-output-escaping = "yes">&lt;![CDATA[</xsl:text>
                <xsl:copy>
                    <xsl:apply-templates/>
                </xsl:copy>
                <xsl:text disable-output-escaping = "yes">]]&gt;</xsl:text>
            </arg0>
            <arg1>
                <xsl:apply-templates mode = "hash"/>
            </arg1>
        </Output>
    </xsl:template>
    
    <xsl:template match = "SKU_SEG">
        <xsl:copy-of select = "."/>
    </xsl:template>
    
    <xsl:template match = "SKU_SEG" mode = "hash">
        <xsl:variable name = "val" select = "."/>
        <!-- delegate to an external REST service to calculate the MD5 hash of the value -->
        <xsl:variable name = "hash-val" select = "unparsed-text(concat('http://localhost/md5?text=', encode-for-uri($val)))"/>
        <!-- the response from this service is wrapped in quotes, so need to trim those off -->
        <xsl:value-of select = "substring($hash-val, 2, string-length($hash-val) - 2)"/>
    </xsl:template>
    
</xsl:transform>

Для справки, вот функция расширения Saxon:

 private void registeriWayXsltExtensions_iFLEval(final XDDocument docIn) {
    log(".init() Registering iWay XSLT extensions...", "info");
    this.iway_xslt_extension_ifl = new ExtensionFunction() {
        public QName getName() {
          return new QName("http://iway.company.com/saxon-extension", "ifl");
        }
        
        public SequenceType getResultType() {
          return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE);
        }
        
        public SequenceType[] getArgumentTypes() {
          return 
            new SequenceType[] { SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE) };
        }
        
        public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
          String iflExpression = ((XdmAtomicValue)arguments[0].itemAt(0)).getStringValue();
          SaxonXsltAgent.this.log(".execute()  Received iFL Expression: " + iflExpression, "info");
          String iflResult = null;
          if (iflExpression != null && !iflExpression.equals(""))
            iflResult = XDUtil.evaluate(iflExpression, docIn, SaxonXsltAgent.this.getSRM()); 
          return (XdmValue)new XdmAtomicValue(iflResult);
        }
      };
    this.xsltProcessor.registerExtensionFunction(this.iway_xslt_extension_ifl);
    log(".execute() \"ifl\" registered.", "info");
  }

Отвечает ли это на ваш вопрос? Рефлексивные вызовы методов Java недоступны в Saxon-HE

f1sh 01.02.2023 15:02

Нет. Одна из версий, вставленных в заданный выше вопрос, уже использует «интегрированные функции расширения» Saxon-HE, и именно так этот фрагмент кода работает во время выполнения. Обратите внимание на использование функций пространства имен «iway». Покупка Saxon-PE/EE не вариант, атм. Поэтому я ищу другие варианты.

Srii 01.02.2023 15:16

Разве Stylus Studio, по крайней мере, не позволяет вам запускать/тестировать ваш XSLT-код с помощью Saxon HE и встроенной функции расширения? Я думаю, что в oXygen вы можете указать путь к библиотеке для таких функций расширения.

Martin Honnen 01.02.2023 15:18

На самом деле вы не говорите, для чего нужна хеш-функция. Это что-то криптографическое или для группировки и сопоставления на равенство?

Michael Kay 01.02.2023 15:42

Приложение генерирует хэш sha1 каждого XML-документа, который оно передает в целевую систему. Сгенерированный хэш хранится в таблице поиска БД по сравнению с тем, что мы называем числом материала. Приложение будет получать документ для одного и того же номера материала более одного раза и должно выполнять передачу в цель только тогда, когда что-то изменилось в текущем документе по сравнению с тем, что было отправлено последним. Это достигается путем сравнения хэша sha1 текущего документа с хешем его предыдущего документа, хранящегося в таблице поиска.

Srii 01.02.2023 15:51

@MartinHonnen - я все еще пытаюсь понять это с помощью XMLSpy...

Srii 01.02.2023 16:08

Я добавил более полный пример предложенного мной подхода к моему ответу; действительно, для этого требуется Saxon HE 10 или более поздняя версия, поскольку, к сожалению, функция более высокого порядка для поиска функций не работает с более ранними версиями HE. Протестировано только с Saxon HE и EE из Java или командной строки, у меня нет доступа к XMLSpy.

Martin Honnen 01.02.2023 18:18
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
7
57
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я бы попробовал, например.

<xsl:value-of 
  select = "iway:ifl($textForHash)" 
  use-when = "exists(function-lookup(QName('http://iway.company.com/saxon-extension', 'ifl'), 1))"/>

и

<xsl:value-of select = "disest:org.apache.commons.codec.digest.DigestUtils.md5Hex($textForHash)" 
  use-when = "exists(function-lookup(QName('java?path=jar:file:///C:/libs/commons-codec-1.13.jar!/', 'org.apache.commons.codec.digest.DigestUtils.md5Hex'), 1))"/>`.

Вот полный пример этого подхода (который должен работать только с Saxon HE 10 или более поздней версии, по общему признанию, поскольку более ранние версии HE не поддерживали функции более высокого порядка):

package org.example;

import net.sf.saxon.s9api.*;

import javax.xml.transform.stream.StreamSource;

public class Main {
    public static void main(String[] args) throws SaxonApiException {
        Processor processor = new Processor(true);

        ExtensionFunction sqrt = new ExtensionFunction() {
            public QName getName() {
                return new QName("http://example.org/mf", "sqrt");
            }

            public SequenceType getResultType() {
                return SequenceType.makeSequenceType(
                        ItemType.DOUBLE, OccurrenceIndicator.ONE
                );
            }

            public SequenceType[] getArgumentTypes() {
                return new SequenceType[]{
                        SequenceType.makeSequenceType(
                                ItemType.DOUBLE, OccurrenceIndicator.ONE)};
            }

            public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
                double arg = ((XdmAtomicValue)arguments[0].itemAt(0)).getDoubleValue();
                double result = Math.sqrt(arg);
                return new XdmAtomicValue(result);
            }
        };

        processor.registerExtensionFunction(sqrt);

        XsltCompiler xsltCompiler = processor.newXsltCompiler();

        Xslt30Transformer xslt30Transformer = xsltCompiler.compile(new StreamSource("sheet1.xsl")).load30();

        xslt30Transformer.callTemplate(null, xslt30Transformer.newSerializer(System.out));
    }
}

XSLT

<?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"
                xmlns:mf = "http://example.org/mf"
                xmlns:java-math = "java:java.lang.Math"
                exclude-result-prefixes = "#all"
                expand-text = "yes">

    <xsl:mode on-no-match = "shallow-copy"/>

    <xsl:output indent = "yes"/>

    <xsl:template match = "/" name = "xsl:initial-template">
        <test>
            <integrated-extension-function>
                <xsl:value-of select = "mf:sqrt(4)" use-when = "exists(function-lookup(QName('http://example.org/mf', 'sqrt'), 1))"/>
            </integrated-extension-function>
            <reflexive-extension-function>
                <xsl:value-of select = "java-math:sqrt(4)" use-when = "exists(function-lookup(QName('java:java.lang.Math', 'sqrt'), 1))"/>
            </reflexive-extension-function>
        </test>
        <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>

При запуске с приведенным выше Java-кодом, регистрирующим функцию в Saxon HE 11, результат будет, например.

<?xml version = "1.0" encoding = "UTF-8"?>
<test>
   <integrated-extension-function>2</integrated-extension-function>
   <reflexive-extension-function/>
</test>
<!--Run with SAXON HE 11.4 -->

при запуске XSLT через Saxon EE без регистрации встроенной функции расширения вывод

<?xml version = "1.0" encoding = "UTF-8"?>
<test>
   <integrated-extension-function/>
   <reflexive-extension-function>2</reflexive-extension-function>
</test>
<!--Run with SAXON EE 11.4 -->

Таким образом, с помощью (xsl):use-when = "exists(function-lookup(..))" вы можете условно вводить код только тогда, когда доступна определенная функция.

Пример проекта на Github: https://github.com/martin-honnen/SaxonHEIntegratedExtFnSample2

Спасибо за ответ. Свойство use-when является функцией более высокого порядка и не поддерживается в Saxon-HE...

Srii 01.02.2023 15:36

Функции более высокого порядка поддерживаются в более поздних версиях Saxon-HE, но они не позволяют обойти ограничения.

Michael Kay 01.02.2023 15:40

Saxon 10 HE и более поздние версии поддерживают функции более высокого порядка.

Martin Honnen 01.02.2023 16:05

@MichaelKay, я не думаю, что вопрос заключается в том, чтобы избежать ограничений, а в том, чтобы иметь одну таблицу стилей, которая использует некоторую функцию рефлексивного расширения, если она доступна, или интегрированную, если она доступна. Мой ответ просто пытается проверить, доступна ли функция, и должен условно вводить/выполнять код, только если он доступен.

Martin Honnen 01.02.2023 18:17

@MartinHonnen Тест с последней версией Saxon-HE (12) действительно решает проблему. Моя благодарность за ваше время и усилия.

Srii 02.02.2023 10:39

Если вы хотите вызывать Java в SaxonJ-HE, вам нужно реализовать «интегрированную функцию расширения» и зарегистрировать ее в конфигурации Saxon, а не полагаться на динамическую загрузку и рефлексивный вызов.

Это не сложно: см. https://www.saxonica.com/documentation12/index.html#!extensibility/extension-functions-J/ext-simple-J

Обратите внимание, что мы уже используем функцию расширения, благодаря которой вычисление/выполнение следующего выражения выполняется успешно: iway:ifl($iflExpression). Я отредактирую вопрос, включив в него функцию расширения для лучшей читабельности, поскольку раздел комментариев не форматируется.

Srii 01.02.2023 15:56

Вы имеете в виду требование написать функцию расширения, которая работает как под Saxon, так и под Altova? Я думаю, вы можете создать две функции расширения, по одной для каждого процессора, с одинаковым интерфейсом и одинаковым эффектом, но я думаю, что вам понадобятся две разные реализации, чтобы удовлетворить разные API.

Michael Kay 01.02.2023 16:08

Другие вопросы по теме