简体   繁体   English

电源 shell XML 节点更新自动编码<>字符

[英]Power shell XML node update automatically encode <> charactors

I am writing a powershell script to create a xml file which i later feed into azure pipeline.我正在编写一个 powershell 脚本来创建一个 xml 文件,我稍后将其输入 azure 管道。 Issue is this generate an encoded output with <, > being converted, which is not in a correct xml format问题是这会生成一个编码的 output,其中 <, > 被转换,它不是正确的 xml 格式在此处输入图像描述 I understand this is related to automatic encoding.我知道这与自动编码有关。 Some help to prevent this is appreciated一些帮助以防止这种情况表示赞赏

$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)

I strongly suggest to avoid working with raw XML strings and instead build the whole XML document element by element, using .NET API.我强烈建议避免使用原始 XML 字符串,而是使用 .NET ZDB974278714CA3ACE1608 逐个元素构建整个 XML 文档。 This way you can just write any data as-is and the API makes sure of the proper XML encoding.这样您就可以按原样写入任何数据,并且 API 可确保正确的 XML 编码。

In general there are two kind of API for building XML:通常有两种 API 用于构建 XML:

  • DOM-based , eg using XmlDocument (type accelerator [xml] ).基于 DOM ,例如使用XmlDocument (类型加速器[xml] )。 This one is easiest to use, but is comparatively slow and stores the whole document in memory, which can be an issue for really large documents.这个最容易使用,但速度相对较慢,并且将整个文档存储在 memory 中,这对于非常大的文档可能是个问题。
  • Stream-based , eg using XmlWriter .基于流,例如使用XmlWriter This is the fastest way and has the lowest memory footprint.这是最快的方式,并且具有最低的 memory 占用空间。 It is more cumbersome to use, as you have to take care that elements are properly closed.使用起来比较麻烦,因为您必须注意元素是否正确关闭。 Also you can't create the elements out of order, they are written in the order you call the API.您也不能乱序创建元素,它们是按照您调用 API 的顺序编写的。

DOM-based solution基于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" )

Output: Output:

<?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>

Stream-based solution基于流的解决方案

$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()  

Output: Output:

<?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>

The way to do this is create an new xml document from your string and import the concerned node ( ImportNode ) in the main document and than append the child ( AppendChild ) to the specific node:这样做的方法是从您的字符串中创建一个新的 xml 文档并在主文档中导入相关节点( ImportNode ),然后将 append 子节点( 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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM