Complex XPath conditional

I am trying to SelectSingleNode based on three conditions. ID and Type apply only to the outer node. But Context could apply to either the outer node or an inner node. So these should be selected...

<Node id="SomeID" type="SomeType" context="UseThis"></Node>

<Node id="SomeID" type="SomeType">
<Section context="UseThis"></Section>
</Node>

<Node id="SomeID" type="SomeType">
<Section context="UseThis"></Section>
<Section context="DontUseThis"></Section>
</Node>

But neither of these would...

<Node id="SomeID" type="SomeType" context="DontUseThis"></Node>

<Node id="SomeID" type="SomeType">
<Section context="DontUseThis"></Section>
</Node>

I am doing this now with two IFs. The first selects for ID & Type, and the second has an OR and two SelectSingleNodes to look for Context in either possible place. I just wonder, is there a more graceful XPath construct that handles this in a single simple IF?

Gordon

February 10th, 2014 6:43am

$xml.SelectNodes('//Node[@context = "UseThis" or Section/@context = "UseThis"]')



Free Windows Admin Tool Kit Click here and download it now
February 10th, 2014 8:02am

$xml.SelectNodes('//Node[@context = "UseThis" or Section/@context = "UseThis"]')



February 10th, 2014 8:05am

$xml.SelectNodes('//Node[@context = "UseThis" or Section/@context = "UseThis"]')



Free Windows Admin Tool Kit Click here and download it now
February 10th, 2014 8:05am

Well, that would be more elegant than what I have now. ;)

            If (($script:Packagexml.SelectSingleNode("/Definitions/Packages/Package[@id='$PackageName' and @type='$PackageType' and @context='$global:Context']")) -or `
                ($script:Packagexml.SelectSingleNode("/Definitions/Packages/Package[@id='$PackageName' and @type='$PackageType']/Section[@context='$global:Context']")))

February 10th, 2014 8:45am

I think this would do the trick (assuming that your child node is still named Section):

#
If ($script:Packagexml.SelectSingleNode("/Definitions/Packages/Package[@id='$PackageName' and @type='$PackageType' and (@context='$global:Context' or Section/@context='$global:Context')]")
#

Free Windows Admin Tool Kit Click here and download it now
February 10th, 2014 10:08am

I think this would do the trick (assuming that your child node is still named Section):

#
If ($script:Packagexml.SelectSingleNode("/Definitions/Packages/Package[@id='$PackageName' and @type='$PackageType' and (@context='$global:Context' or Section/@context='$global:Context')]")
#

February 10th, 2014 10:11am

Dave, I think I have to use a flavor of my other logic anyway, and indeed I fear I may have a flaw in my xml structure. Given this, which is an example of the second condition in my original XPath pattern, I need to select only the nodes directly below Section. All the Var & Value, Source & Destination stuff needs to be handled in the next function. But when my SelectNodes ends with /* I of course get all the layers below. My fear is that I need to use a single Node name for all these, like Task, so my selection can only grab Tasks. Or I need to add a Task attribute. Because thus far I can't grok how to make an XPath pattern stop at a particular level down the tree. A single pattern like you show that could grab either all the nodes one level below Package when the Package context is MachineConfig or all the nodes within the Section who's context is MachineConfig, would be ideal. Have I gone down a path where XPath is not going to get er done?

EDIT: Pretty sure the limitation is not in XML, but perhaps in SelectNodes. Or perhaps in my head, since it is midnight in Berlin and I had a few beers earlier and perhaps this is one that needs a fresh brain. And so, to bed. Probably to dream of Axiis, as that looks promising as a solution. ;)

<Package id="RVT_2014"  type="Rollout" Product="RVT2014">
			<Section context="MachineConfig">
				<Install id="Revit_2014" type="1">%Location_Root%\deploy\Autodesk\2014\Deployments\Revit2014\Img\Revit2014.ini</Install>
				<Rollout>RVT_2014_WU1</Rollout>
				<AddEnvironmentVariable>
					<Var>ADSKFLEX_LICENSE_FILE</Var>
					<Value>%Location_FlexLMServers%</Value>
				</AddEnvironmentVariable>
				<Copy id="RevitINI">
					<Source>%Location_Root%\revit\2014\Beck Support Files\DAL-INI\Revit.ini</Source>
					<Destination>%RVT2014_UserDataCacheINI%</Destination>
				</Copy>
				<DisableCommCenter>True</DisableCommCenter>
				<CreateFolder id="RevitLocalFolder">C:\Revit Locals</CreateFolder>
				<Delete id="Files">C:\msdia80.dll</Delete>
			</Section>
			<Section context="user_initialization">
				<Copy id="SeedUserAppData">
					<Source>%Location_Root%\revit\2014\Beck Support Files\DAL-UDC</Source>
					<Destination>%Product_UserAppDataRoaming%</Destination>
				</Copy>
				<AddTaskbarShortcut>%Product_DefaultShortcut%</AddTaskbarShortcut>
				<AddPlace>
					<Name>Faves</Name>
					<Value>C:\Users\%UserName%\Links</Value>
				</AddPlace>
				<AddPlaceSet>RVT_2014_Places</AddPlaceSet>
				<Copy id="RAC 2013 Keyboard Shortcuts">
					<Source>RAC2013_UserAppData_Roaming\KeyboardShortcuts.xml</Source>
					<Destination>%Product_UserAppDataRoaming%</Destination>
				</Copy>
				<PinShortcut>%ShortcutPath%\%DefaultShortcut%</PinShortcut>
			</Section>
		</Package>



Free Windows Admin Tool Kit Click here and download it now
February 10th, 2014 5:19pm

Dave, I think I have to use a flavor of my other logic anyway, and indeed I fear I may have a flaw in my xml structure. Given this, which is an example of the second condition in my original XPath pattern, I need to select only the nodes directly below Section. All the Var & Value, Source & Destination stuff needs to be handled in the next function. But when my SelectNodes ends with /* I of course get all the layers below. My fear is that I need to use a single Node name for all these, like Task, so my selection can only grab Tasks. Or I need to add a Task attribute. Because thus far I can't grok how to make an XPath pattern stop at a particular level down the tree. A single pattern like you show that could grab either all the nodes one level below Package when the Package context is MachineConfig or all the nodes within the Section who's context is MachineConfig, would be ideal. Have I gone down a path where XPath is not going to get er done?

EDIT: Pretty sure the limitation is not in XML, but perhaps in SelectNodes. Or perhaps in my head, since it is midnight in Berlin and I had a few beers earlier and perhaps this is one that needs a fresh brain. And so, to bed. Probably to dream of Axiis, as that looks promising as a solution. ;)

<Package id="RVT_2014"  type="Rollout" Product="RVT2014">
			<Section context="MachineConfig">
				<Install id="Revit_2014" type="1">%Location_Root%\deploy\Autodesk\2014\Deployments\Revit2014\Img\Revit2014.ini</Install>
				<Rollout>RVT_2014_WU1</Rollout>
				<AddEnvironmentVariable>
					<Var>ADSKFLEX_LICENSE_FILE</Var>
					<Value>%Location_FlexLMServers%</Value>
				</AddEnvironmentVariable>
				<Copy id="RevitINI">
					<Source>%Location_Root%\revit\2014\Beck Support Files\DAL-INI\Revit.ini</Source>
					<Destination>%RVT2014_UserDataCacheINI%</Destination>
				</Copy>
				<DisableCommCenter>True</DisableCommCenter>
				<CreateFolder id="RevitLocalFolder">C:\Revit Locals</CreateFolder>
				<Delete id="Files">C:\msdia80.dll</Delete>
			</Section>
			<Section context="user_initialization">
				<Copy id="SeedUserAppData">
					<Source>%Location_Root%\revit\2014\Beck Support Files\DAL-UDC</Source>
					<Destination>%Product_UserAppDataRoaming%</Destination>
				</Copy>
				<AddTaskbarShortcut>%Product_DefaultShortcut%</AddTaskbarShortcut>
				<AddPlace>
					<Name>Faves</Name>
					<Value>C:\Users\%UserName%\Links</Value>
				</AddPlace>
				<AddPlaceSet>RVT_2014_Places</AddPlaceSet>
				<Copy id="RAC 2013 Keyboard Shortcuts">
					<Source>RAC2013_UserAppData_Roaming\KeyboardShortcuts.xml</Source>
					<Destination>%Product_UserAppDataRoaming%</Destination>
				</Copy>
				<PinShortcut>%ShortcutPath%\%DefaultShortcut%</PinShortcut>
			</Section>
		</Package>



February 10th, 2014 5:22pm

XPath can probably do whatever you need it to do here, but I'm not sure I fully understand what you're trying to accomplish. This query will only return the direct child nodes of Section nodes:

#

$xml.SelectNodes("//Package/Section/*")

#

What may be causing confusion is the way PowerShell tries to display some of the XML data as properties of an object.  So, for instance, when you look at the <AddEnvironmentVariable> node, you'll see string properties named "Var" and "Value".  That doesn't mean the SelectNodes call or the XPath expression returned child nodes of AddEnvironmentVariable; it's just PowerShell being helpful.

I'm not sure how you want to handle the situation where the Package and Section nodes both have "context" attributes that don't agree with one another.  That makes for a more complicated XPath query.



Free Windows Admin Tool Kit Click here and download it now
February 10th, 2014 8:52pm

XPath can probably do whatever you need it to do here, but I'm not sure I fully understand what you're trying to accomplish. This query will only return the direct child nodes of Section nodes:

#

$xml.SelectNodes("//Package/Section/*")

#

What may be causing confusion is the way PowerShell tries to display some of the XML data as properties of an object.  So, for instance, when you look at the <AddEnvironmentVariable> node, you'll see string properties named "Var" and "Value".  That doesn't mean the SelectNodes call or the XPath expression returned child nodes of AddEnvironmentVariable; it's just PowerShell being helpful.

I'm not sure how you want to handle the situation where the Package and Section nodes both have "context" attributes that don't agree with one another.  That makes for a more complicated XPath query.



February 10th, 2014 8:54pm

David,
A little sleep went a long way. Since you verified that /* should only be returning child nodes, I backed up a bit and started debugging. And it seems the issue is one of scope again. In simple terms I have Packages that contain Tasks, of which some are actually other Packages. I have a recursive function called Process-Package which takes a Package Name & Type as parameters, loops through all the Tasks in the package and checks each to see if it references another package. If it doesn't reference another package it runs Process-Task, but if it does, it recursively runs Process-Package. And therein lies the problem, rather than running the passed package, it just repeats the same package over and over. I have verified that I am passing the right values, so I suspect I have something to learn with regards to recursive scope or the like.
In any case, the way it started looping had me thinking it was reading all the sub nodes, but that was not the case at all. Probably I should change my schedule and only work on this project when fresh in the mornings, rather than saving it for evening "personal project" time. ;)

As for the potential issue of both a section and the overall node having context, that will be an issue I have to deal with separately I suspect. I don't think I can flag that as a fault using an XML Schema, so I will likely write a little utility to do some basic validation on the XML file. 

EDIT: Oh, rookie move. Above the Package level I have Sets, and in that code I use an XPath expression to see if I need to run Process-Package at all. And I did a copy paste of that line into Process-Package, and forgot to change the variable names. So Scope bit my but and I just kept repeating the same search over and over. DOH. Fixed now, moving on. ;)

Gordon




Free Windows Admin Tool Kit Click here and download it now
February 11th, 2014 5:57am

David,
A little sleep went a long way. Since you verified that /* should only be returning child nodes, I backed up a bit and started debugging. And it seems the issue is one of scope again. In simple terms I have Packages that contain Tasks, of which some are actually other Packages. I have a recursive function called Process-Package which takes a Package Name & Type as parameters, loops through all the Tasks in the package and checks each to see if it references another package. If it doesn't reference another package it runs Process-Task, but if it does, it recursively runs Process-Package. And therein lies the problem, rather than running the passed package, it just repeats the same package over and over. I have verified that I am passing the right values, so I suspect I have something to learn with regards to recursive scope or the like.
In any case, the way it started looping had me thinking it was reading all the sub nodes, but that was not the case at all. Probably I should change my schedule and only work on this project when fresh in the mornings, rather than saving it for evening "personal project" time. ;)

As for the potential issue of both a section and the overall node having context, that will be an issue I have to deal with separately I suspect. I don't think I can flag that as a fault using an XML Schema, so I will likely write a little utility to do some basic validation on the XML file. 

EDIT: Oh, rookie move. Above the Package level I have Sets, and in that code I use an XPath expression to see if I need to run Process-Package at all. And I did a copy paste of that line into Process-Package, and forgot to change the variable names. So Scope bit my but and I just kept repeating the same search over and over. DOH. Fixed now, moving on. ;)

Gordon




February 11th, 2014 6:00am

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics