Как разрешить элементы ‹xsl:import› и ‹xsl:include› с относительными путями при использовании xsltc.exe XslCompiledTransforms?

В рамках процесса сборки нашего веб-приложения я настроил таблицы стилей XSLT для сборки с помощью всякий раз, когда мы запускаем полную компиляцию. Во время локальной разработки это отлично сработало, так как код компилируется и размещается в одном и том же месте. Однако, как только это было помещено на сервер сборки, возникли проблемы.

Сервер сборки скомпилирует таблицы стилей XSLT точно так же, как я делаю локально, но затем запустится скрипт, который развернет скомпилированный код на нашем внутреннем промежуточном веб-сервере. После перемещения этих двоичных файлов из того места, где они были скомпилированы, относительные пути в элементах <xsl:import> и <xsl:include> больше не разрешаются правильно, вызывая подобные исключения при запуске таблиц стилей XSLT.

Could not find a part of the path 'e:\{PATH}\xslt\docbook\VERSION'.
    at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
    at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath)
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)
    at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
    at System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn)
    at System.Xml.Xsl.Runtime.XmlQueryContext.GetDataSource(String uriRelative, String uriBase)

Вот общее представление о коде в его нынешнем виде:

var xslt = new XslCompiledTransform();
xslt.Load(typeof(Namespace.XslTransforms.CompiledXsltStylesheet));
xslt.Transform("input.xml", "output.xml");

Прямо сейчас я использую метод XslCompiledTransform.Load() с одним параметром «Тип», чтобы добавить предварительно скомпилированные таблицы стилей XSLT на основе xsltc.exe. Из трассировки стека я могу сказать, что платформа .NET использует XmlUrlResolver, чтобы попытаться разрешить фактическое расположение этих внешних таблиц стилей, но я не вижу способа предоставить переопределенную реализацию XmlResolver, где я мог бы передать новый baseUri, который указывает, где эти таблицы стилей находятся на веб-сервере.

Я предполагаю, что смогу решить эту проблему, отказавшись от предварительной компиляции с помощью xsltc.exe и загрузив таблицы стилей XSLT через XmlReaders, поскольку это позволит мне использовать другие методы XslCompiledTransform.Load(), у которых есть параметр, в котором я мог бы предоставить свою собственную реализацию XmlResolver. Однако мне нравится вариант предварительной компиляции для проверки синтаксиса и производительности, поэтому я не хочу отказываться от него, если только в этом нет крайней необходимости.

Есть ли способ использовать xsltc.exe для предварительной компиляции этих таблиц стилей XSLT, но при этом предоставить способ явного указания baseUri для разрешения относительного пути элементов <xsl:include> и <xsl:import> во время выполнения?


person Technetium    schedule 24.01.2012    source источник
comment
Импортированные/включенные таблицы стилей не развертываются вместе с двоичным файлом (на «внутреннем промежуточном веб-сервере»)?   -  person Zach Young    schedule 25.01.2012
comment
Они есть, но они находятся в другом каталоге, а не в том, в котором они были скомпилированы.   -  person Technetium    schedule 25.01.2012
comment
Если вы имитируете структуру каталогов промежуточного веб-сервера на сервере сборки (где вы компилируете таблицу стилей), будет ли это иметь положительное значение?   -  person Zach Young    schedule 25.01.2012
comment
Принуждение файловой системы к определенному формату для XSLT — довольно жесткое требование, которое я не могу требовать. Я бы предпочел съесть хит производительности. Однако это может сработать для кого-то, кто создает веб-службу XSLT в среде SOA.   -  person Technetium    schedule 25.01.2012


Ответы (3)


После много экспериментов с этим я обнаружил, что был прав в том, что предоставленный мной код автоматически использует System.Xml.XmlUrlResolver для разрешения относительных путей <xsl:include> и <xsl:import> во время выполнения. Однако использование XmlUrlResolver не привязано к системе . .Xml.XslCompiledTransform, когда он помещается в двоичный файл с помощью xsltc.exe. XmlResolver фактически выбирается XmlResolver в System.Xml.XmlReaderSettings в System.Xml.XmlReader, который выполняет преобразование в во время выполнения. Как только я установил свой собственный XmlResolver в XsltReaderSettings, который я использовал, я смог управлять относительным разрешением пути.

Если вы хотите переопределить этот XmlResolver, как это сделал я, в качестве руководства можно использовать следующий код:

var customXmlResolver = new SomeCustomXmlResolver();  // Derives from XmlResolver
var xmlReaderSettings = new XmlReaderSettings {
  XmlResolver = customXmlResolver
};

var xslt = new XslCompiledTransform();
xslt.Load(typeof(Namespace.XslTransforms.CompiledXsltStylesheet));

using (var xmlReader = XmlReader.Create("input.xml", xmlReaderSettings)) {
  using (var xmlWriter = XmlWriter.Create("output.xml")) {
    xslt.Transform(xmlReader, null, xmlWriter, customXmlResolver);
  }
}

Я все еще использую xsltc.exe для компиляции своих таблиц стилей XSLT, но когда я загружаю эти скомпилированные таблицы стилей на веб-сервер, внедренный SomeCustomXmlResolver перезаписывает пути в переопределенных методах ResolveUri() и GetEntity(), чтобы файлы, на которые ссылаются, которые находятся в относительных таблицах на основе <xsl:include> и <xsl:import> пути можно найти. В качестве дополнительного бонуса, добавив тот же XmlResolver в конец метода Transform(), относительные пути операций document() в XML также будут корректно разрешены.

person Technetium    schedule 04.04.2012
comment
Вы, должно быть, очень запутались. XmlReader в вашем коде используется только для чтения "input.xml", а не для загрузки таблицы стилей XSLT. Когда выполняется метод xslt.Load(), он вообще не имеет ссылки ни на какой XmlReader. Если есть какие-либо проблемы с выполнением xsl:import и xsl:include, метод Load() вызывает исключение - и этого не происходит в вашем случае - без использования ссылки на XmlReader. - person Dimitre Novatchev; 04.04.2012
comment
Я не смущен, Дмитрий. У меня есть рабочий код, и, проходя через отладчик, я ясно вижу, как относительные пути этих элементов XSL передаются через XmlResolver, который я назначаю во время выполнения. <xsl:include href="../VERSION"/>, <xsl:include href="param.xsl"/>, <xsl:include href="../lib/lib.xsl"/> и т. д. Это правильный ответ. - person Technetium; 04.04.2012
comment
Технециум: у вас есть по крайней мере одна из импортированных / включенных таблиц стилей, использующая функцию document() с относительным или пустым URL-адресом - это настоящая проблема, которую вы никогда не объясняли. Если ни одна из импортированных/включенных таблиц стилей не ссылается на функцию document(), проблем вообще не будет. На следующий день я найду немного свободного времени, чтобы построить примеры, подтверждающие это. - person Dimitre Novatchev; 04.04.2012

Есть ли способ использовать xsltc.exe для предварительной компиляции этих таблиц стилей XSLT, но при этом предоставить способ явного указания baseUri для разрешения относительного пути элементов <xsl:include> и <xsl:import> во время выполнения?

Попробуйте использовать:

XslCompiledTransform.CompileToType()

Один из аргументов, которые принимает этот статический метод:

XmlResolver stylesheetResolver
person Dimitre Novatchev    schedule 25.01.2012
comment
О да! Это выглядит очень многообещающе. Я попробую и отмечу это как правильный ответ, как только у меня будет возможность проверить. В документации msdn даже говорится, что xsltc.exe является оболочкой для этого, поэтому большой вопрос, который остается, заключается в том, кэширует ли XmlResolver baseUri во время компиляции или снова запускается XmlResolver во время выполнения (что и то, что я хочу, и, на основе трассировки стека, то, что я предполагаю, произойдет). - person Technetium; 25.01.2012
comment
@Technetium: я думаю, что нет смысла исправлять преобразователь так рано во время компиляции. Это должно быть то, что вам нужно. - person Dimitre Novatchev; 25.01.2012
comment
Как и в старшей школе, здесь я действовал преждевременно, потому что был так взволнован. На самом деле это неправильный ответ. Хотя Димитре прав в том, что XmlResolver, предоставленный в этом методе, используется для разрешения элементов <xsl:include> и <xml:import> при компиляции таблицы стилей XSLT, он больше не используется во время выполнения. XmlResolver, используемый во время выполнения, выбирается XmlReaderSettings исходного XML-файла. Смотрите мой ответ для получения дополнительной информации. - person Technetium; 04.04.2012

Я не знаю, сломает ли это вашу систему, но как насчет того, чтобы вместо

  1. компиляция с xsltc.exe
  2. развертывание двоичного файла
  3. загрузка двоичного файла с помощью этого Load()

ты

  1. разверните таблицы стилей, однако многие из них требуются с директивами import/include
  2. загрузить основную таблицу стилей с этим Load(), указав преобразователь для import/incldue

Похоже, вы все равно получите преимущество от «скомпилированной» таблицы стилей, по крайней мере, во время выполнения.

person Zach Young    schedule 24.01.2012
comment
Я определенно могу получить доступ к этим таблицам стилей XSLT непосредственно из файловой системы на веб-сервере. Даже если основная таблица стилей скомпилирована с помощью xsltc.exe, они ДОЛЖНЫ быть там, чтобы преобразование функционировало. Однако, если я чего-то не понимаю в указанном вами методе Load(), он будет компилировать таблицы стилей каждый раз, когда они загружаются во время выполнения, чего я пытался избежать из соображений производительности. Это также означало бы, что мне пришлось бы понизить проверку синтаксиса до статуса модульного теста. - person Technetium; 25.01.2012
comment
О, я думал, что DLL загружается один раз, в начале. Да, тогда не получится. - person Zach Young; 25.01.2012