UNPKG

enketo-transformer

Version:

Library that transforms ODK-compliant XForms into a format that Enketo can consume

971 lines (933 loc) 1.67 MB
const DOMParser = globalThis.DOMParser; const XPathResult = globalThis.XPathResult; const XSLTProcessor = globalThis.XSLTProcessor; const NodeTypes = { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11 }; const xslForm = `<?xml version="1.0" encoding="UTF-8"?> <!-- ***************************************************************************************************** XSLT Stylesheet that transforms OpenRosa style (X)Forms into valid HTMl5 forms (exception: when non-IANA lang attributes are used the form will not validate (but that's not serious)) ***************************************************************************************************** --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:orx="http://openrosa.org/xforms" xmlns:enk="http://enketo.org/xforms" xmlns:odk="http://www.opendatakit.org/xforms" xmlns:kb="http://kobotoolbox.org/xforms" xmlns:esri="http://esri.com/xforms" xmlns:oc="http://openclinica.org/xforms" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jr="http://openrosa.org/javarosa" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" xmlns:dyn="http://exslt.org/dynamic" extension-element-prefixes="exsl str dyn" version="1.0" > <xsl:param name="openclinica"/> <xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/><!-- for xml: version="1.0" --> <xsl:key name="bindings-by-id" match="/h:html/h:head/xf:model/xf:bind" use="@id" /> <xsl:key name="nodeset-bindings" match="/h:html/h:head/xf:model/xf:bind" use="@nodeset" /> <xsl:key name="preload-bindings" match="/h:html/h:head/xf:model/xf:bind" use="@jr:preload != ''" /> <xsl:key name="calculate-bindings" match="/h:html/h:head/xf:model/xf:bind" use="@calculate != ''" /> <xsl:key name="instances" match="/h:html/h:head/xf:model/xf:instance" use="true()" /> <xsl:key name="primary-instance-root" match="/h:html/h:head/xf:model/xf:instance[1]/child::node()" use="true()" /> <xsl:key name="model-actions" match="/h:html/h:head/xf:model/xf:setvalue[@event] | /h:html/h:head/xf:model/odk:setgeopoint[@event]" use="local-name()" /> <xsl:key name="fields-by-ref" match="/h:html/h:body//xf:input | /h:html/h:body//xf:upload | /h:html/h:body//xf:select | /h:html/h:body//xf:select1" use="@ref" /> <xsl:key name="default-translation" match="/h:html/h:head/xf:model/xf:itext/xf:translation[@default]" use="true()" /> <xsl:key name="translations" match="/h:html/h:head/xf:model/xf:itext/xf:translation" use="true()" /> <xsl:key name="itext-texts-by-id" match="/h:html/h:head/xf:model/xf:itext/xf:translation/xf:text" use="@id" /> <xsl:variable name="upper-case" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" /> <xsl:variable name="lower-case" select="'abcdefghijklmnopqrstuvwxyz'" /> <xsl:variable name="undefined">undefined</xsl:variable> <xsl:variable name="warning">warning</xsl:variable> <xsl:variable name="error">error</xsl:variable> <xsl:variable name="translated"><!-- assumes that either a whole form is translated or nothing (= real life) --> <xsl:if test="count(key('translations', true())) &gt; 1" > <xsl:value-of select="string('true')" /><!-- no time to figure out how to use real boolean values --> </xsl:if> </xsl:variable> <xsl:variable name="default-lang"> <xsl:choose> <xsl:when test="key('default-translation', true())/@lang"> <xsl:value-of select="key('default-translation', true())/@lang" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="''" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="first-lang"> <!-- first language or empty if itext was not used --> <xsl:value-of select="key('translations', true())[1]/@lang" /> </xsl:variable> <xsl:variable name="current-lang"> <xsl:choose> <xsl:when test="string-length($default-lang) > 0"> <xsl:value-of select="$default-lang" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="$first-lang" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:template match="/"> <xsl:if test="not(function-available('exsl:node-set'))"> <xsl:message terminate="yes">FATAL ERROR: exsl:node-set function is not available in this XSLT processor</xsl:message> </xsl:if> <xsl:for-each select="/h:html/h:head/xf:model/xf:bind"> <xsl:if test="not(substring(./@nodeset, 1, 1) = '/')"> <xsl:message terminate="no">WARNING: Found binding(s) with relative nodeset attribute <!--on element: <xsl:value-of select="./@nodeset" />--> (form may work correctly if relative nodesets were used consistently throughout xml form in bindings as well as body, otherwise it will certainly be messed up). </xsl:message> </xsl:if> </xsl:for-each> <!--> <xsl:text disable-output-escaping='yes'>&lt;!DOCTYPE html&gt;</xsl:text> <html> <head> <title> <xsl:text>Transformation of JR (X)Form to HTML5</xsl:text> </title> <script src="jquery.min.js" type="text/javascript" ><xsl:text> </xsl:text><\/script> <script type="text/javascript"> <xsl:text disable-output-escaping='yes'> $(function() { $('#form-languages a').click(function(){ $('form [lang]').show().not('[lang="'+$(this).attr('lang')+'"], [lang=""], #form-languages a').hide(); }); });</xsl:text> <\/script> </head>--> <root> <form autocomplete="off" novalidate="novalidate" class="clearfix" dir="ltr"> <xsl:attribute name="class"> <xsl:value-of select="'or clearfix'" /> <xsl:if test="/h:html/h:body/@class"> <xsl:value-of select="concat(' ', /h:html/h:body/@class)" /> </xsl:if> </xsl:attribute> <xsl:attribute name="data-form-id"> <xsl:choose> <xsl:when test="key('primary-instance-root', true())/@id"> <xsl:value-of select="key('primary-instance-root', true())/@id" /> </xsl:when> <xsl:otherwise> <xsl:text>_</xsl:text> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:if test="/h:html/h:head/xf:model/xf:submission/@action"> <xsl:attribute name="action"> <xsl:value-of select="/h:html/h:head/xf:model/xf:submission/@action"/> </xsl:attribute> </xsl:if> <xsl:if test="/h:html/h:head/xf:model/xf:submission/@method"> <xsl:attribute name="method"> <xsl:choose> <xsl:when test="/h:html/h:head/xf:model/xf:submission/@method = 'form-data-post'"> <xsl:value-of select="'post'"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="/h:html/h:head/xf:model/xf:submission/@method"/> </xsl:otherwise> </xsl:choose> </xsl:attribute> </xsl:if> <xsl:if test="/h:html/h:head/xf:model/xf:submission/@base64RsaPublicKey"> <xsl:attribute name="data-base64RsaPublicKey"> <xsl:value-of select="/h:html/h:head/xf:model/xf:submission/@base64RsaPublicKey"/> </xsl:attribute> </xsl:if> <xsl:text>&#10;</xsl:text> <xsl:comment>This form was created by transforming an ODK/OpenRosa-flavored (X)Form using an XSL stylesheet created by Enketo LLC.</xsl:comment> <section class="form-logo"> <xsl:text></xsl:text> </section> <h3 dir="auto" id="form-title"> <xsl:choose> <xsl:when test="/h:html/h:head/h:title"> <xsl:value-of select="/h:html/h:head/h:title"/> </xsl:when> <xsl:otherwise> <xsl:text>No Title</xsl:text> </xsl:otherwise> </xsl:choose> </h3> <!-- <div id="stats" style="display: none;"> <span id="jrSelect"><xsl:value-of select="count(/h:html/h:body//xf:select)"/></span> <span id="jrSelect1"><xsl:value-of select="count(/h:html/h:body//xf:select1)"/></span> <span id="jrItemset"><xsl:value-of select="count(/h:html/h:body//xf:itemset)"/></span> <span id="jrItem"><xsl:value-of select="count(/h:html/h:body//xf:item)"/></span> <span id="jrInput"><xsl:value-of select="count(/h:html/h:body//xf:input)"/></span> <span id="jrUpload"><xsl:value-of select="count(/h:html/h:body//xf:upload)"/></span> <span id="jrTrigger"><xsl:value-of select="count(/h:html/h:body//xf:trigger)"/></span> <span id="jrRepeat"><xsl:value-of select="count(/h:html/h:body//xf:repeat)"/></span> <span id="jrRelevant"><xsl:value-of select="count(/h:html/h:head/xf:model/xf:bind[@relevant])"/></span> <span id="jrConstraint"><xsl:value-of select="count(/h:html/h:head/xf:model/xf:bind[@constraint])"/></span> <span id="jrCalculate"><xsl:value-of select="count(/h:html/h:head/xf:model/xf:bind[@calculate])"/></span> <span id="jrPreload"><xsl:value-of select="count(/h:html/h:head/xf:model/xf:bind[@jr:preload])"/></span> </div> --> <xsl:if test="//*/@lang" > <select id="form-languages"> <xsl:if test="$translated != 'true'"> <xsl:attribute name="style">display:none;</xsl:attribute> </xsl:if> <xsl:attribute name="data-default-lang"> <xsl:value-of select="$default-lang" /> </xsl:attribute> <xsl:call-template name="languages" /> </select> </xsl:if> <xsl:apply-templates /> <!-- Create hidden input fields for preload items that do not have a form control. --> <xsl:if test="key('preload-bindings', true())" > <fieldset id="or-preload-items" style="display:none;"> <xsl:apply-templates select="key('preload-bindings', true())"/> </fieldset> </xsl:if> <!-- Create hidden input fields for calculated items that do not have a form control. --> <!-- the template will exclude those that have an input field --> <xsl:if test="key('calculate-bindings', true())"> <fieldset id="or-calculated-items" style="display:none;"> <xsl:apply-templates select="key('calculate-bindings', true())" /> </fieldset> </xsl:if> <!-- Create hidden input fields for calculated items that do not have a form control. --> <!-- the template will exclude those that have an input field --> <xsl:if test="key('model-actions', 'setvalue')"> <fieldset id="or-setvalue-items" style="display:none;"> <xsl:apply-templates select="key('model-actions', 'setvalue')" /> </fieldset> </xsl:if> <xsl:if test="key('model-actions', 'setgeopoint')"> <fieldset id="or-setgeopoint-items" style="display:none;"> <xsl:apply-templates select="key('model-actions', 'setgeopoint')" /> </fieldset> </xsl:if> <!-- <xsl:if test="/h:html/h:body//xf:output"> <xsl:message>WARNING: Output element(s) added but note that only /absolute/path/to/node is properly supported as "value" attribute of outputs. Please test to make sure they do what you want.</xsl:message> </xsl:if> <xsl:if test="/h:html/h:body//xf:itemset"> <xsl:message>WARNING: Itemset support is experimental. Make sure to test whether they do what you want.</xsl:message> </xsl:if> --> <xsl:if test="//xf:submission"> <xsl:message>ERROR: Submissions element(s) not supported.</xsl:message> </xsl:if> </form> </root> </xsl:template> <xsl:template match="h:head"/> <!--[not(self::xf:model/xf:bind[@jr:preload])]" />--> <xsl:template match="xf:group"> <!-- NOTE: TO IMPROVE PERFORMANCE, SUPPORT FOR RELATIVE NODESET BINDINGS HAS BEEN SWITCHED OFF To turn this back on: - uncomment the variable nodeset_used - revert back to commented-out code for variable nodeset - revert back to commented-out code for variable binding - all this takes place in the next 10 lines <xsl:variable name="nodeset_used"> <xsl:call-template name="nodeset_used" /> </xsl:variable> --> <xsl:variable name="nodeset"> <!--<xsl:call-template name="nodeset_absolute"> <xsl:with-param name="nodeset_u" select="$nodeset_used"/> </xsl:call-template>--> <xsl:call-template name="nodeset_used" /> </xsl:variable> <!-- note that bindings are not required --> <!--<xsl:variable name="binding" select="/h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset_used] | /h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset]"/>--> <xsl:variable name="binding" select="key('nodeset-bindings', $nodeset)"/> <section> <xsl:attribute name="class"> <!-- only add or-group if label is present or if it has a repeat as child--> <xsl:choose> <xsl:when test="string(./xf:label/@ref) or string(./xf:label)"> <xsl:value-of select="'or-group '" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="'or-group-data '" /> </xsl:otherwise> </xsl:choose> <xsl:if test="$binding/@relevant"> <xsl:value-of select="'or-branch pre-init '"/> </xsl:if> <xsl:if test="@appearance or @rows"> <xsl:value-of select="'or-appearances-placeholder '"></xsl:value-of> </xsl:if> <!-- Workaround for XLSForm limitation: add "compact" to group if the immediate repeat child has this appearance --> <!-- This should actually be fixed in pyxform instead --> <xsl:if test="contains(./xf:repeat/@appearance, 'compact')"> <xsl:value-of select="'or-appearance-compact '"/> </xsl:if> <!-- same workaround for "no-collapse" --> <xsl:if test="contains(./xf:repeat/@appearance, 'no-collapse')"> <xsl:value-of select="'or-appearance-no-collapse '"/> </xsl:if> </xsl:attribute> <xsl:call-template name="appearance" /> <xsl:if test="string($nodeset)"> <!--<xsl:variable name="nodeset" select="@ref" />--> <xsl:attribute name="name"> <xsl:value-of select="$nodeset"/> </xsl:attribute> <xsl:if test="$binding/@relevant"> <xsl:attribute name="data-relevant"> <!--<xsl:value-of select="/h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset]/@relevant" />--> <xsl:value-of select="$binding/@relevant"/> </xsl:attribute> </xsl:if> </xsl:if> <xsl:if test="string(./xf:label/@ref) or string (./xf:label)"> <h4> <xsl:apply-templates select="xf:label" /> </h4> </xsl:if> <xsl:apply-templates select="*[not(self::xf:label or self::xf:hint)]"/> <xsl:call-template name="constraint-and-required-msg" > <xsl:with-param name="binding" select="$binding"/> </xsl:call-template> <xsl:text> </xsl:text> </section><xsl:comment>end of group <xsl:value-of select="@nodeset" /> </xsl:comment> </xsl:template> <xsl:template match="xf:repeat"> <!-- NOTE: TO IMPROVE PERFORMANCE, SUPPORT FOR RELATIVE NODESET BINDINGS HAS BEEN SWITCHED OFF To turn this back on: - uncomment the variable nodeset_used - revert back to commented-out code for variable nodeset - revert back to commented-out code for variable binding - all this takes place in the next 10 lines <xsl:variable name="nodeset_used"> <xsl:call-template name="nodeset_used" /> </xsl:variable> --> <!-- the correct absolute nodeset as used in HTML --> <xsl:variable name="nodeset"> <!--<xsl:call-template name="nodeset_absolute"> <xsl:with-param name="nodeset_u" select="$nodeset_used"/> </xsl:call-template>--> <xsl:call-template name="nodeset_used" /> </xsl:variable> <!-- note that bindings are not required --> <!--<xsl:variable name="binding" select="/h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset_used] | /h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset]" />--> <!-- Note: not in use, commented out below with "watch out" warning --> <!-- <xsl:variable name="binding" select="key('nodeset-bindings', $nodeset)" /> --> <section> <xsl:attribute name="class"> <xsl:value-of select="'or-repeat '" /> <!-- watch out or-branch pre-init added to or-group parent! --> <!--<xsl:if test="$binding/@relevant"> <xsl:value-of select="'or-branch pre-init '"/> </xsl:if>--> <xsl:if test="@appearance or @rows"> <xsl:value-of select="'or-appearances-placeholder '"></xsl:value-of> </xsl:if> </xsl:attribute> <xsl:call-template name="appearance" /> <xsl:attribute name="name"> <xsl:value-of select="$nodeset"/> </xsl:attribute> <xsl:if test="string(./xf:label/@ref) or string (./xf:label)"> <h4> <xsl:apply-templates select="xf:label" /> </h4> </xsl:if> <xsl:apply-templates select="*[not(self::xf:label or self::xf:hint)]"/> <xsl:text> </xsl:text> </section> <!-- Add a repeat-info node --> <div> <xsl:attribute name="class"> <xsl:value-of select="'or-repeat-info'" /> </xsl:attribute> <xsl:attribute name="data-name"> <xsl:value-of select="$nodeset"/> </xsl:attribute> <xsl:if test="@jr:count"> <xsl:attribute name="data-repeat-count"> <xsl:value-of select="@jr:count" /> </xsl:attribute> </xsl:if> <xsl:if test="@jr:noAddRemove"> <xsl:attribute name="data-repeat-fixed"> <xsl:value-of select="string('fixed')"/> </xsl:attribute> </xsl:if> </div> </xsl:template> <xsl:template name="appearance"> <xsl:variable name="rows" select="./@rows" /> <xsl:if test="local-name() = 'select' or local-name() = 'select1'"> <xsl:attribute name="data-appearances-select-type" /> </xsl:if> <xsl:if test="@appearance or @rows"> <xsl:attribute name="data-appearances"> <xsl:value-of select="@appearance" /> <!-- turn rows attribute into an appearance (which is what it should have been in the first place imho) --> <xsl:if test="$rows"> <xsl:value-of select="concat(' rows-', $rows)" /> </xsl:if> </xsl:attribute> </xsl:if> </xsl:template> <xsl:template match="xf:input | xf:upload | xf:range | xf:item | xf:bind[@jr:preload] | xf:bind[@calculate] | xf:setvalue[@event] | odk:setgeopoint[@event]"> <!-- NOTE: TO IMPROVE PERFORMANCE, SUPPORT FOR RELATIVE NODESET BINDINGS HAS BEEN SWITCHED OFF To turn this back on: - uncomment the variable nodeset_used - revert back to commented-out code for variable nodeset - revert back to commented-out code for variable binding - all this takes place in the next 10 lines <xsl:variable name="nodeset_used"> <xsl:call-template name="nodeset_used" /> </xsl:variable> --> <!-- the correct absolute nodeset as used in HTML --> <xsl:variable name="nodeset"> <!--<xsl:call-template name="nodeset_absolute"> <xsl:with-param name="nodeset_u" select="$nodeset_used"/> </xsl:call-template>--> <xsl:call-template name="nodeset_used" /> </xsl:variable> <!-- note that bindings are not required --> <!--<xsl:variable name="binding" select="/h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset_used] | /h:html/h:head/xf:model/xf:bind[@nodeset=$nodeset]" />--> <xsl:variable name="binding" select="key('nodeset-bindings', $nodeset)" /> <!-- If this is a bind element that also has an input, do nothing as it will be dealt with by the corresponding xf:input --> <!-- Note that this test is not fully spec-compliant. It will work with XLS-form produced forms that have no relative nodes and use the ref atribute only --> <xsl:if test="not( local-name() = 'bind' and count( key('fields-by-ref', $nodeset) ) > 0 )"> <xsl:choose> <xsl:when test="(local-name() = 'range' and contains(@appearance, 'picker'))"> <xsl:call-template name="select-select"> <xsl:with-param name="nodeset" select="$nodeset" /> <xsl:with-param name="binding" select="$binding" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <label> <xsl:attribute name="class"> <xsl:if test="local-name() = 'input' or local-name() = 'upload' or local-name() = 'range'"> <xsl:value-of select="'question '"/> </xsl:if> <xsl:if test="(local-name() = 'input' or local-name() = 'upload' or local-name() = 'range' or local-name() = 'bind') and $binding/@relevant"> <xsl:value-of select="'or-branch pre-init '"/> </xsl:if> <xsl:if test="local-name() = 'bind'"> <xsl:value-of select="'calculation '"/> </xsl:if> <xsl:if test="local-name() = 'setvalue'"> <xsl:value-of select="'setvalue '"/> </xsl:if> <xsl:if test="local-name() = 'setgeopoint'"> <xsl:value-of select="'setgeopoint '"/> </xsl:if> <!--<xsl:if test="local-name() = 'item'"> <xsl:value-of select="'clearfix '"/> </xsl:if>--> <xsl:if test="local-name() != 'item'"> <xsl:value-of select="'non-select '"/> </xsl:if> <xsl:if test="@appearance or @rows"> <xsl:value-of select="'or-appearances-placeholder '"></xsl:value-of> </xsl:if> </xsl:attribute> <xsl:call-template name="appearance" /> <xsl:apply-templates select="./@kb:image-customization"/> <xsl:if test="not(local-name() = 'item' or local-name() = 'bind' or local-name() = 'setvalue' or local-name() = 'setgeopoint')"> <xsl:apply-templates select="xf:label" /> <xsl:if test="not($binding/@readonly = 'true()')"> <xsl:apply-templates select="$binding/@required"/> </xsl:if> </xsl:if> <!-- note: Hints should actually be placed in title attribute (of input) as it is semantically nicer. However, to support multiple languages and parse all of them (to be available offline) they are placed in the label instead. --> <xsl:apply-templates select="xf:hint" /> <xsl:variable name="appearance"> <xsl:value-of select="translate(@appearance, $upper-case, $lower-case)"/> </xsl:variable> <xsl:variable name="element"> <xsl:choose> <xsl:when test="$binding/@type = 'string' and contains($appearance, 'multi-line') or contains($appearance, 'multiline') or contains($appearance, 'text-area') or contains($appearance, 'textarea') or ./@rows"> <xsl:value-of select="string('textarea')" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="string('input')" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="type"> <xsl:if test="$element = 'textarea'"> <xsl:value-of select="$element"/> </xsl:if> </xsl:variable> <xsl:element name="{$element}"> <xsl:choose> <xsl:when test="not(local-name() = 'setvalue' or local-name() = 'setgeopoint') and ancestor::odk:rank"> <xsl:call-template name="rank-item-attributes"/> </xsl:when> <xsl:when test="local-name() = 'setvalue' or local-name() = 'setgeopoint'"> <xsl:call-template name="action-attributes"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="nodeset" select="$nodeset"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="binding-attributes"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="nodeset" select="$nodeset"/> <xsl:with-param name="type" select="$type"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:element> <xsl:if test="local-name() = 'item'"> <xsl:apply-templates select="xf:label" /> </xsl:if> <xsl:if test="not(local-name() = 'item' or local-name() = 'bind' or local-name() = 'setvalue' or local-name() = 'setgeopoint')"> <!-- the only use case at the moment is a <setvalue> and <odk:setgeopoint> child with xforms-value-changed event--> <xsl:if test="./xf:setvalue[@event] or ./odk:setgeopoint[@event]"> <xsl:apply-templates select="./xf:setvalue[@event] | ./odk:setgeopoint[@event]" /> </xsl:if> <xsl:call-template name="constraint-and-required-msg" > <xsl:with-param name="binding" select="$binding"/> </xsl:call-template> </xsl:if> </label> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> <xsl:template match="*" mode="range-option-picker"> <xsl:param name="start" /> <xsl:param name="end" /> <xsl:param name="step" /> <xsl:param name="readonly" /> <xsl:param name="limit" /> <xsl:if test="not($start > $end)"> <option> <xsl:if test="$readonly"> <xsl:attribute name="disabled"> <xsl:value-of select="'disabled'"/> </xsl:attribute> </xsl:if> <xsl:attribute name="value"> <xsl:choose> <xsl:when test="string($start)"> <xsl:value-of select="$start" /> </xsl:when> <xsl:otherwise> <xsl:message>ERROR: Could not determine value of select option.</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:attribute> <xsl:value-of select="$start" /> </option> <span> <xsl:attribute name="data-option-value"> <xsl:value-of select="$start" /> </xsl:attribute> <xsl:value-of select="."/> </span> <xsl:variable name="next" select="$start + $step"/> <xsl:choose> <xsl:when test="$limit > 1"> <xsl:apply-templates select="current()" mode="range-option-picker"> <xsl:with-param name="start" select="$next"/> <xsl:with-param name="end" select="$end"/> <xsl:with-param name="step" select="$step"/> <xsl:with-param name="readonly" select="$readonly"/> <xsl:with-param name="limit" select="$limit - 1"/> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <xsl:message>ERROR: Exceed maximum iterations allowed.</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> <xsl:template match="xf:item" mode="select-option"> <xsl:param name="tolerate-spaces" /> <xsl:param name="readonly" /> <xsl:variable name="label_translations"> <xsl:apply-templates select="xf:label" /> </xsl:variable> <xsl:variable name="value"> <xsl:value-of select="xf:value" /> <xsl:if test="not($tolerate-spaces) and contains(xf:value, ' ')"> <xsl:message terminate="yes">ERROR: (Multi-)select item found with a value that contains spaces!</xsl:message> </xsl:if> <xsl:if test="not(string(xf:value))"> <xsl:message terminate="no">WARNING: Select item found without a value!</xsl:message> </xsl:if> </xsl:variable> <option> <xsl:if test="$readonly"> <xsl:attribute name="disabled"> <xsl:value-of select="'disabled'"/> </xsl:attribute> </xsl:if> <xsl:attribute name="value"> <xsl:choose> <xsl:when test="string($value)"> <xsl:value-of select="$value" /> </xsl:when> <xsl:otherwise> <xsl:message>ERROR: Could not determine value of select option.</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:attribute> <!-- better to use default language if defined and otherwise span[1] --> <xsl:choose> <!-- TODO: IT WOULD BE MORE EFFICIENT TO EXTRACT THIS FROM exsl:node-set($label_translations) --> <xsl:when test="exsl:node-set($label_translations)/span[@lang=$current-lang]"> <xsl:value-of select="exsl:node-set($label_translations)/span[@lang=$current-lang] " /> </xsl:when> <xsl:otherwise> <xsl:value-of select="exsl:node-set($label_translations)/span[1] " /> </xsl:otherwise> </xsl:choose> </option> <xsl:for-each select="exsl:node-set($label_translations)/span" > <span> <xsl:attribute name="data-option-value"> <xsl:value-of select="$value" /> </xsl:attribute> <xsl:attribute name="lang"> <xsl:value-of select="@lang" /> </xsl:attribute> <xsl:value-of select="."/> </span> </xsl:for-each> </xsl:template> <xsl:template match="xf:itemset" mode="templates"> <xsl:param name="nodeset" /> <xsl:param name="binding"/> <xsl:param name="type"/> <xsl:choose> <xsl:when test="not($type = 'option')"> <label class="itemset-template"> <xsl:attribute name="data-items-path"> <xsl:value-of select="@nodeset"/> </xsl:attribute> <!--<xsl:value-of select="'__LABEL__'" />--> <input> <xsl:choose> <xsl:when test="ancestor::odk:rank"> <xsl:call-template name="rank-item-attributes"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="binding-attributes"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="nodeset" select="$nodeset"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> <xsl:attribute name="value"></xsl:attribute> </input> </label> </xsl:when> <xsl:otherwise> <option class="itemset-template" value=""> <xsl:attribute name="data-items-path"> <xsl:value-of select="@nodeset"/> </xsl:attribute> <xsl:if test="$binding/@readonly = 'true()'"> <xsl:attribute name="disabled"/> </xsl:if> <xsl:value-of select="'...'"/> </option> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="xf:itemset" mode="labels"> <xsl:variable name="valueRef" select="@valueRef" /> <xsl:variable name="labelRef" select="@labelRef" /> <xsl:variable name="itextPath" select="@itextPath" /> <span class="itemset-labels"> <xsl:attribute name="data-value-ref"> <xsl:value-of select="$valueRef"/> </xsl:attribute> <xsl:choose> <xsl:when test="contains($labelRef, 'jr:itext(')"> <xsl:attribute name="data-label-type"> <xsl:value-of select="'itext'"/> </xsl:attribute> <xsl:variable name="label-node-name" select="substring(substring-after($labelRef, 'itext('),1,string-length(substring-after($labelRef, 'itext('))-1)"/> <xsl:attribute name="data-label-ref"> <xsl:value-of select="$label-node-name"/> </xsl:attribute> <xsl:for-each select="./*[local-name()=$label-node-name]"> <!-- so this is support for itext(node) (not itext(path/to/node)), but only 'ad-hoc' for itemset labels for now --> <xsl:variable name="id" select="." /> <xsl:call-template name="translations"> <xsl:with-param name="id" select="$id"/> <xsl:with-param name="class" select="'option-label'"/> </xsl:call-template> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:attribute name="data-label-ref"> <xsl:value-of select="$labelRef"/> </xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:text> <!-- This preserves whitespace which affects snapshot tests. --></xsl:text> </span> </xsl:template> <!-- turns STRING: '/path/to/node' into: /*[name()='path'/*[name()='to']/*[name()='node' --> <!-- <xsl:template name="string-to-path"> <xsl:param name="path-string"/> <xsl:if test="starts-with($path-string, '/')"> <xsl:value-of select="'/'"/> </xsl:if> <xsl:choose> <xsl:when test="contains($path-string, '/')"> <xsl:value-of select="substring-before($path-string, '/')"/> <xsl:call-template name="string-to-path"> <xsl:with-param name="path-string" select="substring-after($path-string, '/')"/> </xsl:call-template> </xsl:when> </xsl:choose> </xsl:template> --> <xsl:template name="select-select"> <xsl:param name="nodeset"/> <xsl:param name="binding"/> <xsl:variable name="appearance" select="./@appearance" /> <xsl:variable name="datalist-id" select="translate($nodeset, ' _-.\\/', '')"/> <xsl:variable name="type"> <xsl:choose> <xsl:when test="local-name() = 'select'">select_multiple</xsl:when> <xsl:otherwise>select_one</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="element"> <xsl:choose> <xsl:when test="local-name() = 'select1' and (contains(@appearance, 'autocomplete') or contains(@appearance, 'search'))">datalist</xsl:when> <xsl:otherwise>select</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="options"> <xsl:choose> <xsl:when test="contains(@appearance, 'picker')"> <xsl:apply-templates select="current()" mode="range-option-picker"> <xsl:with-param name="start" select="@start" /> <xsl:with-param name="end" select="@end" /> <xsl:with-param name="step" select="@step" /> <xsl:with-param name="readonly" select="$binding/@readonly = 'true()'" /> <xsl:with-param name="limit" select="500" /> </xsl:apply-templates> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="xf:item" mode="select-option"> <xsl:with-param name="tolerate-spaces" select="$type = 'select_one'" /> <xsl:with-param name="readonly" select="$binding/@readonly = 'true()'" /> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> </xsl:variable> <label> <xsl:attribute name="class"> <xsl:value-of select="'question '"/> <xsl:if test="./@appearance or ./@rows"> <xsl:value-of select="'or-appearances-placeholder '" /> </xsl:if> <xsl:if test="$binding/@relevant"> <xsl:value-of select="' or-branch pre-init '"/> </xsl:if> </xsl:attribute> <xsl:call-template name="appearance" /> <xsl:apply-templates select="./@kb:image-customization"/> <xsl:apply-templates select="xf:label" /> <xsl:apply-templates select="$binding/@required"/> <xsl:apply-templates select="xf:hint" /> <xsl:if test="$element = 'datalist'"> <input> <xsl:call-template name="binding-attributes"> <xsl:with-param name="nodeset" select="$nodeset" /> <xsl:with-param name="binding" select="$binding" /> <xsl:with-param name="type" select="$type" /> </xsl:call-template> <!-- override type attribute --> <xsl:attribute name="type"> <xsl:value-of select="'text'"/> </xsl:attribute> <xsl:attribute name="list"> <xsl:value-of select="$datalist-id"/> </xsl:attribute> </input> </xsl:if> <xsl:element name="{$element}"> <xsl:choose> <xsl:when test="$element != 'datalist'"> <xsl:call-template name="binding-attributes"> <xsl:with-param name="nodeset" select="$nodeset" /> <xsl:with-param name="binding" select="$binding" /> <xsl:with-param name="type" select="$type" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:attribute name="id"> <xsl:value-of select="$datalist-id"/> </xsl:attribute> </xsl:otherwise> </xsl:choose> <xsl:choose> <xsl:when test="not(./xf:itemset)"> <option value="">...</option> <xsl:for-each select="exsl:node-set($options)/option"> <xsl:copy-of select="."/> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="xf:itemset" mode="templates"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="type" select="'option'"/> </xsl:apply-templates> </xsl:otherwise> </xsl:choose> </xsl:element> <span class="or-option-translations" style="display:none;"> <xsl:if test="not(./xf:itemset) and $translated = 'true'"> <xsl:for-each select="exsl:node-set($options)/span"> <xsl:copy-of select="." /> </xsl:for-each> </xsl:if> <xsl:text> </xsl:text> </span> <xsl:if test="./xf:itemset"> <xsl:apply-templates select="xf:itemset" mode="labels"/> </xsl:if> <xsl:if test="./xf:setvalue[@event] or ./odk:setgeopoint[@event]"> <xsl:apply-templates select="./xf:setvalue[@event] | ./odk:setgeopoint[@event]" /> </xsl:if> <xsl:call-template name="constraint-and-required-msg" > <xsl:with-param name="binding" select="$binding"/> </xsl:call-template> </label> </xsl:template> <xsl:template name="select-input"> <xsl:param name="nodeset"/> <xsl:param name="binding"/> <!-- legends are a royal pain-in-the-ass, but semantically correct to use. To restore sanity, the least ugly solution that works regardless of the legend text + hint length (and showing a nice error background) is to use a double fieldset (though another outer element would be okay too). Is consequence of being stingy with # of DOM elements used. --> <fieldset> <xsl:attribute name="class"> <xsl:value-of select="'question '"/> <xsl:if test="not(contains(@appearance, 'compact') or contains(@appearance, 'list-nolabel') or contains(@appearance, 'label') or contains(@appearance, 'likert') or contains(@appearance, 'horizontal-compact') or contains(@appearance, 'no-buttons'))" > <xsl:value-of select="'simple-select '"/> </xsl:if> <xsl:if test="local-name() = 'trigger'"> <xsl:value-of select="'trigger '"/> </xsl:if> <xsl:if test="$binding/@relevant"> <xsl:value-of select="'or-branch pre-init '"/> </xsl:if> <xsl:if test="@appearance"> <xsl:value-of select="'or-appearances-placeholder '"></xsl:value-of> </xsl:if> </xsl:attribute> <xsl:if test="@appearance"> <xsl:call-template name="appearance" /> </xsl:if> <xsl:apply-templates select="./@kb:image-customization"/> <xsl:apply-templates select="./@kb:flash"/> <fieldset> <!--<xsl:if test="./xf:itemset"> <xsl:attribute name="data-itemset"/> </xsl:if>--> <legend> <xsl:apply-templates select="xf:label" /> <xsl:apply-templates select="$binding/@required"/> <xsl:apply-templates select="xf:hint" /> <xsl:text> </xsl:text> </legend> <xsl:if test="local-name() = 'rank'"> <input class="rank"> <xsl:call-template name="binding-attributes"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="nodeset" select="$nodeset"/> </xsl:call-template> </input> </xsl:if> <div class="option-wrapper"> <xsl:choose> <xsl:when test="local-name() = 'trigger'"> <label> <input value="OK"> <xsl:call-template name="binding-attributes"> <xsl:with-param name="binding" select="$binding"/> <xsl:with-param name="nodeset" select="$nodeset"/> </xsl:call-template> </input> <span class="option-label active" lang=""> <xsl:value-of select="'OK'"/> </span> </label> </xsl:when> <xsl:when test="not(./xf:itemset)"> <xsl:apply-templates select="xf:item" /> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="xf:itemset" mode="templates"> <xsl:with-param name="nodeset" select="$nodeset" /> <xsl:with-param name="bindi