Selecting parent/ancestor node based on value in child node
My company makes a publication which outputs a clients current set of diagrams, You can search the publication for diagrams with a given attribute value.
This works but only if the value is contained in the first Attribute node.
Me and another fellow have been trying to fix it so that it searches all the attributes.
Here is the xsl snippet being used to search the attributes. It is looking in Folder Diagram and Shape Elements to see if a child Attribute element contains a the word entered by the user.
<xsl:template name="testObject">
<xsl:if test="(name() = 'Shape' and $includeShapes) or (name() = 'Folder' and $includeFolders) or (name() = 'Document' and $includeDocuments) or (name() = 'Diagram' and $includeDiagrams)">
<xsl:variable name="objXMLLocation">
<xsl:choose>
<xsl:when test="name() = 'Folder'">
<xsl:value-of select="concat(@ID, '/folder.xml')" />
</xsl:when>
<xsl:when test="name() = 'Document'">
<xsl:value-of select="concat(@ID, '/document.xml')" />
</xsl:when>
<xsl:when test="name() = 'Diagram'">
<xsl:value-of select="concat(@ID, '/diagram.xml')" />
</xsl:when>
<xsl:when test="name() = 'Shape'">
<xsl:value-of select="concat(../../@ID, '/', ../../@ID, '_files/', @Source)" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:if test="js:fileExists($objXMLLocation)">
<xsl:variable name="objXML" select="document($objXMLLocation)" />
<xsl:choose>
<xsl:when test="$searchName">
<xsl:if test="js:containsKeywords($objXML/*/Properties/RepositoryName, $searchKeywords)">
<xsl:apply-templates mode="render" select=".">
<xsl:with-param name="fullXML" select="$objXML" />
</xsl:apply-templates>
</xsl:if>
</xsl:when>
<xsl:when test="$searchDescription">
<xsl:if test="js:containsKeywords($objXML/*/Properties/Description, $searchKeywords)">
<xsl:apply-templates mode="render" select=".">
<xsl:with-param name="fullXML" select="$objXML" />
</xsl:apply-templates>
</xsl:if>
</xsl:when>
<xsl:when test="$searchAttributes">
<xsl:if test="name() != 'Folder'">
<xsl:if test="js:containsKeywords($objXML/*/CustomAttributes/Attribute/Value,$searchKeywords)">
<xsl:apply-templates mode="render" select=".">
<xsl:with-param name="fullXML" select="$objXML" />
</xsl:apply-templates>
</xsl:if>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:if>
</xsl:if>
</xsl:template>
Here is the Javascript function containsKeyword it looks to see if the substrings in the needle parameter which are search words entered by the user exists inside the haystack parameter which is the value of an element or attribute that the user is searching the publication for. I am myself unsure of what is going on exactly, but it appears to work correctly.
function containsKeywords(haystack, needles) {
var ks = needles[0].selectNodes('//K');
var n;
if (haystack[0].firstChild) {
n = haystack[0].firstChild.nodeValue.toUpperCase();
} else {
return 0;
}
for (var i = 0; i < ks.length; i++) {
if (n.indexOf(ks[i].firstChild.nodeValue) < 0) {
return 0;
}
}
return 1;
}
The xml being searched.
<Diagram ID="49ab6eb5-c51f-4e36-9495-869897ef0d0d">
<CustomAttributes>
<Attribute>
<Name>Approval Status</Name>
<Description>Document / Diagram / Object Approval开发者_开发技巧 Status</Description>
<Value>Draft - Work in Progress</Value>
<Datatype>Text</Datatype>
</Attribute>
<Attribute>
<Name>Next Document Review Date</Name>
<Description>When is this document to be reviewed next?</Description>
<Value />
<Datatype>Date</Datatype>
</Attribute>
<Attribute>
<Name>Stakeholder View</Name>
<Description>Select the Stakeholder View</Description>
<Value>PMO</Value>
<Datatype>Text</Datatype>
</Attribute>
The current xsl will render a link to the diagram if Draft is enter as it exists in the 1st Attribute element's Value child element. But searching for PMO will return nothing.
The problem is that the xsl will only look at the first Attribute element, when it needs to look at all the child elements in the CustomAttribute element.
We tried using a for-each to go through all the Attribute elements we had trouble traversing the xml tree to get the Diagram ancestor so that it could be selected for the render.
Thanks.
I think that the same task could be done in XSLT. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="CustomAttributes" name="search">
<xsl:param name="pAttributes" select="Attribute/Value"/>
<xsl:param name="pKeywords" select="''"/>
<xsl:choose>
<xsl:when test="$pKeywords != ''">
<xsl:call-template name="search">
<xsl:with-param name="pAttributes"
select="$pAttributes
[contains(
concat(' ',.,' '),
concat(' ',
substring-before(
concat($pKeywords,' '),
' '),
' '))]"/>
<xsl:with-param name="pKeywords" select="substring-after($pKeywords,' ')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<found>
<xsl:copy-of select="$pAttributes"/>
</found>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this proper input:
<Diagram ID="49ab6eb5-c51f-4e36-9495-869897ef0d0d">
<CustomAttributes>
<Attribute>
<Name>Approval Status</Name>
<Description>Document / Diagram / Object Approval Status</Description>
<Value>Draft - Work in Progress</Value>
<Datatype>Text</Datatype>
</Attribute>
<Attribute>
<Name>Next Document Review Date</Name>
<Description>When is this document to be reviewed next?</Description>
<Value />
<Datatype>Date</Datatype>
</Attribute>
<Attribute>
<Name>Stakeholder View</Name>
<Description>Select the Stakeholder View</Description>
<Value>PMO</Value>
<Datatype>Text</Datatype>
</Attribute>
</CustomAttributes>
</Diagram>
Whit param pKeywords
default value set to 'Draft', output:
<found>
<Value>Draft - Work in Progress</Value>
</found>
Note: When param pKeywords
is not set or is set to empty string ''
output all the node set in param pAttributes
, so you can think about this template as a filter. Also, you can edit the ouput to make it usefull for your logic, as example: you could just output the test value for non empty pAttributes
, declare a variable with the content of calling this template, test for the string value of this variable and apply the templates as in your stylesheet fragment.
Okay I have figured out what the problem was.
Here is the solution I will try and explain what is happening.
<xsl:when test="$searchDescription">
<xsl:if test="js:containsKeywords($objXML/*/Properties/Description, $searchKeywords)">
<xsl:apply-templates mode="render" select=".">
<xsl:with-param name="fullXML" select="$objXML" />
</xsl:apply-templates>
</xsl:if>
</xsl:when>
<xsl:when test="$searchAttributes">
<xsl:variable name="source" select="."></xsl:variable>
<xsl:if test="name() != 'Folder'">
<xsl:for-each select="$objXML/*/CustomAttributes//Attribute">
<xsl:if test="js:containsKeywords(./Value,$searchKeywords)">
<xsl:apply-templates mode="render" select="$source">
<xsl:with-param name="fullXML" select="$objXML" />
</xsl:apply-templates>
</xsl:if>
</xsl:for-each>
</xsl:if>
</xsl:when>
This snippet is part of a template that is applied across all a different Xml file to what is being searched.
In the description search when something is found the Publication Node is selected for use in the render. Since there are multiple Attribute nodes doing the same as the Description search will only search the first Attribute element. So using a for-each will go through all attributes but at the same time changes the context to the xml file being searched.
So inorder to send the correct bit to the render template, I saved the context before the for-each in the source variable and when I find a match I send the source variable to the render instead.
精彩评论