将节点添加到XML中使用Powershell 4.0

huangapple go评论85阅读模式
英文:

Add Node to XML with Powershell4.0

问题

我正在尝试向现有的XML(作为API响应)中添加一个新的XML节点,但不幸的是,尝试了许多教程和示例后仍无法使其正常工作。我无法告诉您尝试了哪些选项,但最后一个已包括...

我有这个XML(作为API响应):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AccessPermissions xmlns="http://localhost/RESTApi/v1">
  <AccessProfilePermissions/>
  <ReaderSpecialPermissions/>
  <RoomZoneSpecialPermissions>
    <RoomZoneSpecialPermission>
      <RoomZoneNumber>6</RoomZoneNumber>
      <AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber>
      <ValidFrom>2023-07-13</ValidFrom>
    </RoomZoneSpecialPermission>
  </RoomZoneSpecialPermissions>
</AccessPermissions>

现在我想在XML中添加另一个<RoomZoneSpecialPermission>以将其发送回API端点。我期望的结果如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AccessPermissions xmlns="http://localhost/RESTApi/v1">
  <AccessProfilePermissions/>
  <ReaderSpecialPermissions/>
  <RoomZoneSpecialPermissions>
    <RoomZoneSpecialPermission>
      <RoomZoneNumber>6</RoomZoneNumber>
      <AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber>
      <ValidFrom>2023-07-13</ValidFrom>
    </RoomZoneSpecialPermission>
    <RoomZoneSpecialPermission>
      <RoomZoneNumber>7</RoomZoneNumber>
      <AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber>
      <ValidFrom>2023-07-31</ValidFrom>
    </RoomZoneSpecialPermission>
  </RoomZoneSpecialPermissions>
</AccessPermissions>

以下是您的脚本(不包括API调用部分,因为那部分已经完美运行):

# API响应转为XML对象
$xmlObject = [xml]$apiresponse

# 新权限以XML格式并转为XML对象
$newxml = "<RoomZoneSpecialPermission><RoomZoneNumber>7</RoomZoneNumber><AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber><ValidFrom>2023-07-31</ValidFrom></RoomZoneSpecialPermission>"
$newPermissionObject = [xml]$newxml

# 选择RoomZoneSpecialPermissions节点
$roomZoneSpecialPermissions = $xmlObject.SelectSingleNode("//a:RoomZoneSpecialPermissions", $xmlObject.DocumentElement.NamespaceURI)

# 新的RoomZoneSpecialPermission节点
$newPermissionElement = $xmlObject.CreateElement("RoomZoneSpecialPermission", "http://localhost/RESTApi/v1")

# 将元素添加到上面生成的RoomZoneSpecialPermission节点
$newPermissionElement.AppendChild($xmlObject.CreateElement("RoomZoneNumber", "http://localhost/RESTApi/v1")).InnerText = "7"
$newPermissionElement.AppendChild($xmlObject.CreateElement("AccessWeeklyProfileNumber", "http://localhost/RESTApi/v1")).InnerText = "1"
$newPermissionElement.AppendChild($xmlObject.CreateElement("ValidFrom", "http://localhost/RESTApi/v1")).InnerText = "2023-07-31"

$roomZoneSpecialPermissions.AppendChild($newPermissionElement)

$modifiedxml = $xmlObject.OuterXml
Write-Output $modifiedxml

在尝试多个教程后出现了不同的错误,如:

  • 调用"AppendChild"时出现异常,参数数为1,错误消息为"The node to be inserted is from a different document context"。

  • 无法找到带有"SelectSingleNode"和参数计数为2的重载。

请问您如何完成这个任务?因为迄今为止没有任何解释都有效。

英文:

Im trying to add a new XML Node to a existing XML which is a API Response. But unfortunately i cannot get it to work trying many tutorials and examples. I cannot tell you which Options i tried but the last one is included...

I have this XML (Which is a API Response):

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;
&lt;AccessPermissions xmlns=&quot;http://localhost/RESTApi/v1&quot;&gt;
  &lt;AccessProfilePermissions/&gt;
  &lt;ReaderSpecialPermissions/&gt;
  &lt;RoomZoneSpecialPermissions&gt;
    &lt;RoomZoneSpecialPermission&gt;
      &lt;RoomZoneNumber&gt;6&lt;/RoomZoneNumber&gt;
      &lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;
      &lt;ValidFrom&gt;2023-07-13&lt;/ValidFrom&gt;
    &lt;/RoomZoneSpecialPermission&gt;
&lt;/AccessPermissions&gt;

Now i want to add another &lt;RoomZoneSpecialPermission&gt; into the XML to send it back to the API Endpoint. I expect something like that:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;yes&quot;?&gt;
&lt;AccessPermissions xmlns=&quot;http://localhost/RESTApi/v1&quot;&gt;
  &lt;AccessProfilePermissions/&gt;
  &lt;ReaderSpecialPermissions/&gt;
  &lt;RoomZoneSpecialPermissions&gt;
    &lt;RoomZoneSpecialPermission&gt;
      &lt;RoomZoneNumber&gt;6&lt;/RoomZoneNumber&gt;
      &lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;
      &lt;ValidFrom&gt;2023-07-13&lt;/ValidFrom&gt;
    &lt;/RoomZoneSpecialPermission&gt;
    &lt;RoomZoneSpecialPermission&gt;
      &lt;RoomZoneNumber&gt;7&lt;/RoomZoneNumber&gt;
      &lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;
      &lt;ValidFrom&gt;2023-07-31&lt;/ValidFrom&gt;
    &lt;/RoomZoneSpecialPermission&gt;
&lt;/AccessPermissions&gt;

My Script (Without API Call, since that works perfectly fine):

# API Response to XML Object
$xmlObject = [xml]$apiresponse

# New Permission in XML Format and conver to XML Object
$newxml = &quot;&lt;RoomZoneSpecialPermission&gt;&lt;RoomZoneNumber&gt;7&lt;/RoomZoneNumber&gt;&lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;&lt;ValidFrom&gt;2023-07-31&lt;/ValidFrom&gt;&lt;/RoomZoneSpecialPermission&gt;&quot;
$newPermissionObject = [xml]$newxml

# Select RoomZoneSpecialPermissions Node
$roomZoneSpecialPermissions = $xmlObject.SelectSingleNode(&quot;//a:RoomZoneSpecialPermissions&quot;, $xmlObject.DocumentElement.NamespaceURI)

# New RoomZoneSpecialPermission Node
$newPermissionElement = $xmlObject.CreateElement(&quot;RoomZoneSpecialPermission&quot;, &quot;http://localhost/RESTApi/v1&quot;)

# Add Elements to RoomZoneSpecialPermission Node generated above
$newPermissionElement.AppendChild($xmlObject.CreateElement(&quot;RoomZoneNumber&quot;, &quot;http://localhost/RESTApi/v1&quot;)).InnerText = &quot;7&quot;
$newPermissionElement.AppendChild($xmlObject.CreateElement(&quot;AccessWeeklyProfileNumber&quot;, &quot;http://localhost/RESTApi/v1&quot;)).InnerText = &quot;1&quot;
$newPermissionElement.AppendChild($xmlObject.CreateElement(&quot;ValidFrom&quot;, &quot;http://localhost/RESTApi/v1&quot;)).InnerText = &quot;2023-07-31&quot;


$roomZoneSpecialPermissions.Trust.FrameworkPolicy.ClaimsProviders.AppendChild($newPermissionElement)

$modifiedxml = $xmlObject.OuterXml
Write-Output $modifiedxml

With the many tutoritals i tried came different Errors like:

  • Exception callind "AppendChild" with "1" arguments "The node to be inserted is from a different document context"

  • Cannot find an overload for "SelectSingleNode" and the argument count 2

Could you please tell me how to accomplish this task? Im pretty devastated right now since none of the explanations worked so far.

答案1

得分: 1

你的示例XML格式不正确,但假设你修复了它,请尝试使用以下代码并查看是否有效:

$newPermissionObject = $xmlObject.CreateDocumentFragment()
$newPermissionObject.InnerXML = $newxml

$destination = $xmlObject.SelectSingleNode('//*[local-name()="RoomZoneSpecialPermissions"]')
$destination.AppendChild($newPermissionObject)
英文:

Your sample xml is not well formed, but assuming you fix that, try using the following and see if it works:

$newPermissionObject = $xmlObject.CreateDocumentFragment()
$newPermissionObject.InnerXML=$newxml

$destination=$xmlObject.SelectSingleNode(&#39;//*[local-name()=&quot;RoomZoneSpecialPermissions&quot;]&#39;)
$destination.AppendChild($newPermissionObject)

答案2

得分: 0

你有一个命名空间问题。尝试使用 Xml Linq。

using assembly System.Xml.Linq

$inputFilename = 'c:\temp\test.xml'
$outputFilename = 'c:\temp\test1.xml'

$doc = [System.Xml.Linq.XDocument]::Load($inputFilename)
$ns = $doc.Root.GetDefaultNamespace()

$roomZoneSpecialPermissions = $doc.Descendants($ns + 'RoomZoneSpecialPermissions')

$roomZoneSpecialPermission = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get('RoomZoneSpecialPermission'))

$roomZoneNumber = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get('RoomZoneNumber'), 7)
$roomZoneSpecialPermission.Add($roomZoneNumber)

$accessWeeklyProfileNumber = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get('AccessWeeklyProfileNumber'), 1)
$roomZoneSpecialPermission.Add($accessWeeklyProfileNumber)

$validFrom = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get('ValidFrom'), '2023-07-31')
$roomZoneSpecialPermission.Add($validFrom)

$roomZoneSpecialPermissions.Add($roomZoneSpecialPermission)

$doc.Save($outputFilename)

希望这对你有所帮助。

英文:

You have a namespace issue. Try Xml Linq

<!-- begin snippet: js hide: false console: true babel: false -->

using assembly System.Xml.Linq

$inputFilename = &#39;c:\temp\test.xml&#39;
$outputFilename = &#39;c:\temp\test1.xml&#39;

$doc = [System.Xml.Linq.XDocument]::Load($inputFilename)
$ns = $doc.Root.GetDefaultNamespace()

$roomZoneSpecialPermissions = $doc.Descendants($ns + &#39;RoomZoneSpecialPermissions&#39;)

$roomZoneSpecialPermission = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get(&#39;RoomZoneSpecialPermission&#39;))

$roomZoneNumber = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get(&#39;RoomZoneNumber&#39;), 7)
$roomZoneSpecialPermission.Add($roomZoneNumber)

$accessWeeklyProfileNumber = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get(&#39;AccessWeeklyProfileNumber&#39;), 1)
$roomZoneSpecialPermission.Add($accessWeeklyProfileNumber)

$validFrom = [System.Xml.Linq.XElement]::new($ns + [System.Xml.Linq.XName]::Get(&#39;ValidFrom&#39;), &#39;2023-07-31&#39;)
$roomZoneSpecialPermission.Add($validFrom)

$roomZoneSpecialPermissions.Add($roomZoneSpecialPermission)

$doc.Save($outputFilename)

<!-- end snippet -->

答案3

得分: 0

以下是翻译好的部分:

  • 主要问题是在您的 $xmlObject.SelectSingleNode("//a:RoomZoneSpecialPermissions", $xmlObject.DocumentElement.NamespaceURI) 调用中,第二个参数的类型错误 - 请查看下一部分以了解如何修复这个问题;然而,考虑到您知道目标元素的位置,您可以使用PowerShell对XML DOM的适应,即下面的代码中使用的点表示法,并在下一部分中也有解释。

  • 虽然Jack Fleeting的答案展示了一种一般上更好的方法来将新的嵌套元素插入到现有文档中,即使用[xml].CreateDocumentFragment(),但插入的片段最终会位于错误的命名空间(xmlns="")。

    • 请注意,使用重复的[xml].CreateElement()调用来迭代创建新的子元素,并在每个调用中使用目标命名空间将会起作用,但相当繁琐。

以下解决方案解决了您的问题:

# 使用PowerShell的点表示法获取对父元素的引用。
$roomZoneSpecialPermissions = $xmlObject.AccessPermissions.RoomZoneSpecialPermissions

# 创建新元素 - 现在为空 - 
# 在文档的默认命名空间中,并将其附加为子元素。
$newChild = $roomZoneSpecialPermissions.AppendChild(
  $xmlObject.CreateElement(
    'RoomZoneSpecialPermission', 
    $xmlObject.DocumentElement.NamespaceURI
  )
)

# 现在该元素已附加并成为文档的一部分,
# 分配给它的.InnerXml将尊重元素的默认命名空间
# 并自动应用于新创建的嵌套元素。
# (字符串是您的$newxml变量的值
# 不包含外部的<RoomZoneSpecialPermission>元素。)
$newChild.InnerXml = '<RoomZoneNumber>7</RoomZoneNumber><AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber><ValidFrom>2023-07-31</ValidFrom>'

# 输出扩展文档的XML表示。
$xmlObject.OuterXml

请注意,使用.CreateDocumentFragment()方法的方法通常很简单,并且在片段文本中嵌入与文档的默认命名空间匹配的xmlns=...属性很诱人,但不幸的是,导入片段后会保留此属性,这会在技术上是正确的,但由于此不必要的重复,可能会引发潜在的维护问题。

# !! 有效,但新的子元素具有文档的xmlns=...属性的副本。
$newChildFragment = $xmlObject.CreateDocumentFragment()
# 请注意,使用字符串内插 $(...) 来
# 复制文档元素的命名空间属性,以确保
# 片段处于相同的命名空间中。
# 不幸的是,此副本不会被.AppendChild()方法删除。
$newChildFragment.InnerXML = '<RoomZoneSpecialPermission xmlns="' + $($xmlObject.DocumentElement.NamespaceURI) + '"><RoomZoneNumber>7</RoomZoneNumber><AccessWeeklyProfileNumber>1</AccessWeeklyProfileNumber><ValidFrom>2023-07-31</ValidFrom></RoomZoneSpecialPermission>'
$roomZoneSpecialPermissions.AppendChild($newChildFragment)

至于您看到的错误消息

错误:找不到“SelectSingleNode”的重载和参数计数为2

此错误源自于您的 $xmlObject.SelectSingleNode("//a:RoomZoneSpecialPermissions", $xmlObject.DocumentElement.NamespaceURI) 调用中的第二个参数类型错误:

您可以按以下方式修复此问题:

# 为目标文档的名称表创建一个命名空间管理器...
$nsMgr = [System.Xml.XmlNamespaceManager]::new($xmlObject.NameTable)
# ... 并将文档元素的命名空间URI作为条目添加
#     使用自选前缀,然后可以在XPath查询中使用。
$nsMgr.AddNamespace('a', $xmlObject.DocumentElement.NamespaceURI)
# 使用自选命名空间前缀执行XPath查询。
$roomZoneSpecialPermissions =
  $xmlObject.SelectSingleNode('//a:RoomZoneSpecialPermissions', $nsMgr)

也可以使用PowerShell的XML DOM适配,它允许使用点表示法来钻取XML文档,即允许将XML元素名称(和属性)视为属性名称;由于此表示法忽略XML命名空间,仅操作元素的本地名称,因此获取所需元素非常简单:

# 使用PowerShell的命名空间无关的点表示法来定位感兴趣的元素。
$roomZoneSpecialPermissions =
  $xmlObject.AccessPermissions.RoomZoneSpecialPermissions

调用“AppendChild”时出现异常,并带有“1”参数“要插入的节点来自不同的文档上下文”

您没有显示实际尝试的内容,但此错误可能是由于尝试直接将存储在$newPermissionObject中的单独文档([xml])插入到您的$xmlObject文档中,这是不受支持的。

# !! 失败,因为$newPermissionObject是一个单独的文档。
$roomZoneSpecialPermissions.AppendChild(
  $newPermissionObject.DocumentElement
)

为了将来自不同文档的节点插入到给

英文:

<!-- language-all: sh -->

  • The primary problem is that the 2nd argument in your $xmlObject.SelectSingleNode(&quot;//a:RoomZoneSpecialPermissions&quot;, $xmlObject.DocumentElement.NamespaceURI) call is of the wrong type - see the next section for how to fix this; however, given that you know the position of the target element, you can use PowerShell's adaption of the XML DOM in the form of dot notation, as used in the code below, and as also explained in the next section.

  • While Jack Fleeting's answer shows a generally superior approach to inserting a new nested element into an existing document, namely using [xml].CreateDocumentFragment(), but the inserted fragment would end up in the wrong namespace (xmlns=&quot;&quot;).

    • Note that your attempt of creating the new child element iteratively, using repeated [xml].CreateElement() calls with the target namespace in each call would work - but is quite cumbersome.

The following solution fixes both your problems:

# Use PowerShell&#39;s dot notation to obtain a reference to the the parent element.
$roomZoneSpecialPermissions = $xmlObject.AccessPermissions.RoomZoneSpecialPermissions

# Create the new element - empty for now - 
# in the document&#39;s default namespace, and append it as a child.
$newChild = $roomZoneSpecialPermissions.AppendChild(
  $xmlObject.CreateElement(
    &#39;RoomZoneSpecialPermission&#39;, 
    $xmlObject.DocumentElement.NamespaceURI
  )
)

# Now that the element has been appended and is part of the document, 
# assigning to its .InnerXml respects the element&#39;s default namespace and 
# automatically applies it to the newly created  nested elements.
# (The string is the value of your $newxml variable 
# without the outer &lt;RoomZoneSpecialPermission&gt; element.)
$newChild.InnerXml = &#39;&lt;RoomZoneNumber&gt;7&lt;/RoomZoneNumber&gt;&lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;&lt;ValidFrom&gt;2023-07-31&lt;/ValidFrom&gt;&#39;

# Output the extended document&#39;s XML representation.
$xmlObject.OuterXml

Note that it's tempting to use the .CreateDocumentFragment() approach for simplicity and embed an xmlns=... attribute matching the document's default namespace in the fragment text, but - unfortunately - this attribute is retained on importing the fragment, which - while being technically correct - creates a potential maintenance headache due to this unnecessary duplication.

# !! Works, but the new child element has a duplicate of the document
# !! element&#39;s xmlns=... attribute.
$newChildFragment = $xmlObject.CreateDocumentFragment()
# Note the use of string interpolation $(...) in order to 
# duplicate the document element&#39;s namespace  attribute, which ensures that
# the fragment is in the same namespace.
# Unfortunately, this duplicate is *not* removed by .AppendChild()
$newChildFragment.InnerXML = &quot;&lt;RoomZoneSpecialPermission xmlns=`&quot;$($xmlObject.DocumentElement.NamespaceURI)`&quot;&gt;&lt;RoomZoneNumber&gt;7&lt;/RoomZoneNumber&gt;&lt;AccessWeeklyProfileNumber&gt;1&lt;/AccessWeeklyProfileNumber&gt;&lt;ValidFrom&gt;2023-07-31&lt;/ValidFrom&gt;&lt;/RoomZoneSpecialPermission&gt;&quot; 
$roomZoneSpecialPermissions.AppendChild($newChildFragment)

As for the error messages you saw:

> Error: Cannot find an overload for "SelectSingleNode" and the argument count 2

This error stems from the 2nd argument in your $xmlObject.SelectSingleNode(&quot;//a:RoomZoneSpecialPermissions&quot;, $xmlObject.DocumentElement.NamespaceURI) call being of the wrong type:

You can fix this as follows:

# Create a namespace manager for the target document&#39;s name table...
$nsMgr = [System.Xml.XmlNamespaceManager]::new($xmlObject.NameTable)
# ... and add the document element&#39;s namespace URI as an entry
#     with a self-chosen prefix, which can then be used in XPath queries.
$nsMgr.AddNamespace(&#39;a&#39;, $xmlObject.DocumentElement.NamespaceURI)
# Perform the XPath query, using the self-chosen namespace prefix.
$roomZoneSpecialPermissions =
  $xmlObject.SelectSingleNode(&#39;//a:RoomZoneSpecialPermissions&#39;, $nsMgr)

That said, you can use PowerShell's adaption of the XML DOM, which allows drilling down into XML documents with dot notation, i.e. allows treating XML element names (and attributes) like property names; since this notation ignores XML namespaces and only operates on the elements' local names, getting the desired element is as simple as:

# Use PowerShell&#39;s namespace-agnostic dot notation to target the element of interest.
$roomZoneSpecialPermissions =
  $xmlObject.AccessPermissions.RoomZoneSpecialPermissions

> Exception calling "AppendChild" with "1" arguments "The node to be inserted is from a different document context"

You're not showing the actual attempt, but this error is likely caused by trying to insert the separate document ([xml]) stored in $newPermissionObject directly into your $xmlObject document, which isn't supported.

# !! Fails, because $newPermissionObject is a separate document.
$roomZoneSpecialPermissions.AppendChild(
  $newPermissionObject.DocumentElement
)

In order to insert nodes from a different document into a given document, those nodes must be imported first, using [xml].ImportNode():

$roomZoneSpecialPermissions.AppendChild(
  $xmlObject.ImportNode(
    $newPermissionObject.DocumentElement, 
    $true  # also copy all nested elements
  )
)

However, then too you'll run into the namespace problem discussed in the top section.

huangapple
  • 本文由 发表于 2023年7月31日 22:46:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76804751.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定