XSLT1.0 + Schematron: перевод значений XPath текста экземпляра, чтобы их можно было динамически оценивать во время преобразования XSLT

Я использую Schematron для проверки экземпляров XML-документов в XML-редакторе, поддерживающем настраиваемые схемы (обратите внимание, что проверка Schematron - это просто преобразование XSLT). Экземпляр может содержать элементы, в значении которых указан путь (упрощенное выражение XPath). Примером такого пути может быть:

/p:root/p:level-one/r:level-two/r:level-two-leaf

где оба префикса (p и r) привязаны к пространству имен, определенному в документе экземпляра. My Schematron проверяет такие пути, гарантируя, что элемент, на который указывает путь, действительно существует в документе экземпляра. Для этого он полагается на EXSLT (я вынужден использовать XSLT1.0), точнее на dyn:evaluate(), чтобы оценить текстовое значение элемента, которое, как вы можете видеть по сути является выражением XPath. Работает как часы.

Но есть одна проблема, на самом деле огромная. Вызов dyn:evaluate() выполняется из Schematron XSLT, который оценивает выражение XPath в своем собственном контексте пространства имен. Это означает, что для правильной работы как экземплярный документ, так и XSLT Schematron должны использовать одну и ту же привязку prefix → namespace. Я не могу заставить пользователя использовать одни и те же префиксы для тех же пространств имен, которые указаны в моей схеме ... это было бы глупым требованием (однако гарантируется, что по крайней мере те же самые пространства имен будут использоваться в обоих). Schematron всегда генерируется перед проверкой экземпляра, но это делается только один раз из соображений производительности. Единственный вариант, который у меня есть, - это каким-то образом предварительно обработать пути из экземпляра и выполнить своего рода перевод из «путей экземпляра» в «пути XSLT». Я новичок в XSLT и понятия не имею, как я могу этого добиться.

Как мне преобразовать такие текстовые значения в то, что требуется контексту пространства имен XSLT? Это вообще возможно? В настоящее время я думаю об исправлениях памяти XSLT (все это делается в Java) перед каждым вызовом проверки - переименованием префиксов так, чтобы они соответствовали привязке экземпляров или введению новых атрибутов пространства имен - но это может привести к конфликтам имен префиксов, и я Я не уверен, как это повлияет на производительность проверки. Я открыт для любых предложений, так как предполагаю, что это то, с чем, должно быть, сталкивались и другие люди (при использовании Schematron или dyn:evaluate()).

Изменить: отсюда следует пояснение того, что я пытаюсь сделать.

У меня есть файл экземпляра XML, который редактирует пользователь в редакторе. Примером такого файла может быть:

<?xml version="1.0" encoding="utf-8"?>
<config xmlns="http://example.com/ns/config"
         xmlns:cfg="http://example.com/ns/config"
         xmlns:ns1="http://example.com/ns/custom-01"
         xmlns:ns2="http://example.com/ns/custom-02">
  <ns1:some-element>/cfg:config/ns1:other-element/ns2:nested-element</ns1:some-element>
  <ns1:other-element>
    <ns2:nested-element>some value</ns2:nested-element>
  </ns1:other-element>
</config>

Такой документ затем проходит проверку схемы, которая, по сути, является преобразованием XSLT. Он будет объявлен действительным только в том случае, если путь в ns1:some-element относится к существующему элементу в том же документе (поэтому приведенный выше пример действителен).

Схема XSLT выглядит примерно так (обратите внимание, что она была значительно упрощена):

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--This XSLT was automatically generated from a Schematron schema.-->
<xsl:stylesheet xmlns:iso="http://purl.oclc.org/dsdl/schematron"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:dyn="http://exslt.org/dynamic"
                xmlns:exsl="http://exslt.org/common"
                xmlns:sch="http://www.ascc.net/xml/schematron"
                xmlns:cfg="http://example.com/ns/config"
                xmlns:cust1="http://example.com/ns/custom-01"
                xmlns:cust2="http://example.com/ns/custom-02"
                extension-element-prefixes="dyn exsl"
                version="1.0">

<!-- other templates -->

<xsl:template match="/cfg:config/cust1:some-element">
    <xsl:choose>
        <xsl:when test="dyn:evaluate(.)">
            <!-- do stuff -->
        <xsl:when>
    </xsl:choose>
</xsl:template>

<!-- other templates -->

</xsl:stylesheet>

Я уверен, что это объясняет проблему. Вызов dyn:evaluate(.) будет пытаться оценить выражение /cfg:config/ns1:other-element/ns2:nested-element XPath, которое использует несвязанные префиксы из префикса преобразования (и поэтому всегда имеет значение false).

Вопрос был и остается: как я могу перевести эти выражения XPath, чтобы они действительно имели смысл в преобразовании?


person predi    schedule 05.04.2012    source источник
comment
Пожалуйста, отредактируйте вопрос и дайте четкое, не сбивающее с толку определение проблемы - экземпляр Schematron XSLT, экземпляр XML-документа, кто что делает, желаемый результат.   -  person Dimitre Novatchev    schedule 05.04.2012
comment
@DimitreNovatchev, теперь вопрос яснее?   -  person predi    schedule 05.04.2012


Ответы (1)


XSLT 1 болезненен после десятилетия использования XSLT 2 :-)

Я считаю, что приведенная ниже таблица стилей должна работать, но она не работает ни на одном из двух имеющихся у меня процессоров xslt 1 (xsltproc и saxon6), но, возможно, она будет работать с тем, который у вас есть, или вы можете использовать его в качестве основы.

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

$ xsltproc cfg.xsl cfg.xml
xml
here 1 http://www.w3.org/XML/1998/namespace
ns2
here 2
ns1
here 2
cfg
here 2


===
/cfg:config/ns1:other-element/ns2:nested-element
===
XPath error : Undefined namespace prefix

поэтому, кроме предопределенного пространства имен xml, все узлы пространства имен имеют пустое строковое значение.

saxon6 gets this right:
$ saxon cfg.xml cfg.xsl
xml
here 1 http://www.w3.org/XML/1998/namespace
cfg
here 1 http://example.com/ns/config
ns1
here 1 http://example.com/ns/custom-01
ns2
here 1 http://example.com/ns/custom-02


===
/cfg:config/cust1:other-element/cust2:nested-element
===

Error at xsl:choose on line 26 of file:/C:/tmp/cfg.xsl:
  The URI http://exslt.org/dynamic does not identify an external Java class
Transformation failed: Run-time errors were reported

но не реализует функцию dyn: Assessment.

в любом случае вот код:

<xsl:stylesheet xmlns:iso="http://purl.oclc.org/dsdl/schematron"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:dyn="http://exslt.org/dynamic"
        xmlns:exsl="http://exslt.org/common"
        xmlns:sch="http://www.ascc.net/xml/schematron"
        xmlns:cfg="http://example.com/ns/config"
        xmlns:cust1="http://example.com/ns/custom-01"
        xmlns:cust2="http://example.com/ns/custom-02"
        extension-element-prefixes="dyn exsl"
        version="1.0">

 <!-- other templates -->

 <xsl:variable name="ns-catch"><x/></xsl:variable>
 <xsl:variable name="xsl-ns" select="exsl:node-set($ns-catch)/*/namespace::*[name()]"/>
 <xsl:template match="/cfg:config/cust1:some-element">
  <xsl:variable name="fix-source">
   <xsl:call-template name="nsprefix"/>
  </xsl:variable>
  <xsl:message>

   ===
   <xsl:value-of select="$fix-source"/>
   ===
  </xsl:message>
  <xsl:choose>
   <xsl:when test="dyn:evaluate($fix-source)">
    <!-- do stuff -->
   </xsl:when>
  </xsl:choose>
 </xsl:template>

 <xsl:template name="nsprefix">
  <xsl:param name="source-ns" select="namespace::*[name()]"/>
  <xsl:param name="string" select="."/>
  <xsl:choose>
   <xsl:when test="$source-ns">
    <xsl:message><xsl:value-of select="name($source-ns)"/></xsl:message>
    <xsl:choose>
     <xsl:when test="string($source-ns[1])=$xsl-ns">
      <xsl:message>here 1 <xsl:value-of  select="string($source-ns[1])"/></xsl:message>
      <xsl:variable name="newstring">
       <xsl:call-template name="replace">
    <xsl:with-param name="string" select="$string"/>
    <xsl:with-param name="from" select="concat(name($source-ns[1]),':')"/>
    <xsl:with-param name="to" select="concat(name($xsl-ns[.=$source-ns[1]][1]),':')"/>
       </xsl:call-template>
      </xsl:variable>
      <xsl:call-template name="nsprefix">
       <xsl:with-param name="source-ns" select="$source-ns[position()!=1]"/>
       <xsl:with-param name="string" select="$newstring"/>
      </xsl:call-template>
     </xsl:when>
     <xsl:otherwise>
      <xsl:message>here 2</xsl:message>
      <xsl:call-template name="nsprefix">
       <xsl:with-param name="source-ns" select="$source-ns[position()!=1]"/>
       <xsl:with-param name="string" select="$string"/>
      </xsl:call-template>
     </xsl:otherwise>
    </xsl:choose>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$string"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
 <xsl:template name="replace">
  <xsl:param name="string"/>
  <xsl:param name="from"/>
  <xsl:param name="to"/>
  <xsl:choose>
   <xsl:when test="contains($string,$from)">
    <xsl:value-of select="substring-before($string,$from)"/>
    <xsl:value-of select="$to"/>
    <xsl:call-template name="replace">
     <xsl:with-param name="string" select="substring-after($string,$from)"/>
     <xsl:with-param name="from" select="$from"/>
     <xsl:with-param name="to" select="$to"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$string"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
 <!-- other templates -->

</xsl:stylesheet>
person David Carlisle    schedule 05.04.2012
comment
Я успешно создал мета-таблицу стилей, которая преобразует мой XSLT Schematron путем внедрения в нее варианта вашего решения. Теперь я могу проверить свои выражения XPath с помощью модифицированного преобразования! Спасибо. Ваше преобразование работает без проблем с отладчиком XSLT Xalan и VS2010 (если вы исправите его, чтобы он использовал msxsl: node-set ()). - person predi; 06.04.2012