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' } |