private/ConvertFrom-SfCountriesAndStatesXml.ps1

function ConvertFrom-SfCountriesAndStatesXml {

    <#
        .SYNOPSIS
        Converts AddressSettings country and state nodes into _StateAndCountryPicklists rows.

        .DESCRIPTION
        Iterates over all country nodes of the AddressSettings metadata (countriesAndStates.countries)
        and their nested state nodes. Returns one row in the _StateAndCountryPicklists data dictionary
        schema per country and per state.

        Country rows carry '.' as _StateCode. State rows carry the isoCode of their parent country as
        _CountryCode and their own isoCode as _StateCode.

        The result is sorted by _CountryCode, _StateCode.

        .OUTPUTS
        [PSCustomObject]
        One object per country and per state with the properties _CountryCode, _StateCode, _IsStandard,
        _IsDefaultCountry, _Status, _IntegrationValue and _Label.

        .EXAMPLE
        [xml] $Xml = Get-Content -Path $AddressSettingsPath -Raw
        ConvertFrom-SfCountriesAndStatesXml -Countries $Xml.AddressSettings.countriesAndStates.countries
    #>


    [CmdletBinding()]
    param (
        # Country nodes (XmlElement) as found in AddressSettings.countriesAndStates.countries.
        # Nested state nodes are read from each country node.
        [Parameter(Mandatory, Position = 0)]
        [object[]] $Countries
    )

    $ErrorActionPreference = 'Stop'
    [bool] $IsDebug   = $DebugPreference   -ne 'SilentlyContinue'
    [bool] $IsVerbose = $VerbosePreference -ne 'SilentlyContinue'

    # Nested helpers - reduced overhead applies (coding conventions, section 11).
    # Child elements are read via PSObject.Properties, because direct member access
    # on a missing element throws under StrictMode.

    function GetChildText {
        param ($Node, [string] $Name)

        $Property = $Node.PSObject.Properties[$Name]
        if ($null -eq $Property) {
            return ''
        }
        return [string] $Property.Value
    }

    function GetRequiredText {
        param ($Node, [string] $Name, [string] $Where)

        [string] $Text = GetChildText $Node $Name
        if ([string]::IsNullOrEmpty($Text)) {
            throw "Required element '$Name' is missing or empty in $Where."
        }
        return $Text
    }

    # A missing or empty element counts as $false.
    function GetFlag {
        param ($Node, [string] $Name)

        [string] $Text = GetChildText $Node $Name
        if ($Text -eq '') {
            return $false
        }
        try {
            return [System.Xml.XmlConvert]::ToBoolean($Text)
        }
        catch {
            throw "Element '$Name' contains invalid boolean value '$Text': $_"
        }
    }

    function ConvertToRow {
        param (
            $Node,
            [string] $CountryCode,
            [string] $StateCode,
            [switch] $IsState
        )

        [string] $Where = if ($IsState) { "state '$CountryCode/$StateCode'" } else { "country '$CountryCode'" }

        [string] $IntegrationValue = GetRequiredText $Node 'integrationValue' $Where
        [string] $Label            = GetRequiredText $Node 'label' $Where
        [bool]   $Active           = GetFlag $Node 'active'
        [bool]   $Visible          = GetFlag $Node 'visible'
        [bool]   $Standard         = GetFlag $Node 'standard'
        # orgDefault exists on countries only
        [bool]   $OrgDefault       = (-not $IsState) -and (GetFlag $Node 'orgDefault')

        [string] $Status = '--'
        if ($Active -and $Visible) {
            $Status = 'A+V'
        }
        elseif ($Active) {
            $Status = 'A'
        }
        elseif ($Visible) {
            throw "Entry $Where is visible but not active - this combination is not valid."
        }

        return [PSCustomObject]@{
            _CountryCode      = $CountryCode
            _StateCode        = $StateCode
            _IsStandard       = if ($Standard) { 'X' } else { '--' }
            _IsDefaultCountry = if ($OrgDefault) { 'X' } else { '--' }
            _Status           = $Status
            _IntegrationValue = $IntegrationValue
            _Label            = $Label
        }
    }

    $(
        foreach ($Country in $Countries) {
            [string] $CountryCode = GetRequiredText $Country 'isoCode' "<$($Country.LocalName)> node"
            Write-Debug "Processing country '$CountryCode'"

            # Country row: '.' is the sentinel for the mandatory _StateCode column
            Write-Output (ConvertToRow $Country $CountryCode '.')

            # A country without states has no 'states' property at all - reading it directly would throw
            [PSCustomObject[]] $States = @()
            if ($null -ne $Country.PSObject.Properties['states']) {
                $States = $Country.states
            }

            foreach ($State in $States) {
                [string] $StateCode = GetRequiredText $State 'isoCode' "state node of country '$CountryCode'"
                Write-Output (ConvertToRow $State $CountryCode $StateCode -IsState)
            }
        }
    ) | Sort-Object -Property '_CountryCode', '_StateCode'
}