Сохранение XML в UTF-8 с помощью MSXML

Я пытаюсь загрузить простой файл Xml (в кодировке UTF-8):

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

И сохраните его с помощью MSXML в vbscript:

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

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

xmlDoc.Save "C:\test.xml" 

Проблема в том, что MSXML сохраняет файл в формате ANSI вместо UTF-8 (несмотря на то, что исходный файл закодирован в UTF-8).

В документах MSDN для MSXML говорится, что save ( ) запишет файл в любой кодировке, в которой определен XML:

Кодировка символов основана на атрибуте кодировки в объявлении XML, например. Если атрибут кодировки не указан, по умолчанию используется UTF-8.

Но это явно не работает, по крайней мере, на моей машине.

Как можно сохранить MSXML в UTF-8?


person stung    schedule 07.04.2010    source источник
comment
Я не вижу поведения, о котором вы сообщаете. Когда я запускаю этот код, он сохраняет XML-документ в UTF-8. Я получаю объявление UTF-8, а фактические строки находятся в UTF-8.   -  person Cheeso    schedule 08.04.2010
comment
Да, это вполне может быть только моя машина (Win2k3) и мой коллега (Win2k8 64bit), у которых есть эта проблема. Было бы хорошо, если бы кто-нибудь мог окончательно сказать, почему поведение разных машин различается.   -  person stung    schedule 10.04.2010


Ответы (3)


В вашем XML-файле нет текста, отличного от ANSI, поэтому он будет идентичным независимо от того, закодирован ли он в UTF-8 или ASCII. В моих тестах после добавления текста, отличного от ASCII, в test.xml MSXML всегда сохраняет в кодировке UTF-8, а также записывает спецификацию, если таковая была в начале.

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

person Kyle Alons    schedule 09.04.2010
comment
И поэтому я думаю, что MSXML не может сохранить в UTF-8, если в файле нет байтов Unicode? - person stung; 10.04.2010
comment
По определению нет разницы между файлом ASCII и UTF-8, если он содержит только символы ASCII (кроме спецификации, если она включена) ... - person Kyle Alons; 10.04.2010

Вы используете два других класса в 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;

Для сохранения в файл я вызываю версию Stream с помощью FileStream:

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;

Вы можете преобразовать функции на любой язык, который вам нравится. Это Delphi.

person Ian Boyd    schedule 11.05.2010
comment
Я собираюсь использовать хобби Джеймса Бонда и попытаться воскресить эту ветку. В библиотеке типов C ++ у меня есть следующее определение метода putProperty: HRESULT ISAXXMLReader::putProperty ( unsigned short * pwchName, const _variant_t & varValue ) для этого требуется короткий указатель без знака в качестве параметра. Вы случайно не знаете, есть ли перечисления или #define для поддерживаемых свойств или как я могу указать свойства lexical-handler и declaration-handler? - person Robertas; 21.03.2014
comment
@wenaxus Я даже не знаю, что такое lexical-handler или declaration-handler! :) Вам, вероятно, следует задать это как совершенно новый вопрос. - person Ian Boyd; 21.03.2014
comment
Честно говоря, я только что видел, как вы использовали их в своем ответе: `reader.putProperty ('xml. org / sax / properties / lexical-handler ', писатель); `и подумал, что попробую. - person Robertas; 21.03.2014

При выполнении 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.

person Jarekczek    schedule 10.10.2012
comment
Я искал повсюду, чтобы найти способ удалить инструкцию обработки из файла xml с помощью vbScript; это был единственный способ сделать это, который я видел (как только я это сделал, я, конечно, почувствовал себя тупым). Выбрать узел или узлы, похоже, не работает, потому что инструкции по обработке нет в элементе документа, в котором узел выбора и Xpath начинает поиск. - person Pow-Ian; 20.04.2016