2010-04-07 1 views
2

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

<?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 docs for MSXML говорит, что сохранить() запишет файл в любом кодирующая XML определяется в:

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

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

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

+2

Я не вижу поведения, о котором вы сообщаете. Когда я запускаю этот код, он сохраняет XML-документ в UTF-8. Я получаю декларацию UTF-8, а фактические строки находятся в UTF-8. – Cheeso

+0

Возможно, это может быть только моя машина (Win2k3) и моя коллега (Win2k8 64bit), у которой есть эта проблема. Будет хорошо, если кто-то может сказать окончательно, почему поведение отличается от машин. – stung

ответ

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

+0

И поэтому я думаю, что MSXML не может сохранить в UTF-8, если в файле нет юникодовых байтов? – stung

+3

По определению, нет никакой разницы между файлом ASCII и UTF-8, если он содержит только символы ASCII (кроме спецификации, если они включены) ... –

3

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

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; 

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

+0

Я собираюсь использовать хобби Джеймса Бонда и попытаться воскресить эту тему , В библиотеке типов C++ у меня есть следующее определение метода putProperty: 'HRESULT ISAXXMLReader :: putProperty (unsigned short * pwchName, const _variant_t & varValue)' Для этого требуется неподписанный короткий указатель в качестве параметра. Вы случайно знаете, есть ли перечисления или #defines для поддерживаемых свойств или как я могу указать свойства 'lexical-handler' и' declage-handler'? – Robertas

+0

@wenaxus Я даже не знаю, что такое 'lexical-handler' или' declage-handler'! :) Вероятно, вы должны задать это как новый вопрос. –

+0

Достаточно справедливо, я только видел, что вы использовали их в своем ответе: 'reader.putProperty ('http://xml.org/sax/properties/lexical-handler', writer); и я подумал, что я попытаюсь. – Robertas

1

Когда вы выполняете 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.

+0

Я искал по всему месту, чтобы найти способ удалить инструкцию по обработке из XML-файла с помощью vbScript; это был единственный пример способа сделать это, что я видел (однажды я сделал это, конечно, немым). Выберите узел или узлы, похоже, не работает, потому что инструкция обработки не находится в элементе документа, где находится узел выбора, и Xpath начинает поиск. –