Thursday, June 30, 2011

Select-Xml and nested XPath evaluations

Recall that

>$s = Select-Xml -Xml $x -XPath '//Rule'
>$s | foreach-object { $_.Node ; Select-Xml $_.Node -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
view raw gistfile1.ps1 hosted with ❤ by GitHub

worked but

>$s = Select-Xml -Xml $x -XPath '//Rule'
>$s | foreach-object { $_.Node ; Select-Xml $_ -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
view raw gistfile1.ps1 hosted with ❤ by GitHub

failed with error messages showing that the current iterated value has an XML string representation.

An alternative fix is to explicitly cast the subject of the inner XPath selection to XML, thus

>$s = Select-Xml -Xml $x -XPath '//Rule'
>>$s | foreach-object { $_.Node ; Select-Xml ([xml] $_) -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
view raw gistfile1.ps1 hosted with ❤ by GitHub

though surprisingly

>$s = Select-Xml -Xml $x -XPath '//Rule'
>>$s | foreach-object { [xml] $_ ; Select-Xml ([xml] $_) -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
view raw gistfile1.ps1 hosted with ❤ by GitHub

fails to yield any results from the inner search; and I've not so far determined why. Replacing the final $_.Node by [xml] $_ of course fails because False is not an XML document.

Target Achieved

Arriving home this evening, the bike odometer read 1906.8 miles since I installed it at the end of the first week in July last year. Adding the 97 miles I logged on my spring holiday on a hired bike, that pushes me over the 2000 miles mark. for the year (with 8 days to spare).

So, onwards to 3000 miles by year's end.

Wednesday, June 22, 2011

More PowerShell, XML and XPath -- Select-Xml and multi-node selections

Let's start with the same document as before.

<StyleCopSettings Version="4.3">
<Analyzers>
<Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
<Rules>
<Rule Name="FileMustHaveHeader">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="FileHeaderMustShowCopyright">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
</Rules>
<AnalyzerSettings />
</Analyzer>
</Analyzers>
</StyleCopSettings>
view raw gistfile1.xml hosted with ❤ by GitHub

Now at PowerShell 2, we can also do

>$n = Select-Xml -Xml $x -XPath "//Rule[@Name='FileHeaderMustShowCopyright']//BooleanProperty[@Name='Enabled']"
>$n
Node Path Pattern
---- ---- -------
BooleanProperty InputStream //Rule[@Name='FileHeaderMustShowCopy...
>$n.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False SelectXmlInfo System.Object
>$n.Node
Name #text
---- -----
Enabled False
>$n.Node.ParentNode.ParentNode
Name RuleSettings
---- ------------
FileHeaderMustShowCopyright RuleSettings
view raw gistfile1.ps1 hosted with ❤ by GitHub

which avoids the explicit call to the .net infrastructure, but wraps up the actual content we got in the previous example as the Node field inside an object. This is not so much of a problem when picking a single node, but when you want to do the equivalent of SelectNodes nested (e.g. for each Rule, do something with each setting) some disassembly is required in order to perform the inner selection:

>$s = Select-Xml -Xml $x -XPath '//Rule'
>$s | foreach-object { $_.Node ; Select-Xml $_.Node -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
Name RuleSettings
---- ------------
FileMustHaveHeader RuleSettings
Enabled
FileHeaderMustShowCopyright RuleSettings
Enabled
compare
>$nn = $x.SelectNodes("//Rule")
>$nn | ForEach-Object { $_ ; $_.SelectNodes("descendant::BooleanProperty") }
Name RuleSettings
---- ------------
FileMustHaveHeader RuleSettings
Enabled
FileHeaderMustShowCopyright RuleSettings
Enabled
view raw gistfile1.ps1 hosted with ❤ by GitHub

It is, however, a bit of an oversight that the Select-Xml cmdlet doesn't consume SelectXmlInfo objects through the usual type conversion mechanisms inside the cmdlet infrastructure:

>$s | foreach-object { Select-Xml -xml $_ -XPath 'descendant::BooleanProperty' | foreach-object { $_.Node } }
Select-Xml : Cannot bind parameter 'Xml'. Cannot convert the "<RuleSettings><BooleanProperty Name="Enabled">False</Bool
eanProperty></RuleSettings>" value of type "Microsoft.PowerShell.Commands.SelectXmlInfo" to type "System.Xml.XmlNode".
At line:1 char:38
+ $s | foreach-object { Select-Xml -xml <<<< $_ -XPath '//BooleanProperty' | foreach-object { $_.Node } }
+ CategoryInfo : InvalidArgument: (:) [Select-Xml], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SelectXmlCommand
Select-Xml : Cannot bind parameter 'Xml'. Cannot convert the "<RuleSettings><BooleanProperty Name="Enabled">False</Bool
eanProperty></RuleSettings>" value of type "Microsoft.PowerShell.Commands.SelectXmlInfo" to type "System.Xml.XmlNode".
At line:1 char:38
+ $s | foreach-object { Select-Xml -xml <<<< $_ -XPath '//BooleanProperty' | foreach-object { $_.Node } }
+ CategoryInfo : InvalidArgument: (:) [Select-Xml], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.SelectXmlCommand
view raw gistfile1.ps1 hosted with ❤ by GitHub

but once you know there's that gotcha, it can be worked around.

Sunday, June 19, 2011

PowerShell, XML and XPath

Some of the time, PowerShell makes picking apart an XML file nice and easy by providing properties on the XML object matching nodes; but inevitably you get to a point where there are more than one node of a given type as the child of the one you are at. So then it's time to break out the XPath.

Take a sample XML file (this one being a StyleCop settings file, being to hand, and complicated enough to be interesting):

<StyleCopSettings Version="4.3">
<Analyzers>
<Analyzer AnalyzerId="Microsoft.StyleCop.CSharp.DocumentationRules">
<Rules>
<Rule Name="FileMustHaveHeader">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="FileHeaderMustShowCopyright">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
</Rules>
<AnalyzerSettings />
</Analyzer>
</Analyzers>
</StyleCopSettings>
view raw gistfile1.xml hosted with ❤ by GitHub

And we want to extract the "FileHeaderMustShowCopyright" enabled property. So we can do something like this:

>[xml] $x = Get-Content "Settings.StyleCop"
>$x.stylecopsettings.Analyzers.Analyzer.Rules.SelectSingleNode("Rule[@Name='FileHeaderMustShowCopyright']").RuleSettings.SelectSingleNode("BooleanProperty[@Name='Enabled']")
Name #text
---- -----
Enabled False
>
view raw gistfile1.ps1 hosted with ❤ by GitHub

using PowerShell to navigate where there is no ambiguity, and XPath at each for in the road; or

$x.SelectSingleNode("//Rule[@Name='FileHeaderMustShowCopyright']").RuleSettings.SelectSingleNode("BooleanProperty[@Name='Enabled']")
view raw gistfile1.ps1 hosted with ❤ by GitHub

or

$x.SelectSingleNode("//Rule[@Name='FileHeaderMustShowCopyright']").SelectSingleNode("//BooleanProperty[@Name='Enabled']")
view raw gistfile1.ps1 hosted with ❤ by GitHub

or simply

$x.SelectSingleNode("//Rule[@Name='FileHeaderMustShowCopyright']//BooleanProperty[@Name='Enabled']")
view raw gistfile1.ps1 hosted with ❤ by GitHub

depending how many of the steps above where we want to end up are of interest.

Friday, June 17, 2011

Splitting Pascal-Cased names in PowerShell

Something I needed to whip up recently, now slightly more polished. Starting with Jon Galloway's Splitting Camel Case with RegEx, and transposing to PowerShell's built-in regex syntax, the core operation becomes like

("ZomgWtfBbq" -creplace '[A-Z]', ' $&').Trim().Split($null)
view raw gistfile1.ps1 hosted with ❤ by GitHub

where we have to use the case sensitive -creplace operator to be able to insert spaces before capital letters. This outputs

Zomg
Wtf
Bbq

Wrapping it up into a function that handles a general batch of strings or things looks like this

function PascalSplit {
$args | ForEach-Object {
if ($_ -is [array]) {
$_ | ForEach-Object { PascalSplit $_ }
} else {
($_.ToString() -creplace '[A-Z]', ' $&').Trim().Split($null)
}
}
}
view raw gistfile1.ps1 hosted with ❤ by GitHub

so we can do this:

>PascalSplit @("ZomgWtfBbq", "ShazBot")
Zomg
Wtf
Bbq
Shaz
Bot
>

flattening the string array as we go (there is probably a better way to do that, but this works).

Following the link to James "poprhythm" Kolpack's enhancement, we can upgrade the simple function with his look-behind extensions to handle TLAs in names:

function PascalSplit {
$args | ForEach-Object {
if ($_ -is [array]) {
$_ | ForEach-Object { PascalSplit $_ }
} else {
($_.ToString() -creplace '(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z])', ' $&').Split($null)
}
}
}
view raw gistfile1.ps1 hosted with ❤ by GitHub

which we can test with

>PascalSplit LearnWCFInSixEasyMonths
Learn
WCF
In
Six
Easy
Months
>$a = PascalSplit @("ZomgWtfBbq", "ShazBot", "LearnWCFInSixEasyMonths")
>$a
Zomg
Wtf
Bbq
Shaz
Bot
Learn
WCF
In
Six
Easy
Months
>$a.Length
11
>

Saturday, June 04, 2011

And he still thinks "Referism" would work

One of those rare political rants...

Referism -- the quaint notion that a yearly budget referendum would do anything to check the ruling class.

The EU Referendum blog has started pushing this idea recently -- and in a massive act of cognitive dissonance does so while reporting how, when faced with a tight budget, the pols will protect their core function of providing sinecures to the nomenklatura, and trim the fluff at the margins (like providing services for the hoi polloi). And if that had been voted on, then next year, after parading the bleeding stumps, they'd come back for more ("or we'll kill this kitten/shut this library/sack these nurses..."). And that's assuming they don't take the get-out clause of Parliament being unable to bind itself and pass whatever emergency legislation to bypass.

The key problems are that politicians lie, money is fungible, and any stated budget breakdowns would be worth exactly as much as manifesto commitments are under universal suffrage. Oh, and it cuts out all the persiflage in the way of the root problem with democracy:

"A democracy cannot survive as a permanent form of government. It can last only until its citizens discover that they can vote themselves largesse from the public treasury."

Even a radical replacement like having MPs and Senators chosen by lot from the populace -- a way of taking power from those who most obviously actively seek it -- would still leave the problem of the entrenched Civil Service and the quangocrats.