Обновление XML-узла Power Shell автоматически кодирует символы <>
Я пишу сценарий powershell для создания XML-файла, который позже загружаю в лазурный конвейер. Проблема заключается в том, что это генерирует закодированный вывод с преобразованием < , >, который не соответствует правильному формату xml. Я понимаю, что это связано с автоматическим кодированием. Некоторая помощь для предотвращения этого приветствуется
$myitems =
@([pscustomobject]
@{AssertName="Joe";TestPass=$true;},
[pscustomobject]
@{AssertName="Sue";TestPass=$false;},
[pscustomobject]
@{AssertName="Cat";TestPass=$true;})
Set-Content $path <?xml version="1.0"?><testsuites></testsuites>'
$xml = New-Object XML
$xml.Load($path)
$element = $xml.SelectSingleNode("testsuites")
$innerText=""
foreach ($item in $myitems )
{
$innerText=$innerText + '<testsuite errors="0" failures="0" id="0" name="$item.AssertName" tests="1"><testcase classname="some.class.name" name="Test1" time="123.345000"/></testsuite>'
}
[xml] $xml = Get-Content -Raw $path
$xml.testsuites = $innerText
$xml.Save($path)
2 ответа
Я настоятельно рекомендую избегать работы с необработанными строками XML и вместо этого создавать весь XML-документ поэлементно, используя .NET API. Таким образом, вы можете просто записывать любые данные как есть, а API обеспечивает правильную кодировку XML.
В целом существует два вида API для построения XML:
- на основе DOM, например, используя
XmlDocument
(ускоритель типа[xml]
). Этот самый простой в использовании, но он сравнительно медленный и хранит весь документ в памяти, что может быть проблемой для действительно больших документов. - На основе потока, например, с использованием
XmlWriter
. Это самый быстрый способ с наименьшим объемом памяти. Это более громоздко в использовании, так как вам нужно позаботиться о том, чтобы элементы были правильно закрыты. Также нельзя создавать элементы не по порядку, они пишутся в том порядке, в котором вы вызываете API.
Решение на основе DOM
$myitems = @(
[pscustomobject] @{AssertName="Joe";TestPass=$true}
[pscustomobject] @{AssertName="Sue";TestPass=$false}
[pscustomobject] @{AssertName="Cat";TestPass=$true}
)
$xml = [xml]::new()
$null = $xml.AppendChild( $xml.CreateXmlDeclaration('1.0', 'utf-8', $null) )
$root = $xml.AppendChild( $xml.CreateElement('testsuites') )
foreach ($item in $myitems )
{
$testSuite = $root.AppendChild( $xml.CreateElement('testsuite') )
$testSuite.SetAttribute('errors', 0)
$testSuite.SetAttribute('failures', 0)
$testSuite.SetAttribute('id', 0)
$testSuite.SetAttribute('name', $item.AssertName)
$testSuite.SetAttribute('tests', 1)
$testCase = $testSuite.AppendChild( $xml.CreateElement('testcase') )
$testCase.SetAttribute('classname', 'some.class.name')
$testCase.SetAttribute('name', 'Test1')
$testCase.SetAttribute('time', '123.345000')
}
$xml.Save( "$PSScriptRoot\test.xml" )
Выход:
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite errors="0" failures="0" id="0" name="Joe" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Sue" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Cat" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
</testsuites>
Потоковое решение
$path = "$PSScriptRoot\test.xml"
$myitems = @(
[pscustomobject] @{AssertName="Joe";TestPass=$true}
[pscustomobject] @{AssertName="Sue";TestPass=$false}
[pscustomobject] @{AssertName="Cat";TestPass=$true}
)
$writerSettings = [Xml.XmlWriterSettings] @{
Encoding = [Text.Encoding]::UTF8
Indent = $true
IndentChars = "`t"
WriteEndDocumentOnClose = $true # Write document end tag automatically
}
$writer = [xml.XmlWriter]::Create( $path, $writerSettings )
$writer.WriteStartDocument() # writes the XML declaration
$writer.WriteStartElement('testsuites')
foreach ($item in $myitems )
{
# Indentation is used to show the nesting of the XML elements
$writer.WriteStartElement('testsuite')
$writer.WriteAttributeString('errors', 0)
$writer.WriteAttributeString('failures', 0)
$writer.WriteAttributeString('id', 0)
$writer.WriteAttributeString('name', $item.AssertName)
$writer.WriteAttributeString('tests', 1)
$writer.WriteStartElement('testcase')
$writer.WriteAttributeString('classname', 'some.class.name')
$writer.WriteAttributeString('name', 'Test1')
$writer.WriteAttributeString('time', '123.345000')
$writer.WriteEndElement()
$writer.WriteEndElement()
}
# Very important - writes document end tag and closes the file
$writer.Dispose()
Выход:
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite errors="0" failures="0" id="0" name="Joe" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Sue" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Cat" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
</testsuites>
Способ сделать это - создать новый XML-документ из вашей строки и импортировать соответствующий узел (ImportNode
) в основном документе, а затем добавить дочерний (AppendChild
) к конкретному узлу:
$myitems =
@{ AssertName="Joe"; TestPass=$true },
@{ AssertName="Sue"; TestPass=$false },
@{ AssertName="Cat"; TestPass=$true }
$Main = [xml]'<?xml version="1.0"?><testsuites></testsuites>'
foreach ($item in $myitems) {
$String = '<testsuite errors="0" failures="0" id="0" name="' + $item.AssertName + '" tests="1"><testcase classname="some.class.name" name="Test1" time="123.345000"/></testsuite>'
$Xml = [xml]$String
$Node = $Main.ImportNode($Xml.testsuite, $True)
$Null = $Main.SelectSingleNode('testsuites').AppendChild($Node)
}
[System.Xml.Linq.XDocument]::Parse($Main.OuterXml).ToString()
<testsuites>
<testsuite errors="0" failures="0" id="0" name="Joe" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Sue" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
<testsuite errors="0" failures="0" id="0" name="Cat" tests="1">
<testcase classname="some.class.name" name="Test1" time="123.345000" />
</testsuite>
</testsuites>