Powershell XMLDocument сохранить как UTF-8 без спецификации

Aug 19 2020

Я построил объект XML типа System.Xml.XmlDocument.

$scheme.gettype()
IsPublic IsSerial Name BaseType                                                         
-------- -------- ---- --------                                                         
True     False    XmlDocument System.Xml.XmlNode 

Я использую метод save (), чтобы сохранить его в файл.

$scheme.save()

Это сохраняет файл в формате UTF-8 с BOM. Спецификация вызывает проблемы с другими скриптами в дальнейшем.

Когда мы открываем XML-файл в Notepad ++ и сохраняем его как UTF-8 (без спецификации), другие скрипты в дальнейшем не имеют проблем. Поэтому меня попросили сохранить сценарий без спецификации.

В документации MS для метода сохранения указано:

Значение атрибута кодировки берется из свойства XmlDeclaration.Encoding. Если XmlDocument не имеет XmlDeclaration или если XmlDeclaration не имеет атрибута кодирования, сохраненный документ также не будет иметь его.

В документации MS по XmlDeclaration перечислены свойства кодирования UTF-8, UTF-16 и других. В нем не упоминается спецификация.

Есть ли у XmlDeclaration свойство кодирования, которое не учитывает спецификацию?

PS. Это поведение идентично в Powershell 5 и Powershell 7.

Ответы

2 mklement0 Aug 19 2020 at 09:39

К сожалению, явное присутствие encoding="utf-8"атрибута в объявлении XML-документа приводит к тому, что .NET превращает .Save()документ в файл в кодировке UTF-8 со спецификацией, если указан путь к целевому файлу, что действительно может вызвать проблемы.

Запрос на изменение этого параметра был отклонен из-за опасения нарушить обратную совместимость; вот просьба хотя бы задокументировать поведение.

Несколько по иронии судьбы, отсутствие из encodingатрибута приводит .Save()создать UTF-8-закодированные файлы без спецификации.

Поэтому простое решение - удалить атрибут кодирования [1] ; например:

# Create a sample XML document:
$xmlDoc = [xml] '<?xml version="1.0" encoding="utf-8"?><foo>bar</foo>' # Remove the 'encoding' attribute from the declaration. # Without this, the .Save() method below would create a UTF-8 file *with* BOM. $xmlDoc.ChildNodes[0].Encoding = $null # Now, saving produces a UTf-8 file *without* a BOM. $xmlDoc.Save("$PWD/out.xml")

[1] Это безопасно, потому что рекомендация XML W3C фактически предписывает UTF-8 по умолчанию при отсутствии как спецификации, так и encodingатрибута.

1 MathiasR.Jessen Aug 19 2020 at 05:20

Как поясняет BACON в комментариях , строковое значение Encodingатрибута в объявлении XML не имеет никакого отношения к тому, как кодируется файл, содержащий документ.

Вы можете управлять этим путем создания либо StreamWriterили XmlWriterс не-бом UTF8Encoding, а затем передать , что к Save($writer):

$filename = Resolve-Path path\to\output.xml

# Create UTF8Encoding instance, sans BOM
$encoding = [System.Text.UTF8Encoding]::new($false)

# Create StreamWriter instance
$writer = [System.IO.StreamWriter]::new($filename, $false, $encoding)

# Save using (either) writer
$scheme.Save($writer)

# Dispose of writer
$writer.Dispose()

В качестве альтернативы используйте [XmlWriter]:

# XmlWriter Example
$writer = [System.Xml.XmlWriter]::Create($filename, @{ Encoding = $encoding })

Второй аргумент - это [XmlWriterSettings]объект, с помощью которого мы можем осуществлять больший контроль над параметрами форматирования в дополнение к явно заданной кодировке:

$settings = [System.Xml.XmlWriterSettings]@{ Encoding = $encoding
  Indent = $true NewLineOnAttributes = $true
}
$writer = [System.Xml.XmlWriter]::Create($filename, $settings)

#  <?xml version="1.0" encoding="utf-8"?>
#  <Config>
#    <Group
#      name="PropertyGroup">
#      <Property
#        id="1"
#        value="Foo" />
#      <Property
#        id="2"
#        value="Bar"
#        exclude="false" />
#    </Group>
#  </Config>