Private/MPath.ps1

function Resolve-MonocleMPathExpression
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        $Expression,

        [Parameter(Mandatory=$true, ParameterSetName='Document')]
        $Document,

        [Parameter(Mandatory=$true, ParameterSetName='Elements')]
        $Elements
    )

    # Regex to match an individual mpath expression
    $regex = '^(?<tag>[a-zA-Z]+)(?<filter>\[(?<attr>\@[a-zA-Z\-]+|\d+)((?<opr>(\!){0,1}(\=|\~))(?<value>.+?)){0,1}\](\[(?<index>\d+)\]){0,1}){0,1}$'
    $foundElements = $null

    # ensure the expression is valid against the regex
    if ($Expression -match $regex)
    {
        $tag = $Matches['tag']

        # find initial elements based on the tag from document or previously found elements
        if ($PSCmdlet.ParameterSetName -ieq 'Document') {
            $foundElements = $Document.IHTMLDocument3_getElementsByTagName($tag)
        }
        else {
            $foundElements = ($Elements | ForEach-Object { $_.IHTMLDocument3_getElementsByTagName($tag) })
        }

        # if there's a filter, then filter down the found elements above
        if (![string]::IsNullOrWhiteSpace($Matches['filter']))
        {
            $attr = $Matches['attr']
            $opr = $Matches['opr']
            $value = $Matches['value']
            $index = $Matches['index']

            # filtering by attributes starts with an '@', else we have an index into the elements
            if ($attr.StartsWith('@'))
            {
                $attr = $attr.Trim('@')

                # if there's no operator, then use all elements that have a non-empty attribute
                if ([string]::IsNullOrWhiteSpace($opr)) {
                    $foundElements = $foundElements | Where-Object { ![string]::IsNullOrWhiteSpace($_.getAttribute($attr)) }
                }
                else
                {
                    # find elements based on validaity of attribute to passed value
                    switch ($opr)
                    {
                        '=' {
                            $foundElements = $foundElements | Where-Object { $_.getAttribute($attr) -ieq $value }
                        }

                        '~' {
                            $foundElements = $foundElements | Where-Object { $_.getAttribute($attr) -imatch $value }
                        }

                        '!=' {
                            $foundElements = $foundElements | Where-Object { $_.getAttribute($attr) -ine $value }
                        }

                        '!~' {
                            $foundElements = $foundElements | Where-Object { $_.getAttribute($attr) -inotmatch $value }
                        }
                    }
                }

                # select a element from the filtered elements based on index (could sometimes happen)
                if (![string]::IsNullOrWhiteSpace($index)) {
                    $foundElements = $foundElements | Select-Object -Skip ([int]$index) -First 1
                }
            }
            else {
                # select the element based on index of found elements
                $foundElements = $foundElements | Select-Object -Skip ([int]$attr) -First 1
            }
        }
    }
    else {
        throw "MPath expression is not valid: $Expression"
    }

    if (($foundElements | Measure-Object).Count -eq 0) {
        throw "Failed to find elements for: $Expression"
    }

    return $foundElements
}

function Resolve-MonocleMPath
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $MPath
    )

    # split into multiple expressions
    $exprs = $MPath -split '/'

    # if there are no expression, return null
    if (($null -eq $exprs) -or ($exprs.length -eq 0)) {
        return [System.DBNull]::Value
    }

    # find initial elements based on the document and first expression
    $elements = Resolve-MonocleMPathExpression -Expression $exprs[0] -Document $Browser.Document

    # find rest of elements from the previous elements found above
    for ($i = 1; $i -lt $exprs.length; $i++) {
        $elements = Resolve-MonocleMPathExpression -Expression $exprs[$i] -Elements $elements
    }

    # Monocle only deals with single elements, so return the first
    return ($elements | Select-Object -First 1)
}