Мы пытались сгенерировать хэш определенного текста из данного документа и придумали следующую версию 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"><![CDATA[</xsl:text>
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
<xsl:text disable-output-escaping = "yes">]]></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");
}
Нет. Одна из версий, вставленных в заданный выше вопрос, уже использует «интегрированные функции расширения» Saxon-HE, и именно так этот фрагмент кода работает во время выполнения. Обратите внимание на использование функций пространства имен «iway». Покупка Saxon-PE/EE не вариант, атм. Поэтому я ищу другие варианты.
Разве Stylus Studio, по крайней мере, не позволяет вам запускать/тестировать ваш XSLT-код с помощью Saxon HE и встроенной функции расширения? Я думаю, что в oXygen вы можете указать путь к библиотеке для таких функций расширения.
На самом деле вы не говорите, для чего нужна хеш-функция. Это что-то криптографическое или для группировки и сопоставления на равенство?
Приложение генерирует хэш sha1 каждого XML-документа, который оно передает в целевую систему. Сгенерированный хэш хранится в таблице поиска БД по сравнению с тем, что мы называем числом материала. Приложение будет получать документ для одного и того же номера материала более одного раза и должно выполнять передачу в цель только тогда, когда что-то изменилось в текущем документе по сравнению с тем, что было отправлено последним. Это достигается путем сравнения хэша sha1 текущего документа с хешем его предыдущего документа, хранящегося в таблице поиска.
@MartinHonnen - я все еще пытаюсь понять это с помощью XMLSpy...
Я добавил более полный пример предложенного мной подхода к моему ответу; действительно, для этого требуется Saxon HE 10 или более поздняя версия, поскольку, к сожалению, функция более высокого порядка для поиска функций не работает с более ранними версиями HE. Протестировано только с Saxon HE и EE из Java или командной строки, у меня нет доступа к XMLSpy.




Я бы попробовал, например.
<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...
Функции более высокого порядка поддерживаются в более поздних версиях Saxon-HE, но они не позволяют обойти ограничения.
Saxon 10 HE и более поздние версии поддерживают функции более высокого порядка.
@MichaelKay, я не думаю, что вопрос заключается в том, чтобы избежать ограничений, а в том, чтобы иметь одну таблицу стилей, которая использует некоторую функцию рефлексивного расширения, если она доступна, или интегрированную, если она доступна. Мой ответ просто пытается проверить, доступна ли функция, и должен условно вводить/выполнять код, только если он доступен.
@MartinHonnen Тест с последней версией Saxon-HE (12) действительно решает проблему. Моя благодарность за ваше время и усилия.
Если вы хотите вызывать Java в SaxonJ-HE, вам нужно реализовать «интегрированную функцию расширения» и зарегистрировать ее в конфигурации Saxon, а не полагаться на динамическую загрузку и рефлексивный вызов.
Это не сложно: см. https://www.saxonica.com/documentation12/index.html#!extensibility/extension-functions-J/ext-simple-J
Обратите внимание, что мы уже используем функцию расширения, благодаря которой вычисление/выполнение следующего выражения выполняется успешно: iway:ifl($iflExpression). Я отредактирую вопрос, включив в него функцию расширения для лучшей читабельности, поскольку раздел комментариев не форматируется.
Вы имеете в виду требование написать функцию расширения, которая работает как под Saxon, так и под Altova? Я думаю, вы можете создать две функции расширения, по одной для каждого процессора, с одинаковым интерфейсом и одинаковым эффектом, но я думаю, что вам понадобятся две разные реализации, чтобы удовлетворить разные API.
Отвечает ли это на ваш вопрос? Рефлексивные вызовы методов Java недоступны в Saxon-HE