Установите значение узла x XML в Powershell - PullRequest
3 голосов
/ 18 февраля 2020

У меня есть сценарий PowerShell, в котором значения для XML -файла будут устанавливаться сценарием. Это прекрасно работает, когда все дочерние узлы имеют уникальные имена. Однако я адаптирую XML -файл к тому, где некоторые узлы повторяются. Теперь я получаю сообщение об ошибке в Powershell.

У меня вопрос: как установить X-й узел в XML на определенное значение через PowerShell?

Короче говоря, мой скрипт работает следующим образом:

cls

[xml] $xml1 = '<Lvl1>
                    <Lvl2>""</Lvl2>
                    <Lvl2>""</Lvl2>
                </Lvl1>' 

$xml1.Lvl1.Lvl2='./'

$xml1.Save("text.xml")

Существует два раза один и тот же узел (Lvl2), поэтому я получаю следующую ошибку в PowerShell: «Невозможно установить« Lvl2 », поскольку в качестве значений для установки свойств XmlNode могут использоваться только строки».

Когда я удаляю один (Lvl2) узел, скрипт работает как шарм.

Пожалуйста, совет.

Ответы [ 5 ]

3 голосов
/ 18 февраля 2020

Вы всегда можете использовать. NET синтаксис, он работает как шарм.

$xml1.SelectSingleNode('Lvl1/Lvl2[2]').InnerText='./'
1 голос
/ 18 февраля 2020

Вы можете получить дочерние узлы Lvl1 как aray, а затем использовать индекс для настройки требуемого узла:

[xml] $xml1 = '<Lvl1>
                    <Lvl2>""</Lvl2>
                    <Lvl2>""</Lvl2>
                </Lvl1>' 

$lvl2Nodes = @($xml1.Lvl1.ChildNodes)

$lvl2Nodes[1].'#text' = "blah"  # updating the second childnode only

$xml1.Save("D:\text.xml")

Результат:

<Lvl1>
  <Lvl2>""</Lvl2>
  <Lvl2>blah</Lvl2>
</Lvl1>
1 голос
/ 18 февраля 2020
$xml1 = [xml]@'
    <Lvl1>
        <Lvl2>""</Lvl2>
        <Lvl2>""</Lvl2>
    </Lvl1>
'@

$xml1.GetElementsByTagName('Lvl2') | ForEach-Object { $_.InnerText = './' }

$xml1.Save("text.xml")

Get-Content -Path "text.xml" # debugging output

Отладочный вывод : .\SO\60280990.ps1

<Lvl1>
  <Lvl2>./</Lvl2>
  <Lvl2>./</Lvl2>
</Lvl1>

Приведенный выше подход работает даже для немного более сложных входных данных, например, следующим образом:

$xml1 = [xml]@'
<root>
    <Lvl1>
        <Lvl2>"a"</Lvl2>
        <Lvl2>"b"</Lvl2>
    </Lvl1>
    <Lvl0>
        <Lvl1>
            <Lvl2>"c"</Lvl2>
            <Lvl2>"d"</Lvl2>
        </Lvl1>
    </Lvl0>
</root>
'@
0 голосов
/ 22 февраля 2020

Полезный ответ Павла Дила предоставляет краткое решение, которое обходит Адаптация PowerShell XML DOM , которая использует знакомую точечную нотацию , в пользу непосредственного использования. NET методов.

Однако Точечная запись PowerShell удобна и использует знакомый синтаксис, поэтому стоит придерживаться ее, где это возможно и уместно .

К сожалению, в данном случае это возможно только при гибридном подходе , объединяющем точечную запись (используя имена элементов или атрибутов так же, как * 1019). * property names) с собственными свойствами базовых типов System.Xml.XmlNode:

Примечание: я использую следующие измененные XML и имя переменной $xml в следующих командах:

[xml] $xml = @'
<Lvl1>
  <Lvl2>a</Lvl2>
  <Lvl2>b</Lvl2>
  <Other><Stuff>c</Stuff></Other>
</Lvl1>
'@ 

Чтобы присвоить значение first Lvl2 child , вы можете использовать следующий подход:

# Assign to the *first* Lvl2 element.
$xml.Lvl1['Lvl2'].InnerText = './'
  • Использование индексатора * 104 1 * (['Lvl2']) вместо точечная запись (.Lvl2) использует System.Xml.XmlElement типовой собственный индексатор типа для нацеливания на первый дочерний элемент с указанным именем .

  • Свойству .InnerText этого элемента можно затем присвоить.

Если вам необходимо присвоить дочернему элементу с заданным индексом , необходим другой подход с использованием индексации на основе чисел c (0) коллекция XmlElement .ChildNodes (упрощенная версия полезный ответ Тео ):

# Assign to the *last* Lvl2 element.
$xml1.Lvl1.ChildNodes[-1].InnerText = 'last'

Что не работа с PowerShell [Core] 7.0:

К сожалению, индексирование непосредственно в дочерние элементы с использованием только точечной нотации не работает для назначений :

# DOESN'T WORK: PowerShell *quietly ignores* the assignment.
$xml.Lvl1.Lvl2[-1] = 'last' 

Об этом неожиданном поведении было сообщено в этой проблеме GitHub .

Однако получение a ценить таким образом (обычно) w orks fine :

# OK
PS $xml.Lvl1.Lvl2[0]
a

Предостережение :

Если именованный элемент оказывается дочерним элементом only и сам по себе имеет элемент children , индексирование не работает , предотвращая унифицированную обработку скаляров и коллекций, которую обычно обеспечивает PowerShell:

# DOES NOT WORK, because there is only a single 'Other' element
# *and* it has a child element.
PS $xml.Lvl1.Other[0]
 # !! No output

Удивительно, но PowerShell использует собственный тип по имени indexer для поиска элемента с именем '0' - который по определению завершается ошибкой, учитывая, что XML имена элементов должны не начинать с цифр .

В этом предложении GitHub предлагается использовать автоматическое позиционное индексирование PowerShell c в этом случае на основе аргумента, являющегося целое число .

0 голосов
/ 18 февраля 2020
PS D:\TEMP> type lvl.xml
<Lvl1>
                    <Lvl2>"test1"</Lvl2>
                    <Lvl2>"test2"</Lvl2>
                </Lvl1>
PS D:\TEMP> $xml = New-Object XML
PS D:\TEMP> $xml.Load("lvl.xml")
PS D:\TEMP> $element =  $xml.SelectSingleNode("/Lvl1/Lvl2[2]")
PS D:\TEMP> $element.InnerText ="bla"
PS D:\TEMP> $xml.Save("lvl2.xml")
PS D:\TEMP> type lvl2.xml
<Lvl1>
  <Lvl2>"test1"</Lvl2>
  <Lvl2>bla</Lvl2>
</Lvl1>
PS D:\TEMP>

В XmlPath (то есть '/ Lvl1 / Lvl2 [2]') '[2]' относится ко второму разу, когда эта вещь существует.

...