├── .github
└── workflows
│ └── go.yml
├── LICENSE
├── README.md
├── example_test.go
├── go.mod
├── testdata
├── document.xml
├── result1.xml
├── result2.xhtml
├── style1.xsl
└── style2.xsl
├── xslt.c
├── xslt.go
├── xslt.h
└── xslt_test.go
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Go
3 |
4 | on:
5 | push:
6 | branches: [master]
7 | pull_request:
8 | branches: [master]
9 |
10 | jobs:
11 |
12 | build:
13 | strategy:
14 | matrix:
15 | os: [ubuntu-latest, macos-latest]
16 | runs-on: ${{ matrix.os }}
17 | steps:
18 | - uses: actions/checkout@v3
19 |
20 | - name: Set up Go
21 | uses: actions/setup-go@v3
22 | with:
23 | go-version: 1
24 |
25 | - name: Install Linux deps
26 | if: runner.os == 'Linux'
27 | run: |
28 | sudo apt-get update
29 | sudo apt-get -y install --no-install-recommends \
30 | libxml2-dev libxslt1-dev liblzma-dev zlib1g-dev
31 |
32 | - name: Install macOS deps
33 | if: runner.os == 'macOS'
34 | run: brew install libxml2 libxslt
35 |
36 | - name: Build
37 | run: go build -v ./...
38 |
39 | - name: Test
40 | run: go test -v -coverprofile=coverage.txt -covermode=atomic -race ./...
41 |
42 | - name: Coverage
43 | if: runner.os == 'Linux'
44 | run: bash <(curl -s https://codecov.io/bash)
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 William Muir
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-xslt
2 | =====
3 |
4 | [](https://pkg.go.dev/github.com/wamuir/go-xslt)
5 | [](https://github.com/wamuir/go-xslt/actions/workflows/go.yml?query=event%3Apush+branch%3Amaster)
6 | [](https://codecov.io/gh/wamuir/go-xslt)
7 | [](https://goreportcard.com/report/github.com/wamuir/go-xslt)
8 |
9 | # Description
10 |
11 | `go-xslt` is a Go module that performs basic XSLT 1.0 transformations via Libxslt.
12 |
13 | # Installation
14 |
15 | You'll need the development libraries for libxml2 and libxslt, along with those for liblzma and zlib. Install these via your package manager. For instance, if using `apt` then:
16 |
17 | sudo apt install libxml2-dev libxslt1-dev liblzma-dev zlib1g-dev
18 |
19 | This module can be installed with the `go get` command:
20 |
21 | go get -u github.com/wamuir/go-xslt
22 |
23 |
24 | # Usage
25 |
26 | ```go
27 |
28 | // style is an XSLT 1.0 stylesheet, as []byte.
29 | xs, err := xslt.NewStylesheet(style)
30 | if err != nil {
31 | panic(err)
32 | }
33 | defer xs.Close()
34 |
35 | // doc is an XML document to be transformed and res is the result of
36 | // the XSL transformation, both as []byte.
37 | res, err := xs.Transform(doc)
38 | if err != nil {
39 | panic(err)
40 | }
41 |
42 | ```
43 |
--------------------------------------------------------------------------------
/example_test.go:
--------------------------------------------------------------------------------
1 | package xslt_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/wamuir/go-xslt"
7 | )
8 |
9 | func Example() {
10 | // doc is the xml document to be transformed.
11 | var doc = []byte(
12 | `
13 |
14 |
15 | John
16 | Smith
17 |
18 |
19 | Morka
20 | Ismincius
21 |
22 | `,
23 | )
24 |
25 | // style is the xsl stylesheet to be used for transformation.
26 | var style = []byte(
27 | `
28 |
32 |
33 |
34 |
35 |
36 | Testing XML Example
37 |
38 |
39 | Persons
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ,
52 |
53 |
54 |
55 | `,
56 | )
57 |
58 | // Create Stylesheet xs from xsl stylesheet style.
59 | xs, err := xslt.NewStylesheet(style)
60 | if err != nil {
61 | panic(err)
62 | }
63 | defer xs.Close()
64 |
65 | // Transform xml document doc using Stylesheet xs.
66 | res, err := xs.Transform(doc)
67 | if err != nil {
68 | panic(err)
69 | }
70 |
71 | // Print the result of the transformation.
72 | fmt.Println(string(res))
73 | // Output:
74 | //
75 | //
76 | //
77 | // Testing XML Example
78 | //
79 | //
80 | // Persons
81 | //
82 | // - Ismincius, Morka
83 | // - Smith, John
84 | //
85 | //
86 | //
87 | }
88 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wamuir/go-xslt
2 |
3 | go 1.16
4 |
--------------------------------------------------------------------------------
/testdata/document.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | John
5 | Smith
6 |
7 |
8 | Morka
9 | Ismincius
10 |
11 |
12 |
--------------------------------------------------------------------------------
/testdata/result1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | John
4 | Morka
5 |
6 |
--------------------------------------------------------------------------------
/testdata/result2.xhtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Testing XML Example
5 |
6 |
7 | Persons
8 |
9 | - Ismincius, Morka
10 | - Smith, John
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/testdata/style1.xsl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/testdata/style2.xsl:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 | Testing XML Example
12 |
13 | Persons
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ,
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/xslt.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include "xslt.h"
12 |
13 | /*
14 | * Function: apply_style
15 | * ----------------------------
16 | * Restyle an XML document using a parsed XSL stylesheet.
17 | *
18 | * style: parsed XSL stylesheet
19 | * xml: XML to be transformed
20 | * params: parameters to be passed
21 | * xml_txt: output from the transform
22 | * xml_txt_len: length in bytes of output
23 | *
24 | * returns 0 if the transform is successful or -1 in case of error
25 | */
26 | int apply_style(xsltStylesheetPtr style, const char *xml, const char **params,
27 | char **xml_txt, size_t *xml_txt_len) {
28 |
29 | int ok;
30 | size_t len;
31 | xmlChar *xml_output;
32 | xmlDocPtr xml_doc, result;
33 |
34 | // avoid overflow on conversion from size_t to int
35 | len = strlen(xml);
36 | if (len > INT32_MAX) {
37 | return -1;
38 | }
39 |
40 | // parse the provided xml document
41 | xml_doc = xmlParseMemory(xml, (int)strlen(xml));
42 | if (xml_doc == NULL) {
43 | xmlResetLastError();
44 | return -1;
45 | }
46 |
47 | const xmlError *error = xmlGetLastError();
48 | if (error) {
49 | xmlResetLastError();
50 | if (error->level > XML_ERR_WARNING) {
51 | return -1;
52 | }
53 | }
54 |
55 | // obtain the result from transforming xml_doc using the style
56 | result = xsltApplyStylesheet(style, xml_doc, params);
57 | if (result == NULL) {
58 | xmlFreeDoc(xml_doc);
59 | return -1;
60 | }
61 |
62 | // save the transformation result
63 | ok = xsltSaveResultToString(&xml_output, (int *)xml_txt_len, result, style);
64 | if (ok == 0 && *xml_txt_len > 0) {
65 | *xml_txt = malloc(*xml_txt_len);
66 | strncpy(*xml_txt, (const char *)xml_output, *xml_txt_len);
67 | xmlFree(xml_output);
68 | }
69 |
70 | xmlFreeDoc(xml_doc);
71 | xmlFreeDoc(result);
72 |
73 | return ok;
74 | }
75 |
76 | /*
77 | * Function: free_style
78 | * ----------------------------
79 | * Free memory allocated by the style.
80 | * Note that the stylesheet document (style_doc) is also automatically
81 | * freed. See: http://xmlsoft.org/XSLT/html/libxslt-xsltInternals.html
82 | * @ #xsltParseStylesheetDoc.
83 | *
84 | * style: an XSL stylesheet pointer
85 | *
86 | * returns void
87 | */
88 | void free_style(xsltStylesheetPtr *style) { xsltFreeStylesheet(*style); }
89 |
90 | /*
91 | * Function: make_style
92 | * ----------------------------
93 | * Parse an XSL stylesheet.
94 | *
95 | * xsl: XSL to be transformed
96 | * style: parse XSL stylesheet
97 | *
98 | * returns 0 if parsing is successful or -1 in case of error
99 | */
100 | int make_style(const char *xsl, xsltStylesheetPtr *style) {
101 |
102 | size_t len;
103 | xmlDocPtr style_doc;
104 |
105 | len = strlen(xsl);
106 | if (len > INT32_MAX) {
107 | return -1;
108 | }
109 |
110 | style_doc = xmlParseMemory(xsl, (int)len);
111 | if (style_doc == NULL || xmlGetLastError()) {
112 | xmlResetLastError();
113 | return -1;
114 | }
115 |
116 | *style = xsltParseStylesheetDoc(style_doc);
117 | if (*style == NULL || (*style)->errors) {
118 | xmlFreeDoc(style_doc);
119 | return -1;
120 | }
121 |
122 | return 0;
123 | }
124 |
125 | /*
126 | * Function: xslt
127 | * ----------------------------
128 | * Transforms an XML document using an XSL stylesheet.
129 | *
130 | * xsl: the stylesheet to be used
131 | * xml: the document to transform
132 | * params: parameters to be passed
133 | * xml_txt: output from the transform
134 | * xml_txt_len: length in bytes of output
135 | *
136 | * returns 0 if the transform is successful or -1 in case of error
137 | */
138 | int xslt(const char *xsl, const char *xml, const char **params, char **xml_txt,
139 | size_t *xml_txt_len) {
140 |
141 | int ok;
142 | xsltStylesheetPtr style;
143 |
144 | ok = make_style(xsl, &style);
145 | if (ok < 0) {
146 | return -1;
147 | }
148 |
149 | ok = apply_style(style, xml, params, xml_txt, xml_txt_len);
150 |
151 | free_style(&style);
152 |
153 | return ok;
154 | }
155 |
156 | /*
157 | * Function: init_exslt
158 | * ----------------------------
159 | * Calls exsltRegisterAll() to enable exsl namespace at templates
160 | */
161 | void init_exslt() {
162 | LIBXML_TEST_VERSION
163 |
164 | xmlInitParser();
165 | xsltInit();
166 | exsltRegisterAll();
167 | }
168 |
169 | const char **make_param_array(int num_tuples) {
170 | const char **a = calloc(sizeof(char *), 2 * num_tuples + 1);
171 | a[2 * num_tuples] = NULL;
172 | return a;
173 | }
174 |
175 | void set_param(char **a, char *n, char *v, int t) {
176 | a[2 * t] = n;
177 | a[2 * t + 1] = v;
178 | }
179 |
180 | void free_param_array(char **a, int num_tuples) {
181 | int i;
182 | for (i = 0; i < 2 * num_tuples; i++)
183 | free(a[i]);
184 | free(a);
185 | }
186 |
--------------------------------------------------------------------------------
/xslt.go:
--------------------------------------------------------------------------------
1 | package xslt
2 |
3 | /*
4 | #cgo LDFLAGS: -lxml2 -lxslt -lexslt -lz -llzma -lm
5 | #cgo CFLAGS: -I/usr/include -I/usr/include/libxml2
6 | #cgo freebsd LDFLAGS: -L/usr/local/lib
7 | #cgo freebsd CFLAGS: -I/usr/local/include -I/usr/local/include/libxml2
8 | #include
9 | #include
10 | #include "xslt.h"
11 | */
12 | import "C"
13 |
14 | import (
15 | "errors"
16 | "strings"
17 | "unicode/utf8"
18 | "unsafe"
19 | )
20 |
21 | // Package errors.
22 | var (
23 | ErrMixedQuotes = errors.New("unable to quote parameter value")
24 | ErrUTF8Validation = errors.New("input failed utf-8 validation")
25 | ErrXSLTFailure = errors.New("xsl transformation failed")
26 | ErrXSLParseFailure = errors.New("failed to parse xsl")
27 | )
28 |
29 | func init() {
30 | C.init_exslt()
31 | }
32 |
33 | // Parameter is a parameter to be passed to a stylesheet.
34 | type Parameter interface {
35 | name() (*C.char, error)
36 | value() (*C.char, error)
37 | }
38 |
39 | // StringParameter is a stylesheet parameter consisting of a name/value pair,
40 | // where name and value are UTF-8 strings.
41 | type StringParameter struct {
42 | Name string
43 | Value string
44 | }
45 |
46 | var _ Parameter = StringParameter{}
47 |
48 | func (p StringParameter) name() (*C.char, error) {
49 | if ok := utf8.ValidString(p.Name); !ok {
50 | return nil, ErrUTF8Validation
51 | }
52 |
53 | return C.CString(p.Name), nil
54 | }
55 |
56 | func (p StringParameter) value() (*C.char, error) {
57 | var (
58 | r rune
59 | s string
60 | )
61 |
62 | const (
63 | doublequote = rune(0x0022)
64 | singlequote = rune(0x0027)
65 | )
66 |
67 | if strings.ContainsRune(p.Value, doublequote) {
68 | if strings.ContainsRune(p.Value, singlequote) {
69 | return nil, ErrMixedQuotes
70 | }
71 | r = singlequote
72 | } else {
73 | r = doublequote
74 | }
75 |
76 | s = string(r) + p.Value + string(r)
77 | if ok := utf8.ValidString(s); !ok {
78 | return nil, ErrUTF8Validation
79 | }
80 |
81 | return C.CString(s), nil
82 | }
83 |
84 | // XPathParameter is a stylesheet parameter consisting of a name/value pair,
85 | // where name is a QName or a UTF-8 string of the form {URI}NCName and value is
86 | // a UTF-8 XPath expression. A quoted value (single or double) will be treated
87 | // as a string rather than as an XPath expression, however the use of
88 | // StringParameter is preferable when passing string parameters to a
89 | // stylesheet.
90 | type XPathParameter struct {
91 | Name string
92 | Value string
93 | }
94 |
95 | var _ Parameter = XPathParameter{}
96 |
97 | func (p XPathParameter) name() (*C.char, error) {
98 | if ok := utf8.ValidString(p.Name); !ok {
99 | return nil, ErrUTF8Validation
100 | }
101 |
102 | return C.CString(p.Name), nil
103 | }
104 |
105 | func (p XPathParameter) value() (*C.char, error) {
106 | if ok := utf8.ValidString(p.Value); !ok {
107 | return nil, ErrUTF8Validation
108 | }
109 |
110 | return C.CString(p.Value), nil
111 | }
112 |
113 | // Stylesheet represents an xsl stylesheet.
114 | type Stylesheet struct {
115 | ptr C.xsltStylesheetPtr
116 | }
117 |
118 | // Close frees memory associated with a stylesheet. Additional calls to Close
119 | // will be ignored.
120 | func (xs *Stylesheet) Close() {
121 | if xs.ptr != nil {
122 | C.free_style(&xs.ptr)
123 | xs.ptr = nil
124 | }
125 | }
126 |
127 | // Transform applies receiver stylesheet xs to xml and returns the result of an
128 | // xsl transformation and any error. The resulting document may be nil (a
129 | // zero-length and zero-capacity byte slice) in the case of an error.
130 | func (xs *Stylesheet) Transform(xml []byte, params ...Parameter) ([]byte, error) {
131 | var (
132 | cxml *C.char
133 | cout *C.char
134 | cpar **C.char
135 | ret C.int
136 | size C.size_t
137 | )
138 |
139 | cxml = C.CString(string(xml))
140 | defer C.free(unsafe.Pointer(cxml))
141 |
142 | cpar = C.make_param_array(C.int(len(params)))
143 | for i, p := range params {
144 | cname, err := p.name()
145 | if err != nil {
146 | return nil, err
147 | }
148 |
149 | cval, err := p.value()
150 | if err != nil {
151 | return nil, err
152 | }
153 |
154 | C.set_param(cpar, cname, cval, C.int(i))
155 | }
156 | defer C.free_param_array(cpar, C.int(len(params)))
157 |
158 | ret = C.apply_style(xs.ptr, cxml, cpar, &cout, &size)
159 |
160 | ptr := unsafe.Pointer(cout)
161 | defer C.free(ptr)
162 |
163 | if ret != 0 {
164 | return nil, ErrXSLTFailure
165 | }
166 |
167 | return C.GoBytes(ptr, C.int(size)), nil
168 | }
169 |
170 | // NewStylesheet creates and returns new stylesheet xs along with any error.
171 | // The resulting stylesheet may be nil if an error is encountered during
172 | // parsing. This implementation relies on Libxslt, which supports XSLT 1.0.
173 | func NewStylesheet(xsl []byte) (*Stylesheet, error) {
174 | var (
175 | cxsl *C.char
176 | cssp C.xsltStylesheetPtr
177 | ret C.int
178 | )
179 |
180 | cxsl = C.CString(string(xsl))
181 | defer C.free(unsafe.Pointer(cxsl))
182 |
183 | ret = C.make_style(cxsl, &cssp)
184 | if ret != 0 {
185 | return nil, ErrXSLParseFailure
186 | }
187 |
188 | return &Stylesheet{ptr: cssp}, nil
189 | }
190 |
--------------------------------------------------------------------------------
/xslt.h:
--------------------------------------------------------------------------------
1 | #ifndef GOXSLT_H
2 | #define GOXSLT_H
3 |
4 | #include
5 |
6 | int apply_style(xsltStylesheetPtr style, const char *xml, const char **params,
7 | char **xml_txt, size_t *xml_txt_len);
8 |
9 | void free_style(xsltStylesheetPtr *style);
10 |
11 | int make_style(const char *xsl, xsltStylesheetPtr *style);
12 |
13 | int xslt(const char *xsl, const char *xml, const char **params, char **xml_txt,
14 | size_t *xml_txt_len);
15 |
16 | void init_exslt();
17 |
18 | const char **make_param_array(int num_tuples);
19 |
20 | void set_param(char **a, char *n, char *v, int t);
21 |
22 | void free_param_array(char **a, int num_tuples);
23 |
24 | #endif
25 |
--------------------------------------------------------------------------------
/xslt_test.go:
--------------------------------------------------------------------------------
1 | package xslt_test
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "testing"
7 |
8 | "github.com/wamuir/go-xslt"
9 | )
10 |
11 | func TestNewStylesheet(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | xslFile string
15 | }{
16 | {"style1", "testdata/style1.xsl"},
17 | {"style2", "testdata/style2.xsl"},
18 | }
19 |
20 | for _, c := range tests {
21 | t.Run(c.name, func(t *testing.T) {
22 | xsl, _ := ioutil.ReadFile(c.xslFile)
23 |
24 | xs, err := xslt.NewStylesheet(xsl)
25 | if err != nil {
26 | t.Errorf("got: %v, want: %v", err, nil)
27 | }
28 |
29 | if xs == nil {
30 | t.Errorf("got: %v, want: %v", xs, "non-nil")
31 | }
32 | })
33 | }
34 |
35 | errorTests := []struct {
36 | name string
37 | xslStr string
38 | err error
39 | }{
40 | {"emptyXSL", "", xslt.ErrXSLParseFailure},
41 | }
42 |
43 | for _, c := range errorTests {
44 | t.Run(c.name, func(t *testing.T) {
45 | xs, err := xslt.NewStylesheet([]byte(c.xslStr))
46 | if xs != nil {
47 | t.Errorf("got: %v, expected %v", xs, nil)
48 | }
49 |
50 | if err != c.err {
51 | t.Errorf("got: %v, expected %v", err, c.err)
52 | }
53 | })
54 | }
55 | }
56 |
57 | func TestStylesheetClose(t *testing.T) {
58 | tests := []struct {
59 | name string
60 | xslFile string
61 | }{
62 | {"style1", "testdata/style1.xsl"},
63 | {"style2", "testdata/style2.xsl"},
64 | {"nil", ""},
65 | }
66 |
67 | for _, c := range tests {
68 | t.Run(c.name, func(t *testing.T) {
69 | xs := new(xslt.Stylesheet)
70 | if len(c.xslFile) > 0 {
71 | xsl, _ := ioutil.ReadFile(c.xslFile)
72 | xs, _ = xslt.NewStylesheet(xsl)
73 | }
74 | func(xs *xslt.Stylesheet) {
75 | defer func() {
76 | if r := recover(); r != nil {
77 | t.Errorf("unexpected panic: %v", r)
78 | }
79 | }()
80 | xs.Close()
81 | }(xs)
82 | })
83 | }
84 | }
85 |
86 | func TestStylesheetTransform(t *testing.T) {
87 | tests := []struct {
88 | name string
89 | xmlFile string
90 | xslFile string
91 | resFile string
92 | }{
93 | {"style1", "testdata/document.xml", "testdata/style1.xsl", "testdata/result1.xml"},
94 | {"style2", "testdata/document.xml", "testdata/style2.xsl", "testdata/result2.xhtml"},
95 | }
96 |
97 | for _, c := range tests {
98 | t.Run(c.name, func(t *testing.T) {
99 | xml, _ := ioutil.ReadFile(c.xmlFile)
100 | xsl, _ := ioutil.ReadFile(c.xslFile)
101 |
102 | xs, _ := xslt.NewStylesheet(xsl)
103 |
104 | got, err := xs.Transform(xml)
105 | if err != nil {
106 | t.Errorf("got: %v, want: %v", err, nil)
107 | }
108 |
109 | want, _ := ioutil.ReadFile(c.resFile)
110 | if !bytes.Equal(got, want) {
111 | t.Errorf("got: %s, want: %s", got, want)
112 | }
113 | })
114 | }
115 |
116 | errorTests := []struct {
117 | name string
118 | xmlStr string
119 | xslFile string
120 | err error
121 | }{
122 | {"emptyXML", "", "testdata/style2.xsl", xslt.ErrXSLTFailure},
123 | }
124 |
125 | for _, c := range errorTests {
126 | t.Run(c.name, func(t *testing.T) {
127 | xsl, _ := ioutil.ReadFile(c.xslFile)
128 |
129 | xs, _ := xslt.NewStylesheet(xsl)
130 |
131 | got, err := xs.Transform([]byte(c.xmlStr))
132 | if got != nil {
133 | t.Errorf("got: %s, want: %v", got, nil)
134 | }
135 | if err != c.err {
136 | t.Errorf("got: %v, want: %v", err, c.err)
137 | }
138 | })
139 | }
140 | }
141 |
142 | func TestStylesheetTransformExslt(t *testing.T) {
143 | tests := []struct {
144 | name string
145 | xml []byte
146 | xsl []byte
147 | res []byte
148 | }{
149 | {
150 | "math/min",
151 | []byte(`
152 |
153 |
154 | 7
155 | 11
156 | 8
157 | 4
158 |
159 | `),
160 | []byte(`
161 |
166 |
167 |
168 |
169 | Minimum:
170 |
171 |
172 |
173 |
174 | `),
175 | []byte(`
176 | Minimum: 4
177 | `),
178 | },
179 | }
180 |
181 | for _, c := range tests {
182 | t.Run(c.name, func(t *testing.T) {
183 | xs, err := xslt.NewStylesheet(c.xsl)
184 | if err != nil {
185 | t.Fatal(err)
186 | }
187 |
188 | got, err := xs.Transform(c.xml)
189 | if err != nil {
190 | t.Fatal(err)
191 | }
192 |
193 | want := c.res
194 | if !bytes.Equal(got, want) {
195 | t.Errorf("got: %s, want: %s", got, want)
196 | }
197 | })
198 | }
199 |
200 | }
201 |
202 | func TestStylesheetTransformParameter(t *testing.T) {
203 | xml := []byte(`
204 |
205 | one
206 |
207 | `)
208 | xsl := []byte(`
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | `)
217 |
218 | tests := []struct {
219 | name string
220 | xml []byte
221 | xsl []byte
222 | par xslt.Parameter
223 | res []byte
224 | }{
225 | {"xpath", xml, xsl, xslt.XPathParameter{"a", "data/entry[@name='a']/text()"}, []byte(`one`)},
226 | {"str", xml, xsl, xslt.StringParameter{"a", "two"}, []byte(`two`)},
227 | {"str/dq", xml, xsl, xslt.StringParameter{"a", "th'ree"}, []byte(`th'ree`)},
228 | {"str/sq", xml, xsl, xslt.StringParameter{"a", "fo\"ur"}, []byte(`fo"ur`)},
229 | }
230 |
231 | for _, c := range tests {
232 | t.Run(c.name, func(t *testing.T) {
233 | xs, err := xslt.NewStylesheet(c.xsl)
234 | if err != nil {
235 | t.Fatal(err)
236 | }
237 |
238 | got, err := xs.Transform(c.xml, c.par)
239 | if err != nil {
240 | t.Fatal(err)
241 | }
242 |
243 | want := c.res
244 | if !bytes.Equal(got, want) {
245 | t.Errorf("got: %s, want: %s", got, want)
246 | }
247 | })
248 | }
249 |
250 | is := string([]byte{0xff, 0xfe, 0xfd}) // an invalid UTF-8 string
251 |
252 | errorTests := []struct {
253 | name string
254 | xml []byte
255 | xsl []byte
256 | par xslt.Parameter
257 | err error
258 | }{
259 | {"xpath/in", xml, xsl, xslt.XPathParameter{is, "x"}, xslt.ErrUTF8Validation},
260 | {"xpath/in", xml, xsl, xslt.XPathParameter{"a", is}, xslt.ErrUTF8Validation},
261 | {"str/in", xml, xsl, xslt.StringParameter{is, "x"}, xslt.ErrUTF8Validation},
262 | {"str/iv", xml, xsl, xslt.StringParameter{"a", is}, xslt.ErrUTF8Validation},
263 | {"str/mq", xml, xsl, xslt.StringParameter{"a", `x'"z`}, xslt.ErrMixedQuotes},
264 | }
265 |
266 | for _, c := range errorTests {
267 | t.Run(c.name, func(t *testing.T) {
268 | xs, err := xslt.NewStylesheet(c.xsl)
269 | if err != nil {
270 | t.Fatal(err)
271 | }
272 |
273 | got, err := xs.Transform(c.xml, c.par)
274 | if got != nil {
275 | t.Errorf("got: %s, want: %v", got, nil)
276 | }
277 | if err != c.err {
278 | t.Errorf("got: %v, want: %v", err, c.err)
279 | }
280 | })
281 | }
282 |
283 | }
284 |
285 | func BenchmarkStylesheetTransform(b *testing.B) {
286 | xml, _ := ioutil.ReadFile("testdata/document.xml")
287 | xsl, _ := ioutil.ReadFile("testdata/style1.xsl")
288 | xs, _ := xslt.NewStylesheet(xsl)
289 |
290 | b.ResetTimer()
291 | for i := 0; i < b.N; i++ {
292 | if _, err := xs.Transform(xml); err != nil {
293 | b.Errorf("got %v, want %v", err, nil)
294 | }
295 | }
296 | }
297 |
298 | func BenchmarkStylesheetTransformStringParam(b *testing.B) {
299 | xml, _ := ioutil.ReadFile("testdata/document.xml")
300 | xsl, _ := ioutil.ReadFile("testdata/style1.xsl")
301 | xs, _ := xslt.NewStylesheet(xsl)
302 |
303 | p := []xslt.Parameter{
304 | xslt.StringParameter{"a", "b"},
305 | xslt.StringParameter{"c", "d"},
306 | xslt.StringParameter{"e", "f"},
307 | xslt.StringParameter{"g", "h"},
308 | xslt.StringParameter{"i", "j"},
309 | xslt.StringParameter{"k", "l"},
310 | xslt.StringParameter{"m", "n"},
311 | xslt.StringParameter{"o", "p"},
312 | xslt.StringParameter{"q", "r"},
313 | xslt.StringParameter{"s", "t"},
314 | xslt.StringParameter{"u", "v"},
315 | xslt.StringParameter{"w", "x"},
316 | xslt.StringParameter{"y", "z"},
317 | }
318 |
319 | b.ResetTimer()
320 | for i := 0; i < b.N; i++ {
321 | if _, err := xs.Transform(xml, p...); err != nil {
322 | b.Errorf("got %v, want %v", err, nil)
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------