Как установить оператор кодирования в объявлении XML при выполнении преобразования XSL с использованием COM Msxml2.XSLTemplate? - PullRequest
2 голосов
/ 11 ноября 2009

Я написал простой установщик пакетов в WinBatch, который должен обновить XML-файл с информацией о содержимом пакета. Моя первая попытка была связана с загрузкой файла с помощью Msxml2.DOMDocument, добавлением узлов и данных по мере необходимости, затем сохранением данных на диск. Это сработало достаточно хорошо, за исключением того, что в новых данных не было бы табуляции и пробелов CR / LF. Решением, которое я придумал, было написание таблицы стилей XSL, которая воссоздаст файл XML с добавленными пробелами. Я делаю это с помощью:

  1. загрузка файла XSL в объект Msxml2.FreeThreadedDOMDocument
  2. установка этого объекта в качестве свойства таблицы стилей объекта Msxml2.XSLTemplate
  3. создание процессора XSL через Msxml2.XSLTemplate.createProcessor ()
  4. установка моего исходного Msxml2.DOMDocument в качестве входного свойства процессора XSL
  5. Вызов метода transform () процессора XSL и сохранение вывода в файл.

Это работает так же, как и переформатирование XML-файла с использованием табуляции и возврата каретки, но мое объявление XML выглядит как <?xml version="1.0"?> или <?xml version="1.0" encoding="UTF-16"?> в зависимости от того, использовал ли я объекты Msxml2. *. 6.0 или Msxml2. * (A отступить, если система не имеет 6.0).

Если кодировка установлена ​​в UTF-16, Msxml12.DOMDocument жалуется на попытку преобразовать UTF-16 в 1-байтовую кодировку при следующем запуске программы установки моего пакета. Я попытался создать и добавить декларацию XML с использованием метода createProcessingInstruction () для объектов XML и XSL DOM, но ни один из них не влияет на выходные данные процессора XSLTemplate. Я также установил кодировку UTF-8 в теге <xsl:output/> в моем XSL-файле.

Вот соответствующий код в моем скрипте Winbatch:

    xmlDoc = ObjectCreate("Msxml2.DOMDocument.6.0")
    if !xmlDoc then xmlDoc = ObjectCreate("Msxml2.DOMDocument")

    xmlDoc.async = @FALSE
    xmlDoc.validateOnParse = @TRUE
    xmlDoc.resolveExternals = @TRUE
    xmlDoc.preserveWhiteSpace = @TRUE
    xmlDoc.setProperty("SelectionLanguge", "XPath")
    xmlDoc.setProperty("SelectionNamespaces", "xmlns:fns='http://www.abc.com/f_namespace'")
    xmlDoc.load(xml_file_path)

    xslStyleSheet = ObjectCreate("Msxml2.FreeThreadedDOMDocument.6.0")
    if !xslStyleSheet then xslStyleSheet = ObjectCreate("Msxml2.FreeThreadedDOMDocument")

    xslStyleSheet.async = @FALSE
    xslStyleSheet.validateOnParse = @TRUE
    xslStyleSheet.load(xsl_style_sheet_path)

    xslTemplate = ObjectCreate("Msxml2.XSLTemplate.6.0")
    if !xslTemplate then xslTemplate = ObjectCreate("Msxml2.XSLTemplate")

    xslTemplate.stylesheet = xslStyleSheet

    processor = xslTemplate.createProcessor()
    processor.input = xmlDoc
    processor.transform()

    ; create a new file and write the XML processor output to it
    fh = FileOpen(output_file_path, "WRITE" , @FALSE)
    FileWrite(fh, processor.output)
    FileClose(fh)

Таблица стилей с небольшими изменениями для защиты невинных:

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

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>
    <xsl:template match="/">
        <fns:test_station xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fns="http://www.abc.com/f_namespace">
            <xsl:for-each select="/fns:test_station/identification">
                <xsl:text>&#x0A;    </xsl:text>
                <identification>
                    <xsl:for-each select="./*">
                        <xsl:text>&#x0A;        </xsl:text>
                        <xsl:copy-of select="."/>
                    </xsl:for-each>
                    <xsl:text>&#x0A;    </xsl:text>
                </identification>
            </xsl:for-each>
            <xsl:for-each select="/fns:test_station/software">
                <xsl:text>&#x0A;    </xsl:text>
                <software>
                    <xsl:for-each select="./package">
                        <xsl:text>&#x0A;        </xsl:text>
                        <package>
                            <xsl:for-each select="./*">
                                <xsl:text>&#x0A;            </xsl:text>
                                <xsl:copy-of select="."/>
                            </xsl:for-each>
                            <xsl:text>&#x0A;        </xsl:text>
                        </package>
                    </xsl:for-each>
                    <xsl:text>&#x0A;    </xsl:text>
                </software>
            </xsl:for-each>
            <xsl:for-each select="/fns:test_station/calibration">
                <xsl:text>&#x0A;    </xsl:text>
                <calibration>
                    <xsl:for-each select="./item">
                        <xsl:text>&#x0A;        </xsl:text>
                        <item>
                            <xsl:for-each select="./*">
                                <xsl:text>&#x0A;            </xsl:text>
                                <xsl:copy-of select="."/>
                            </xsl:for-each>
                        <xsl:text>&#x0A;        </xsl:text>
                        </item>
                    </xsl:for-each>
                    <xsl:text>&#x0A;    </xsl:text>
                </calibration>
            </xsl:for-each>
        </fns:test_station>
    </xsl:template>
</xsl:stylesheet>

И это пример выходного файла:

<?xml version="1.0" encoding="UTF-16"?>
<fns:test_station xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fns="http://www.abc.com/f_namespace">
    <software>
        <package>
            <part_number>123456789</part_number>
            <version>00</version>
            <test_category>1</test_category>
            <description>name of software package</description>
            <execution_path>c:\program files\test\test.exe</execution_path>
            <execution_arguments>arguments</execution_arguments>
            <crc_path>c:\ste_config\crc\123456789.lst</crc_path>
            <uninstall_path>c:\ste_config\uninstall\uninst_123456789.bat</uninstall_path>
            <install_timestamp>2009-11-09T14:00:44</install_timestamp>
        </package>
    </software>
</fns:test_station>

Ответы [ 4 ]

1 голос
/ 12 декабря 2013

Grrrrr. Работал над этим весь день. Если вы создаете объект без номера версии:

Server.CreateObject("MSXML2.FreeThreadedDOMDocument")

Это будет вставлять <META http-equiv="Content-Type" content="text/html; charset=UTF-16"> в заголовке.

Но если вы указали номер версии, например:

Server.CreateObject("MSXML2.FreeThreadedDOMDocument.5.0")

или .4.0 или .6.0 (независимо от того, что установлено) помещает это в заголовок:

<META http-equiv="Content-Type" content="text/html">
1 голос
/ 16 ноября 2009

Вы можете попробовать использовать ADODB.Stream, чтобы сохранить его в кодировке UTF-8.

Хотя у меня нет Winbatch, экстраполяция из VBScript примерно так будет работать:

Set oStream = ObjectCreate("ADODB.Stream")
oStream.Open
oStream.Charset = "UTF-8"

processor.Output = oStream
processor.Transform

oStream.SaveToFile(output_file_path)
oStream.Close
1 голос
/ 24 февраля 2012

Вы можете сделать это с помощью JavaScript (Windows Script Host запустит его):

function xmlTransformAndSave(xml, xsl, saveXmlPath, saveEnableOverwrite) {
 // Transforms input XML and saves output to file, preserving encoding specified
 // by xsl:output encoding attribute. The method used resolves the issue of XSL
 // XML output forced to UTF-16 encoding the moment it becomes a string in
 // JavaScript (JavaScript strings are UTF-16). Note saveEnableOverwrite is an
 // optional parameter enabled by default.

 // Optional input parameter default value
 saveMode = typeof saveMode != 'undefined' ? saveMode : true;
 // Convert to stream saveToFile parameter (1 = create; 2 = overwrite)   
 saveMode = saveMode = true ? 2 : 1;

 // Output object, a stream (to preserve output encoding set in XSL)
 var stream = WScript.createObject("ADODB.Stream");
 stream.open();
 stream.type = 1;

 // Transform and save to file
 xml.transformNodeToObject(xsl, stream);
 stream.saveToFile(saveXmlPath, saveMode);
 stream.close();
}

Параметры xml и xsl - это объекты DOMDocument с уже загруженными xml и xsl. Например, xsl может прийти из этой функции:

function getXsl(xslPath) {
 //Returns XSL loaded from xslPath supplied
 //Create DOM "xsl" for XSL, set DOM options, and load XSL file
 var xsl = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.3.0");
 xsl.async = false;
 xsl.resolveExternals = false;
 xsl.validateOnParse = false;
 xsl.load(xslPath);
 //Return xsl
 return xsl;
}

Используя этот метод преобразования, вы можете установить входные параметры XSL с помощью следующего кода:

function xslSetParam(xsl, paramName, paramValue) {
 // Sets parameter value in xsl (call before transform)
 // Requires XSL structure "xsl:stylesheet" (NOT "xsl:transform", and NOT "xslt:")
 // Select parameter to set
 var xslParam = xsl.selectSingleNode("/xsl:stylesheet/xsl:param[@name='" + paramName + "']");
 // Set parameter value
 xslParam.setAttribute("select", paramValue);
}

Выходная кодировка, указанная в XSL, теперь будет выходной кодировкой файла и указана в объявлении XML файла, как и ожидалось. Таким образом, выходная кодировка в вашем XSL выглядит так:

<xsl:output method="xml" indent="yes" encoding="UTF-8"/>

Даст вам желаемый результат - вот так:

<?xml version="1.0" encoding="UTF-8"?>
1 голос
/ 12 ноября 2009

Проблема заключается в том, что выходные данные метода transform() процессора XSLT сериализуются в виде строки при доступе к свойству output (прямо или косвенно), а Windows использует для строк кодировку UTF-16. В документации MSDN свойства output это почти случайно упоминается в нижней части страницы:

В этом случае выходные данные всегда генерируются в кодировке Unicode, а атрибут кодирования элемента игнорируется.

(где они имеют в виду UTF-16, когда говорят «кодировка Unicode».)

Если вы используете transformNodeToObject, указав в качестве вывода новый объект DOMDocument, вы можете сохранить сериализацию контента в кодировке UTF-8 из этого.

Еще лучше для вашего случая, если у вас есть объект, реализующий интерфейс IStream, такой как поток, связанный с файлом, который вы пытаетесь сохранить, вы можете передать его в transformNodeToObject для отправки вывода UTF-8 прямо на диск. (Я не помню, нужно ли открывать и закрывать файл вручную в этом случае, поэтому вам придется поэкспериментировать с этим.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...