Обновление 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>
Другие вопросы по тегам