0

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>
tat
  • 321
  • 1
  • 19
  • 1
    I won't elaborate too much on the topic, but I will point out that XML-based manipulation of RDF can be pretty error-prone. The same RDF graph can be written in lots of different ways using RDF/XML, and a given XML-based approach won't work on all of them. If you can, it might be better to process the RDF as RDF with an RDF processing tool. See [my answer](http://stackoverflow.com/a/17052385/1281433) to "How to access OWL documents using XPath in Java?" for some examples of what can go wrong. – Joshua Taylor May 06 '15 at 18:37
  • @JoshuaTaylor, thanks. I realize that working with RDF/XML is not ideal, but the scope here is fairly limited: use XForms for data entry and pipeline the resulting RDF/XML to a triplestore. From there, it can be handled with SPARQL and RDF processing tools. To my knowledge, there are not currently many data entry tools for creating new RDF data. I know of [RDForms](http://rdforms.org/#!index.md), [Graphity](https://github.com/Graphity), and [Callimachus](http://callimachusproject.org/), but for our particular project, we needed something that was a little more flexible and customizable. – tat May 06 '15 at 18:57
  • Could you please post a more explicit test case? I understand your question as an XPath one, am I right? XForms 2.0 allows variables and it might simplify your XPath expressions. XSLTForms latest builds allow var use. – Alain Couthures May 06 '15 at 19:03
  • 1
    @tat It's not so bad to work with the XML if you have some control over how it's being generated, and it sounds like in this case you do. I.e., you don't have to accept arbitrary RDF/XML. This is one of the times when (despite that it may be fragile) it's probably OK to treat it as XML. – Joshua Taylor May 06 '15 at 19:15
  • @AlainCouthures I've tried to make the example more explicit, but I'm not sure I succeeded. The basic use case is that I need to control the two `bf:subject` elements in my `xf:model` separately. So, I need to be able to insert and delete them independently of each other. I can do this with XPath predicates, but of course the predicate of `@rdf:resource = ''` does not work once a value is supplied. I think the functionality needed would be similar to using modes in XSLT. – tat May 06 '15 at 19:34
  • @AlainCouthures, by the way, I'm very happy to hear about support for variables in the latest build. Are there tests or documentation demonstrating usage? I'm a big fan of XSLTForms and appreciate your work! – tat May 07 '15 at 00:57

2 Answers2

1

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>
tat
  • 321
  • 1
  • 19
0

If you are looking for an XPath way to know if an element is the first or the second child, you could consider using axes, such as in .[preceding-sibling::bf:subject]

Alain Couthures
  • 1,488
  • 9
  • 5