Search code examples
xmlxpathrdfxformsxsltforms

XForms repeat: same element name, different value constraints


I am attempting to edit RDF/XML in XForms (XSLTForms implementation in eXist-db), and I need to enforce different value constraints on elements with the same name within xf:repeat structures. For example, I have a bf:subject element that can take either a default URI as the value of its @rdf:resource attribute or an arbitrary URI that links to some other resource defined in the form (for the sake of brevity I have omitted these from the example provided below).

In an xf:repeat structure, how can I differentiate between elements with the same name? I can handle the first scenario with a predicate that limits the value of the @rdf:resource to the default URI specified in the xf:model, but I can't find a way to achieve differential processing for cases when the @rdf:resource can take an arbitrary URI.

Note: there are no form controls within the 2nd nested xf:repeat because the value of @rdf:resource is updated dynamically using a separate JavaScript library (jsPlumb) that updates the XForms instance.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="http://localhost:8080/exist/apps/xsltforms/xsltforms.xsl" type="text/xsl"?>
<?xsltforms-options debug="yes"?>
<?css-conversion no?>
<?xml-model href="http://www.oxygenxml.com/1999/xhtml/xhtml-xforms.nvdl" schematypens="http://purl.oclc.org/dsdl/nvdl/ns/structure/1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:bf="http://bibframe.org/vocab/"
    xmlns:ev="http://www.w3.org/2001/xml-events"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:xf="http://www.w3.org/2002/xforms"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Editor</title>
        <!--Model-->
        <xf:model id="rdf-model">
            <xf:instance id="graph">
                <rdf:RDF>
                    <bf:Work rdf:about="">
                        <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-ag"></bf:subject>
                        <bf:subject rdf:resource=""/>
                    </bf:Work>
                </rdf:RDF>
            </xf:instance>
            <!-- Template -->
            <xf:instance id="bf-Work-template">
                <rdf:RDF>
                    <bf:Work rdf:about="">
                        <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-ag"></bf:subject>
                        <bf:subject rdf:resource=""/>
                    </bf:Work>
                </rdf:RDF>
            </xf:instance>
        </xf:model>
    </head>
    <body>
        <div id="header">
            <h1>Editor</h1>
        </div>
        <div id="forms">
            <!-- Repeat for Work entity -->
            <xf:repeat nodeset="instance('graph')/bf:Work" id="repeat-Work-graph">

                <!-- Repeat bf:subject elements that have a default value. -->
                <xf:repeat
                    nodeset="bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]">
                    <div style="border:solid black 1px;">
                        <xf:input
                            ref="@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']">
                            <xf:label>Subject</xf:label>
                        </xf:input>

                        <!-- Add new bf:subject elements that have a default value -->
                        <xf:trigger ref=".">
                            <xf:label>+</xf:label>
                            <xf:action ev:event="DOMActivate">
                                <xf:insert
                                    nodeset="../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]"
                                    origin="instance('bf-Work-template')/bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]"
                                    at="last()" position="after"></xf:insert>
                            </xf:action>
                        </xf:trigger>

                        <!-- Delete bf:subject elements that have a default value -->
                        <xf:trigger
                            ref=".[count(../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]) &gt; 1]">
                            <xf:label>-</xf:label>
                            <xf:delete ev:event="DOMActivate" nodeset="." at="last()"
                                if="count(../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]) &gt; 1"
                            ></xf:delete>
                        </xf:trigger>
                    </div>
                </xf:repeat>

                <!-- Add new bf:subject elements that can take an arbitrary value -->
                <xf:trigger ref="bf:subject[@rdf:resource = '']">
                    <xf:label>+</xf:label>
                    <xf:action ev:event="DOMActivate">
                        <xf:insert nodeset="."
                            origin="instance('bf-Work-template')/bf:Work/bf:subject[@rdf:resource = '']"
                            at="last()" position="after"></xf:insert>
                    </xf:action>
                </xf:trigger>

                <!-- Delete bf:subject elements that can take an arbitrary value -->
                <xf:trigger
                    ref="bf:subject[@rdf:resource = ''][count(../bf:subject[@rdf:resource = '']) &gt; 1]">
                    <xf:label>-</xf:label>
                    <xf:action ev:event="DOMActivate">
                        <xf:delete nodeset="../bf:subject[@rdf:resource = '']" at="last()"
                            if="count(../bf:subject[@rdf:resource = '']) &gt; 1"></xf:delete>
                    </xf:action>
                </xf:trigger>

                <!-- Repeat bf:subject elements that can take an arbitrary value -->
                <xf:repeat nodeset="bf:subject[@rdf:resource = '']">
                    <div style="border:solid black 1px;">
                    <!-- Value of @rdf:resource is updated using jsPlumb library -->
                        <span class="label">Subject</span>
                        <br />
                        <span>Link to:</span>
                        <br />
                        <span class="connect-to">Work</span>
                        <br />
                        <span class="connect-to">Topic</span>
                        <br />
                        <span class="connect-to">Place</span>
                    </div>
                </xf:repeat>

            </xf:repeat>
        </div>
    </body>
</html>

Solution

  • At present, the default URIs are all coming from the same namespace (http://id.loc.gov/vocabulary/...), so a temporary solution is just to filter for that value: not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/')). For the longer term, I'm investigating nomisma.org, which embodies a more sustainable approach to linked data vocab management in XForms.

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet href="http://localhost:8080/exist/apps/xsltforms/xsltforms.xsl" type="text/xsl"?>
    <?xsltforms-options debug="yes"?>
    <?css-conversion no?>
    <?xml-model href="http://www.oxygenxml.com/1999/xhtml/xhtml-xforms.nvdl" schematypens="http://purl.oclc.org/dsdl/nvdl/ns/structure/1.0"?>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:bf="http://bibframe.org/vocab/"
        xmlns:ev="http://www.w3.org/2001/xml-events"
        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:xf="http://www.w3.org/2002/xforms"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title>Editor</title>
            <!--Model-->
            <xf:model id="rdf-model">
                <xf:instance id="graph">
                    <rdf:RDF>
                        <bf:Work rdf:about="">
                            <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-ag"></bf:subject>
                            <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-bl"></bf:subject>
                            <bf:subject rdf:resource=""/>
                        </bf:Work>
                    </rdf:RDF>
                </xf:instance>
                <!-- Template -->
                <xf:instance id="bf-Work-template">
                    <rdf:RDF>
                        <bf:Work rdf:about="">
                            <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-ag"></bf:subject>
                            <bf:subject rdf:resource="http://id.loc.gov/vocabulary/geographicAreas/s-bl"></bf:subject>
                            <bf:subject rdf:resource=""/>
                        </bf:Work>
                    </rdf:RDF>
                </xf:instance>
            </xf:model>
        </head>
        <body>
            <div id="header">
                <h1>Editor</h1>
            </div>
            <div id="forms">
                <!-- Repeat for Work entity -->
                <xf:repeat nodeset="instance('graph')/bf:Work" id="repeat-Work-graph">
    
                    <!-- Repeat bf:subject elements that have a default value. -->
                    <xf:repeat
                        nodeset="bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]">
                        <div style="border:solid black 1px;">
                            <xf:input
                                ref="@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']">
                                <xf:label>Subject</xf:label>
                            </xf:input>
    
                            <!-- Add new bf:subject elements that have a default value -->
                            <xf:trigger ref=".">
                                <xf:label>+</xf:label>
                                <xf:action ev:event="DOMActivate">
                                    <xf:insert
                                        nodeset="../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]"
                                        origin="instance('bf-Work-template')/bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]"
                                        at="last()" position="after"></xf:insert>
                                </xf:action>
                            </xf:trigger>
    
                            <!-- Delete bf:subject elements that have a default value -->
                            <xf:trigger
                                ref=".[count(../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]) &gt; 1]">
                                <xf:label>-</xf:label>
                                <xf:delete ev:event="DOMActivate" nodeset="." at="last()"
                                    if="count(../bf:subject[@rdf:resource[. = 'http://id.loc.gov/vocabulary/geographicAreas/s-ag']]) &gt; 1"
                                    ></xf:delete>
                            </xf:trigger>
                        </div>
                    </xf:repeat>
    
                    <!-- Add new bf:subject elements that can take an arbitrary value -->
                    <xf:trigger ref="bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]">
                        <xf:label>+</xf:label>
                        <xf:action ev:event="DOMActivate">
                            <xf:insert nodeset="."
                                origin="instance('bf-Work-template')/bf:Work/bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]"
                                at="last()" position="after"></xf:insert>
                        </xf:action>
                    </xf:trigger>
    
                    <!-- Delete bf:subject elements that can take an arbitrary value -->
                    <xf:trigger
                        ref="bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))][count(../bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]) &gt; 1]">
                        <xf:label>-</xf:label>
                        <xf:action ev:event="DOMActivate">
                            <xf:delete nodeset="../bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]" at="last()"
                                if="count(../bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]) &gt; 1"></xf:delete>
                        </xf:action>
                    </xf:trigger>
    
                    <!-- Repeat bf:subject elements that can take an arbitrary value -->
                    <xf:repeat nodeset="bf:subject[not(starts-with(@rdf:resource, 'http://id.loc.gov/vocabulary/geographicAreas/'))]">
                        <div style="border:solid black 1px;">
                            <!-- Value of @rdf:resource is updated using jsPlumb library -->
                            <span class="label">Subject</span>
                            <br />
                            <span>Link to:</span>
                            <br />
                            <span class="connect-to">Work</span>
                            <br />
                            <span class="connect-to">Topic</span>
                            <br />
                            <span class="connect-to">Place</span>
                        </div>
                    </xf:repeat>
    
                </xf:repeat>
            </div>
        </body>
    </html>