1// Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
2//
3// This program is free software; you can redistribute it and/or
4// modify it under the terms of The BSD 3-Clause License
5// that can be found in the LICENSE file.
6
7package uritemplate
8
9import (
10 "regexp"
11 "strconv"
12 "strings"
13)
14
15type template interface {
16 expand(*strings.Builder, Values) error
17 regexp(*strings.Builder)
18}
19
20type literals string
21
22func (l literals) expand(b *strings.Builder, _ Values) error {
23 b.WriteString(string(l))
24 return nil
25}
26
27func (l literals) regexp(b *strings.Builder) {
28 b.WriteString("(?:")
29 b.WriteString(regexp.QuoteMeta(string(l)))
30 b.WriteByte(')')
31}
32
33type varspec struct {
34 name string
35 maxlen int
36 explode bool
37}
38
39type expression struct {
40 vars []varspec
41 op parseOp
42 first string
43 sep string
44 named bool
45 ifemp string
46 escape escapeFunc
47 allow runeClass
48}
49
50func (e *expression) init() {
51 switch e.op {
52 case parseOpSimple:
53 e.sep = ","
54 e.escape = escapeExceptU
55 e.allow = runeClassU
56 case parseOpPlus:
57 e.sep = ","
58 e.escape = escapeExceptUR
59 e.allow = runeClassUR
60 case parseOpCrosshatch:
61 e.first = "#"
62 e.sep = ","
63 e.escape = escapeExceptUR
64 e.allow = runeClassUR
65 case parseOpDot:
66 e.first = "."
67 e.sep = "."
68 e.escape = escapeExceptU
69 e.allow = runeClassU
70 case parseOpSlash:
71 e.first = "/"
72 e.sep = "/"
73 e.escape = escapeExceptU
74 e.allow = runeClassU
75 case parseOpSemicolon:
76 e.first = ";"
77 e.sep = ";"
78 e.named = true
79 e.escape = escapeExceptU
80 e.allow = runeClassU
81 case parseOpQuestion:
82 e.first = "?"
83 e.sep = "&"
84 e.named = true
85 e.ifemp = "="
86 e.escape = escapeExceptU
87 e.allow = runeClassU
88 case parseOpAmpersand:
89 e.first = "&"
90 e.sep = "&"
91 e.named = true
92 e.ifemp = "="
93 e.escape = escapeExceptU
94 e.allow = runeClassU
95 }
96}
97
98func (e *expression) expand(w *strings.Builder, values Values) error {
99 first := true
100 for _, varspec := range e.vars {
101 value := values.Get(varspec.name)
102 if !value.Valid() {
103 continue
104 }
105
106 if first {
107 w.WriteString(e.first)
108 first = false
109 } else {
110 w.WriteString(e.sep)
111 }
112
113 if err := value.expand(w, varspec, e); err != nil {
114 return err
115 }
116
117 }
118 return nil
119}
120
121func (e *expression) regexp(b *strings.Builder) {
122 if e.first != "" {
123 b.WriteString("(?:") // $1
124 b.WriteString(regexp.QuoteMeta(e.first))
125 }
126 b.WriteByte('(') // $2
127 runeClassToRegexp(b, e.allow, e.named || e.vars[0].explode)
128 if len(e.vars) > 1 || e.vars[0].explode {
129 max := len(e.vars) - 1
130 for i := 0; i < len(e.vars); i++ {
131 if e.vars[i].explode {
132 max = -1
133 break
134 }
135 }
136
137 b.WriteString("(?:") // $3
138 b.WriteString(regexp.QuoteMeta(e.sep))
139 runeClassToRegexp(b, e.allow, e.named || max < 0)
140 b.WriteByte(')') // $3
141 if max > 0 {
142 b.WriteString("{0,")
143 b.WriteString(strconv.Itoa(max))
144 b.WriteByte('}')
145 } else {
146 b.WriteByte('*')
147 }
148 }
149 b.WriteByte(')') // $2
150 if e.first != "" {
151 b.WriteByte(')') // $1
152 }
153 b.WriteByte('?')
154}
155
156func runeClassToRegexp(b *strings.Builder, class runeClass, named bool) {
157 b.WriteString("(?:(?:[")
158 if class&runeClassR == 0 {
159 b.WriteString(`\x2c`)
160 if named {
161 b.WriteString(`\x3d`)
162 }
163 }
164 if class&runeClassU == runeClassU {
165 b.WriteString(reUnreserved)
166 }
167 if class&runeClassR == runeClassR {
168 b.WriteString(reReserved)
169 }
170 b.WriteString("]")
171 b.WriteString("|%[[:xdigit:]][[:xdigit:]]")
172 b.WriteString(")*)")
173}