├── .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 | [![Go Reference](https://pkg.go.dev/badge/github.com/wamuir/go-xslt.svg)](https://pkg.go.dev/github.com/wamuir/go-xslt) 5 | [![Build Status](https://github.com/wamuir/go-xslt/actions/workflows/go.yml/badge.svg?branch=master&event=push)](https://github.com/wamuir/go-xslt/actions/workflows/go.yml?query=event%3Apush+branch%3Amaster) 6 | [![codecov](https://codecov.io/gh/wamuir/go-xslt/branch/master/graph/badge.svg)](https://codecov.io/gh/wamuir/go-xslt) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/wamuir/go-xslt)](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 |
    41 | 42 | 43 | 44 |
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 | // 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 | 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 |
      15 | 16 | 17 | 18 |
    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 | --------------------------------------------------------------------------------