首页 > 解决方案 > 尝试将 XML 子项从一个文件导入到另一个文件

问题描述

我查看了这篇文章,发现这几乎正是我需要做的。但是,鉴于本文中的建议,我无法产生预期的输出。基本上,我正在尝试</parameter>从包含以下内容的 XML ( $ManifestFile) 文件中导入元素:

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

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

标签: xmlpowershellxpathxml-namespaces

解决方案


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

  • 您不会将源文档中的元素导入目标文档,即使这是将元素插入目标文档的 DOM 的先决条件。

  • .SelectSingleNode()用于选择源文档节点,即使 - 我想 - 您打算用来.SelectNodes()选择所有 <parameter>元素。

  • 您缺少文档的名称空间管理,这是通过/成功进行 XPath 查询的先决条件.SelectSingleNode().SelectNodes()

    • 鉴于命名空间管理很复杂,下面的解决方案依赖于PowerShell 的 XML DOM 点表示法,您的问题部分采用了这种表示法,它避免了命名空间处理的需要,以及一种解决方法。如果您确实想处理命名空间(这是完全正确的方法),请参阅Ansgar Wiechers 的有用答案

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

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

可选背景信息:

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

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

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

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

      • 将命名空间的 URI 与符号标识符相关联。如果输入文档中的命名空间声明用于默认命名空间 -xmlns您不能将其用作符号标识符(名称xmlns是保留的),而必须简单地选择一个。

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

      • Ansgar Wiechers 的有用回答证明了这一切。

    • 考虑将PowerShell 的Select-Xmlcmdlet作为替代方案:作为围绕它的高级包装器也支持 XPath 查询,但使命名空间管理更容易 - 请参阅此答案.SelectNodes()的底部部分。

  • 相比之下,PowerShell 的点表示法始​​终与命名空间无关,因此它不需要显式的命名空间处理。

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

    • PowerShell 的点表示法:

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

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

      • 与点表示法一样方便,但它也有一些陷阱,其中最值得注意的是准叶元素- 那些根本没有子节点或只有非元素子节点(如文本节点)的元素 -以字符串形式返回,而不是元素,这使得修改它们变得困难。
        此外,类型原生属性与 PowerShell 添加的反映特定文档的元素和属性名称的属性之间可能存在名称冲突- 请参阅此答案
        简而言之:XML DOM 和 PowerShell 的对象模型之间的映射不是——也不能是——精确和完整的


推荐阅读