expression.go

  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}