Last active
June 22, 2022 06:37
-
-
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 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