суббота, 5 июня 2021 г.

Поддержка дополнительных языков программирования во внедренных скриптах XSLT

Поддержка дополнительных языков программирования во внедренных скриптах XSLT

 

В предыдущем посте я рассматривал возможность реализации поддержки внедренных скриптов в .Net 5 и .Net Core. В развитие темы я подумал, что не стоит ограничиваться языками C# и VB.Net и решил поэкспериментировать и с другими языками программирования. В первую очередь мой выбор пал на языки WSH, то есть Jscript и VBScript. Эти языки поддерживаются  библиотекой msxml (пример можно посмотреть здесь), механизм внедрения такой же как и в случае преобразования с помощью XsltCompiledTransform, только в скриптах поддерживаются языки Jscript и VBScript. Я подумал, почему бы не добавить и их. Скажу сразу, что эксперимент оказался удачным только отчасти. Во-первых, когда я все запустил, начали вываливаться сообщения о том, что не хватает ссылок на библиотеки четвертого фреймворка. Конечно, добавив их я решил проблему, но это было уже не очень красиво, поскольку предполагалось их не использовать. Во-вторых, эти языки в упор не понимают переданных им узлов и наборов узлов, хотя и вполне справляются с работой со строками, числами и т. п. Мало того, если в коде XSLT перед передачей в функцию какой-нибудь узел сначала конвертировать в строку или число, то JScript с обработкой справляется, а вот VBScript и этого не может. Понятно, что функциям передаются CLR-типы, невидимые из COM, но решение этой проблемы – задача довольно трудоемкая и, как мне показалось, оно того не стоит. В конце концов я перенес код в приложение .Net Framework 4.7.2, поскольку все равно его библиотеки используются. Ну и пришлось внести некоторые изменения в код. Компиляция теперь выполняется средствами CodeDom, а не Roslyn, встроенная поддержка скриптов реализуется штатными средствами, а все мои дополнения используют собственное пространство имен.

Реализовав поддержку языков WSH, я не стал останавливаться на достигнутом и подумал о том, почему бы не добавить еще что-нибудь, тем более, что эксперимент был не очень удачным, а его код частично можно использовать и для других целей. В результате я добавил поддержку JavaScript и IronPython. Кроме того, реализовал поддержку загрузки объекта расширения из внешней библиотеки.

Общие принципы

 

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

  1. У нас будет некоторое пространство имен к которому будут относиться все блоки скриптов. Я использовал пространство из прошлой темы urn:my-xslt-extension-object-generator .
  2. С каждым скриптовым блоком будет ассоциироваться какое-нибудь собственное пространство имен и именно его мы будем использовать для добавления объекта расширения, созданного на базе кода этого блока и его префикс также будет использоваться при вызове функций. Я постарался сделать модель максимально похожей на msxsl, чтобы не путаться. Ну в частности атрибуты типа implements-prefix и тому подобное.
  3. При загрузке XSLT из каждого такого блока генерируется код прокси класса, который потом компилируется, создается экземпляр этого класса и уже он добавляется в XsltArgumentList при трансформации.

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

MSScriptControl и языки WSH

 

Для поддержки этих языков нам надо будет добавить в проект ссылку на библиотеку Microsoft Script Control 1.0, которую можно найти на вкладке COM при добавлении ссылки. В свойствах ссылки нужно для свойства «Внедрить типы взаимодействия» установить значение False, поскольку нам нужно будет ссылаться на сборку взаимодействия из сборки, которую мы скомпилируем из кода прокси-класса.

В библиотеке нас интересуют два типа: ScriptControl и Procedure. ScriptControl позволяет добавлять код, получать необходимую информацию о нем и выполнять его. Из Procedure мы можем получить имя и количество аргументов отдельной процедуры, чего вполне достаточно для создания метода.

Прокси класс будет устроен следующим образом:

На уровне класса будет объявлено поле, содержащее собственно экземпляр ScriptControl, оно будет инициализировано в конструкторе. Свойству Language нужно присвоить значение JScript или VBScript. В конструкторе же мы создадим переменную code и передадим ей код нашего скрипта, после чего все это дело нужно будет передать как аргумент методу AddCode скриптконтрола. Методы класса будут иметь те же имена, что и имена процедур. Поскольку скриптконтрол не дает информации об именах параметров мы их сами создадим, я использовал имена типа arg1, arg2 и т. д. Типом параметров и возвращаемого значения будет Object.

Возьмем для примера следующий код

function Func1(x, y, z)

{

    return "This is Func1 value = " + (x + y + z);

}

function Func2(a, b)

{

    return "a * b = " + (a * b);

}

На его базе нам нужно сгенерировать класс следующего вида

Public Class ProxyClass

 

    Private scriptControl As MSScriptControl.ScriptControl

 

    Public Sub New()

        MyBase.New

        scriptControl = New MSScriptControl.ScriptControl()

        scriptControl.Language = "JScript"

        Dim code As String

        code = "" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "function Func1(x, y, z)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "{" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    return ""This is Func1 value = "" + (x + y + z)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) &

            "}" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "function Func2(a, b)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "{" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    return ""This is Func2 value""" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "}" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10)

        scriptControl.AddCode(code)

    End Sub

 

    Public Overridable Function Func1(ByVal arg1 As Object, ByVal arg2 As Object, ByVal arg3 As Object) As Object

        Return scriptControl.Run("Func1", arg1, arg2, arg3)

    End Function

 

    Public Overridable Function Func2(ByVal arg1 As Object, ByVal arg2 As Object) As Object

        Return scriptControl.Run("Func2", arg1, arg2)

    End Function

End Class

При компиляции нужно будет добавить ссылку на сборку взаимодействия, ссылка на которую есть в основном проекте, так что найти будет несложно. Если потребуются какие-то дополнительные референсы, то можно этот вопрос предусмотреть в коде XSLT. Также как это было реализовано в msxsl можно добавить элементы assembly и все такое. Я этого не делал, но процесс обработки таких элементов аналогичен тому, как такие элементы обрабатывались в прошлом посте.

Как уже было сказано выше, эти языки не видят узлы и наборы узлов, мало того, VBScript не видит даже значений, полученных при конвертировании узла в строку, а может принимать только объекты, созданные с помощью литералов. Так что польза этого всего небольшая и большого смысла в использовании скриптконтрола в данном случае нет.

В XSLT скрипты этого движка выглядят так

       <my:wshscript implements-prefix="vbs" language="vbs">

             <![CDATA[
             Function TestVBS(input)
                    TestVBS = "VBS got " & input
             End Function
            
]]>

       </my:wshscript>

 

       <my:wshscript implements-prefix="js" language="js">

             <![CDATA[

             function testJS(input)

             {

                    return "JScript got " + input;

             }

             ]]>

       </my:wshscript>

Движок jint и JavaSсript 5.1

 

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

Public Class ProxyClass

 

    Private jintEngine As Jint.Engine

 

    Public Sub New()

        MyBase.New

        jintEngine = New Jint.Engine(New System.Action(Of Jint.Options)(AddressOf Me.ConfigInit))

        Dim code As String

        code = "" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "function Func1(x, y, z)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "{" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    return ""This is Func1 value = "" + (x + y + z)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) &

            "}" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "function Func2(a, b)" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "{" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    return ""This is Func2 value""" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "}" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10)

        jintEngine.Execute(code)

        jintEngine.SetValue("EngineLocation", "C:\Users\laet\source\repos\XsltExtension\XsltExtension\bin\Debug\XsltExtension.ex" &

                "e")

    End Sub

 

    Private Sub ConfigInit(ByVal cfg As Jint.Options)

        cfg.AllowClr(System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll"), System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.dll"), System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Xml.dll"), System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Xml.Linq.dll"), System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll"), System.Reflection.[Assembly].LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.VisualBasic.dll"))

    End Sub

 

    Public Overridable Function Func1(ByVal x As Object, ByVal y As Object, ByVal z As Object) As Object

        Return jintEngine.Invoke("Func1", x, y, z).ToObject

    End Function

 

    Public Overridable Function Func2(ByVal a As Object, ByVal b As Object) As Object

        Return jintEngine.Invoke("Func2", a, b).ToObject

    End Function

End Class

 

Здесь при создании экземпляра движка ему еще передается ссылка на метод инициализации конфигурации ConfigInit. Я здесь в конфигурации просто добавил список сборок, которые должны быть доступны в коде скрипта. Если этот список нужно расширить, то опять-таки в блок скрипта надо будет ввести элементы assembly с данными о сборках. А в коде предусмотреть этот момент. Для этого в классе ProxyGenerator ( он был в проекте из прошлого поста, просто сейчас он несколько вырос) есть свойство References, представляющее список, в который можно добавить пути к сборкам, прежде чем выполнять генерацию кода. Еще здесь устанавливается значение для переменной EngineLocation, содержащее путь к текущему приложению. Я его использовал в скрипте, поскольку в том же каталоге находился еще один файл, содержимое которого было вставлено в документ. Но, естественно, это сделано просто как пример, так можно передать в скрипт любую другую информацию.

В XSLT скрипт этого движка выглядит так

       <my:jintscript implements-prefix="jint">

             <![CDATA[

             function quadsFromString(input)

             {

                    input.MoveNext();

                    return  input.Current.Value.split(",").map(function(i){ return (+i * +i).toString();}).join(",");

             }

            

             function between(n, start, end)

             {

                    return n >= start && n <= end;

             }

            

             function textFromFile()

             {

                    var io = importNamespace('System.IO')

                    var path = io.Path.GetDirectoryName(EngineLocation)

                    var txt = io.File.ReadAllText(io.Path.Combine(path, "TextFile1.txt"));

                    return txt;

             }

             ]]>

       </my:jintscript>

IronPython

 

Добавление поддержки разных реализаций EcmaScript – задача малополезная. Все дело в том, что скрипты от вендора поддерживают язык JScript.Net, это не тот же самый язык, однако он совместим (не уверен, что полностью) с EcmaScript 3. То есть в принципе можно написать код на этом языке и, с высокой вероятностью, компилятор его поймет (но не наоборот: код Jscript.Net обычным интерпретаторам JavaScript непонятен). А вот добавить поддержку Python – это уже серьезное расширение функционала. IronPython, насколько я понимаю – это тот же Python, только имеет ряд дополнительных возможностей, необходимых для взаимодействия CLR-типами и платформой .Net вообще. На данный момент поддерживается версия совместимая с Python 2.7, поддержка третьего питона вроде как уже есть в бета-версии. Документация здесь.

Нужно установить пакет NuGet IronPrthon. Работа с движком этого языка немного отличается от работы с предыдущими рассмотренными движками. В частности, функции не вызываются с помощью специального метода, которому передается имя функции и аргументы, а можно получить объект функции и работать с ним как с функцией (делегатом или методом). Особенность здесь заключается в том, что объект функции является динамическим объектом. Из-за этого, например, объект CodeDom из которого будет генерироваться код, не будет подходить для генерации как на VB.Net, так и на C#, из-за того, что в этих языках работа с динамическими объектами немного отличается. Поскольку изначально мы генерируем код на VB.Net, то это не так и важно, тем не менее упомянуть об этом стоило.

Для примера возьмем код, который я использовал в XSLT

def factorial(number):

    result = 1

    for i in xrange(2, number + 1):

        result *= i

    return result

            

def intsum(nodes):

       sum = 0

       for n in nodes:

             sum += n.ValueAsInt

       return sum 

 

Из него создается следующий класс

Public Class ProxyClass

 

    Private funcDictionary As System.Collections.Generic.Dictionary(Of String, Object)

 

    Public Sub New()

        MyBase.New

        funcDictionary = New System.Collections.Generic.Dictionary(Of String, Object)()

        Dim code As String = "" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "def factorial(number):" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    result = 1" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    for i in xrange(2, number + 1):" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "  " &

            "      result *= i" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "    return result" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & Global.Microsoft.VisualBasic.ChrW(9) & Global.Microsoft.VisualBasic.ChrW(9) & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "def intsum(nodes):" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & Global.Microsoft.VisualBasic.ChrW(9) & "sum = 0" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & Global.Microsoft.VisualBasic.ChrW(9) & "for n i" &

            "n nodes:" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & Global.Microsoft.VisualBasic.ChrW(9) & Global.Microsoft.VisualBasic.ChrW(9) & "sum += n.ValueAsInt" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & Global.Microsoft.VisualBasic.ChrW(9) & "return sum" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10)

        Dim engine As Microsoft.Scripting.Hosting.ScriptEngine = IronPython.Hosting.Python.CreateEngine

        Dim scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope

        engine.Execute(code, scope)

        funcDictionary.Add("factorial", scope.GetVariable("factorial"))

        funcDictionary.Add("intsum", scope.GetVariable("intsum"))

    End Sub

 

    Public Overridable Function factorial(ByVal number As Object) As Object

        Return funcDictionary("factorial")(number)

    End Function

 

    Public Overridable Function intsum(ByVal nodes As Object) As Object

        Return funcDictionary("intsum")(nodes)

    End Function

End Class

Здесь вместо того, чтобы на уровне класса объявить поле, в котором будет храниться движок, я создал поле со словарем функций, который заполняется в конструкторе. А методы класса, просто получают нужную функцию по имени и вызывают ее.

При компиляции в референсы я добавил все библиотеки, которыми прирос мой проект с добавлением пакета IronPython. Честно говоря, не знаю, нужны они все или нет.

Код в XSLT

       <my:ipscript implements-prefix="ip">

             <![CDATA[

def factorial(number):

    result = 1

    for i in xrange(2, number + 1):

        result *= i

    return result

            

def intsum(nodes):

    sum = 0

    for n in nodes:

        sum += n.ValueAsInt

    return sum

            

             ]]>

       </my:ipscript>

 

Добавление внешнего объекта

 

Эта тема простая, не требующая ни кодогенерации, ни компиляции. Просто в документе размещаем что-то типа такого

       <my:external

             assembly-path="C:\path\to\HelperLibrary.dll"

             implements-prefix="ext"

             type="HelperLibrary.XsltHelper"/>

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

Результаты

 

XML

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

<input>

       <pow base="5" exp="3"/>

       <dblstr>ma</dblstr>

       <mid start="9" length="5">This is input string</mid>

       <split>string1,string2,string3</split>

       <testVBS>vbsinput</testVBS>

       <testJS>jsinput</testJS>

       <quadsFromString>1,2,3,4,5,6,7,8,9</quadsFromString>

       <csv>

             <v>1</v>

             <v>2</v>

             <v>3</v>

             <v>4</v>

             <v>5</v>

       </csv>

       <factorial>6</factorial>

</input>

 

XSLT

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

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl s1 s2 my math str vbs js jint ip ext"

xmlns:s1="urn:included-script:s1"

xmlns:s2="urn:included-script:s2"

xmlns:vbs="urn:included-script:vbs"

xmlns:js="urn:included-script:js"

xmlns:jint="urn:included-script:jint"

xmlns:ip="urn:included-script:ironpython"

xmlns:my="urn:my-xslt-extension-object-generator"

xmlns:math="urn:my-xslt-extension-object-generator:math"

xmlns:str="urn:my-xslt-extension-object-generator:strings"

xmlns:ext="urn:my-xslt-extension-object-generator:ext"

                                                      >

       <xsl:output method="xml" indent="yes"/>

 

       <xsl:template match="@* | node()">

             <xsl:copy>

                    <xsl:apply-templates select="@* | node()"/>

             </xsl:copy>

       </xsl:template>

 

       <xsl:template match="/">

             <result>

                    <pow>

                           <xsl:value-of select="s1:pow(*/pow/@base, */pow/@exp)"/>

                    </pow>

                    <dblstr>

                           <xsl:value-of select="s2:dblstr(*/dblstr/text())"/>

                    </dblstr>

                    <exp>

                           <xsl:value-of select="math:Exp(1)"/>

                    </exp>

                    <mid>

                           <xsl:value-of select="str:Mid(*/mid/text(), */mid/@start, */mid/@length)"/>

                    </mid>

                    <split>

                           <xsl:for-each select="str:Split(*/split/text(), ',', -1, 1)">

                                  <fragment>

                                        <xsl:value-of select="."/>

                                  </fragment>

                           </xsl:for-each>

                    </split>

                    <testVBS>

                           <xsl:value-of select="vbs:TestVBS('qwerty')"/>

                    </testVBS>

                    <testJS>

                           <xsl:value-of select="js:testJS(string(*/testJS/text()))"/>

                    </testJS>

                    <quadsFromString>

                           <xsl:value-of select="jint:quadsFromString(*/quadsFromString/text())"/>

                    </quadsFromString>

                    <between>

                           <xsl:value-of select="jint:between(5,3,8)"/>

                    </between>

                    <textFromFile>

                           <xsl:value-of select="jint:textFromFile()"/>

                    </textFromFile>

                     <csv>

                           <xsl:value-of select="ext:Csv(*/csv/v)"/>

                    </csv>

                    <factorial>

                           <xsl:value-of select="ip:factorial(number(*/factorial/text()))"/>

                    </factorial>

                    <intsum>

                           <xsl:value-of select="ip:intsum(*/csv/v)"/>

                    </intsum>

             </result>

       </xsl:template>

 

       <my:static-object assembly-name="mscorlib" type="System.Math" implements-prefix="math"/>

       <my:static-object assembly-name="Microsoft.VisualBasic" type="Microsoft.VisualBasic.Strings" implements-prefix="str"/>

       <my:external

             assembly-path="C:\Users\laet\source\repos\XsltExtension\HelperLibrary\bin\Debug\HelperLibrary.dll"

             implements-prefix="ext"

             type="HelperLibrary.XsltHelper"/>

       <msxsl:script implements-prefix="s1" language="visualbasic">

             <![CDATA[

             Public Function pow(base As Double, exp As Double)

                    Return Math.Pow(base, exp)

             End Function

             ]]>

       </msxsl:script>

 

       <msxsl:script implements-prefix="s2" language="csharp">

             <![CDATA[

             public string dblstr(string input)

             {

                    return input + input;

             }

             ]]>

       </msxsl:script>

 

       <my:wshscript implements-prefix="vbs" language="vbs">

             <![CDATA[

             Function TestVBS(input)

            

                    TestVBS = "VBS got " & input

             End Function

             ]]>

       </my:wshscript>

 

       <my:wshscript implements-prefix="js" language="js">

             <![CDATA[

             function testJS(input)

             {

                    return "JScript got " + input;

             }

             ]]>

       </my:wshscript>

 

       <my:jintscript implements-prefix="jint">

             <![CDATA[

             function quadsFromString(input)

             {

                    input.MoveNext();

                    return  input.Current.Value.split(",").map(function(i){ return (+i * +i).toString();}).join(",");

             }

            

             function between(n, start, end)

             {

                    return n >= start && n <= end;

             }

            

             function textFromFile()

             {

                    var io = importNamespace('System.IO')

                    var path = io.Path.GetDirectoryName(EngineLocation)

                    var txt = io.File.ReadAllText(io.Path.Combine(path, "TextFile1.txt"));

                    return txt;

             }

             ]]>

       </my:jintscript>

       <my:ipscript implements-prefix="ip">

             <![CDATA[

def factorial(number):

    result = 1

    for i in xrange(2, number + 1):

        result *= i

    return result

            

def intsum(nodes):

    sum = 0

    for n in nodes:

        sum += n.ValueAsInt

    return sum

            

             ]]>

       </my:ipscript>

</xsl:stylesheet>

 

Результат

<?xml version="1.0" encoding="utf-16"?>

<result>

       <pow>125</pow>

       <dblstr>mama</dblstr>

       <exp>2.718281828459045</exp>

       <mid>input</mid>

       <split>

             <fragment>string1</fragment>

             <fragment>string2</fragment>

             <fragment>string3</fragment>

       </split>

       <testVBS>VBS got qwerty</testVBS>

       <testJS>JScript got jsinput</testJS>

       <quadsFromString>1,4,9,16,25,36,49,64,81</quadsFromString>

       <between>true</between>

       <textFromFile>Текст из обновленного файла</textFromFile>

       <csv>1,2,3,4,5</csv>

       <factorial>720</factorial>

       <intsum>15</intsum>

</result>

   

Во вложении решение, содержащее два проекта: основной, о котором шла речь в посте и HelperLibrary, написанный специально для добавления из него функционала с помощью элемента external. Проект нужно собрать, в XSLT раскомментировать как сам элемент my:external,

<my:external

        assembly-path="C:\path\to\HelperLibrary.dll"

        implements-prefix="ext"

        type="HelperLibrary.XsltHelper"/>

так и xsl:value-of, включающий вызов функции

<xsl:value-of select="ext:Csv(*/csv/v)"/>

А в атрибуте assembly-path указать полный путь к сборке.

  XsltExtension.zip