PowerShell合并XML按VATRegistrationNumber去重并保留空值条目
10万级条目XML低内存合并方案(适配Windows每日定时运行)
需求规则
两个结构完全一致的XML文件,单文件条目量均超10万,需满足以下合并要求:
- 以File1为基准,输出文件完整保留File1全部条目
- File2中仅
VATRegistrationNumber字段值未在File1出现过的条目,才会被追加到输出文件 - 所有
VATRegistrationNumber为空的条目不受去重限制,无论是否重复全部保留 - 方案需低内存占用,支持通过Windows任务计划程序每日定时运行,可选用XSLT、PowerShell、批处理实现
原有实现问题
现有两段PowerShell代码可实现基础合并去重,但存在两个核心缺陷:
- 全量合并生成中间文件代码:
[xml]$File1 = Get-Content file1.xml [xml]$File2 = Get-Content file2.xml ForEach ($XmlNode in $File2.DocumentElement.ChildNodes){$File1.DocumentElement.AppendChild($File1.ImportNode($XmlNode, $true))} $File1.Save('file1and2combined.xml')
- 合并后去重代码:
[xml]$myfile = [xml](Get-content "file1and2combined.xml") $nodesToRemove = $myfile.suppliers.childnodes | Group-Object VATRegistrationNumber | Foreach-Object { $_.Group | Select-Object -Skip 1 } $nodesToRemove | Foreach-Object { $myfile.suppliers.RemoveChild($_) } #save XML $myfile.Save("file1and2cleaned_result.xml")
缺陷说明
- 去重逻辑错误:分组去重时会把
VATRegistrationNumber为空的重复条目一并删除,不符合空值条目全保留的规则 - 内存占用过高:采用全量DOM加载方式处理,10万级条目场景下内存开销通常超过1GB,还会额外生成中间文件,不适合长期定时稳定运行
优化后PowerShell实现(流式低内存版本)
采用XmlReader/XmlWriter做流式读写,全程不加载全量XML到内存,仅用哈希表存储File1中出现过的非空VAT号(查询时间复杂度O(1)),内存占用可降低90%以上,同时跳过中间文件生成步骤,处理速度更快。
逻辑修正点:单独对空VAT号条目做放行处理,自动过滤File2中与File1非空VAT号重复的条目,同时自动拦截File2内部的非空VAT重复条目
# 配置文件路径,可根据实际存放位置修改 $file1Path = "file1.xml" $file2Path = "file2.xml" $outputPath = "merged_final_result.xml" # 初始化哈希表存储已存在的非空VAT号 $existingVatSet = @{} # 初始化XML流式写入器 $writeSetting = New-Object System.Xml.XmlWriterSettings $writeSetting.Indent = $true $writeSetting.Encoding = [System.Text.Encoding]::UTF8 $xmlWriter = [System.Xml.XmlWriter]::Create($outputPath, $writeSetting) $xmlWriter.WriteStartDocument() # 流式读取File1,写入根节点 $reader1 = [System.Xml.XmlReader]::Create($file1Path) while ($reader1.Read()) { if ($reader1.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader1.Name -eq "suppliers") { $xmlWriter.WriteStartElement("suppliers") # 复制根节点所有属性 if ($reader1.HasAttributes) { while ($reader1.MoveToNextAttribute()) { $xmlWriter.WriteAttributeString($reader1.Name, $reader1.Value) } $reader1.MoveToElement() } break } } # 逐行读取File1的供应商条目,写入输出并收集非空VAT号 while ($reader1.Read()) { if ($reader1.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader1.Name -eq "supplier") { $subReader = $reader1.ReadSubtree() $tempDoc = New-Object System.Xml.XmlDocument $tempDoc.Load($subReader) $currentNode = $tempDoc.DocumentElement # 提取当前条目的VAT号 $currentVat = $currentNode.SelectSingleNode("VATRegistrationNumber")?.InnerText.Trim() if (-not [string]::IsNullOrEmpty($currentVat)) { $existingVatSet[$currentVat] = $true } # 直接写入条目到输出文件 $xmlWriter.WriteNode($tempDoc.CreateNavigator().ReadSubtree(), $false) $subReader.Close() } } $reader1.Close() # 流式读取File2,按规则筛选条目写入 $reader2 = [System.Xml.XmlReader]::Create($file2Path) while ($reader2.Read()) { if ($reader2.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader2.Name -eq "suppliers") { break } } while ($reader2.Read()) { if ($reader2.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader2.Name -eq "supplier") { $subReader = $reader2.ReadSubtree() $tempDoc = New-Object System.Xml.XmlDocument $tempDoc.Load($subReader) $currentNode = $tempDoc.DocumentElement $currentVat = $currentNode.SelectSingleNode("VATRegistrationNumber")?.InnerText.Trim() $keepNode = $false # 空VAT号直接保留 if ([string]::IsNullOrEmpty($currentVat)) { $keepNode = $true } # 非空VAT号未在已有集合中则保留,同时加入集合避免File2内部重复 elseif (-not $existingVatSet.ContainsKey($currentVat)) { $keepNode = $true $existingVatSet[$currentVat] = $true } if ($keepNode) { $xmlWriter.WriteNode($tempDoc.CreateNavigator().ReadSubtree(), $false) } $subReader.Close() } } $reader2.Close() # 写入文档结束标签,释放资源 $xmlWriter.WriteEndElement() $xmlWriter.WriteEndDocument() $xmlWriter.Close()
Windows任务计划部署步骤
- 将上述代码保存为
XmlMerge.ps1,根据实际文件路径修改脚本内的路径配置 - 打开Windows任务计划程序,新建任务,触发器设置为每日需要运行的时间点
- 操作项选择「启动程序」,程序路径填写
powershell.exe,参数填写-ExecutionPolicy Bypass -File "脚本存放的完整绝对路径\XmlMerge.ps1" - 可根据需要勾选「不管用户是否登录都要运行」,配置对应目录的读写权限即可
内容的提问来源于stack exchange,提问作者Jana
相关产品推荐
相关产品推荐

