Powershell XMLDocument guardar como UTF-8 sin BOM

Aug 19 2020

Construí un objeto XML de tipo System.Xml.XmlDocument.

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

Utilizo el método save () para guardarlo en un archivo.

$scheme.save()

Esto guarda el archivo en formato UTF-8 con BOM. La lista de materiales causa problemas con otros scripts en el futuro.

Cuando abrimos el archivo XML en Notepad ++ y lo guardamos como UTF-8 (sin la lista de materiales), otros scripts en la línea no tienen ningún problema. Así que me pidieron que guardara el script sin la lista de materiales.

La documentación de MS para el método de guardado dice:

El valor del atributo de codificación se toma de la propiedad XmlDeclaration.Encoding. Si XmlDocument no tiene un XmlDeclaration, o si XmlDeclaration no tiene un atributo de codificación, el documento guardado tampoco lo tendrá.

La documentación de MS sobre XmlDeclaration enumera las propiedades de codificación de UTF-8, UTF-16 y otras. No menciona una lista de materiales.

¿XmlDeclaration tiene una propiedad de codificación que omite la lista de materiales?

PD. Este comportamiento es idéntico en Powershell 5 y Powershell 7.

Respuestas

2 mklement0 Aug 19 2020 at 09:39

Desafortunadamente, la presencia explícita de un encoding="utf-8"atributo en la declaración de un documento XML hace que .NET cambie .Save()el documento a un archivo codificado en UTF-8 con BOM si se proporciona una ruta de archivo de destino, lo que de hecho puede causar problemas.

Se rechazó una solicitud para cambiar esto por temor a romper la compatibilidad con versiones anteriores; aquí hay una solicitud para al menos documentar el comportamiento.

Irónicamente, la ausencia de un encodingatributo hace .Save()que se creen archivos codificados en UTF-8 sin una lista de materiales.

Por tanto, una solución sencilla es eliminar el atributo de codificación [1] ; p.ej:

# 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] Esto es seguro de hacer, porque la Recomendación XML W3C exige de manera efectiva UTF-8 como predeterminado en ausencia de BOM y encodingatributo.

1 MathiasR.Jessen Aug 19 2020 at 05:20

Como explica BACON en los comentarios , el valor de cadena del Encodingatributo en la declaración XML no influye en cómo se codifica el archivo que contiene el documento.

Esto se puede controlar mediante la creación de una cualquiera StreamWritero un XmlWritercon un no-BOM UTF8Encoding, luego pasar que a 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()

Alternativamente, use un [XmlWriter]:

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

El segundo argumento es un [XmlWriterSettings]objeto, a través del cual podemos ejercer un mayor control sobre las opciones de formato además de establecer explícitamente la codificación:

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