【问题标题】:Saving XML in UTF-8 with MSXML使用 MSXML 将 XML 保存为 UTF-8
【发布时间】:2010-04-07 21:31:12
【问题描述】:

我正在尝试加载一个简单的 Xml 文件(以 UTF-8 编码):

<?xml version="1.0" encoding="UTF-8"?>
<Test/>

并在 vbscript 中用 MSXML 保存:

Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")

xmlDoc.Load("C:\test.xml")

xmlDoc.Save "C:\test.xml" 

问题是,MSXML 将文件保存为 ANSI 而不是 UTF-8(尽管原始文件以 UTF-8 编码)。

MSDN docs for MSXML 表示 save() 将以 XML 定义的任何编码写入文件:

字符编码基于 XML 声明中的 encoding 属性,例如 .未指定编码属性时,默认设置为 UTF-8。

但这显然至少在我的机器上不起作用。

MSXML 如何以 UTF-8 格式保存?

【问题讨论】:

  • 我没有看到您报告的行为。当我运行该代码时,它会将 XML 文档保存为 UTF-8。我得到一个 UTF-8 声明,实际的字符串是 UTF-8。
  • 是的,很可能只有我的机器(Win2k3)和我同事的(Win2k8 64bit)有这个问题。如果有人能明确说明为什么机器之间的行为会有所不同,那就太好了。

标签: xml localization vbscript utf-8 msxml


【解决方案1】:

您的 XML 文件中没有任何非 ANSI 文本,因此无论是 UTF-8 还是 ASCII 编码,它都是相同的。在我的测试中,将非 ASCII 文本添加到 test.xml 后,MSXML 始终以 UTF-8 编码保存,并且如果有一个 BOM,也会写入 BOM。

http://en.wikipedia.org/wiki/UTF-8
http://en.wikipedia.org/wiki/Byte_order_mark

【讨论】:

  • 所以我猜如果文件中没有 unicode 字节,MSXML 就无法以 UTF-8 保存?
  • 根据定义,如果 ASCII 文件和 UTF-8 文件只包含 ASCII 字符(如果包含 BOM 除外),则它没有区别...
【解决方案2】:

您使用 MSXML 中的另外两个类将正确编码的 XML 写入输出流。

这是我写入通用 IStream 的辅助方法:

class procedure TXMLHelper.WriteDocumentToStream(const Document60: IXMLDOMDocument2; const stream: IStream; Encoding: string = 'UTF-8');
var
    writer: IMXWriter;
    reader: IVBSAXXMLReader;
begin
{
    From http://support.microsoft.com/kb/275883
    INFO: XML Encoding and DOM Interface Methods

    MSXML has native support for the following encodings:
        UTF-8
        UTF-16
        UCS-2
        UCS-4
        ISO-10646-UCS-2
        UNICODE-1-1-UTF-8
        UNICODE-2-0-UTF-16
        UNICODE-2-0-UTF-8

    It also recognizes (internally using the WideCharToMultibyte API function for mappings) the following encodings:
        US-ASCII
        ISO-8859-1
        ISO-8859-2
        ISO-8859-3
        ISO-8859-4
        ISO-8859-5
        ISO-8859-6
        ISO-8859-7
        ISO-8859-8
        ISO-8859-9
        WINDOWS-1250
        WINDOWS-1251
        WINDOWS-1252
        WINDOWS-1253
        WINDOWS-1254
        WINDOWS-1255
        WINDOWS-1256
        WINDOWS-1257
        WINDOWS-1258
}

    if Document60 = nil then
        raise Exception.Create('TXMLHelper.WriteDocument: Document60 cannot be nil');
    if stream = nil then
        raise Exception.Create('TXMLHelper.WriteDocument: stream cannot be nil');

    // Set properties on the XML writer - including BOM, XML declaration and encoding
    writer := CoMXXMLWriter60.Create;
    writer.byteOrderMark := True; //Determines whether to write the Byte Order Mark (BOM). The byteOrderMark property has no effect for BSTR or DOM output. (Default True)
    writer.omitXMLDeclaration := False; //Forces the IMXWriter to skip the XML declaration. Useful for creating document fragments. (Default False)
    writer.encoding := Encoding; //Sets and gets encoding for the output. (Default "UTF-16")
    writer.indent := True; //Sets whether to indent output. (Default False)
    writer.standalone := True;

    // Set the XML writer to the SAX content handler.
    reader := CoSAXXMLReader60.Create;
    reader.contentHandler := writer as IVBSAXContentHandler;
    reader.dtdHandler := writer as IVBSAXDTDHandler;
    reader.errorHandler := writer as IVBSAXErrorHandler;
    reader.putProperty('http://xml.org/sax/properties/lexical-handler', writer);
    reader.putProperty('http://xml.org/sax/properties/declaration-handler', writer);


    writer.output := stream; //The resulting document will be written into the provided IStream

    // Now pass the DOM through the SAX handler, and it will call the writer
    reader.parse(Document60);

    writer.flush;
end;

为了保存到文件,我用 FileStream 调用 Stream 版本:

class procedure TXMLHelper.WriteDocumentToFile(const Document60: IXMLDOMDocument2; const filename: string; Encoding: string='UTF-8');
var
    fs: TFileStream;
begin
    fs := TFileStream.Create(filename, fmCreate or fmShareDenyWrite);
    try
        TXMLHelper.WriteDocumentToStream(Document60, fs, Encoding);
    finally
        fs.Free;
    end;
end;

您可以将函数转换为您喜欢的任何语言。这些是德尔福。

【讨论】:

  • 我将利用詹姆斯邦德的爱好并尝试复活这个线程。在 C++ 类型库中,我对 putProperty 方法有以下定义:HRESULT ISAXXMLReader::putProperty ( unsigned short * pwchName, const _variant_t &amp; varValue ) 这需要无符号短指针作为参数。您是否有机会知道是否有任何枚举或#defines 来支持受支持的属性,或者我如何指定lexical-handlerdeclaration-handler 属性?
  • @wenaxus 我什至不知道lexical-handlerdeclaration-handler 是什么! :) 您可能应该将其作为一个全新的问题提出。
  • 很公平,我刚刚看到您在回答中使用了它们:`reader.putProperty('xml.org/sax/properties/lexical-handler', writer);` 我想我试一试。
【解决方案3】:

当您执行load 时,msxml 不会将处理指令中的编码复制到创建的文档中。所以它不包含任何编码,似乎 msxml 选择了它喜欢的东西。在我的环境中,我不喜欢 UTF-16。

解决方案是提供处理指令并在那里指定编码。如果知道文档没有处理指令,代码就小菜一碟了:

Set pi = xmlDoc.createProcessingInstruction("xml", _
         "version=""1.0"" encoding=""windows-1250""")
If xmlDoc.childNodes.Length > 0 Then
  Call xmlDoc.insertBefore(pi, xmlDoc.childNodes.Item(0))
End If

如果文档可能包含其他处理指令,则必须先将其删除(因此下面的代码必须在上面的代码之前)。我不知道怎么用selectNode来做,所以我只是迭代了所有根节点:

For ich=xmlDoc.childNodes.Length-1 to 0 step -1
  Set ch = xmlDoc.childNodes.Item(ich)
  If ch.NodeTypeString = "processinginstruction" and ch.NodeName = "xml" Then
    xmlDoc.removeChild(ch)
  End If
Next ich

对不起,如果代码没有直接执行,因为我修改了工作版本,它是用自定义的东西而不是 vbscript 编写的。

【讨论】:

  • 我到处搜索,想找到一种使用 vbScript 从 xml 文件中删除处理指令的方法;这是我看到的唯一一个这样做的例子(一旦我这样做了,我当然觉得很愚蠢)。选择一个或多个节点似乎不起作用,因为处理指令不在选择节点和 Xpath 开始搜索的文档元素中。
猜你喜欢
  • 1970-01-01
  • 2020-12-10
  • 1970-01-01
  • 2010-10-19
  • 2015-02-15
  • 1970-01-01
  • 2015-09-19
  • 1970-01-01
  • 2012-04-27
相关资源
最近更新 更多