XSLT to process XML with very loose standards (EAD) - xslt

I've been having a hell of a week trying to write XSLT code that can process XML documents that conform to the (very permissive) EAD standards.
The useful information in an EAD document is hard to locate precisely. Different EAD documents can place the same bit of information in entirely different parts of the data tree. In addition, within a single EAD document, the same tag can be used numerous times in different locations for different information. For an example of this, please see this SO post. This makes it hard to design a single XSLT file that properly handles these different files.
In general terms, the problem can be described as:
How do I select a specific EAD node which is in an unknown location,
Without accidentally selecting unwanted nodes that have the same name()?
I've finally put together the XSLT I needed and thought it would be best to drop a generic version of the code here so others can benifit from it or improve upon it.
I'd love to tag this question with an "EAD" tag, but I don't have enough rep. If anyone with the appropriate amount of rep thinks it would be useful, please do so.

First a quick description of the solution, followed by the code.
Check if this EAD document contains component (child) records (designated with a <cXX>). If not, we don't have to worry about duplicate EAD tags. The tags can still be burried under arbitrary wrappers. To find them, see step 3.
If child records exist, be careful to not process the <dsc> tag until other tags are processed. To find the other tags, see step 3, then step 4 to process child records.
Recurse through the various wrappers with a template that matches them and calls apply-template on any element node farther down the tree.
We are now processing a child record. Do this by repeating step 2 (carefully process all other tags before tackling the children of this child record), then step 4.
Here's the (generic version of the) XSLT code I came up with:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="ISO-8859-1" indent="yes"/>
<xsl:template match="/ead">
<records>
<xsl:if test="//dsc">
<!-- if there are <cXX> nodes, we'll handle the main record differently.
<cXX> nodes are always found in the 'dsc' node, which contains nothing else -->
<xsl:call-template name="carefully_process"/>
</xsl:if>
<xsl:if test="not(//dsc)">
<record>
<!-- Just process the existing nodes -->
<xsl:apply-templates select="*"/>
</record>
</xsl:if>
</records>
</xsl:template>
<xsl:template name="carefully_process">
<!-- first we'll process all the nodes for the main
record. Then we'll call the child records -->
<record>
<!-- have to be careful not to process //archdesc/dsc yet -->
<xsl:apply-templates select="*[not(self::archdesc)]"/>
<xsl:apply-templates select="archdesc/*[not(self::dsc)]"/>
<!-- Now we can close off the master record, -->
</record>
<!-- and process the child records -->
<xsl:apply-templates select="/ead/archdesc/dsc"/>
</xsl:template>
<xsl:template match="dsc">
<!-- Start processing the child records (we use for-each to get a good position() -->
<xsl:for-each select="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']">
<xsl:apply-templates select=".">
<!-- we pass the unittitle and unitid of the master record, so that child
records can be linked to it. We pass the position of the child so that
a unitid can be created if it doesn't exist -->
<xsl:with-param name="partitle" select="normalize-space(/ead/archdesc/did/unittitle)"/>
<xsl:with-param name="parid" select="normalize-space(/ead/archdesc/did/unitid)"/>
<xsl:with-param name="pos" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<!-- process child nodes -->
<xsl:template match="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']" >
<xsl:param name="partitle"/>
<xsl:param name="parid"/>
<xsl:param name="pos"/>
<!-- start this child record -->
<record>
<!-- EAD does not require a unitid, but my code does.
If it doesn't exist, create it -->
<xsl:if test="not(./did/unitid)">
<atom name="unitid">
<xsl:value-of select="$parid"/><xsl:text>-</xsl:text><xsl:value-of select="$pos"/>
</atom>
</xsl:if>
<!-- get the level of this component -->
<atom name="eadlevel">
<xsl:value-of select="concat(translate(substring(#level,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),substring(#level,2))"/>
</atom>
<!-- Do *something* to attach this record to it's parent.
Probably involves $partitle and $parid. For example: -->
<ref>
<atom name="unittitle"><xsl:value-of select="$partitle"/></atom>
<atom name="unitid"><xsl:value-of select="$parid"/></atom>
</ref>
<!-- now process all the other nodes -->
<xsl:apply-templates select="*[not(starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c')]"/>
<!-- finish this child record -->
</record>
<!-- prep the variables we'll need for attaching any child records (<cXX+1>) to this record -->
<xsl:variable name="this_title">
<xsl:value-of select="normalize-space(./did/unittitle)"/>
</xsl:variable>
<xsl:variable name="this_id">
<xsl:if test="./did/unitid">
<xsl:value-of select="./did/unitid"/>
</xsl:if>
<xsl:if test="not(./did/unitid)">
<xsl:value-of select="$parid"/><xsl:text>-</xsl:text><xsl:value-of select="$pos"/>
</xsl:if>
</xsl:variable>
<!-- now process the children of this node -->
<xsl:for-each select="*[starts-with(name(),'c0') or starts-with(name(),'c1') or name() = 'c']">
<xsl:apply-templates select=".">
<xsl:with-param name="partitle" select="$this_title"/>
<xsl:with-param name="parid" select="$this_id"/>
<xsl:with-param name="pos" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<!-- these are usually just wrappers. Go one level deeper -->
<xsl:template match="descgrp|eadheader|revisiondesc|filedesc|titlestmt|profiledesc|archdesc|archdescgrp|daogrp|langusage|did|frontmatter">
<xsl:apply-templates select="*"/>
</xsl:template>
<!-- below this point, add templates for processing specific EAD units
of information. For example, the template might look like
<xsl:template match="titleproper">
<atom name="titleproper">
<xsl:value-of select="normalize-space(.)"/>
</atom>
</xsl:template>
-->
<!-- instead of having a template for each EAD information unit, consider
a generic template that handles them all the same way. For example:
-->
<xsl:template match="*">
<atom>
<xsl:attribute name="name"><xsl:value-of select="name()"/></xsl:attribute>
<xsl:value-of select="normalize-space(.)"/>
</atom>
</xsl:template>
</xsl:stylesheet>

Related

XSL copy without values is it possible?

I want to compare two xmls.
1. First compare XML strucutre/schema.
2. Compare values.
I am using beyond compare tool to compare. Since these two xmls are different values, there are lot many differences in comparison report, for which I am not interested. Since, my focus now is to only compare structure/schema.
I tried to copy the xmls by following template, and other as well. But every time it is with values.
I surfed on google, xsl-copy command itself copies everything for selected node/element..
Is there any ways with which I can filter out values and only schema is copied ?
My Data :
<root>
<Child1>xxxx</Child1>
<Child2>yyy</Child2>
<Child3>
<GrandChild1>dddd<GrandChild1>
<GrandChild2>erer<GrandChild2>
</Child3>
</root>
Template used :
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:copy/>
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()|comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
OutPut Required :
Something like..
<root>
<Child1></Child1>
<Child2></Child2>
<Child3>
<GrandChild1><GrandChild1>
<GrandChild2><GrandChild2>
</Child3>
</root>
At the moment, you have a template matching text() to copy it. What you need to do is remove this match from that template, and have a separate template match, that matches only non-whitespace text, and remove it.
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
For white-space only text (as used in indentation), these will be matched by XSLT'S built-in templates.
For attributes, use xsl:attribute to create a new attribute, without a value, rather than using xsl:copy which will copy the whole attribute.
<xsl:attribute name="{name()}" />
Note the use of Attribute Value Templates (the curly braces) to indicate the expression is to be evaluated to get the string to use.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- for all elements (tags) -->
<xsl:template match="*">
<!-- create a copy of the tag (without attributes and children) in the output -->
<xsl:copy>
<!-- For all attributes of the current tag -->
<xsl:for-each select="#*">
<xsl:sort select="name( . )" order="ascending" case-order="lower-first" />
<xsl:attribute name="{name()}" />
</xsl:for-each>
<!-- recurse through all child tags -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="comment()|processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="text()[normalize-space()]" />
</xsl:stylesheet>
Also note that attributes are considered to be unordered in XML, so although you have code to sort the attributes, and they probably will appear in the right order, you can't guarantee it.

XSL - How can I compare 2 sets of nodes?

I have the following data
<parent>
<child>APPLES</child>
<child>APPLES</child>
<child>APPLES</child>
</parent>
<parent>
<child>APPLES</child>
<child>BANANA</child>
<child>APPLES</child>
</parent>
Is there a simple way to compare the parent nodes? Or will I have to nest a for-each within a for-each and test every child manually with position()?
XSLT 2.0 has the function http://www.w3.org/TR/2013/CR-xpath-functions-30-20130521/#func-deep-equal so you could write a template
<xsl:template match="parent[deep-equal(., preceding-sibling::parent[1])]">...</xsl:template>
to process those parent elements that are equal to their preceding sibling parent.
If you want to do it with XSLT 1.0 then for your simple case of a sequence of child elements with plain text content it should suffice to write a template
<xsl:template match="parent" mode="sig">
<xsl:for-each select="*">
<xsl:if test="position() > 1">|</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
and then to use it as follows:
<xsl:template match="parent">
<xsl:variable name="this-sig">
<xsl:apply-templates select="." mode="sig"/>
</xsl:variable>
<xsl:variable name="pre-sig">
<xsl:apply-templates select="preceding-sibling::parent[1]" mode="sig"/>
</xsl:variable>
<!-- now compare e.g. -->
<xsl:choose>
<xsl:when test="$this-sig = $pre-sig">...</xsl:when>
<xsl:otherwise>...</xsl:otherwise>
</xsl:choose>
</xsl:template>
For more complex content you would need to refine the implementation of the template computing a "signature" string, you might want to search the web, I am sure Dimitre Novatchev has posted solutions on earlier, similar questions.

Selectively copy and update xml nodes using XSLT

I'm working with an xml that I need to copy and update to pass on for further processing. The issue I'm having is that I have not figured out an efficient method to do this. Essentially, I want to update some data, conditionally, then copy all the nodes that were not updated. Why this is challenging is due to the volume and variance in the number and name of nodes to be copied. I also want to NOT copy nodes that have no text value. Here is an example:
INPUT XML
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>John</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>OR</HomeState>
...
<nodeN>text</nodeN>
</PersonProfile>
</root>
The "PersonProfile" node is just one of several node sets within the "root" element, each with their own subset of data. Such as mailing address, emergency contact info, etc. What I am attempting to do is update nodes if the variable has a new value for them then copy all the nodes that were not updated.
Here is my current XSLT
<xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:variable name='updateData' select='document("report")'/>
<!-- Identity Transform -->
<xsl:template match='#* | node()'>
<xsl:if test'. != ""'>
<xsl:copy>
<xsl:apply-templates select='#* | node()'/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Template to update Person Profile -->
<xsl:template match='PersonProfile'>
<xsl:copy>
<xsl:apply-templates select='*'/>
<xsl:element name='Name'>
<xsl:if test='exists($updateData/Preferred)'>
<xsl:element name='FirstName'>
<xsl:value-of select='$reportData/FirstName'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/Preferred)'>
<xsl:element name='PreferredName'>
<xsl:value-of select='$updateData/Preferred'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/Middle)'>
<xsl:element name='MiddleName'>
<xsl:value-of select='$updateData/Middle'/>
</xsl:element>
</xsl:if>
<xsl:if test='exists($updateData/LastName)'>
<xsl:element name='LastName'>
<xsl:value-of select='$updateData/wd:LastName'/>
</xsl:element>
</xsl:if>
</xsl:element>
<xsl:if test='exists($updateData/Country)'>
<xsl:element name='Country'>
<xsl:value-of select='$updateData/Country'/>
</xsl:element>
</xsl:if>
....
<!-- follows same structure until end of template -->
</xsl:copy>
</xsl:template>
<!-- More Templates to Update other Node sets -->
</xsl:stylesheet>
What's happening right now, is that it's copying ALL the nodes and then adding the updates values. Using Saxon-PE 9.3.0.5, I'll get an output similar to this:
Sample Output
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>John</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>OR</HomeState>
...
<nodeN>text</nodeN>
<PreferredName>Jonathan</PreferredName>
<HomeState>WA</HomeState>
</PersonProfile>
</root>
I realize this is happening because I am applying the templates to all the nodes in PersonProfile and that I could specify which nodes to exclude, but I feel like this is a very poor solution as the volume of nodes could be upwards of 30 or more and that would require a written value for each one. I trust XML has a more elegant solution than to explicitly list each of these nodes. I would like to have an out like this:
Desired Output
<root>
<PersonProfile xmlns:'namespace'>
<ID>0001</ID>
<Name>
<FirstName>Jonathan</FirstName>
<PreferredName>Jonathan</PreferredName>
<MiddleName>A</MiddleName>
<LastName>Doe</LastName>
</Name>
<Country>US</Country>
<Biirthdate>01-01-1980</Birthdate>
<BirthPlace>
<City>Townsville</City>
<State>OR</State>
<Country>US</Country>
</Birthplace>
<Gender>Male</Gender>
<HomeState>WA</HomeState>
...
<nodeN>text</nodeN>
</PersonProfile>
</root>
If anyone could help me create a template structure that would work for the xml structure, I would GREATLY appreciate it. It would need to be "reusable" as there are similar node structures like Person Profile I would have to apply it to, but with different node names and number of elements, etc.
Thanks in advance for any help!
J
This should work for your original question:
<xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:variable name='updateData' select='document("report")'/>
<!-- Identity Transform -->
<xsl:template match='#* | node()' name='copy'>
<xsl:copy>
<xsl:apply-templates select='#* | node()'/>
</xsl:copy>
</xsl:template>
<xsl:template match='*[not(*)]'>
<xsl:variable name='matchingValue'
select='$updateData/*[name() = name(current())]'/>
<xsl:choose>
<xsl:when test='$matchingValue'>
<xsl:copy>
<xsl:apply-templates select='#*' />
<xsl:value-of select='$matchingValue'/>
</xsl:copy>
</xsl:when>
<xsl:when test='normalize-space()'>
<xsl:call-template name='copy' />
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As far as inserting new elements that are not present in the source XML, that's trickier. Could you open a separate question for that? I may have some ideas for how to approach that.

How to loop through a list of nodes and create new nodes based on the child nodes of those I'm looping through

This may be a basic question but this newbie has been struggling and Googling and hasn't been able to figure it out.
I have an xml document similar to this.
<x99:events xmlns:x99="http://www.foo.com/x99" xmlns:xl="http://www.w3.org/1999/xlink" pubdate="2012-05-29T11:14:14-06:00">
<x99:event xl:href="event.xml?event_id=255918" id="foo" status="new">
<x99:event_id>255918</x99:event_id>
<x99:custom_attribute xmlns:x99="http://www.foo.com/x99" status="new">
<x99:attribute_id>22</x99:attribute_id>
<x99:attribute_value>hi there</x99:attribute_value>
</x99:custom_attribute>
<x99:custom_attribute xmlns:x99="http://www.foo.com/x99" status="new">
<x99:attribute_id>26</x99:attribute_id>
<x99:attribute_value>this is a test</x99:attribute_value>
</x99:custom_attribute>
<x99:custom_attribute xmlns:x99="http://www.foo.com/x99" status="new">
<x99:attribute_id>12</x99:attribute_id>
<x99:attribute_value>Yes</x99:attribute_value>
</x99:custom_attribute>
</x99:event>
</x99:events>
And I have some xsl that transforms the xml.
In my xsl I need to be able to loop through the custom_attribute nodes and, for each attribute_id I find I need to create a node with some values based on child nodes of the custom_attribute node.
Here's my pseudo-code. I need something like this.
<xsl:for-each select="x99:custom_attribute">
<xsl:when test="number(x99:attribute_id) = 22>
<x99:text>You chose twenty two and your attribute value is <x99:attribute_value></x99:text>
</xsl:when>
<xsl:when test="number(x99:attribute_id) = 26>
<x99:text>Twenty six is a great answer! and your attribute value is <x99:attribute_value></x99:text></x99:text>
</xsl:when>
</xsl:for-each>
And here is my xsl.
My xsl skills are at the most basic level and my xml isn't much better either. Can some kind soul give me some advice? I'm in a bit over my head.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xl="http://www.w3.org/1999/xlink"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:b="http://www.someurl.com/b"
xmlns:s="http://www.someurl.com/s"
xmlns:c="http://foo.com/c"
xmlns:x99="http://foo.com/x99"
exclude-result-prefixes="xl x99">
<xsl:param name="base_url" select="''" />
<xsl:param name="session_id" select="''" />
<xsl:param name="TaskDir" select="''" />
<xsl:template match="/">
<xsl:call-template name="SendConfirmationEmail">
<xsl:with-param name="EventID" select="/x99:events/x99:event/x99:event_id"/>
<xsl:with-param name="SiteURL" select="'https://foobar.com/123Test/#details'" />
</xsl:call-template>
<xsl:apply-templates select="node()" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="SendConfirmationEmail">
<xsl:param name="EventID" select="''"/>
<xsl:result-document>
<schedule>
<job>
<name>Email Confirmation</name>
<active>T</active>
<http>
<body>
<x99:email xmlns:x99="http://foo.com/x99">
<x99:mail>
<x99:body>
<x99:text>Your event ID is <xsl:value-of select="$EventID"/></x99:text>
// I want to be able to loop through my x99:attribute_id values here and create new x99:text nodes.
</x99:body>
</x99:mail>
</x99:email>
</body>
</http>
</job>
</schedule>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
So frankly I need to know how to...
Loop through the custom_attribute nodes and for each node create a x99:text node that contains
a string based on the value of the child attribute_id
the contents of the attribute_value node
First, instead of using a named template with parameters as if you were programming in FORTRAN using CALL to invoke subroutines, just define a template to handle the event_id element, such as <xsl:template match="event_id">. I assume you mean html, not http. You'll need to specify a href attribute on the <xsl:result-document> element so it knows where to put the document. You're apparently writing elements into your HTML from the x99 namespace--is that really what you want to do?
With regard to "looping" over the attribute elements, in an XSLT mindset it would be preferable to say "process" them. Under <x99:text> add a <xsl:apply-templates/> element, then define a template to handle the custom attribute elements (<xsl:template match="x99:custom-attribute">. Within that template, place something like what you have within the for-each of your pseudo-code, except that instead of saying number(x99:attribute_id) you'll just want number(.) since we are already at that node. However, note that <xsl:when> elements can only go within a <xsl:choose>.

How to mimic calling template-match with a parameter value?

I'm looking for a workaround on passing parameters to a template-match. I'm aware this isn't allowed within XPath, and therefore I'm looking for a 'plan B' solution.
This is what I wished would work :
Part 1 of xslt (2.0) :
<xsl:template match="/">
<xsl:for-each select="//Main/PageList/Page">
<xsl:result-document href="{#ID}.xml">
<Page ID="{#ID}">
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
</Page>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
So fairly straightforward, don't bother about the tags used, what it does in essence is go through a node, and for each node it creates a single XML file. Each node starts with an ID, and it's this ID I'd like to make available for other templates. Unfortunately this works fine for named templates, but it doesn't work for matched ones (if I understood the theory correctly at least)
So below is what I'd like to see working :
<!-- identity template -->
<xsl:template match="node()|#*">
<xsl:param name="theID"/>
<xsl:copy>
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="theID" select="$theID"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- lots of templates here doing what I want them to do -->
<!-- one template creating an issue -->
<xsl:template match="#src">
<!-- would be nice to know the current ID, but unfortunately this one stays empty... -->
<xsl:param name="theID"/>
<!-- clean current attribute a bit -->
<xsl:variable name="S1" select="replace(.,'\.\.\/','')"/>
<xsl:attribute name="src">
<xsl:choose>
<xsl:when test="contains($S1,'common')">
<!-- just use current value, don't bother about current ID -->
<xsl:value-of select="$S1"/>
</xsl:when>
<xsl:otherwise>
<!-- use ID parameter -->
<xsl:value-of select="concat($theID,'_',$S1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</xsl:template>
Don't look at the code as such, it's just a smaller part of the whole file, but the essence is that I want to use the ID parameter from my first part (match="/") inside the other template (match="#src"), but this seems to be rather complex.
Am I missing something ? If I'm just having bad luck and it's not possible indeed, would anyone have an advice how I could proceed ?
Thanks in advance !
Your problem is here:
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
the select attribute specifies that the processing should continue on any children nodes of the current node.
However node() only selects children nodes (elements, text nodes, processing instructions and comments) -- not attributes.
Solution:
In order to directly cause processing of (all) attributes of the current node, use:
--
<xsl:apply-templates select="#*">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
.2. In order to cause the processing only of the src attribute of the current node use:
--
<xsl:apply-templates select="#src">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
.3. In order to process indirectly attributes tof the descendants of the current node, do:
--
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
You also must ensure that any template that processes node() must have an xsl:param named theID and that it must pass this param in any xsl:apply-templates instruction.
In practice this means that you mus override all XSLT built-in templates, because they aren't aware of your xsl:param.
I think instead of
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
you rather want
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="parent::Page/#ID"/>
</xsl:apply-templates>
assuming you want to pass on the ID attribute of the parent Page element.
On the other hand the select="node()" select any child nodes like child elements, child comment nodes, child text nodes, child processing instruction nodes, so I don't see why you later show a template matching an attribute node.