VBScript FileSystemObject如何对字符进行编码?

4

我有这个vbscript代码:

    Set fs = CreateObject("Scripting.FileSystemObject")
    Set ts = fs.OpenTextFile("tmp.txt", 2, True)

    for i = 128 to 255
        s = chr(i)
        if lenb(s) <>2 then
            wscript.echo i
            wscript.quit
        end if
        ts.write s
    next
    ts.close

在我的系统上,每个整数都被转换为双字节字符:在该范围内没有无法用字符表示的数字,也没有需要超过2个字节的数字。但是当我查看文件时,我只发现了127个字节。
这个答案:https://dev59.com/cl0Z5IYBdhLWcg3wdQEy#31436726 暗示FSO创建UTF文件并插入BOM。但是该文件仅包含127个字节,没有字节顺序标记。
FSO如何决定如何编码文本?哪种编码允许8位单字节字符?哪些编码不包括255个8位单字节字符?
(关于FSO如何读取字符的答案也可能很有趣,但这不是我具体在问的)。
编辑:我将我的问题限制为高位字符,以明确问题是什么。(关于低位字符的答案也可能很有趣,但这不是我具体在问的)。

这个回答解决了你的问题吗?FileSystemObject - 读取Unicode文件 - user692942
它不会创建UTF-8文件,请不要混淆Unicode和UTF-8。 - user692942
这个回答解决了你的问题吗?在VBScript中读取UTF-8文本文件 - user692942
正如所述,我特别询问写作方面的问题。以及关于写作方面已经提出的声明。不涉及写作的答案不回答问题。我使用“UTF”一词来包括UTF-16(包括UCS-2)。如果您知道一个回答可以回答在任何情况下FSO写入哪种类型的UTF(或类似格式)的问题,那可能是相关的。 - david
实际上,在我的系统上,Chr返回16位UTF字符。与ChrW中的字符相同。 ascb(midb(chr(i),1,1))= ascb(midb(chrw(i),1,1)).. ChrB返回单字节字符(作为无效的bstr)。 - david
显示剩余2条评论
2个回答

4

简短回答:

文件系统对象使用与系统区域设置相关联的代码页将"Unicode"映射到"ASCII"。(Chr和ChrW使用用户区域设置。)

应用:

系统代码页和线程(用户)代码页之间可能存在静默转位错误。如果代码页中缺少代码点,或者像日语和UTF-8一样,代码页包含多字节字符,则可能会出现编码和解码错误。

VBScript没有本地方法来检测用户、线程或系统代码页。可以从通过SetLocale设置的区域设置或由GetLocale返回的线程(用户)代码页推断出来(这里有一个列表: https://www.science.co.il/language/Locale-codes.php),但是似乎没有任何MS文档。在Win2K+上,可以使用WMI查询系统代码页。CHCP命令查询和更改OEM代码页,它既不是用户代码页也不是系统代码页。

系统代码页可能会被应用程序清单欺骗。除非创建具有新清单的新进程或更改注册表后重新启动系统,否则应用程序(如cscript或wscript)或脚本(如VBScript或JScript)无法更改其父级系统。
 s = chr(i) 
'creates a Unicode string, using the Thread Locale Codepage. 

不存在的字符编码点将被映射为控制字符:127 变成 U+00FF(一个标准的Unicode控制字符),128 变成 U+20AC(欧元符号)而 129 变成 0081(这是一个Unicode控制字符区域中的编码点)。在VBScript中,线程区域设置可以通过SetLocale和GetLocale进行设置和读取。

    createobject("Scripting.FileSystemObject").OpenTextFile(strOutFile, 2, True).write s
   'creates a 'code page' string, using the System Locale Codepage. 

有两种方法可以处理Windows无法映射的Unicode值:它可以映射到默认字符,或返回错误。 "Scripting.FileSystemObject"使用错误设置,并抛出异常。
更详细地说,线程区域设置默认情况下是用户区域设置,即“区域和语言”控制面板小程序中的日期和时间格式设置(在不同版本的Windows中称为不同的名称)。它有一个关联的代码页。根据MS国际化专家Michka(Michael Kaplan,已故)的说法,它具有代码页是为了使月份和星期几可以用适当的字符编写,并且不应用于任何其他目的。
ASP经典人员显然有其他想法,因为Response.CodePage是线程区域设置,并且可以通过vbscript GetLocale和SetLocale等方法进行控制。如果更改了用户区域设置,则会通知所有进程,并更新使用默认值的任何线程。(我没有测试当前使用非默认值的线程会发生什么)。 系统区域设置也称为“非 Unicode 程序的语言”,可以在“区域和语言”应用程序中找到,但需要重新启动才能更改。这是 Windows(“系统”)内部用于映射“A” API 和“W” API 之间的值。更改此设置不会影响 Windows GUI 的语言(即不是“非 Unicode 程序”)。

假设“时间和日期”设置与“非 Unicode 程序的语言”匹配,任何可以创建有效 Unicode 代码点的 Chr(i) 都将从 Unicode 精确地映射回“代码页”。请注意,对于“控制字符”,此方法也适用:还要注意,它不可逆转:UTF-CodePage-UTF 并不总是完全往返。众所周知(字符、修饰符)-CodePage-(复杂字符)无法正确往返,其中 Unicode 定义了构造语言字符表示的多种方式。

如果"时间和日期"与"非Unicode程序的语言"不匹配,就会出现任何翻译问题,例如U+0101在cp28594上是0xE0,在cp28603上是0xE2:Chr(224)会通过U+0101进行处理,并写成226。
即使没有转置错误,如果"时间和日期"与"非Unicode程序的语言"不匹配,则在翻译为系统区域设置时程序可能会失败:如果Unicode代码点没有匹配的代码页代码点,则FileSystemObject将引发异常。

Chr(i)可能存在映射错误,从代码页到Unicode。 代码页1041(日语)是一个双字节代码页(可能是Shift JIS)。0x81仅是双字节对的第一个字节。为了与其他代码页面保持一致,0x81应映射到控制字符0081,但在给定81和代码页1041时,Windows假定缓冲区或BSTR中的下一个字节是双字节对的第二个字节(我还没有确定转换之前或之后是否出现错误)。 Chr(& amp; H81)被映射到U + xx81(81,xx)。当我这样做时,我得到了U + 4581,这是一个CJK统一表意符号(紫色水鱼草):它不被代码页1041映射。

Chr(1)处的映射错误不会导致VBScript异常发生在创建点。如果创建的UTF-16代码点无效或不在系统区域设置代码页上,将在.write处发生FileSystemObject异常。通过使用ChrW(i)而不是Chr(i),可以避免这种特定问题。在代码页1041上,ChrW(129)变成Unicode控制字符0081,而不是xx81。

背景:

程序可以使用任何已安装的代码页映射Unicode和“codepage”:Windows函数MultiByteToWideCharWideCharToMultiByte将[UINT CodePage]作为第一个参数。该机制在Windows内部用于将“A”API映射到“W”API,例如GetAddressByNameA和GetAddressByNameW。Windows在内部是“W”(宽,16位),并且在调用时将“A”字符串映射到“W”字符串,并在返回时从“W”映射回“A”。当Windows进行映射时,它使用与“系统区域设置”关联的代码页,也称为“非Unicode程序的语言”。

Windows API函数WriteFile写入字节而不是字符,因此它不是“A”或“W”函数。使用它的任何程序都必须处理字符串和字节之间的转换。c函数fwrite写入字符,因此它可以处理16位字符,但它无法处理像UTF-8或UTF-16这样的可变长度码点:同样,使用“fwrite”的任何程序都必须处理字符串和单词之间的转换。

C++函数fwrite可以处理UTF格式,而编译器函数_fwrite则根据编译器的不同而有所不同。在Windows上,如果需要进行代码页转换,则使用MultiByteToWideChar和WideCharToMultiByte API。 "A"代码页和"A" API被称为"ANSI"或"ASCII"或"OEM",最初是8位字符,然后发展为双字节字符,现在已经发展为UTF-8(1..3个字节)。 "W" API最初是16位字符,然后发展为UTF-16(1..6个字节)。两者都是多字节字符编码:区别在于对于"A" API和代码页,字长为8位:对于"W" API和UTF-16,字长为16位。由于它们都是多字节映射,并且因为“byte”、“word”、“char”和“character”在不同的上下文中意义不同,以及因为“A”和特别是“W”与多年前的含义不同,我只是使用“A”和“W”和“code page”和“Unicode”。
"OEM"是与另一个语言环境相关联的代码页:控制台I/O API。它是每个进程的(它是一个线程区域设置),可以动态更改(使用CHCP命令),其默认值在安装时设置:没有提供GUI来更改存储在注册表中的值。大多数控制台程序不使用控制台I/O API,并且如所写,使用系统语言环境、用户语言环境或两者混合(有时是无意中)。系统语言环境可以通过使用“清单”进行欺骗,并且有一个名为“AppLocale”的WinXP实用程序也可以做到这一点。"

3

FSO决定如何在文件打开时对文本进行编码。使用以下format参数:

Set ts = fs.OpenTextFile("tmp.txt", 2, True, -1)
'                                            ↑↑ 

资源:OpenTextFile 方法

Syntax


object.OpenTextFile(filename[, iomode[, create[, format]]])

Arguments

object - Required. Object is always the name of a FileSystemObject.

filename - Required. String expression that identifies the file to open.

iomode - Optional. Can be one of three constants: ForReading, ForWriting, or ForAppending.

create - Optional. Boolean value that indicates whether a new file can be created if the specified filename doesn't exist. The value is True if a new file is created, False if it isn't created. If omitted, a new file isn't created.

format - Optional. One of three Tristate values used to indicate the format of the opened file.

TristateTrue = -1 to open the file as Unicode,
TristateFalse = 0 to open the file as ASCII,
TristateUseDefault = -2 to open the file as the system default.

If omitted, the file is opened as ASCII.


虽然这不是他们所问的,但是。 - user692942
更不用说这个问题已经被多次讨论过了,而ADODB.Stream是正确的方法。你知道 Unicode 和 UTF-8 是有区别的吧? - user692942
我不清楚值128-255是否具有“ASCII”表示形式,也不知道这些值如何从UTF-16值进行编码。我将尝试扩展问题。 - david
@david 微软在他们永恒的智慧中,一直在不断地使用误称 ASCIIAnsi... - JosefZ

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