尝试将 XML 个子文件从一个文件导入到另一个文件

Trying to import XML children from one file to another

我查看了 post 并发现这几乎正是我需要做的。但是,根据 post 中的建议,我无法产生预期的输出。基本上,我试图从 XML ($ManifestFile) 文件中导入 </parameter> 元素,该文件包含如下内容:

<?xml version="1.0" encoding="utf-8"?>
<plasterManifest
  schemaVersion="1.1"
  templateType="Project" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
  <metadata>
    <name>PlasterTestProject</name>
    <id>4c08dedb-7da7-4193-a2c0-eb665fe2b5e1</id>
    <version>0.0.1</version>
    <title>Testing creating custom Plaster Template for CI/CD</title>
    <description>Testing out creating a module project with Plaster for complete CI/CD files.</description>
    <author>Catherine Meyer</author>
    <tags></tags>
  </metadata>
  <parameters>
        <parameter name='AuthorName' type="user-fullname" prompt="Module author's name" />
        <parameter name='ModuleName' type="text" prompt="Name of your module" />
        <parameter name='ModuleDescription' type="text" prompt="Brief description on this module" />
        <parameter name='ModuleVersion' type="text" prompt="Initial module version" default='0.0.1' />
        <parameter name='GitLabUserName' type="text" prompt="Enter the GitLab Username to be used" default="${PLASTER_PARAM_FullName}"/>
        <parameter name="GitLubRepo" type="text" prompt="GitiLab repo name for this module" default="${PLASTER_PARAM_ModuleName}"/>
        <parameter name='ModuleFolders' type = 'multichoice' prompt='Please select folders to include' default='0,1'>
            <choice label='&amp;Public' value='Public' help='Folder containing public functions that can be used by the user.'/>
            <choice label='&amp;Private' value='Private' help='Folder containing internal functions that are not exposed to users'/>
        </parameter>
    </parameters>
</plasterManifest>

我尝试导入的文档 ($NewManifestFile) 看起来像:

<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.1" templateType="Project" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
  <metadata>
     <name>test3</name>
     <id>8c028f40-cdc6-40dc-8442-f5256a8c0ed9</id>
     <version>0.0.1</version>
     <title>test3</title>
     <description>SDSKL</description>
     <author>NAME</author>
    <tags> </tags>
  </metadata>
  <parameters>
  </parameters>
  <content>
  </content>
</plasterManifest>

我编写的代码类似于:

$ManifestFile = [xml](Get-Content ".\PlasterManifest.xml")
$NewManifestFile = [xml](Get-Content $PlasterMetadata.Path)
$NewManifestFile.plasterManifest.metadata.name

$Parameters = $ManifestFile.SelectSingleNode("//plasterManifest/parameters/parameter")
$Parameters
$NewParameters = $NewManifestFile.SelectSingleNode("//plasterManifest/parameters")
#Importing the parameters and content
foreach ($parameter in $Parameters) {
   $NewParamElem = $ManifestFile.ImportNode($parameter, $true)
   $NewParameters.AppendChild($NewParamElem)
}
[void]$NewManifestFile.save($PlasterMetadata.Path)

现在,它不会出错,但也根本不会导入。似乎某些元素没有在某处正确分配。我尝试了很多替代方案,这似乎是唯一接近我想要的方案。有什么建议么?

您当前的方法存在几个问题:

  • 您没有将元素从源文档导入到目标文档中,尽管这是将元素插入到目标文档的 DOM.

  • 您正在使用 .SelectSingleNode() 到 select 源文档节点,即使 - 我推测 - 您打算使用 .SelectNodes() 到 select 全部 <parameter>个元素.

  • 您缺少文档的 命名空间管理 ,这是 的先决条件通过 .SelectSingleNode() / .SelectNodes().

    成功的 XPath 查询
    • 鉴于名称空间管理复杂,下面的解决方案依赖于, which your question partly employs, which avoids the need for namespace handling, along with a workaround. If you do want to deal with namespaces - which is the strictly correct way to do it - see

这是一个带注释的解决方案:

$ManifestFile = [xml](Get-Content -Raw ./PlasterManifest.xml)
$NewManifestFile = [xml](Get-Content -Raw $PlasterMetadata.Path)

# Get the <parameters> element in the *source* doc.
# Note that PowerShell's dot notation-based access to the DOM does
# NOT require namespace management.
$ParametersRoot = $ManifestFile.plasterManifest.parameters

# Get the parent of the <parameter> elements, <parameters>, in the *destination* doc.
# Note: Ideally we'd also use dot notation in order for this, 
#       but since the target <parameters> element is *empty*, 
#       PowerShell represents it as a *string* rather than as an XML element.
#       Instead, we use the type-native index indexer ([...]) to get the
#       (first and only) <parameters> child element of the 
#       <plasterManifest> element by name.
$NewParametersRoot = $NewManifestFile.plasterManifest['parameters']

# Import the source element's subtree into the destination document, so it can
# be inserted into the DOM later.
$ImportedParametersRoot = $NewManifestFile.ImportNode($ParametersRoot, $True)

# For simplicity, replace the entire <parameters> element, which
# obviates the need for a loop.
# Note the need to call .ReplaceChild() on the .documentElement property,
# not on the document object itself.
$null = $NewManifestFile.documentelement.ReplaceChild($ImportedParametersRoot, $NewParametersRoot)

# Save the modified destination document.
$NewManifestFile.Save($PlasterMetadata.Path)

可选背景信息:

  • .SelectSingleNode() / .SelectNodes() 方法,因为它们接受 XPath queries,所以是 在 XML 文档中定位感兴趣的元素(节点)的最灵活和最强大的方法,但它们需要显式命名空间处理 如果输入文档声明了名称空间(例如您的 xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1"):

    • 注意:如果给定的输入文档声明了命名空间,而您忽略了如下所述的处理它们,.SelectSingleNode() / .SelectNodes() 只需 return $null 对于所有查询,如果使用不合格的元素名称(例如 parameters)并且 失败 与名称空间限定(名称空间前缀)的名称(例如 plaster:parameters ).

    • 命名空间处理涉及以下步骤(请注意,给定文档可能有 多个 命名空间声明,但为简单起见,说明仅假定一个):

      • 实例化命名空间管理器并将其与输入文档[的名称table]相关联。

      • 将名称空间的 URI 与符号标识符相关联。如果输入文档中的名称空间声明用于 default 名称空间 - xmlns - 你不能将其用作你的符号标识符(名称 xmlns 被保留)并且必须简单地选择一个。

      • 然后,当您调用 .SelectSingleNode() / .SelectNodes() 时,您 必须 使用此符号标识符作为元素名称前缀您的查询字符串;例如,如果您的(自选)符号标识符是 plaster 并且您正在文档中的任何位置查找元素 parameters,则您将使用查询字符串 '//plaster:pararameters'

      • 证明了这一切。

    • 考虑 PowerShell 的 Select-Xml cmdlet 作为替代方案:作为 .SelectNodes() 的高级包装器,它也支持 XPath 查询,但使名称空间管理更容易 - 请参阅 .

    • 的底部部分
  • 相比之下,PowerShell 的点表示法总是 命名空间不可知,因此它需要 没有 显式名称空间处理。

    • 警告:虽然这降低了复杂性,但只有在您知道正确的命名空间处理不是正确处理输入文档所必需的情况下才应使用它。

    • PowerShell 的点符号:

      • PowerShell 方便地将 XML 文档的 DOM - 输入文档中节点的层次结构 - 映射到具有属性的嵌套对象上,允许您 使用常规点符号向下钻取 文档;例如,XPath 查询 '/root/elem' 的等价物是 $xmlDoc.root.elem
        但是,这意味着您只能使用此表示法来访问其在层次结构中的路径已知的元素 - 不支持查询(尽管启用了 XPath Select-Xml cmdlet 退出)。

      • 此映射忽略名称空间限定符(前缀),因此您必须使用纯元素名称,没有任何命名空间前缀;例如,如果输入文档有一个 plaster:parameters 元素,您必须将其引用为 parameters.

      • 虽然点号方便,但有陷阱,最没有table其中 准叶元素 - 那些根本没有子节点或只有 非元素 子节点的元素,例如文本节点- return编辑为字符串,而不是元素,这使得修改变得困难他们。
        此外,类型本机属性与 PowerShell 添加的反映特定文档的元素和属性名称的属性之间可能存在 名称冲突 - 请参阅 .
        简而言之:XML DOM 和 PowerShell 的对象模型之间的映射不是——也不可能是——精确和完整的

正如 指出的那样,您的 XML 文档具有名称空间,因此在选择具有 XPath 表达式的节点时需要名称空间管理器。使用点访问来选择节点可以让您绕过命名空间管理,但由于点访问并不总是按照人们预期的方式工作,我仍然建议坚持使用 SelectNodes() 并使用适当的命名空间管理器。

$uri = 'http://www.microsoft.com/schemas/PowerShell/Plaster/v1'

[xml]$ManifestFile = Get-Content 'C:\path\to\old.xml'
$nm1 = New-Object Xml.XmlNamespaceManager $ManifestFile.NameTable
$nm1.AddNamespace('ns1', $uri)

[xml]$NewManifestFile = Get-Content 'C:\path\to\new.xml'
$nm2 = New-Object Xml.XmlNamespaceManager $NewManifestFile.NameTable
$nm2.AddNamespace('ns2', $uri)

$ManifestFile.SelectNodes('//ns1:parameter', $nm1) | ForEach-Object {
    $newnode = $NewManifestFile.ImportNode($_, $true)
    $parent  = $NewManifestFile.SelectSingleNode('//ns2:parameters', $nm2)
    $parent.AppendChild($newnode) | Out-Null
}

$NewManifestFile.Save('C:\path\to\new.xml')