3

I am trying to create a nested xml from a flat XML using an XSLT however I have found that it only creates one nest and ignores the rest of the records in the source XML.

My XML input looks like this:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!-- Data -->
<table name="ecatalogue">
  <!-- Row 1 -->
  <tuple>
    <atom name="irn">2470</atom>
    <atom name="EADUnitID">da.01</atom>
    <atom name="EADUnitTitle">Some title</atom>
    <tuple name="AssParentObjectRef" />
  </tuple>
    <!-- Row 2 -->
  <tuple>
    <atom name="irn">5416</atom>
    <atom name="EADUnitID">da.01.01</atom>
    <atom name="EADUnitTitle">Child of Some title</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Some Title</atom>
    <atom name="irn">2470</atom>
    </tuple>
  </tuple>
    <!-- Row 3 -->
  <tuple>
    <atom name="irn">6</atom>
    <atom name="EADUnitID">da.01.02</atom>
    <atom name="EADUnitTitle">Child of Some title 2</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Some Title</atom>
    <atom name="irn">2470</atom>
    </tuple>
  </tuple>
    <!-- Row 4 -->
  <tuple>
    <atom name="irn">8</atom>
    <atom name="EADUnitID">da.01.02.01</atom>
    <atom name="EADUnitTitle">3rd Generation</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Child of Some Title 2</atom>
    <atom name="irn">6</atom>
    </tuple>
  </tuple>
    <!-- Row 5 -->
  <tuple>
    <atom name="irn">1130</atom>
    <atom name="EADUnitID">da.02</atom>
    <atom name="EADUnitTitle">Another title</atom>
    <tuple name="AssParentObjectRef" />
  </tuple>
    <!-- Row 6 -->
  <tuple>
    <atom name="irn">54</atom>
    <atom name="EADUnitID">da.02.01</atom>
    <atom name="EADUnitTitle">Child of Another title</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Another Title</atom>
    <atom name="irn">1130</atom>
    </tuple>
  </tuple>
    <!-- Row 7 -->
  <tuple>
    <atom name="irn">16</atom>
    <atom name="EADUnitID">da.02.02</atom>
    <atom name="EADUnitTitle">Child of Another Title 2</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Another Title</atom>
    <atom name="irn">1130</atom>
    </tuple>
  </tuple>
    <!-- Row 8 -->
  <tuple>
    <atom name="irn">22</atom>
    <atom name="EADUnitID">da.02.02.01</atom>
    <atom name="EADUnitTitle">3rd Generation</atom>
    <tuple name="AssParentObjectRef">
    <atom name="EADUnitTitle">Child of Another Title 2</atom>
    <atom name="irn">1130</atom>
    </tuple>
  </tuple>
</table>

The XSLT should identify the top level record and then add the children. For the top record it should duplicate its irn and EADUnitTitle as TopID and TopTitle respectively. For each child it should include the immediate ParentID and ParentTitle as well as the TopID and TopTitle. The output should look like:

<?xml version="1.0" encoding="UTF-8"?>
<table name="ecatalogue">
   <collection>
      <tuple>
         <atom name="irn">2470</atom>
         <atom name="EADUnitID">da.01</atom>
         <atom name="EADUnitTitle">Some title</atom>
         <atom name="TopTitle">Some title</atom>
         <atom name="TopID">2470</atom>
         <tuple name="children">
            <tuple>
               <atom name="irn">5416</atom>
               <atom name="EADUnitID">da.01.01</atom>
               <atom name="EADUnitTitle">Child of Some title</atom>
               <atom name="ParentTitle">Some title</atom>
               <atom name="ParentID">2470</atom>
               <atom name="TopTitle">Some title</atom>
               <atom name="TopID">2470</atom>
            </tuple>
            <tuple>
                <atom name="irn">6</atom>
               <atom name="EADUnitID">da.01.02</atom>
               <atom name="EADUnitTitle">Child of Some title 2</atom>
               <atom name="ParentTitle">Some title</atom>
               <atom name="ParentID">2470</atom>
               <atom name="TopTitle">Some title</atom>
               <atom name="TopID">2470</atom>
               <tuple name="children">
                  <tuple>
                    <atom name="irn">8</atom>
                    <atom name="EADUnitID">da.01.02.01</atom>
                    <atom name="EADUnitTitle">3rd Generation</atom>
                    <atom name="ParentTitle">Child of Some title 2</atom>
                    <atom name="ParentID">6</atom>
                    <atom name="TopTitle">Some title</atom>
                    <atom name="TopID">2470</atom>
                  </tuple>
               </tuple>
            </tuple>
         </tuple>
      </tuple>
   </collection>
   <collection>
      <tuple>
         <atom name="irn">1130</atom>
         <atom name="EADUnitID">da.02</atom>
         <atom name="EADUnitTitle">Another title</atom>
         <atom name="TopTitle">Another title</atom>
         <atom name="TopID">1130</atom>
         <tuple name="children">
            <tuple>
               <atom name="irn">54</atom>
               <atom name="EADUnitID">da.02.01</atom>
               <atom name="EADUnitTitle">Child of Another title</atom>
               <atom name="ParentTitle">Another title</atom>
               <atom name="ParentID">1130</atom>
               <atom name="TopTitle">Another title</atom>
               <atom name="TopID">1130</atom>
            </tuple>
            <tuple>
                <atom name="irn">16</atom>
               <atom name="EADUnitID">da.02.02</atom>
               <atom name="EADUnitTitle">Child of Another title 2</atom>
               <atom name="ParentTitle">Another title</atom>
               <atom name="ParentID">1130</atom>
               <atom name="TopTitle">Another title</atom>
               <atom name="TopID">1130</atom>
               <tuple name="children">
                  <tuple>
                    <atom name="irn">22</atom>
                    <atom name="EADUnitID">da.02.02.01</atom>
                    <atom name="EADUnitTitle">3rd Generation</atom>
                    <atom name="ParentTitle">Child of Another title 2</atom>
                    <atom name="ParentID">16</atom>
                    <atom name="TopTitle">Another title</atom>
                    <atom name="TopID">1130</atom>
                  </tuple>
               </tuple>
            </tuple>
         </tuple>
      </tuple>

....

   </collection>
</table>

The XSLT I have is:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:key name="child" match="tuple" use="tuple[@name='AssParentObjectRef']/atom[@name='irn']" />

<xsl:template match="/table">
    <table name="ecatalogue">
        <collection>
            <xsl:apply-templates select="tuple[not(tuple[@name='AssParentObjectRef']/atom[@name='irn'])]"/>
        </collection>
    </table>
</xsl:template>

<xsl:template match="tuple">
    <tuple>
        <xsl:copy-of select="atom"/>
        <xsl:if test="key('child', atom[@name='irn'])">
            <tuple name="children">
                <xsl:apply-templates select="key('child', atom[@name='irn'])"/>
             </tuple>
        </xsl:if>
    </tuple>
</xsl:template>

</xsl:stylesheet>

And whilst this will group the records, the output is just one of these collections. So from a file of 3524 records, I get one collection of 24 records.

I've experimented with the XSLT replacing:

<xsl:template match="/table">
    <table name="ecatalogue">
        <collection>
            <xsl:apply-templates select="tuple[not(tuple[@name='AssParentObjectRef']/atom[@name='irn'])]"/>
        </collection>
    </table>
</xsl:template>

With:

<xsl:template match="node()|@*">
   <xsl:copy>
     <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>
</xsl:template>

And whilst this returns all the nested structures, it also duplicates the records within the nests so they become collections in themselves.

Any ideas on where I'm going wrong?

EDIT 06/06/17

When I use:

<xsl:template match="node()|@*">
       <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
    </xsl:template>

I get duplicates (note: the 'id' in the below example is added for illustration):

 <record id='1'>
   <children>
        <record id='2'>
            <children>
                <record id='3'>
                    <children>
                        <record id='4'></record>
                    </children>
                </record>
            </children>
        </record>
    </children>
</record>

<record id='2'>
        <children>
            <record id='3'>
                <children>
                    <record id='4'></record>
                </children>
            </record>
        </children>
</record>

<record id='3'>
    <children>
        <record id='4'></record>
    </children>
</record>

<record id='2'></record>
<record id='3'></record>
<record id='4'></record>

Is there anyway to remove the duplicates so I'm just left with the nested records?

EDIT - Problem Tuples

 <!-- Row 3378 -->
  <tuple>
    <atom name="irn">115024</atom>
    <atom name="ObjectType">Archives</atom>
    <atom name="EADLevelAttribute">Series</atom>
    <atom name="EADUnitID">D42.PL.05</atom>
    <atom name="EADUnitTitle">Correspondence and Company Administration: Box Files</atom>
    <atom name="EADScopeAndContent">Box files of Port Line official company correspondence and administrative papers. These papers were collected towards historical research and include correspondence from earlier periods c.1890 although the bulk of the papers relate to the two periods 1937-1939 and 1949-1951.</atom>
    <atom name="EADBiographyOrHistory"></atom>
    <tuple name="AssParentObjectRef">
    </tuple>
    <atom name="EADArrangement">The papers in this series have been retained in the original order as stored by Port Line Ltd. The contents of each box file are listed as a typescript paper and have been listed in this catalogue. Box file titles have been listed in the title field of each item in this series.</atom>
    <atom name="EADUnitDate">1890-1952</atom>
    <table name="EADExtent_tab">
      <tuple>
        <atom name="EADExtent">7 boxes.</atom>
      </tuple>
    </table>
    <atom name="EADAccruals"></atom>
    <atom name="EADOtherFindingAid"></atom>
    <atom name="EADRelatedMaterial"></atom>
    <tuple name="EADAcquisitionInformationRef">
    </tuple>
    <atom name="EADAppraisalInformation"></atom>
    <atom name="EADSeparatedMaterial"></atom>
    <atom name="EADTitleProper"></atom>
    <atom name="EADPublicationStatement"></atom>
    <atom name="EADCustodialHistory"></atom>
    <atom name="EADSource"></atom>
    <atom name="EADNote"></atom>
    <atom name="EADAccessRestrictions">Some items in this series are closed access.</atom>
    <atom name="EADUseRestrictions"></atom>
  </tuple>

  <!-- Row 3379 -->
  <tuple>
    <atom name="irn">115025</atom>
    <atom name="ObjectType">Archives</atom>
    <atom name="EADLevelAttribute">Item</atom>
    <atom name="EADUnitID">D42.PL.05.01</atom>
    <atom name="EADUnitTitle">File: Australian Homeward Trade</atom>
    <atom name="EADScopeAndContent">Various papers relating to Australian Homeward Trade and includes the following:For proof copies of the Australian Homeward Agreement see D42/PL5/6.</atom>
    <atom name="EADBiographyOrHistory"></atom>
    <tuple name="AssParentObjectRef">
      <atom name="EADUnitTitle">Correspondence and Company Administration: Box Files</atom>
      <atom name="irn">115024</atom>
    </tuple>
    <atom name="EADArrangement"></atom>
    <atom name="EADUnitDate">1920-1936</atom>
    <table name="EADExtent_tab">
      <tuple>
        <atom name="EADExtent">1 file.</atom>
      </tuple>
    </table>
    <atom name="EADAccruals"></atom>
    <atom name="EADOtherFindingAid"></atom>
    <atom name="EADRelatedMaterial"></atom>
    <tuple name="EADAcquisitionInformationRef">
    </tuple>
    <atom name="EADAppraisalInformation"></atom>
    <atom name="EADSeparatedMaterial"></atom>
    <atom name="EADTitleProper"></atom>
    <atom name="EADPublicationStatement"></atom>
    <atom name="EADCustodialHistory"></atom>
    <atom name="EADSource"></atom>
    <atom name="EADNote"></atom>
    <atom name="EADAccessRestrictions"></atom>
    <atom name="EADUseRestrictions"></atom>
  </tuple>
3
  • 1
    Can you give us a minimal but complete input sample for which Tim's suggestion fails, showing us the result you want and the one you get? Commented Jun 8, 2017 at 16:53
  • There's a bug in your sample input XML -- the atom @name="irn" value in the last tuple (for 3rd Generation) is erroneously listed as 1130, when it should be 16 instead. (Probably a copy-paste error?) Commented Jun 8, 2017 at 23:00
  • Just a thought. Could it be an issue with the way you're reading the transformed XML? An xmlreader being closed too soon, so only returning the first chunk of data? Your question mentions nothing about what you're actually using to do your xslt transform. I might be way off. I've just encountered odd issues like this with .net xmlreader. Commented Jun 15, 2017 at 9:55

2 Answers 2

2

If you want a collection element for each top-level parent tuple, I think all you need to do is have an xsl:for-each to get the parents, and move the creation of the collection elements in that.

<xsl:template match="/table">
    <table name="ecatalogue">
        <xsl:for-each select="tuple[not(tuple[@name='AssParentObjectRef']/atom[@name='irn'])]">
            <collection>
                <xsl:apply-templates select="." />
            </collection>
        </xsl:for-each>
    </table>
</xsl:template>
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks - it works for my example above but not for my input file with 3524 records. I think I'm going to try splitting the file in to smaller chunks and see how that goes...
1
+50

This is a bit long; I try to address everything relevant that caught my attention.

The existing templates

First, let's break down what's going on with your own XSL code.

<xsl:template match="/table">
    <table name="ecatalogue">
        <collection>
            <xsl:apply-templates select="tuple[not(tuple[@name='AssParentObjectRef']/atom[@name='irn'])]"/>
        </collection>
    </table>
</xsl:template>
  • You have <collection> here in the template that matches /table. Since there is only one /table match, you will only ever have one <collection> in the output.

  • Also, your selection of top-level tuples (tuple elements that do not refer to a parent object) could be simplified. Instead of:

    select="tuple[not(tuple[@name='AssParentObjectRef']/atom[@name='irn'])]"
    

    you could more simply say:

    select="tuple[not(tuple/*)]"
    

    since we can tell from your sample that top-level tuples only ever contain an empty self-closing <tuple> tag.

The next bit:

<xsl:key name="child" match="tuple" use="tuple[@name='AssParentObjectRef']/atom[@name='irn']" />

So we have a key matching on tuples and using the ID of the parent tuple.

<xsl:template match="tuple">
    <tuple>
        <xsl:copy-of select="atom"/>

        <!-- If this `tuple` is a parent (i.e. if it's included in
             the list of parent IDs in the key), then we add a
             wrapper for the children and process the children.  -->
        <xsl:if test="key('child', atom[@name='irn'])">
            <tuple name="children">
                <!-- Now we apply templates to the `tuple`s 
                     in the key -->
                <xsl:apply-templates select="key('child', atom[@name='irn'])"/>
            </tuple>
        </xsl:if>
    </tuple>
</xsl:template>

This mostly works. Comparing the output from this against your sample desired output, the bits you're missing are the <collection> wrapper tag (explained above), and the parent and top-level ancestor titles and IDs (for which you don't have any XSL code).

You state,

"And whilst this will group the records, the output is just one of these collections. So from a file of 3524 records, I get one collection of 24 records."

I can hypothesize that the XML structure of the remainder of your actual input might differ from what your XSL is targeting. But without seeing your actual input, I cannot tell why this might be the case.

Your edit

You describe adding in the following template:

<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
</xsl:template>

This is the "identity" template, so named because it simply copies over elements identically. The duplication you're seeing is expected: the XSLT processor goes through the flat set of tuples in the input file and copies those over, and then also processes some of these within the context of the template specifically matching tuple, which applies templates to the tuples that matched the key (the nested children).

Alternative approach

The table template isn't too different:

<xsl:template match="table">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <!-- Target only parent-level `tuple`s.  This excludes
            child-level tuples, helping to prevent duplicates.-->
        <xsl:apply-templates select="tuple[not(tuple/*)]" mode="top"/>
    </xsl:copy>
</xsl:template>

Notably, we don't do anything with <collection> here -- we want to wrap each top-level tuple (and its children) in a <collection>, so we need to add that <collection> at the tuple level.

<!-- Add the `collection` wrapper only to top-level tuples -->
<xsl:template match="tuple" mode="top">
    <collection>
        <!-- Pass on this tuple to the main `tuple` template -->
        <xsl:apply-templates select="."/>
    </collection>
</xsl:template>

I match the tuple, but using a special mode -- we only want to add the <collection> wrapper for top-level tuples. I then have a separate template that handles all tuples:

<!-- This is the main template for processing `tuple` elements.
    Most of the changes needed are common to all `tuple`s, so it
    makes sense to keep all the logic in one place. -->
<xsl:template match="tuple">
    <tuple>
        <!-- Copy each existing `atom` child -->
        <xsl:copy-of select="atom"/>
        <!-- Add in metadata about parent and top-level ancestor titles and IDs -->
            <xsl:choose>
                <!-- If this is a top-level item, just use its own values -->
                <xsl:when test="not(tuple/*)">
                    <atom name="TopTitle"><xsl:value-of select="atom[@name='EADUnitTitle']"/></atom>
                    <atom name="TopID"><xsl:value-of select="atom[@name='irn']"/></atom>
                </xsl:when>
                <!-- If this is a descendant, we need to find its parent and its top-level ancestor -->
                <xsl:when test="tuple/*">
                    <atom name="ParentTitle"><xsl:value-of select="tuple/atom[@name='EADUnitTitle']"/></atom>
                    <atom name="ParentID"><xsl:value-of select="tuple/atom[@name='irn']"/></atom>
                <!-- For convenience, grab the top-level ancestor `tuple` and stuff it in a variable.
                    This is vaguely annalogous to your use of `key`. -->

                <!-- Finding the top-level `tuple` is complicated by the fact that the ID values in 
                    `<atom name="irn">` do not have a standardized format, other than that the whole
                    strings appear to consist of atomic values separated by single periods, with 
                    descendant `irn` values appending to the precedent values.  Examples:
                      Top:        `da.04`
                      Descendant: `da.04.11.02`
                      Top:        `D42.PL.05`
                      Descendant: `D42.PL.05.01`
                    So chunking the ID values is a problematic approach, since we don't know how many
                    chunks comprise the initial non-numeric portion: `da`, or `D42.PL`, or ... ???.
                    Top-level elements *do* also have empty `<tuple name="AssParentObjectRef">` elements.
                    So we _can_ find all the top-level elements, and then look in those for the one that
                    has an `irn` value that matches the start of the `irn` value of this current `tuple`. -->
                <xsl:variable name="top" select="/table/tuple[tuple[@name='AssParentObjectRef'][not(*)]]
                    ['The above statement grabs all the `tuple`s that have an empty `tuple[@name=`AssParentObjectRef``.
                      The below statement then goes through all those `tuple`s to find the ones where the `irn`
                      values match the start of the `irn` value of the current `tuple`.']
                    [starts-with(current()/atom[@name='EADUnitID'], atom[@name='EADUnitID'])]"/>
                    <!-- Now we can reference that variable to get the top-level ancestor values -->
                    <atom name="TopTitle"><xsl:value-of select="$top/atom[@name='EADUnitTitle']"/></atom>
                    <atom name="TopID"><xsl:value-of select="$top/atom[@name='irn']"/></atom>
                </xsl:when>
            </xsl:choose>
        <!-- Process any children of this tuple, based on `irn` values.
            Basically, we look for any other `tuple`s in the `table`
            that point to this current `tuple`'s `irn` value. -->
        <xsl:if test="/table/tuple[tuple/atom[@name='irn'] = current()/atom[@name='irn']]">
            <tuple name="children">
                <xsl:apply-templates select="/table/tuple[tuple/atom[@name='irn'] = current()/atom[@name='irn']]"></xsl:apply-templates>
            </tuple>
        </xsl:if>
    </tuple>
</xsl:template>

I can't speak to your full dataset of 3524 records, but running the above against your sample input XML, my output is identical to your desired output (except for the one bug in your sample input XML about a referenced irn value, mentioned in a comment on your initial post).

Suggestions about your desired output XML

As a data format, your desired output XML has a number of aspects that strike me as a bit odd.

  • The <collection> wrapper
    This seems superfluous; it's easy enough to see if a given tuple 1) is top-level, and 2) has any children.

  • The Parent and Top titles and IDs
    These similarly seem superfluous. So long as your data is structured hierarchically, all of this becomes clear without any need to specifically include it. Including this metadata just duplicates already-available information.

  • The <tuple name="children"> wrapper
    This could also be omitted without any loss of information. The simple presence of one tuple element nested within another is evidence enough of children.

I don't know if you have any control over, or influence on, the design of the output XML file format, but if you do, I'd suggest a shorter and more streamlined structure, such as the following:

<table name="ecatalogue">
    <tuple>
        <atom name="irn">2470</atom>
        <atom name="EADUnitID">da.01</atom>
        <atom name="EADUnitTitle">Some title</atom>
        <tuple>
            <atom name="irn">5416</atom>
            <atom name="EADUnitID">da.01.01</atom>
            <atom name="EADUnitTitle">Child of Some title</atom>
        </tuple>
        <tuple>
            <atom name="irn">6</atom>
            <atom name="EADUnitID">da.01.02</atom>
            <atom name="EADUnitTitle">Child of Some title 2</atom>
            <tuple>
                <atom name="irn">8</atom>
                <atom name="EADUnitID">da.01.02.01</atom>
                <atom name="EADUnitTitle">3rd Generation</atom>
            </tuple>
        </tuple>
    </tuple>
</table>

In this structure, a tuple can only contain either atoms or other tuples. We can identify a collection by simply finding any top-level tuple that contains other tuples. We can identify children by simply finding any tuple that has a tuple parent. We can find top-level and parent-level titles and IDs by simply picking a tuple and looking further up the element tree.

This structure is simpler, avoids data duplication, and is arguably clearer and easier to process. That said, you know your needs! Do what works for you. :)


Please have a look through the code and comments, and let me know if you have any lingering questions.

Update 2017-06-15: Problem tuples

I added these to your previous sample input XML, and tried applying my previous XSL code. In reviewing the problem tuples, two things occurred to me:

  1. My previous code makes use of the tokenize function, which is only available in XSL 2.0 and newer. Your post's tags did not specify XSL 1.0, and I failed to notice that you'd specified this in your sample XSL header.

    I've reworked the XSL code above (the template that matches on tuple) to rely only on XSL 1.0 features.

  2. The EADUnitID values in your initial sample input XML are not representative, so any attempt to code specifically against that sample is bound to fail when applied to your fuller, unseen input XML.

    Your initial sample only includes EADUnitID values of the format da.XX, where XX is digits, and the .XX pattern can repeat. I had made some assumptions about chunking this string and comparing the pieces.

    However, your problem tuples have EADUnitID values of a very different format, which appears to be DXX.PL.XX, where again XX is digits and the .XX pattern on the end can repeat. This means that relying on conformity of the chunks between periods is not a safe approach.

    I've reworked the XSL code to instead match the entire front end of the irn string, which appears to work reliably.

Have a look at the code, and the comments, and let me know if anything remains unclear or non-functional.

6 Comments

Thanks for taking the time to answer. A lot of the constraints are placed because of the system that is ingesting this data (e.g. the collection wrapper) and this is something beyond my control. I've tried your suggestions but I end up with the following error:
Error on line 45 of emu-nest-top.xsl: XTTE0570: An empty sequence is not allowed as the value of variable $top at xsl:apply-templates (file:/usr/local/vufind/emu-import/xsl/emu-nest-top.xsl#56) processing /table/tuple[3379] at xsl:apply-templates (file:/usr/local/vufind/emu-import/xsl/emu-nest-top.xsl#21) processing /table/tuple[3378] at xsl:apply-templates (file:/usr/local/vufind/emu-import/xsl/emu-nest-top.xsl#13) processing /table/tuple[3378] in built-in template rule Transformation failed: Run-time errors were reported
The data at row 3378 has an empty parent tag: <tuple name="AssParentObjectRef"> </tuple> this is how the top level elements are defined.
1) When I saw you were using XSLT 2.0 I installed Saxon.
2) Sorry, I didn't mean to let you assume that the information in the EADUnitID was in any way consistent. The only way you can tell whether an item belongs to another is by looking to see if it has a <tuple name="AssParentObjectRef">
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.