Форум программистов, компьютерный форум, киберфорум
diadiavova
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 1.

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

Запись от diadiavova размещена 07.06.2021 в 11:00

В предыдущем посте я рассматривал возможность реализации поддержки внедренных скриптов в .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.
Возьмем для примера следующий код
Javascript
1
2
3
4
5
6
7
8
function Func1(x, y, z)
{
    return "This is Func1 value = " + (x + y + z);
}
function Func2(a, b)
{
    return "a * b = " + (a * b);
}
На его базе нам нужно сгенерировать класс следующего вида
VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 скрипты этого движка выглядят так
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    <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
VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 скрипт этого движка выглядит так
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    <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
Python
1
2
3
4
5
6
7
8
9
10
11
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
Из него создается следующий класс
VB.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <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>
Добавление внешнего объекта

Эта тема простая, не требующая ни кодогенерации, ни компиляции. Просто в документе размещаем что-то типа такого
XML
1
2
3
4
    <my:external 
        assembly-path="C:\path\to\HelperLibrary.dll"
        implements-prefix="ext"
        type="HelperLibrary.XsltHelper"/>
При загрузке XSLT нужно найти соответствующую сборку загрузить, создать экземпляр указанного типа, и добавить с пространством имен, соответствующим префиксу. В примере во вложении я это все закомментирую, поскольку здесь нужно указывать полный путь к сборке, а на другом компьютере он будет другим. Но указанная библиотека в решении есть. Так что можно будет раскоментировать, указать правильный путь к ней и запустить, все должно работать.

Результаты

XML
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?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:\path\to\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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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,
XML
1
2
3
4
    <my:external 
        assembly-path="C:\path\to\HelperLibrary.dll"
        implements-prefix="ext"
        type="HelperLibrary.XsltHelper"/>
так и xsl:value-of, включающий вызов функции
XML
1
<xsl:value-of select="ext:Csv(*/csv/v)"/>
А в атрибуте assembly-path указать полный путь к сборке.

>>
Вложения
Тип файла: zip XsltExtension.zip (15.60 Мб, 453 просмотров)
Размещено в Без категории
Показов 2652 Комментарии 0
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2023, CyberForum.ru