Search code examples
xpathxformsxsltforms

XForms conditional itemset


I am attempting to limit items in an item set dependent on the data (contained in instance 'i-rec') and a series of constraints (contained in instance 'i-schemaConstraints'). In essence, if there are a certain number of elements in i-rec, then the option will no longer be displayed in the dropdown menu, which is based on elements in the i-schemaConstraints instance. So far I have not found a way to tie these together with XPath. Is it possible? I am using XSLTForms version 1.3.

I have tried the following:

<xf:itemset ref="instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements[1]/descendant-or-self::*:element[count(@ident[. = current()/child::*/local-name()]) &lt; instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements[1]//*:element/@maxOccurs]">
    <xf:label ref="@label"/>
    <xf:value ref="@ident"/>
</xf:itemset>

The full XForm code is below:

<?xml-stylesheet href="xsl/xsltforms.xsl" type="text/xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
   xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xi="http://www.w3.org/2001/XInclude"
   xmlns:tei="http://www.tei-c.org/ns/1.0">
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      <title>Bug Demo</title>
      <xf:model xmlns="http://www.w3.org/2002/xforms" id="m-mss">
         <xf:instance id="i-rec">
            <TEI xmlns="http://www.tei-c.org/ns/1.0">
               <titleStmt>
                  <title/>
                  <title/>
                  <title/>
               </titleStmt>
            </TEI>
         </xf:instance>
         <xf:instance id="i-availableElements">
            <TEI xmlns="http://www.tei-c.org/ns/1.0">
               <titleStmt/>
               <title/>
               <author/>
            </TEI>
         </xf:instance>
         <xf:instance id="i-schemaConstraints">
            <TEI xmlns="http://www.tei-c.org/ns/1.0">
               <titleStmt ident="titleStmt" elementName="titleStmt" elementLabel="Title Statment">
                  <childElements>
                     <element ident="title" label="title" minOccurs="1" maxOccurs="2"/>
                     <element ident="author" label="author" minOccurs="0" maxOccurs="2"/>
                  </childElements>
               </titleStmt>
               <title ident="title" elementName="title" elementLabel="Title Element"/>
               <author ident="author" elementName="author" elementLabel="Author Element"/>
            </TEI>
         </xf:instance>
         <xf:instance id="i-insert-elements">
            <TEI xmlns="http://www.tei-c.org/ns/1.0">
               <element/>
            </TEI>
         </xf:instance>
         <xf:bind id="elementRules" nodeset="instance('i-rec')//*:titleStmt/*:title" constraint="count(instance('i-rec')//*:titleStmt/*:title) &lt;= 2"/>
      </xf:model>
   </head>
   <body>
      <h2>Debug restrictions </h2>
      <!-- Main element -->
      <xf:repeat xmlns="http://www.w3.org/2002/xforms" ref="instance('i-rec')//tei:titleStmt"
         id="titleStmtLevel1">
         <div xmlns="http://www.w3.org/1999/xhtml"
            style="margin:12px; padding:12px; border:1px solid #ccc;">
            <!-- Element Label --> TitleStmt 
            <!-- Select1 to add available elements -->
            <xf:select1 xmlns="http://www.w3.org/2002/xforms"
               ref="instance('i-availableElements')/*[local-name() = local-name(current())]">
               <xf:label/>
               <xf:itemset
                  ref="instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements[1]/descendant-or-self::*:element
                  [count(@ident[. = current()/child::*/local-name()]) &lt; instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements[1]//*:element/@maxOccurs]">
                  <xf:label ref="@label"/>
                  <xf:value ref="@ident"/>
               </xf:itemset>
               <xf:action ev:event="xforms-value-changed">
                  <xf:setvalue ref="instance('i-insert-elements')//*:element"
                     value="instance('i-availableElements')/*[local-name() = local-name(current())]"
                  />
               </xf:action>
            </xf:select1>
            <xf:trigger class="btn btn-outline-secondary btn-sm controls add" appearance="full">
               <xf:label><i class="bi bi-plus-circle"></i> Add element </xf:label>
               <xf:insert ev:event="DOMActivate" context="." at="."
                  origin="instance('i-availableElements')/*[local-name() = instance('i-insert-elements')//*:element][1]"
                  position="after"/>
               <xf:setvalue ev:event="DOMActivate" ref="instance('i-insert-elements')//*:element"/>
               <xf:setvalue ev:event="DOMActivate"
                  ref="instance('i-availableElements')/*[local-name() = local-name(current())][instance('i-schemaConstraints')/*[local-name() = local-name(current())]/*:childElements[1]/*:child/*:element]"
               />
            </xf:trigger>
            <div> Element Value: <xf:input xmlns="http://www.w3.org/2002/xforms"
                  ref=".[not(instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:controlledValues) and instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements/*:textNode[@type='input']]">
                  <xf:alert>Error: No more than 2</xf:alert>
               </xf:input>
               <!-- Child elements -->
               <xf:repeat xmlns="http://www.w3.org/2002/xforms"
                  id="additionalGrpRepeatLevel11GrpRepeatLevel1" ref="./*">
                  <div xmlns="http://www.w3.org/1999/xhtml"
                     style="margin:12px; padding:12px; border:1px solid #ccc;">
                     <xf:trigger xmlns="http://www.w3.org/2002/xforms" appearance="minimal" ref="."
                           ><xf:label>x</xf:label>
                        <xf:delete ev:event="DOMActivate" ref="."/>
                     </xf:trigger>
                     <!-- Element Label --> level 2 <xf:output
                        value="instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/@elementLabel"
                     /> Element Value: <xf:input
                        ref=".[not(instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:controlledValues) and instance('i-schemaConstraints')/*[local-name() = local-name(current())][1]/*:childElements/*:textNode[@type='input']]">
                        <xf:alert>Error: No more than 1 title allowed here</xf:alert>
                     </xf:input>
                  </div>
               </xf:repeat>
            </div>
         </div>
      </xf:repeat>
   </body>
</html>

Solution

  • You effectivly want to convert the childElements in your i-schemaConstraints

    <titleStmt>
      <childElements>
        <element ident="title" label="title" minOccurs="1" maxOccurs="2" />
        <element ident="author" label="author" minOccurs="0" maxOccurs="2" />
      </childElements>
    </titleStmt>
    

    into binding constraints

    <xf:bind
      nodeset="instance('i-rec')//titleStmt"
      constraint="count(title) &gt;= 1 and count(title) &lt;= 2" />
    <xf:bind
      nodeset="instance('i-rec')//titleStmt"
      constraint="count(author) &gt;= 0 and count(author) &lt;= 2" />
    

    Since these binding constraints must be evaluated on every change of the model, it certainly pays off not to evaluate them dynamically at runtime but to carry out the conversion into xf:bind elements once before starting the XForms processor. (I assume that the i-schemaConstraints are not going to change at runtime.)

    The following XSLT 1.0 transformation can be applied to the XForms document and inserts the necessary xf:bind elements.

    <xsl:stylesheet
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xf="http://www.w3.org/2002/xforms" version="1.0">
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
      </xsl:template>
      <xsl:template match="xf:model">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()" />
          <xsl:apply-templates
            select="xf:instance[@id='i-schemaConstraints']//childElements/element"
            mode="bind" />
        </xsl:copy>
      </xsl:template>
      <xsl:template match="element" mode="bind">
        <xf:bind nodeset="instance('i-rec')//{../../@ident}"
          constraint="count({@ident}) &gt;= {@minOccurs} and count({@ident}) &lt;= {@maxOccurs}" />
      </xsl:template>
    </xsl:stylesheet>