Last active
February 15, 2025 06:38
-
-
Save Warrenn/2fefcee7277580567b48 to your computer and use it in GitHub Desktop.
Powershell script to create a XML-Document-Transform. This auto generates a transform file by comparing a production file to the development file and figuring out what the transform needs to look like in order to transform the development file to the production equivalent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Create-Xdt( | |
| [string] $prodConfig, | |
| [string] $devConfig, | |
| [string] $xdtFile, | |
| [string[]] $keepNodes = @(), | |
| [hashtable] $findReplace = @{}, | |
| [string[]] $keyAttributes = @("name", "key", "path", "contract") | |
| ) | |
| { | |
| if([System.String]::IsNullOrEmpty($prodConfig) -or | |
| [System.String]::IsNullOrEmpty($devConfig) -or | |
| -not(Test-Path($prodConfig)) -or | |
| -not(Test-Path($devConfig))){ | |
| return | |
| } | |
| function recurse($node, $path, $parent, $xmlDoc){ | |
| if($node.NodeType -eq [System.Xml.XmlNodeType]::Element){ | |
| $selectedAttr = $null | |
| $node.Attributes | | |
| %{ | |
| if((rating -item $_.Name -itemColl $keyAttributes) -gt | |
| (rating -item $selectedAttr.Name -itemColl $keyAttributes)) | |
| { | |
| $selectedAttr = @{ | |
| Name = $_.Name | |
| Value = $_.Value | |
| } | |
| } | |
| } | |
| if(($xmlDoc.SelectNodes($path).Count -eq 1) -and | |
| ($keyAttributes -notcontains $selectedAttr.Name)){ | |
| $selectedAttr = $null | |
| } | |
| if($selectedAttr -ne $null){ | |
| $path = "$($path)[@$($selectedAttr.Name)='$($selectedAttr.Value)']" | |
| } | |
| $path | |
| $node.Attributes | | |
| %{ | |
| if($_.Name -notlike $selectedAttr.Name){ | |
| "$($path)/@$($_.Name)" | |
| } | |
| } | |
| $selectedAttr = $null | |
| } | |
| $node.ChildNodes | | |
| %{recurse -node $_ -path "$($path)/$($_.LocalName)" -parent $path -xmlDoc $xmlDoc } | |
| } | |
| function ignorePath([string]$xpath, [string[]]$ignorePaths){ | |
| if([string]::IsNullOrEmpty($xpath)){ | |
| return $true | |
| } | |
| foreach($ignore in $ignorePaths){ | |
| if([string]::IsNullOrEmpty($ignore)){ | |
| continue | |
| } | |
| if($xpath.StartsWith($ignore)){ | |
| return $true | |
| } | |
| } | |
| return $false | |
| } | |
| function rating([string]$item, [string[]] $itemColl){ | |
| if([string]::IsNullOrEmpty($item) -or ($itemColl.Count -eq 0)){ | |
| return -1; | |
| } | |
| for($i = 0; $i -lt $itemColl.Count; $i++){ | |
| if($itemColl[$i] -eq $item){ | |
| return $i + 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| function addAttributes([System.Xml.XmlAttributeCollection] $attrs, [string] $xpath, [hashtable] $dict){ | |
| foreach($attr in $attrs){ | |
| $attrPath = "$($xpath)/@$($attr.Name)" | |
| if(-not $dict.ContainsKey($attrPath)){ | |
| $dict.Add($attrPath, $attr.Value) | |
| } | |
| } | |
| } | |
| [xml]$prodXml = (gc $prodConfig) -replace "xmlns\s*=\s*""[^""]*""","" | |
| [xml]$devXml = (gc $devConfig) -replace "xmlns\s*=\s*""[^""]*""","" | |
| $prodNodes = recurse -node $prodXml -path "" -parent "" -xmlDoc $prodXml | |
| $devNodes = recurse -node $devXml -path "" -parent "" -xmlDoc $devXml | |
| $missingElms = @{} | |
| $missingAttrs = @{} | |
| $wrongElmValues = @{} | |
| $wrongAttrValues = @{} | |
| $ignoreList = New-Object System.Collections.ArrayList -ArgumentList @(,$keepNodes) | |
| foreach($xpath in $prodNodes) | |
| { | |
| if(ignorePath -xpath $xpath -ignorePaths $ignoreList){ | |
| continue | |
| } | |
| $devNode = $devXml.SelectSingleNode($xpath) | |
| $prodNode = $prodXml.SelectSingleNode($xpath) | |
| if($devNode -eq $null){ | |
| [void]$ignoreList.Add($xpath) | |
| if(($prodNode.NodeType -eq [System.Xml.XmlNodeType]::Attribute)){ | |
| $missingAttrs.Add($xpath, $prodNode.Value) | |
| } | |
| else{ | |
| $missingElms.Add($xpath, $prodNode.OuterXml) | |
| } | |
| continue | |
| } | |
| if(($devNode.NodeType -eq [System.Xml.XmlNodeType]::Attribute) -and | |
| ($devNode.Value -notlike $prodNode.Value)){ | |
| $wrongAttrValues.Set_Item($xpath, $prodNode.Value) | |
| continue | |
| } | |
| if($devNode.HasChildNodes -ne $prodNode.HasChildNodes) | |
| { | |
| addAttributes -attrs $prodNode.Attributes -xpath $xpath -dict $wrongAttrValues | |
| addAttributes -attrs $devNode.Attributes -xpath $xpath -dict $wrongAttrValues | |
| [void]$ignoreList.Add($xpath) | |
| $wrongElmValues.Add($xpath, $prodNode.InnerXml) | |
| continue | |
| } | |
| if($devNode.'#text' -like $prodNode.'#text'){ | |
| continue | |
| } | |
| addAttributes -attrs $prodNode.Attributes -xpath $xpath -dict $wrongAttrValues | |
| addAttributes -attrs $devNode.Attributes -xpath $xpath -dict $wrongAttrValues | |
| $wrongElmValues.Add($xpath, $prodNode.InnerXml) | |
| } | |
| foreach($xpath in $devNodes) | |
| { | |
| if(($prodNodes -contains $xpath) -or | |
| (ignorePath -xpath $xpath -ignorePaths $ignoreList)){ | |
| continue | |
| } | |
| $node = $devXml.SelectSingleNode($xpath) | |
| $mustChange = $false | |
| $nodeValue = "" | |
| if($node.NodeType -eq [System.Xml.XmlNodeType]::Attribute){ | |
| $nodeValue = $node.Value | |
| } | |
| else{ | |
| $nodeValue = $node.InnerXml | |
| } | |
| $findReplace.GetEnumerator() | | |
| %{ | |
| if($nodeValue -match $_.Name ){ | |
| $mustChange = $true | |
| $newValue = $_.Value | |
| for($i = 0;$i -lt $Matches.Count; $i++){ | |
| $newValue = $newValue -replace "\{\\$($i)\}",$Matches[$i] | |
| } | |
| } | |
| } | |
| if(-not $mustChange){ | |
| continue | |
| } | |
| if($node.NodeType -eq [System.Xml.XmlNodeType]::Attribute){ | |
| $missingAttrs.Set_Item($xpath, $newValue) | |
| continue | |
| } | |
| addAttributes -attrs $node.Attributes -xpath $xpath -dict $wrongAttrValues | |
| $wrongElmValues.Set_Item($xpath, $newValue) | |
| } | |
| foreach($xpath in $keepNodes) | |
| { | |
| $node = $prodXml.SelectSingleNode($xpath) | |
| if($node.NodeType -eq [System.Xml.XmlNodeType]::Attribute){ | |
| $wrongAttrValues.Add($xpath, $node.Value) | |
| continue | |
| } | |
| addAttributes -attrs $node.Attributes -xpath $xpath -dict $wrongAttrValues | |
| $wrongElmValues.Add($xpath, $node.InnerXml) | |
| } | |
| $tempOut = $null | |
| if(-not[string]::IsNullOrEmpty($xdtFile)){ | |
| [xml]$tempOut = New-Object System.XML.XMLDocument | |
| [System.Xml.XmlNamespaceManager] $nsMgr = $tempOut.NameTable | |
| $nsMgr.AddNamespace("xdt","http://schemas.microsoft.com/XML-Document-Transform") | |
| $decl = $tempOut.CreateXmlDeclaration("1.0","utf-8",$null) | |
| [void]$tempOut.InsertBefore($decl, $tempOut.DocumentElement) | |
| } | |
| function reportProgress($items,$text,$transformType) | |
| { | |
| if($items.Count -gt 0 ){ | |
| Write-Output $text | |
| } | |
| $items.GetEnumerator() | | |
| %{ | |
| Write-Output $_.Name | |
| createXdtDoc -doc $tempOut -xpath $_.Name -value $_.Value -transformType $transformType | |
| } | |
| } | |
| reportProgress -items $missingElms -text "Missing Elements" -transformType "Insert" | |
| reportProgress -items $missingAttrs -text "Missing Attributes" -transformType "SetAttributes" | |
| reportProgress -items $wrongAttrValues -text "Wrong Attribute Values" -transformType "SetAttributes" | |
| reportProgress -items $wrongElmValues -text "Wrong Element Values" -transformType "Replace" | |
| if($tempOut -ne $null) | |
| { | |
| if(($tempOut.DocumentElement -ne $null) -and | |
| ($tempOut.DocumentElement.GetAttribute("xmlns:xdt") -ne "http://schemas.microsoft.com/XML-Document-Transform")) | |
| { | |
| $tempOut.DocumentElement.SetAttribute("xmlns:xdt", "http://schemas.microsoft.com/XML-Document-Transform") | |
| } | |
| $tempOut.Save($xdtFile) | |
| } | |
| } | |
| function addXdt([xml]$doc, [System.Xml.XmlElement]$node,[string] $xdtType, [string]$xdtValue){ | |
| $anode = $doc.CreateAttribute("xdt:$($xdtType)","http://schemas.microsoft.com/XML-Document-Transform") | |
| $anode.Value = $xdtValue | |
| [void]$node.Attributes.Append($anode) | |
| } | |
| function createXdtDoc([xml]$doc, [string]$xpath, [string]$value, [string]$transformType) | |
| { | |
| if($doc -eq $null) | |
| { | |
| return | |
| } | |
| $node=[xml]$doc | |
| foreach ($part in [regex]::Split($xpath, "(/)(?=(?:[^']|'[^']*')*$)", [System.Text.RegularExpressions.RegexOptions]::ExplicitCapture) ) | |
| { | |
| if([string]::IsNullOrEmpty($part)) | |
| { | |
| continue | |
| } | |
| $child=$node.SelectSingleNode($part) | |
| if ($child -ne $null) | |
| { | |
| $node = $child | |
| continue | |
| } | |
| if ($part.StartsWith("@")) | |
| { | |
| $anode = $doc.CreateAttribute($part.Substring(1)); | |
| $anode.Value = $value | |
| [void]$node.Attributes.Append($anode); | |
| $attrList = "" | |
| $identifier = "" | |
| $hasReplace = $false | |
| foreach($atr in $node.Attributes) | |
| { | |
| if($atr.Name -eq "xdt:Locator") | |
| { | |
| [void]($atr.Value -match "Match\((.*)\)") | |
| $identifier = $Matches[1] | |
| continue | |
| } | |
| if(($atr.Name -eq "xdt:Transform") -and | |
| (($atr.Value -eq "Replace") -or | |
| ($atr.Value -eq "Insert"))) | |
| { | |
| $hasReplace = $true | |
| } | |
| if($atr.Name.Contains(":")) | |
| { | |
| continue | |
| } | |
| if($atr.Name -eq $identifier) | |
| { | |
| continue | |
| } | |
| $attrList = "$($attrList),$($atr.Name)" | |
| } | |
| $attrList = $attrList.Trim(',') | |
| if(-not([string]::IsNullOrEmpty($attrList)) -and (-not $hasReplace)) | |
| { | |
| addXdt -doc $doc -node $node -xdtType "Transform" -xdtValue "SetAttributes($($attrList))" | |
| } | |
| return | |
| } | |
| $attrib = $null | |
| $elmName = $part | |
| if ($part.Contains("[")) | |
| { | |
| $attrDecl = $part.Split('[',2) | |
| $elmName = $attrDecl[0] | |
| $attrParts = $attrDecl[1].Split('=',2) | |
| $attrib = @{ | |
| Name = $attrParts[0].TrimStart('@') | |
| Value = $attrParts[1].Trim(" ']".ToCharArray()) | |
| } | |
| } | |
| $child = $doc.CreateElement($elmName) | |
| if ($attrib -ne $null) | |
| { | |
| addXdt -doc $doc -node $child -xdtType "Locator" -xdtValue "Match($($attrib.Name))" | |
| $anode=$doc.CreateAttribute($attrib.Name) | |
| $anode.Value=$attrib.Value | |
| [void]$child.Attributes.Append($anode) | |
| } | |
| [void]$node.AppendChild($child) | |
| $node = $child | |
| } | |
| if($transformType -eq "Insert") | |
| { | |
| $outerXml = [xml]$value | |
| foreach($atr in $outerXml.DocumentElement.Attributes) | |
| { | |
| $anode = $doc.CreateAttribute($atr.Name) | |
| $anode.Value = $atr.Value | |
| [void]$node.Attributes.Append($anode) | |
| } | |
| addXdt -doc $doc -node $node -xdtType "Transform" -xdtValue $transformType | |
| $node.InnerXml = $outerXml.DocumentElement.InnerXml | |
| } | |
| if($transformType -eq "Replace") | |
| { | |
| addXdt -doc $doc -node $node -xdtType "Transform" -xdtValue $transformType | |
| $node.InnerXml = $value | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment