Powershell XMLDocument zapisuje jako UTF-8 bez BOM

Aug 19 2020

Zbudowałem obiekt XML typu System.Xml.XmlDocument.

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

Używam metody save (), aby zapisać go do pliku.

$scheme.save()

Spowoduje to zapisanie pliku w formacie UTF-8 z BOM. Zestawienie komponentów powoduje problemy z innymi skryptami w przyszłości.

Kiedy otwieramy plik XML w Notepad ++ i zapisujemy go jako UTF-8 (bez BOM), inne skrypty w dół linii nie mają problemu. Więc zostałem poproszony o zapisanie skryptu bez BOM.

Dokumentacja MS dotycząca metody zapisu podaje:

Wartość atrybutu kodowania jest pobierana z właściwości XmlDeclaration.Encoding. Jeśli XmlDocument nie ma XmlDeclaration lub jeśli XmlDeclaration nie ma atrybutu kodowania, zapisany dokument też nie będzie miał.

Dokumentacja MS dotycząca XmlDeclaration zawiera listę właściwości kodowania UTF-8, UTF-16 i innych. Nie wspomina o BOM.

Czy XmlDeclaration ma właściwość kodowania, która pomija BOM?

PS. To zachowanie jest identyczne w programach Powershell 5 i Powershell 7.

Odpowiedzi

2 mklement0 Aug 19 2020 at 09:39

Niestety, jawna obecność encoding="utf-8"atrybutu w deklaracji dokumentu XML powoduje, że .NET przesyła dokument do .Save()pliku zakodowanego w UTF-8 z BOM, jeśli podana jest ścieżka do pliku docelowego, co może rzeczywiście powodować problemy.

Prośba o zmianę tego została odrzucona z obawy o zerwanie wstecznej kompatybilności; oto prośba, aby przynajmniej udokumentować zachowanie.

Nieco Paradoksalnie, brak o encodingatrybut powoduje .Save()tworzyć UTF-8 zakodowanych plików bez LM.

Dlatego prostym rozwiązaniem jest usunięcie atrybutu kodowania [1] ; na przykład:

# 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] Jest to bezpieczne, ponieważ zalecenie XML W3C skutecznie narzuca UTF-8 jako domyślny w przypadku braku zarówno BOM, jak i encodingatrybutu.

1 MathiasR.Jessen Aug 19 2020 at 05:20

Jak wyjaśnia BACON w komentarzach , wartość ciągu Encodingatrybutu w deklaracji XML nie ma żadnego wpływu na sposób kodowania pliku zawierającego dokument.

Można to kontrolować poprzez tworzenie albo StreamWriteralbo XmlWriterz braku BOM UTF8Encoding, a następnie przekazać to do 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()

Alternatywnie użyj [XmlWriter]:

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

Drugi argument to [XmlWriterSettings]obiekt, dzięki któremu możemy sprawować większą kontrolę nad opcjami formatowania oprócz jawnego ustawienia kodowania:

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