want to group request based on similar package id using manual XSLT
Here is input xml which contains plan_id on which it needs to be grouped
<subscriptions>
<package>
<package_plan>
<plan_id>1111</plan_id>
<plan_name>economy1</plan_name>
</package_plan>
<rate_channel>
<rateid>1F1</rateid>
<currency>USD</currency>
<package_duration>monthly</package_duration>
<price>3</price>
</rate_channel>
</package>
<package>
<package_plan>
<plan_id>1111</plan_id>
<plan_name>economy1</plan_name>
</package_plan>
<rate_channel>
<rateid>1F2</rateid>
<currency>USD</currency>
<package_duration>quaterly</package_duration>
<price>11</price>
</rate_channel>
</package>
<package>
<package_plan>
<plan_id>2222</plan_id>
<plan_name>economy2</plan_name>
</package_plan>
<rate_channel>
<rateid>1F3</rateid>
<currency>INR</currency>
<package_duration>monthly</package_duration>
<price>250</price>
</rate_channel>
</package>
</subscriptions>
Looking for output as:
<subscriptions>
<package>
<plan_id>1111</plan_id>
<plan_name>economy1</plan_name>
<channels>
<rate_channel>
<rateid>1F1</rateid>
<currency>USD</currency>
<package_duration>monthly</package_duration>
<price>3</price>
</rate_channel>
<rate_channel>
<rateid>1F2</rateid>
<currency>USD</currency>
<package_duration>quaterly</package_duration>
<price>11</price>
</rate_channel>
</channels>
</package>
<package>
<plan_id>2222</plan_id>
<plan_name>economy2</plan_name>
<channels>
<rate_channel>
<rateid>1F3</rateid>
<currency>INR</currency>
<package_duration>monthly</package_duration>
<price>250</price>
</rate_channel>
</channels>
</package>
</subscriptions>
I've been doing some search and reading the other posts, and I don't think they cover exactly what I want to do
Thanks in advance...
Below are the XSLT 1.0 and XSLT 2.0 solutions. Both the solutions use a different approach as rightly pointed by #Tim C in the comment.
XSLT 1.0
In case of XSLT 1.0, muenchian grouping is used. A <xsl:key> needs to be defined to group the elements. In this case, <plan_id> is the key on which the grouping will be done.
<xsl:key name="plan" match="package" use="package_plan/plan_id" />
The package elements are matched accordingly and the grouped data is copied to the output.
<xsl:template match="package[generate-id() = generate-id(key('plan', package_plan/plan_id)[1])]">
<xsl:copy>
<xsl:variable name="varPlan" select="key('plan', package_plan/plan_id)" />
<xsl:apply-templates select="$varPlan[1]/package_plan/plan_id" />
<xsl:apply-templates select="$varPlan[1]/package_plan/plan_name" />
<channels>
<xsl:for-each select="$varPlan">
<xsl:apply-templates select="rate_channel" />
</xsl:for-each>
</channels>
</xsl:copy>
</xsl:template>
The rest of the <package> elements are removed.
<xsl:template match="package" />
XSLT 2.0
In XSLT 2.0, <xsl-for-each-group> feature is available especially for grouping of elements. In this case, using this feature and the current-group() function, grouping can be achieved.
<xsl:template match="subscriptions">
<xsl:copy>
<xsl:for-each-group select="package" group-by="package_plan/plan_id">
<package>
<xsl:apply-templates select="current-group()[1]/package_plan/plan_id" />
<xsl:apply-templates select="current-group()[1]/package_plan/plan_name" />
<channels>
<xsl:for-each select="current-group()">
<xsl:apply-templates select="rate_channel" />
</xsl:for-each>
</channels>
</package>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
In both the above cases, an identity transform template should be used to copy the data as is to the output.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
Related
Hi I'm processing huge file (50k of lines) and need to know about nodes which were not processed.
I was thinking about this solutions:
create copy of processing file and when matching template is found, then remove it from copied file
create "reverse template" of all templates and select all what was not processed (this probably won't work)
process file normally and then create diff between original file and file created with this template.
So what is the best approach for this? If there's need to provide more details, let me know please.
Here is my sample xml:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd
http://www.liquibase.org/xml/ns/dbchangelog">
<changeSet id="1" author="a">
<createTable tableName="TABLE1">
<column></column>
</createTable>
</changeSet>
<changeSet id="1-1" author="a">
<createSequence sequenceName="SEQ_TABLE1" />
</changeSet>
<changeSet id="4" author="A">
<createTable tableName="TABLE4">
<column></column>
</createTable>
</changeSet>
</databaseChangeLog>
here is xslt template:
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.liquibase.org/xml/ns/dbchangelog">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="coreTables"
select="('TABLE1','TABLE2')"/>
<xsl:template match="node()[not(self::*)]">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<xsl:template match="databaseChangeLog">
<!-- CORE-->
<xsl:comment> CORE TABLES </xsl:comment>
<xsl:apply-templates select="changeSet[createTable/#tableName=$coreTables]"/>
<xsl:comment>CORE SEQUENCES</xsl:comment>
<xsl:apply-templates
select="changeSet[createSequence[starts-with(#sequenceName, 'SEQ_') and substring-after(#sequenceName, 'SEQ_') = $coreTables]]"/>
<xsl:comment> CORE INDEXES </xsl:comment>
<xsl:apply-templates select="changeSet[createIndex/#tableName=$coreTables]"/>
<xsl:comment> CORE FOREIGN CONSTRAINTS </xsl:comment>
<xsl:apply-templates select="changeSet[addForeignKeyConstraint/#baseTableName=$coreTables]"/>
<xsl:comment> CORE VIEWS </xsl:comment>
<xsl:apply-templates select="changeSet[createView/#viewName=$coreTables]"/>
</xsl:template>
</xsl:transform>
I'm using xslt 2 and saxom 9.8he
Thanks
Instead of doing this...
<xsl:comment> CORE TABLES </xsl:comment>
<xsl:apply-templates select="changeSet[createTable/#tableName=$coreTables]"/>
Do this, to save the elements selected
<xsl:variable name="tables" select="changeSet[createTable/#tableName=$coreTables]"/>
<xsl:apply-templates select="$tables" />
And similarly for other statements. Then to get the elements in your XML that have not been matched you can do this...
<xsl:apply-templates select="changeSet[not(some $set in ($tables | $sequences | $indexes | $fkeys | $views) satisfies $set is .)]" />
Try this template
<xsl:template match="databaseChangeLog">
<!-- CORE-->
<xsl:comment> CORE TABLES </xsl:comment>
<xsl:variable name="tables" select="changeSet[createTable/#tableName=$coreTables]"/>
<xsl:apply-templates select="$tables" />
<xsl:comment>CORE SEQUENCES</xsl:comment>
<xsl:variable name="sequences" select="changeSet[createSequence[starts-with(#sequenceName, 'SEQ_') and substring-after(#sequenceName, 'SEQ_') = $coreTables]]"/>
<xsl:apply-templates select="$sequences"/>
<xsl:comment> CORE INDEXES </xsl:comment>
<xsl:variable name="indexes" select="changeSet[createIndex/#tableName=$coreTables]"/>
<xsl:apply-templates select="$indexes"/>
<xsl:comment> CORE FOREIGN CONSTRAINTS </xsl:comment>
<xsl:variable name="fkeys" select="changeSet[addForeignKeyConstraint/#baseTableName=$coreTables]"/>
<xsl:apply-templates select="$fkeys"/>
<xsl:comment> CORE VIEWS </xsl:comment>
<xsl:variable name="views" select="changeSet[addForeignKeyConstraint/#baseTableName=$coreTables]"/>
<xsl:apply-templates select="$views"/>
<xsl:comment> UNMATCHED </xsl:comment>
<xsl:apply-templates select="changeSet[not(some $set in ($tables | $sequences | $indexes | $fkeys | $views) satisfies $set is .)]" />
</xsl:template>
EDIT: Thanks to Martin Honnen, the final expression can be simplified to this...
<xsl:apply-templates select="changeSet except ($tables, $sequences, $indexes, $fkeys, $views)" />
I'm not quite sure what you mean by "not processed". Do you mean "not selected by any call on xsl:apply-templates"? That's not the same thing, of course, a node might be processed using xsl:for-each, etc. Also, I suspect you're only interested in elements that weren't "processed" in this way, not in other nodes such as attributes and namespaces.
One approach that might (or might not) meet your requirements is to write a TraceListener. If you attach a TraceListener to your transformation, it will be notified every time an instruction changes the context item (that's another definition of "being processed"). Your TraceListener can then build a Java Set containing all nodes that were touched, and can then difference this with the set of all nodes on completion of processing.
This is the source XML:
<root>
<!-- a and b have the same date entries, c is different -->
<variant name="a">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="b">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="c">
<booking>
<date from="2017-04-06" to="2017-04-07" />
<date from="2017-04-07" to="2017-04-09" />
</booking>
</variant>
</root>
I'd like to group the three variants so that each variants with same #from and #to in each date should be grouped together.
My attempt is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"></xsl:output>
<xsl:template match="root">
<variants>
<xsl:for-each-group select="for $i in variant return $i" group-by="booking/date/#from">
<group>
<xsl:attribute name="cgk" select="current-grouping-key()"/>
<xsl:copy-of select="current-group()"></xsl:copy-of>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
</xsl:stylesheet>
But this gives too many groups. (How) is this possible to achieve?
Using a composite key and XSLT 3.0 you could use
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="booking/date/(#from, #to)" composite="yes">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
which should group any variant elements together which have the same descendant date element sequence.
XSLT 3.0 is supported by Saxon 9.8 (any edition) or 9.7 (PE and EE) or a 2017 release of Altova XMLSpy/Raptor.
Using XSLT 2.0 you could concatenate all those date values with string-join():
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="string-join(booking/date/(#from, #to), '|')">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
Like the XSLT 3.0 solution, it only groups variant with the same sequence of date descendants, I am not sure whether that suffices or whether you might want to sort any date descendants first before computing the grouping key. In the XSLT 3 case you could do that easily with
<xsl:for-each-group select="variant" group-by="sort(booking/date, (), function($d) { xs:date($d/#from), xs:date($d/#to) })!(#from, #to)" composite="yes">
inline (although that leaves 9.8 HE behind as it does not support function expressions/higher order functions, so there you would need to move the sorting to your own user-defined xsl:function and in there use xsl:perform-sort).
I have a XML structure where the XML schema is irregular/not formatted. The structure looks like this-
<Host>
<element1>type0</element1>
<element2>Fruits</element2>
....
<elementn>Price0</elementn>
<Menu>
<NodeA>
<element1>type1</element1>
<element2>Fruits</element2>
....
<elementn>Price1</elementn>
<Menu>
<NodeB>
<element1>type2</element1>
<element2>Fruits</element2>
....
<elementn>Price2</elementn>
<Menu>
<NodeC>
<element1>type3</element1>
<element2>Fruits</element2>
....
<elementn>Price3</elementn>
<Menu>
<NodeD>
<Element1>type4</element1>
<Element2>Vegetables</Element2>
....
<Elementn>Price4</elementn>
</NodeD>
</Menu>
</NodeC>
</Menu>
</NodeB>
</Menu>
</NodeA>
<NodeE>
<element1>type5</element1>
<element2>Fruits</element2>
....
<elementn>Price5</elementn>
<Menu>
<NodeF>
<element1>type6</element1>
<element2>Vegetables</element2>
....
<elementn>Price6</elementn>
</NodeF>
</Menu>
</NodeE>
</Menu>
</Host>
Now my expected XML is as follows-
a) if <element2> == fruits in all the nodes, I need XML schema as follows. I may include or exclude the below n elements right under host -
`<element1>type0</element>
<element2>Fruits</element2>
....
<elementn>Price0</elementn>`
.Expected Result -
<Host>
<NodeA>
<element1>type1</element1>
<element2>Fruits</element2>
....
<elementn>Price1</elementn>
</NodeA>
<NodeB>
<element1>type2</element1>
<element2>Fruits</element2>
....
<elementn>Price2</elementn>
</NodeB>
<NodeC>
<element1>type3</element1>
<element2>Fruits</element2>
....
<elementn>Price3</elementn>
</NodeC>
<NodeE>
<element1>type5</element1>
<element2>Fruits</element2>
....
<elementn>Price5</elementn>
</NodeE>
</Host>
b) if <element2> == vegetables in all the nodes, I need XML schema as follows
Note: <element2> == Vegetables is always at the last node in the schema
<Host>
<NodeD>
<element1>type4</element1>
<element2>Vegetables</element2>
....
<elementn>Price4</elementn>
</NodeD>
<NodeF>
<element1>type6</element1>
<element2>Vegetables</element2>
....
<elementn>Price6</elementn>
</NodeF>
</Host>
Any help for getting the above XML formats through XSLT would be a great help.
If you want 2 separate document, you don't actually need 2 XSLTs. You can use one XSLT but with a parameter
<xsl:param name="element2" select="'Fruits'" />
(Here 'Fruits' is just the default value, should the parameter not be specified by the calling application).
You would start off by selecting the nodes which have element2 equal to the parameter (Do note XML and XSLT is case-sensitive, so element2 is not the same as Element2 in your XML, but I assumed that was a typo in your XML).
<xsl:apply-templates select="//*[element2=$element2]"/>
You would also need a template to ensure when a node is matched, it does not copy the child nodes...
<xsl:template match="*[element2]">
<xsl:copy>
<xsl:apply-templates select="*[not(*)]" />
</xsl:copy>
</xsl:template>
The other nodes would be handled by the identity template.
Try this XSLT...
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:param name="element2" select="'Fruits'" />
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="//*[element2=$element2]" mode="copy"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[element2]" mode="copy">
<xsl:copy>
<xsl:apply-templates select="*[not(*)]" mode="copy"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="copy"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note that if you were using XSLT 2.0, you could create multiple documents in one call, using xsl:for-each-group to get the distinct groups, and xsl:result-document to create a file for each.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/*">
<xsl:for-each-group select="//*[element2]" group-by="element2">
<xsl:result-document href="{current-grouping-key()}.xml" method="xml">
<Host>
<xsl:apply-templates select="current-group()" mode="copy" />
</Host>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="*[element2]" mode="copy">
<xsl:copy>
<xsl:apply-templates select="*[not(*)]" mode="copy"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" mode="copy">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="copy" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
A global variable indicates, for each of various kinds of elements, what attributes need to be processed.
<xsl:variable name="attributes.rtf">
<element name="measure">
<att>type</att>
<att>quantity</att>
<att>unit</att>
</element>
<element name="milestone">
<att>n</att>
</element>
<element name="lb">
<att>ed</att>
<att>type</att>
<att>subtype</att>
<att>n</att>
</element>
</xsl:variable>
<xsl:variable name="attributes" select="exslt:node-set($attributes.rtf)" /> <!-- This is XSLT 1.0 -->
When the context is an element, I need a for-each that will loop through attributes for that kind of element. I don't see how to do this in one XPath expression. Right now I have these two:
<xsl:variable name="temp" select="." /> <!-- For, say, a <measure> element, -->
<xsl:for-each select="$attributes/element[#name=name($temp)]/att"> <!-- loop through the names "type", "quantity", and "unit" -->
<xsl:for-each select="$temp/#*[name()=current()]"> <!-- If an attribute of that name exists in the current <measure> element -->
<!-- (now stored as $temp), then process that attribute. -->
</xsl:for-each>
</xsl:for-each>
Is there a way to do this in one expression, without creating $temp.
But also, I need an outer test condition that indicates whether the context element has any of the listed attributes at all. For that test, I'd like one expression.
(Processing of the attributes does need to be ordered, so maybe that requires the two loops. But order would not be relevant in the test condition. So maybe just that could be done with one expression.)
I. The "simple" problem:
When the context is an element, I need a for-each that will loop through attributes for that kind of element.
No xsl-for-each is needed.
I don't see how to do this in one XPath expression.
Just use:
<xsl:apply-templates select=
"#*[name()=$attributes/element[#name=name(current())]/att]"/>
Explanation:
Proper use of the current() function.
II. The "complex" problem:
Processing of the attributes does need to be ordered, so maybe that requires the two loops
Here is a complete example showing that no other nested <xsl:apply-templates> is necessary to produce the attributes in the order specified in $attributes:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="attributes.rtf">
<element name="measure">
<att>type</att>
<att>quantity</att>
<att>unit</att>
</element>
<element name="milestone">
<att>n</att>
</element>
<element name="lb">
<att>ed</att>
<att>type</att>
<att>subtype</att>
<att>n</att>
</element>
</xsl:variable>
<xsl:variable name="attributes" select="exslt:node-set($attributes.rtf)" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="t/*">
<xsl:copy>
<xsl:apply-templates select="$attributes/element[#name=name(current())]/att">
<xsl:with-param name="pCurrent" select="current()"/>
</xsl:apply-templates>
<xsl:apply-templates select=
"#*[not(name() = $attributes/element[#name=name(current())]/att)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="att">
<xsl:param name="pCurrent" select="/.."/>
<xsl:if test="$pCurrent[#*[name()=current()]]">
<xsl:attribute name="{.}">
<xsl:value-of select="concat(.,'-',.)"/>
</xsl:attribute>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (none was provided!!!):
<t>
<lb type="normal" ed="uni" n="3"
subtype="initial" other="yes"/>
<milestone n="2" other="yes"/>
<measure quantity="3" unit="cm"
type="length" other="yes"/>
</t>
the wanted, correct result is produced:
<t>
<lb ed="ed-ed" type="type-type" subtype="subtype-subtype" n="n-n" other="yes"/>
<milestone n="n-n" other="yes"/>
<measure type="type-type" quantity="quantity-quantity" unit="unit-unit" other="yes"/>
</t>
New to XSLT, but have been learning a lot from posts here. However, I'm stuck on one problem.
I am using XSLT to create a report for a device installation. The input XML looks like this:
<DeviceTypes>
<DeviceInfo Model="51473">
<Channels>
<ChannelInfo ChannelId="1" IsImplemented="false" SampRateHardware="448" />
<ChannelInfo ChannelId="2" IsImplemented="true" SampRateHardware="224" />
</Channels>
</DeviceInfo>
<DeviceInfo Model="51474">
<Channels>
<ChannelInfo ChannelId="1" IsImplemented="true" SampRateHardware="448" />
<ChannelInfo ChannelId="2" IsImplemented="true" SampRateHardware="224" />
</Channels>
</DeviceInfo>
</DeviceTypes>
<Installation>
<InstalledDevice Serial="597657" Model="51473">
<Channels>
<InstalledChannel ChannelId="1" Name="foo" />
<InstalledChannel ChannelId="2" Name="bar" />
</Channels>
</InstalledDevice>
</Installation>
I want to only process the InstallChannel node if the corresponding ChannelInfo has an "IsImplemented" set to true. By "corresponding" I mean I am looking for the ChannelInfo with the same ChannelId and the same Model under the parent node. Note that channels with the same ChannelId may have different IsImplemented values depending on what device they are under.
I've been using and the key() function to successfully lookup, but this nested lookup has me stumped.
Thanks,
-Mat
Here is a short and simple (no conditionals, no variables no xsl:for-each) solution using keys:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kCI-ByIdImpl" match="ChannelInfo"
use="concat(#ChannelId,
'+', #IsImplemented,
'+', ../../#Model)"/>
<xsl:template match="/*">
<xsl:copy-of select=
"Installation/*/*
/InstalledChannel
[key('kCI-ByIdImpl',
concat(#ChannelId, '+true',
'+', ../../#Model)
)
]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML fragment (wrapped into a single top element to be made a well-formed XML document):
<t>
<DeviceTypes>
<DeviceInfo Model="51473">
<Channels>
<ChannelInfo ChannelId="1" IsImplemented="false" SampRateHardware="448" />
<ChannelInfo ChannelId="2" IsImplemented="true" SampRateHardware="224" />
</Channels>
</DeviceInfo>
<DeviceInfo Model="51474">
<Channels>
<ChannelInfo ChannelId="1" IsImplemented="true" SampRateHardware="448" />
<ChannelInfo ChannelId="2" IsImplemented="true" SampRateHardware="224" />
</Channels>
</DeviceInfo>
</DeviceTypes>
<Installation>
<InstalledDevice Serial="597657" Model="51473">
<Channels>
<InstalledChannel ChannelId="1" Name="foo" />
<InstalledChannel ChannelId="2" Name="bar" />
</Channels>
</InstalledDevice>
</Installation>
</t>
only the wanted InstalledChannel element is processed (in this case simply copied to the output):
<InstalledChannel ChannelId="2" Name="bar"/>
Explanation: Appropriate use of a composite key.
I believe that using the templates makes for better readability/extendability: The key is using the variable to be able to reference both the Model and the ChannelId for the ChannelInfo node in the xpath for the InstalledChannel, so start with the InstalledDevice, and work your way down the heirarchy
<xsl:apply-templates select="//InstalledDevice"/>
<xsl:template match="//InstalledDevice">
<xsl:variable name="model">
<xsl:value-of select="#Model"/>
</xsl:variable>
<xsl:for-each select="Channels/InstalledChannel">
<xsl:variable name="channelId">
<xsl:value-of select="#ChannelId"/>
</xsl:variable>
<xsl:if test="//DeviceInfo[#Model=$model]/Channels/ChannelInfo[#ChannelId=$channelId and #IsImplemented='true']">
Processing Goes Here
</xsl:if>
</xsl:for-each>
</xsl:template>
So that we can preserve context of our model variable, I moved the InstalledChannel processing into the same template, and added the for-each. That way each InstalledChannel instance can be examined individually for whether it needs to be processed, and handled accordingly.
Something like this should work.
/Installation/InstalledDevice/Channels/InstalledChannel/[count(/DeviceTypes/DeviceInfo/Channels/ChannelInfo[#ChannelId = #ChannelId and #IsImplemented = 'true') = 1]