PowerShell - ASCII编码将特殊字符更改为问号。

4

我正在使用以下Powershell脚本将字符串转换为XML,然后导出到文件(以此方式来保留缩进):

[xml]$xmloutput = $xml
$sw = New-Object System.IO.StringWriter
$writer = New-Object System.Xml.XmlTextWriter($sw)
$writer.Formatting = [System.Xml.Formatting]::Indented
$xmloutput.WriteContentTo($writer)
$sw.ToString() | Set-Content -Encoding 'ASCII' $filepath

由于供应商的限制,目标必须是ASCII格式。我遇到的问题是ASCII只会将特殊字符转换为问号(例如:Ö变成?)。

如果我使用UTF8编码,则输出看起来完全正常。我甚至尝试过先保存为UTF8然后再转换为ASCII,但仍然会出现同样的问题(导出问号):

[System.Io.File]::ReadAllText($filepath) | Out-File -FilePath $filepath -Encoding ASCII

如果在将字符串转换为XML之前尝试替换字符(使用ASCII代码Ö),它只会转换和保留剩余的和号,使其无用。

有没有办法让Powershell正确地将这些字符保存到文件中?

编辑:我想在输出的文件中看到特殊字符,但如果那不符合ASCII标准,我希望看到它的ASCII代码(例如Ö

我也不想只看到一个O,我需要实际的字符。


5
你期望看到哪个ASCII字符? - Josh Lee
@JoshLee - 我想看到 Ö 字符,但如果它不符合 ASCII 标准,我希望看到 Ö - chazbot7
那是一个扩展ASCII字符,所以我认为它应该可以工作。我不太了解PowerShell以及它如何处理代码页。 - Squashman
尝试使用“-Encoding Default”或“-Encoding OEM”。根据此Microsoft文档。https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-5.1 - Squashman
他们没有告诉你为什么它被拒绝了吗?我猜他们需要使用单字节编码。当使用 Out-File -FilePath $filepath -Encoding default 时,应该可以工作,因为它使用的是 ANSI 代码页。 - Squashman
显示剩余3条评论
2个回答

6

XML文档中的所有字符都是Unicode编码。然而,XML文档的表示形式有一个文档编码。不属于该字符集的字符会被写成字符实体引用,通常以十六进制表示。数字是Unicode码点。

看起来你的合作伙伴要求使用ASCII作为文档编码。

XmlDocument 不太好处理,但是带有文档编码设置的 XmlWriter 可以使用:

$myString = 'hellÖ'

[xml]$myXml = [System.Management.Automation.PSSerializer]::Serialize($myString)

$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = [System.Text.Encoding]::ASCII
$settings.Indent = $true

$writer = [System.Xml.XmlWriter]::Create("./test.xml", $settings)
$myXml.Save($writer)
$writer.Dispose()

这将生成一个ASCII编码的文本文件,其中包含XML声明,声明文档编码为ASCII,并使用十六进制数字字符实体引用来表示在ASCII中无法表示的XML内容字符:

<?xml version="1.0" encoding="us-ascii"?>
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <S>hell&#xD6;</S>
</Objs>

正如您在这里所看到的,在C1控制字符和Latin-1补充块中,U+00D6(&#D6;)代表着带有分音符的拉丁大写字母O,即Ö。


1
太好了。我一直在尝试在我的答案中让它工作;看来问题是必须使用XmlWriter而不是XmlTextWriter(当我为文本编写器设置编码时,它绝对没有创建实体)。我希望@chazbot7将他的接受改为这个答案。 - briantist
1
没错,这也可以!根据@briantist的建议,我决定接受这个答案。 - chazbot7
1
哦,是的。我们曾经遇到过一个SAS程序输出XML的问题,我记得我们使用了类似的解决方案。当它写出XML文件时,必须加上encoding=ISO-8859-1 - Squashman
2
@briantist XmlDocument类非常灵活,但也异常复杂。我看到了你的努力,觉得一定有其他方法。 - Tom Blodget

2
这并不仅限于PowerShell,而是普遍存在的字符编码问题。
基本上,该字符不是ASCII,而是ISO 8859-1。
但是,也可以通过让XmlTextWriter直接向文件写入来简化此过程,因为您可以通过它控制编码。请尝试以下操作:
$myString = 'hellÖ'

[xml]$myXml = [System.Management.Automation.PSSerializer]::Serialize($myString)

$myEncoding = [System.Text.Encoding]::GetEncoding('iso-8859-1')

$writer = New-Object System.Xml.XmlTextWriter($filepath, $myEncoding)
$writer.Formatting = [System.Xml.Formatting]::Indented

$myXml.WriteContentTo($writer)

$writer.Flush()
$writer.Close()
$writer.Dispose()

这将使用ISO 8859-1编码写入文件,但不会转换为XML实体。
因此,如果您的应用程序需要真正的ASCII,而没有扩展集,则这将无法工作。 如果它只需要单字节编码并且该编码中的字符集足够,则可以使用。
如何使用实体:
步骤1:忽略我写的内容,改用Tom Blodget的答案
你可以在ASCII编码器上设置一个自定义回调函数,当它遇到无法表示为ASCII的字符时,就调用你的函数来获取替代字符。你的函数会返回字符实体版本。
从技术上讲,这可能会产生反效果。由于必须从编码器返回和号&,XmlWriter可能会看到它并“有用地”将其替换为&amp;,这会破坏你的编码。
直接从PowerShell使用此回调可能是可能的,但会有点麻烦。使用一些C#和Add-Type会更容易。
或者你可以使用游击战版本的这种方法:编写你的XML字符串,然后手动替换任何不是ASCII的字符。
我在这里使用了一个正则表达式引擎的替换方法,它接受一个函数用于匹配评估。该正则表达式仅匹配不在“BasicLatin”Unicode命名块中的任何字符。
$myString = 'hellÖ'

[xml]$myXml = [System.Management.Automation.PSSerializer]::Serialize($myString)

$sw = New-Object System.IO.StringWriter
$writer = New-Object System.Xml.XmlTextWriter($sw)
$writer.Formatting = [System.Xml.Formatting]::Indented
$myXml.WriteContentTo($writer)

$output = [RegEx]::Replace($sw.ToString(), '\P{IsBasicLatin}', { param($match) '&#{0};' -f [int][char]$match.Value })
$output  | Set-Content -Encoding 'ASCII' -LiteralPath $filepath

据我所知,这将完全符合你的要求。

这让我得到了我想要的确切输出,但供应商拒绝了编码。可悲的是,我没有他们关于这个的确切需求文档(非常令人沮丧),所以我会继续尝试! - chazbot7
搞定了!!我已经拿到了ASCII码并且它们被接受了。非常感谢。 - chazbot7
1
@chazbot7 请查看Tom Blodget的回答。我相信那是正确的方法。这是我在我的答案中希望达到的目标,但没有完全做到。我强烈建议您使用该方法,而不是使用正则表达式替换。 - briantist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接