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

qlfbtfca  于 2022-11-10  发布在  Shell
关注(0)|答案(2)|浏览(125)

我查看了this 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)

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

ryevplcw

ryevplcw1#

您当前的方法存在以下几个问题:
您并没有将源文档中的元素“导入”到目标文档中,即使这是将元素插入到目标文档的DOM中的先决条件。

  • 您使用.SelectSingleNode()来选择源文档节点,尽管-我想-您的意思是使用.SelectNodes()来选择所有<parameter>元素。
  • 您缺少文档的*命名空间管理**,这是**通过.SelectSingleNode()/.SelectNodes()**成功查询XPath的先决条件。
  • 考虑到命名空间管理很复杂,下面的解决方案依赖于PowerShell's XML DOM dot notation,您的问题部分*使用了PowerShell's XML DOM dot notation避免了命名空间处理,以及解决方法。如果您确实想要处理名称空间--这是严格意义上的正确方法--请参阅Ansgar Wiechers' helpful answer

以下是带注解的解决方案:

$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"),它们确实需要**显式的命名空间处理*:

  • 注意:如果给定的输入文档声明了名称空间,而您忽略了如下所述的处理,则对于所有查询,如果使用了不限定的元素名称(例如,parameters),则.SelectSingleNode()/.SelectNodes()将简单地返回$null,而使用名称空间限定(名称空间前缀)的元素名称(例如,plaster:parameters)则会失败
  • 命名空间处理涉及以下步骤(请注意,给定文档可能有多个命名空间声明,但为简单起见,本说明仅假定其中一个):
  • 示例化命名空间管理器,并将其与输入文档[的名称表]相关联。
  • 将命名空间的URI与符号标识符相关联。如果输入文档中的名称空间声明是针对默认名称空间-xmlns-您不能将其用作符号标识符(名称xmlns是保留的),而只需“选择”一个即可。
  • 然后,当您调用.SelectSingleNode()/.SelectNodes()时,您必须在查询字符串中使用此符号标识符作为元素名称前缀;例如,如果您(自己选择的)符号标识符是plaster,并且您要在文档中的任何位置查找元素parameters,则可以使用查询字符串'//plaster:pararameters'
  • Ansgar Wiechers' helpful answer展示了这一切。
  • 考虑PowerShell的Select-Xml cmdlet作为.SelectNodes()的高级 Package 器,它也支持XPath查询,但使命名空间管理更容易-请参阅this answer
  • 相比之下,**PowerShell的点符号总是名称空间不可知的,因此它不需要*显式的名称空间处理。
  • 警告**:虽然这降低了复杂性,但只有在您知道正确处理输入文档不需要正确的名称空间处理时才应该使用它。
  • PowerShell的点号:**
  • PowerShell可以方便地将XML文档的DOM(输入文档中节点的层次结构)Map到具有属性的嵌套对象,允许您使用常规的点符号向下钻取文档;例如,XPath查询'/root/elem'的等价物将是$xmlDoc.root.elem

然而,这意味着您只能使用这种表示法来访问您已经知道其在层次结构中的路径的元素-不支持查询(尽管存在支持XPath的Select-Xmlcmdlet)。

  • 此Map忽略名称空间限定符(前缀),因此必须使用仅元素名称,不带任何名称空间前缀;例如,如果输入文档有plaster:parameters元素,则必须将其引用为parameters
  • 像点符号一样方便,但它也有陷阱,其中最值得注意的是准叶元素-那些根本没有子节点或只有非元素子节点的元素,如文本节点-被返回为字符串**而不是元素*,这使得修改它们变得困难。

此外,在反映特定文档的元素和属性名称的本机类型属性和PowerShell添加的属性之间存在名称冲突的可能性--请参阅this answer
简而言之:XMLDOM和PowerShell对象模型之间的Map不是--也不可能是--准确和完整的

ffdz8vbo

ffdz8vbo2#

正如mklement0所指出的,您的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')

相关问题