xmlpowershellxpathxml-attributeselectnodes

PowerShell - xpath for Case Insensitive XML attribute name


I have a XML where the attribute names can be combinations of lower-case and upper-case letters. In the below example, 'datasource' attribute can have any number of lowercase and uppercase letters.

I need to fetch those nodes where the 'datasource' is XML. I have searched all over the internet, but could not find any solution for this. Several examples are there for translate(), lower-case() but they don't fit my scenario.

[xml] $GM_ProcessXML =@'
<Process>
    <Parameter Name="Parameter1" Datasource="XML"><![CDATA[Sujeet]]></Parameter>
    <Parameter Name="Parameter2" DataSource="XML"><![CDATA[Padhi]]></Parameter>     
    <Parameter Name="Parameter3" DatASource="XML"><![CDATA[Padhi]]></Parameter>     
    <Parameter Name="Parameter4" datASource="XML"><![CDATA[Padhi]]></Parameter>     
    <Node>
        <Node1 Name="Node1" Datasource="XML"><![CDATA[Sujeet]]></Node1>
        <Node2 Name="Node2" DataSource="XML"><![CDATA[Padhi]]></Node2>      
        <Node3 Name="Node3" DatASource="XML"><![CDATA[Padhi]]></Node3>      
        <Node4 Name="Node4" datASource="XML"><![CDATA[Padhi]]></Node4>    
    </Node>
</Process>
'@

$XPath = "//*[@datasource='XML']"

$Nodes = $GM_ProcessXML.SelectNodes($XPath)

$Nodes

Solution

  • So, on the face of it, you are out of luck. But there is a work-around by combining PowerShell code and XPath translate().

    [xml] $GM_ProcessXML =@'
    <Process>
        <Parameter Name="Parameter1" Datasource="XML"><![CDATA[Sujeet]]></Parameter>
        <Parameter Name="Parameter2" DataSource="XML"><![CDATA[Padhi]]></Parameter>     
        <Parameter Name="Parameter3" DatASource="XML"><![CDATA[Padhi]]></Parameter>     
        <Parameter Name="Parameter4" datASource="XML"><![CDATA[Padhi]]></Parameter>     
        <Node>
            <Node1 Name="Node1" Datasource="XML"><![CDATA[Sujeet]]></Node1>
            <Node2 Name="Node2" DataSource="XML"><![CDATA[Padhi]]></Node2>      
            <Node3 Name="Node3" DatASource="XML"><![CDATA[Padhi]]></Node3>      
            <Node4 Name="Node4" datASource="XML"><![CDATA[Padhi]]></Node4>    
        </Node>
    </Process>
    '@
    
    $attributeName = 'DataSource'
    $lc = $attributeName.ToLowerInvariant()    # -> 'datasource'
    $uc = $attributeName.ToUpperInvariant()    # -> 'DATASOURCE'
    
    $XPath = "//*[@*[translate(name(), '$uc', '$lc') = '$lc'] = 'XML']"
    # ->      //*[@*[translate(name(), 'DATASOURCE', 'datasource') = 'datasource'] = 'XML']
    
    $Nodes = $GM_ProcessXML.SelectNodes($XPath)
    

    Of course you can write the translate function by hand, especially in cases where the string to translate is basically hard-coded.

    But this approach is generic, so you can also use it to search case-insensitively for any user-provided/dynamic value, e.g.:

    $value = 'sujeet'   # as the user has entered it
    
    $lc = $value.ToLowerInvariant()
    $uc = $value.ToUpperInvariant()
    
    $XPath = "//Parameter[translate(., '$uc', '$lc') = '$lc']"
    # ->      //Parameter[translate(., 'SUJEET', 'sujeet') = 'sujeet']
    

    This is a lot better than the often-proposed

    translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')
    

    since that is limited to pre-defined alphabets, and fails for e.g. accented characters unless you explicitly include them, which quickly becomes unwieldy.