package goxic
import (
"fmt"
"reflect"
"strconv"
"strings"
"git.fractalqb.de/fractalqb/goxic/content"
)
const (
BftMarker = "$"
BftPathSep = "."
)
var zero = reflect.Value{}
func bftResolve(path string, data interface{}) (bindThis interface{}, err error) {
psegs := strings.Split(path, BftPathSep)
for si, seg := range psegs {
rval := reflect.ValueOf(data)
if idx, err := strconv.Atoi(seg); err == nil {
switch rval.Type().Kind() {
case reflect.Array, reflect.Slice:
if idx < 0 {
idx = rval.Len() + idx
}
if idx < 0 || idx >= rval.Len() {
return nil, nil
}
data = rval.Index(idx).Interface()
default:
return nil, fmt.Errorf("segemnt %d in path '%s' requires slice or array, got %s",
si,
path,
rval.Type().Kind())
}
} else {
switch rval.Type().Kind() {
case reflect.Map:
tmp := rval.MapIndex(reflect.ValueOf(seg))
if tmp == zero {
return nil, nil
} else {
data = tmp.Interface()
}
case reflect.Struct:
tmp := rval.FieldByName(seg)
if tmp == zero {
return nil, nil
} else {
data = tmp.Interface()
}
default:
return nil, fmt.Errorf("segemnt %d in path '%s' requires map or struct, got %s",
si,
path,
rval.Type().Kind())
}
}
}
return data, nil
}
func bftSplitSpec(specPh string) (fmt string, path string) {
sep := strings.Index(specPh, " ")
if sep > 0 {
return specPh[:sep], specPh[sep+1:]
}
return "", specPh
}
// Fill the placeholders using the bind from template (BFT) feature. The BFT
// feature allows template authors to specify a 'path' that reaches into the
// Go data object passed to Fill, i.e. the binding is determined by the template
// not the program. BFT uses a special notation for placeholder names to achieve
// this.
// TODO …
func (bt *BounT) Fill(data interface{}, overwrite bool) (missed int, err error) {
tpl := bt.Template()
for ph, idxs := range tpl.phNm2Idxs {
if !strings.HasPrefix(ph, BftMarker) {
continue
}
ph := ph[1:]
// TODO maybe its efficient to 1st check if there is something to bind
// TODO consider overwrite
fmt, path := bftSplitSpec(ph)
bv, err := bftResolve(path, data) // TODO slow?
if err != nil {
return -1, err
}
if bv == nil {
missed++
} else if len(fmt) == 0 {
bt.Bind(content.P(bv), idxs...)
} else {
bt.Bind(content.Fmt(fmt, bv), idxs...)
}
}
return missed, nil
}
package goxic
import (
"io"
)
type EscGoTmpl struct {
To io.Writer
Esc func(io.Writer, []byte)
}
func (ew *EscGoTmpl) Write(p []byte) (n int, err error) {
ew.Esc(ew.To, p)
return len(p), nil
}
package goxic
import (
"bytes"
"fmt"
"io"
"regexp"
"sort"
"strings"
)
type fragment []byte
type PhIdx = int
type PhIdxs = []PhIdx
// Template holds a sequence of fixed (dstatic) content fragment that
// have to be emitted verbatim. These fragments are intermixed with
// named placeholders. The content to fill in the placeholders
// generally is computed dynamically. A placeholder can appear several
// times in different posotions within a template. A template can
// start or end either with a placeholder or with fixed content.
//
// To bind cotent to placeholders one first has to crate a bound
// template (BounT) to hold the bindings. When all placeholders ar
// bound the resulting content can be emitted.
type Template struct {
Name string
fix []fragment
plhAt []string
cxfAt []CntXformer // TODO
phNm2Idxs map[string][]int
}
func NewTemplate(name string) *Template {
res := Template{
Name: name,
phNm2Idxs: make(map[string][]int)}
return &res
}
// Pack compacts memory used for the fix parts and the placeholder indices to
// improve locality. This may also release unused memory when the fix parts are
// slices into some bigger and otherwise unused piece of memory.
func (t *Template) Pack() (self *Template) {
t.packFix()
t.packIdxs()
return t
}
func (t *Template) packFix() {
psz := 0
for _, frag := range t.fix {
psz += len(frag)
}
pfix := make([]byte, 0, psz)
for _, frag := range t.fix {
pfix = append(pfix, frag...)
}
start := 0
for i, f := range t.fix {
slice := pfix[start : start+len(f)]
t.fix[i] = slice
start += len(f)
}
}
func (t *Template) packIdxs() {
isz := 0
for _, idxs := range t.phNm2Idxs {
isz += len(idxs)
}
pidx := make([]int, 0, isz)
var phs []string
for ph, idxs := range t.phNm2Idxs {
phs = append(phs, ph)
pidx = append(pidx, idxs...)
}
start := 0
for _, p := range phs {
i := t.phNm2Idxs[p]
slice := pidx[start : start+len(i)]
t.phNm2Idxs[p] = slice
start += len(i)
}
}
// Add adds a new piece of static content to the end of the template.
// Note that static context is merged to preceeding static content as
// long as no placholder was added before.
func (t *Template) AddFix(fixFragment []byte) *Template {
if phnm := t.PhAt(len(t.fix)); len(phnm) > 0 {
t.fix = append(t.fix, fixFragment)
} else if len(fixFragment) > 0 {
if len(t.fix) == 0 {
t.fix = []fragment{fixFragment}
} else {
lFrag := t.fix[len(t.fix)-1]
tmp := make(fragment, len(lFrag)+len(fixFragment))
copy(tmp, lFrag)
copy(tmp[len(lFrag):], fixFragment)
t.fix[len(t.fix)-1] = tmp
}
}
return t
}
// AddStr adds a string as static content to the end of the temlate.
func (t *Template) AddStr(str string) *Template {
return t.AddFix(fragment(str))
}
// Ph adds a new placeholder to the end of the template.
func (t *Template) Ph(name string) *Template {
idx := t.FixCount()
if phnm := t.PhAt(idx); len(phnm) > 0 {
t.AddFix([]byte{})
idx++
}
for len(t.plhAt) < idx {
t.plhAt = append(t.plhAt, "")
}
t.plhAt = append(t.plhAt, name)
if idxs, ok := t.phNm2Idxs[name]; ok {
idxs = append(idxs, idx)
t.phNm2Idxs[name] = idxs
} else {
t.phNm2Idxs[name] = []int{idx}
}
return t
}
func (t *Template) PhWrap(name string, wrapper CntXformer) *Template {
res := t.Ph(name)
t.Wrap(wrapper, len(t.plhAt)-1)
return res
}
func (t *Template) Wrap(wrapper CntXformer, idxs ...int) {
for _, idx := range idxs {
if cap(t.cxfAt) <= idx {
nesc := make([]CntXformer, idx+1)
copy(nesc, t.cxfAt)
nesc[idx] = wrapper
t.cxfAt = nesc
} else {
t.cxfAt[idx] = wrapper
}
}
}
// FixCount returns the number of pieces of static content in the
// template.
func (t *Template) FixCount() int {
return len(t.fix)
}
// FixAt returns the piece of static content with the index idx
// (indices are zero-based).
func (t *Template) FixAt(idx int) []byte {
if idx < 0 || idx >= len(t.fix) {
return nil
} else {
return t.fix[idx]
}
}
func (t *Template) ForeachPh(r func(name string, idxs []int)) {
for nm, idxs := range t.phNm2Idxs {
r(nm, idxs)
}
}
// PlaceholderNum returns the number of placeholders defined in the
// template.
func (t *Template) PhCount() int {
return len(t.phNm2Idxs)
}
// Placeholders returns all placeholders – more precisely placeholder
// names – defined in the template.
func (t *Template) Phs() []string {
res := make([]string, 0, len(t.phNm2Idxs))
for nm := range t.phNm2Idxs {
res = append(res, nm)
}
return res
}
// PlaceholderAt returns the placeholder that will be emitted between
// static content idx-1 and static content idx. Note that
// PlaceholderAt(0) will be emitted before the first piece of static
// content. This placeholder is optional.
func (t *Template) PhAt(idx int) string {
if t.plhAt == nil || idx >= len(t.plhAt) {
return ""
}
return t.plhAt[idx]
}
func (t *Template) WrapAt(idx int) CntXformer {
if t.cxfAt == nil || idx >= len(t.cxfAt) {
return nil
}
return t.cxfAt[idx]
}
// PlaceholderIdxs returns the positions in which one placeholder will
// be emitted. Note that placeholder index 0 is – if define – emitted
// before the first piece of fixed content.
func (t *Template) PhIdxs(name string) []int {
if res, ok := t.phNm2Idxs[name]; !ok {
return nil
} else if len(res) == 0 {
delete(t.phNm2Idxs, name)
return nil
} else {
return res
}
}
type renameErr []string
func (re renameErr) Error() string {
switch len(re) {
case 1:
return fmt.Sprintf("template has no placeholder '%s'", re[0])
case 2:
return fmt.Sprintf("cannot rename '%s', new name '%s' already exists",
re[0], re[1])
default:
return "general renaming error"
}
}
func RenameUnknown(err error) bool {
re, ok := err.(renameErr)
if ok {
return len(re) == 1
} else {
return false
}
}
func RenameExists(err error) bool {
re, ok := err.(renameErr)
if ok {
return len(re) == 2
} else {
return false
}
}
// RenamePh renames the current placeholder to a new name. If merge is true
// the current placeholder will be renamed even if a placeholder with the
// newName already exists. Otherwise an error is retrned. Renaming a placeholder
// that does not exists also result in an error.
func (t *Template) RenamePh(current, newName string, merge bool) error {
var cIdxs []int
var ok bool
if cIdxs, ok = t.phNm2Idxs[current]; !ok {
return renameErr{current}
}
if current == newName {
return nil
}
if nIdxs, ok := t.phNm2Idxs[newName]; ok && !merge {
return renameErr{current, newName}
} else if ok {
cIdxs = append(cIdxs, nIdxs...)
sort.Slice(cIdxs, func(i, j int) bool { return cIdxs[i] < cIdxs[j] })
}
delete(t.phNm2Idxs, current)
t.phNm2Idxs[newName] = cIdxs
for i := range t.plhAt {
if t.plhAt[i] == current {
t.plhAt[i] = newName
}
}
return nil
}
func (t *Template) RenamePhs(merge bool, current, newNames []string) error {
n2i := make(map[string][]int)
for i, cn := range current {
if cidxs, ok := t.phNm2Idxs[cn]; !ok {
return renameErr{cn}
} else {
nn := newNames[i]
if nidxs, ok := n2i[nn]; ok {
if !merge {
return renameErr{cn, nn}
}
nidxs = append(nidxs, cidxs...)
sort.Slice(nidxs, func(i, j int) bool { return nidxs[i] < nidxs[j] })
n2i[nn] = nidxs
} else {
n2i[nn] = cidxs
}
}
delete(t.phNm2Idxs, cn)
}
for n, idxs := range t.phNm2Idxs {
n2i[n] = idxs
}
t.phNm2Idxs = n2i
for n, idxs := range n2i {
for _, i := range idxs {
t.plhAt[i] = n
}
}
return nil
}
func StripPath(ph string) string {
sep := strings.LastIndexByte(ph, byte(NameSep))
if sep >= 0 {
ph = ph[sep+1:]
}
return ph
}
func (t *Template) XformPhs(merge bool, x func(string) string) error {
cur := t.Phs()
nnm := make([]string, len(cur))
for i := range cur {
nnm[i] = x(cur[i])
}
return t.RenamePhs(merge, cur, nnm)
}
func (t *Template) FlattenPhs(merge bool) error {
return t.XformPhs(merge, StripPath)
}
// Static returns the fixed part as byte slice if there are no placeholders.
// Otherwise, i.e. the template is not static, it returns nil.
func (t *Template) Static() []byte {
if t.PhCount() == 0 {
switch t.FixCount() {
case 0:
return []byte{}
case 1:
return t.FixAt(0)
default:
panic("template " + t.Name + " without placeholder has many fix fragments")
}
} else {
return nil
}
}
func (t *Template) StaticWith(fill Content) ([]byte, error) {
bt := t.NewInitBounT(fill, nil)
t, err := bt.Fixate()
if err != nil {
return nil, err
}
return t.Static(), nil
}
func (t *Template) Write(wr io.Writer, phStr func(string) string) (err error) {
for i, f := range t.fix {
if ph := t.PhAt(i); len(ph) > 0 {
_, err = wr.Write([]byte(phStr(ph)))
if err != nil {
return err
}
}
_, err = wr.Write(f)
if err != nil {
return err
}
}
if ph := t.PhAt(len(t.fix)); len(ph) > 0 {
_, err = wr.Write([]byte(phStr(ph)))
if err != nil {
return err
}
}
return nil
}
// Content will write the content to an io.Writer.
type Content = io.WriterTo
type empty int
func (e empty) WriteTo(_ io.Writer) (int64, error) { return 0, nil }
// Constant Empty can be use as empty Content, i.e. nothing will be emitted as
// output.
const Empty empty = 0
func Must(n int64, err error) int64 {
if err != nil {
panic(err)
}
return n
}
type CntXformer func(Content) (wrapped Content)
// CntXChain creates a content transformer from a list of content transgformers
// applying them in the order they were passed in cxs.
func CntXChain(cxs ...CntXformer) CntXformer {
return func(cnt Content) Content {
for _, cx := range cxs {
cnt = cx(cnt)
}
return cnt
}
}
// BounT keeps the placeholder bindings for one specific Template. Use
// NewBounT or NewInitBounT to create a binding object from a
// Template.
type BounT struct {
tmpl *Template
fill []Content
}
// NewBounT initializes a new binding objekt for template t. When nil is passed
// a new BounT object is allocated on the heap. Otherwise the passed BounT
// object is reused.
func (t *Template) NewBounT(reuse *BounT) *BounT {
if reuse == nil {
reuse = new(BounT)
}
reuse.tmpl = t
reuse.fill = make([]Content, t.FixCount()+1)
return reuse
}
// NewInitBounT first uses NewBounT to initialize a BounT object for temlate t.
// All placeholders are then initially bound to content cnt.
func (t *Template) NewInitBounT(cnt Content, reuse *BounT) *BounT {
reuse = t.NewBounT(reuse)
for i := 0; i < len(reuse.fill); i++ {
if len(t.PhAt(i)) > 0 {
if esc := t.WrapAt(i); esc == nil {
reuse.fill[i] = cnt
} else {
reuse.fill[i] = esc(cnt)
}
}
}
return reuse
}
// Template returns the template that bt is the binding for.
func (bt *BounT) Template() *Template {
return bt.tmpl
}
// HasUnbound checks if there are placehoders that are not yet bound to content.
// When a map is passed to HasUnbound all placeholder names are put into the
// map along with the indices of the yet unbound placeholders.
func (bt *BounT) HasUnbound(unbound map[string]PhIdxs) (res bool) {
tmpl := bt.tmpl
for i := range bt.fill {
if len(tmpl.PhAt(i)) > 0 && bt.fill[i] == nil {
if unbound == nil {
return true
}
res = true
idxs := unbound[tmpl.plhAt[i]]
idxs = append(idxs, i)
unbound[tmpl.plhAt[i]] = idxs
}
}
return res
}
// Bind returns the number of "anonymous binds", i.e. placeholders with empty
// names that got a binding.
func (bt *BounT) Bind(cnt Content, phIdxs ...int) (anonymous int) {
t := bt.Template()
for _, i := range phIdxs {
if len(t.PhAt(i)) == 0 {
anonymous++
}
if esc := t.WrapAt(i); esc == nil {
bt.fill[i] = cnt
} else {
bt.fill[i] = esc(cnt)
}
}
return anonymous
}
// BindName binds the content cnt to the placeholders by name. If no placeholder
// with name exists an error is returned.
func (bt *BounT) BindName(cnt Content, name string) error {
idxs := bt.Template().PhIdxs(name)
if idxs == nil {
return fmt.Errorf("no placeholder: '%s'", name)
} else {
bt.Bind(cnt, idxs...)
return nil
}
}
// BindName binds the content cnt to the placeholders by name. If no placeholder
// with name exists nothings is bound.
func (bt *BounT) BindIfName(cnt Content, name string) {
idxs := bt.Template().PhIdxs(name)
if idxs != nil {
bt.Bind(cnt, idxs...)
}
}
func (bt *BounT) BindMatch(cnt Content, pattern *regexp.Regexp) {
for _, ph := range bt.Template().Phs() {
if pattern.MatchString(ph) {
bt.BindName(cnt, ph)
}
}
}
type EmitError struct {
Count int64
Err error
}
func (ee EmitError) Error() string {
return ee.Err.Error()
}
// WriteTo writes the BounT content to wr.
func (bt *BounT) WriteTo(wr io.Writer) (res int64, err error) {
fixs := bt.tmpl.fix
fCount := len(fixs)
for i := 0; i < fCount; i++ {
if f := bt.fill[i]; f != nil {
n, err := f.WriteTo(wr)
res += n
if err != nil {
return res, EmitError{res, err}
}
} else if ph := bt.tmpl.PhAt(i); len(ph) > 0 {
return res, EmitError{res,
fmt.Errorf("unbound placeholder '%s' in template '%s'",
ph,
bt.tmpl.Name)}
}
n, err := wr.Write(fixs[i])
res += int64(n)
if err != nil {
return res, EmitError{res, err}
}
}
if f := bt.fill[fCount]; f != nil {
n, err := f.WriteTo(wr)
res += n
if err != nil {
return res, EmitError{res, err}
}
} else if ph := bt.tmpl.PhAt(fCount); len(ph) > 0 {
return res, EmitError{res,
fmt.Errorf("unbound placeholder '%s' in template '%s'",
ph,
bt.tmpl.Name)}
}
return res, nil
}
func (bt *BounT) String() string {
var sb strings.Builder
if _, err := bt.WriteTo(&sb); err != nil {
panic(err)
}
return sb.String()
}
const NameSep = ':'
// Returns nil if there is nothing to fixate.
func (bt *BounT) Fixate() (*Template, error) {
it := bt.Template()
if it.PhCount() == 0 {
return nil, nil
}
res := NewTemplate(it.Name)
if err := bt.fix(res, ""); err != nil {
return nil, err
}
return res, nil
}
func (bt *BounT) fix(to *Template, phPrefix string) error {
it := bt.Template()
for idx, frag := range it.fix {
pre := bt.fill[idx]
if pre == nil {
if phnm := it.PhAt(idx); len(phnm) > 0 {
if cxf := it.WrapAt(idx); cxf == nil {
to.Ph(phPrefix + phnm)
} else {
to.PhWrap(phPrefix+phnm, cxf)
}
}
} else if sbt, ok := pre.(*BounT); ok {
subPrefix := phPrefix + sbt.Template().Name + string(NameSep)
if err := sbt.fix(to, subPrefix); err != nil {
return err
}
} else {
var buf bytes.Buffer
if _, err := pre.WriteTo(&buf); err != nil {
return err
}
to.AddStr(buf.String())
}
to.AddFix(frag)
}
idx := len(it.fix)
pre := bt.fill[idx]
if pre == nil {
if phnm := it.PhAt(idx); len(phnm) > 0 {
if cxf := it.WrapAt(idx); cxf == nil {
to.Ph(phPrefix + phnm)
} else {
to.PhWrap(phPrefix+phnm, cxf)
}
}
} else if sbt, ok := pre.(*BounT); ok {
subPrefix := phPrefix + sbt.Template().Name + string(NameSep)
if err := sbt.fix(to, subPrefix); err != nil {
return err
}
} else {
buf := bytes.NewBuffer(nil)
if _, err := pre.WriteTo(buf); err != nil {
return err
}
to.AddStr(buf.String())
}
return nil
}
// Wrap all bound content into the transformer 'wrapper'.
func (bt *BounT) Wrap(wrapper CntXformer) {
for i, f := range bt.fill {
if f != nil {
w := wrapper(f)
bt.fill[i] = w
}
}
}
type tagMode int
//go:generate stringer -type tagMode
const (
tagNone tagMode = iota
tagMand
tagOpt
tagIgnore
)
func parseTag(tag string) (mode tagMode, placeholder string, err error) {
mode = tagMand
if len(tag) > 0 {
if sep := strings.IndexRune(tag, ' '); sep >= 0 {
if sep == 0 {
return mode, "", fmt.Errorf("goxic imap: tag format '%s'", tag)
}
placeholder = tag[:sep]
if placeholder == "-" {
return tagIgnore, placeholder, nil
}
switch tag[sep+1:] {
case "opt":
mode = tagOpt
default:
return mode, "", fmt.Errorf("goxic imap: illgeal tag option '%s'", tag[sep+1:])
}
return mode, placeholder, nil
} else {
return mode, tag, nil
}
} else {
return tagNone, "", nil
}
}
// BindTmpls fills the placeholders of bt with new BounTs created for the
// respective templates from ts.
func (bt *BounT) BindTmpls(ts map[string]*Template, selectOnly bool, phSelect map[string]string) {
if selectOnly && phSelect == nil {
return
}
for dph, _ := range bt.tmpl.phNm2Idxs {
sph := dph
if phSelect != nil {
if tmp, ok := phSelect[dph]; ok {
sph = tmp
} else if selectOnly {
continue
}
}
if st := ts[sph]; st != nil {
bt.BindName(st.NewBounT(nil), dph)
}
}
}
/* MergeTemplates binds temlpates into the template into accoriding to ph names */
func MergedTemplates(
into *Template,
ts map[string]*Template,
selectOnly bool,
phSelect map[string]string,
) (*Template, error) {
var bi BounT
into.NewBounT(&bi)
bi.BindTmpls(ts, selectOnly, phSelect)
res, err := bi.Fixate()
if err != nil {
return res, err
}
err = res.FlattenPhs(true)
return res, err
}
package html
// Copyright © Marcus Perlick
import (
"fmt"
"io"
"text/template"
"git.fractalqb.de/fractalqb/goxic"
)
func NewParser() *goxic.Parser {
res := goxic.NewParser("`", "`", "<!--", "-->")
return res
}
type Escaper struct {
Cnt goxic.Content
}
func (hc Escaper) WriteTo(wr io.Writer) (int64, error) {
esc := goxic.EscGoTmpl{To: wr, Esc: template.HTMLEscape}
return hc.Cnt.WriteTo(&esc)
}
func EscWrap(cnt goxic.Content) goxic.Content { return Escaper{cnt} }
// Span wraps content into a HTML <span></span> element
type Span struct {
id string
class string
Wrapped goxic.Content
}
func NewSpan(around goxic.Content, spanId string, spanClass string) *Span {
res := Span{
id: template.HTMLEscapeString(spanId),
class: template.HTMLEscapeString(spanClass),
Wrapped: around,
}
return &res
}
func (s *Span) WriteTo(wr io.Writer) (res int64, err error) {
if len(s.id) > 0 {
if len(s.class) > 0 {
n, err := fmt.Fprintf(wr,
"<span id=\"%s\" class=\"%s\">",
s.id,
s.class)
res += int64(n)
if err != nil {
return res, goxic.EmitError{res, err}
}
} else {
n, err := fmt.Fprintf(wr, "<span id=\"%s\">", s.id)
res += int64(n)
if err != nil {
return res, goxic.EmitError{res, err}
}
}
} else if len(s.class) > 0 {
n, err := fmt.Fprintf(wr, "<span class=\"%s\">", s.class)
res += int64(n)
if err != nil {
return res, goxic.EmitError{res, err}
}
} else {
n, err := wr.Write([]byte("<span>"))
res += int64(n)
if err != nil {
return res, goxic.EmitError{res, err}
}
}
if n, err := s.Wrapped.WriteTo(wr); err != nil {
res += int64(n)
return res, goxic.EmitError{res, err}
} else {
res += int64(n)
}
if n, err := wr.Write([]byte("</span>")); err != nil {
res += int64(n)
return res, goxic.EmitError{res, err}
} else {
res += int64(n)
}
return res, nil
}
package goxic
import (
"fmt"
"reflect"
)
func IdName(nm string) string { return nm }
func InitIndexMap(
imap interface{},
tmpl *Template,
mapNames func(string) string,
) (phCount int, err error) {
imVal := reflect.ValueOf(imap).Elem()
if imVal.Kind() != reflect.Struct {
return 0, fmt.Errorf("cannto make index map in %s", imVal.Type().Kind().String())
}
imTyp := imVal.Type()
for fidx := 0; fidx < imVal.NumField(); fidx++ {
imf := imTyp.Field(fidx)
if imf.Anonymous {
if imf.Type == reflect.TypeOf(tmpl) {
// TODO at most once!
fVal := imVal.Field(fidx)
fVal.Set(reflect.ValueOf(tmpl))
} else if imf.Type.Kind() == reflect.Struct {
fVal := imVal.Field(fidx)
phc, err := InitIndexMap(fVal.Addr().Interface(), tmpl, mapNames)
phCount += phc
if err != nil {
return phCount, err
}
}
} else if isPhIdxs(&imf) {
if ph, opt, err := phIdxsArgs(&imf, mapNames); len(ph) > 0 {
idxs := tmpl.PhIdxs(ph)
if idxs != nil {
phCount++
sf := imVal.Field(fidx)
sf.Set(reflect.ValueOf(idxs))
} else if opt {
sf := imVal.Field(fidx)
sf.Set(reflect.ValueOf(emptyIndices))
} else {
return phCount, fmt.Errorf(
"template '%s' has no placeholder '%s' for field '%s'",
tmpl.Name,
ph,
imf.Name,
)
}
} else {
return phCount, fmt.Errorf("failed to index field: %s", err.Error())
}
}
}
return phCount, nil
}
func MustIndexMap(imap interface{}, t *Template, complete bool, mapNames func(string) string) {
phc, err := InitIndexMap(imap, t, mapNames)
if err != nil {
panic(err)
}
if complete {
switch {
case phc == t.PhCount():
return
case phc < t.PhCount():
panic(fmt.Errorf("only %d / %d placeholders mapped in template %s",
phc,
t.PhCount(),
t.Name))
default:
panic(fmt.Errorf("mapped more placeholders (%d / %d) than exist in template %s",
phc,
t.PhCount(),
t.Name))
}
}
}
var emptyIndices = []int{}
func isPhIdxs(f *reflect.StructField) bool {
fty := f.Type
if fty.Kind() != reflect.Slice {
return false
}
if fty.Elem().Kind() != reflect.Int {
return false
}
return true
}
func phIdxsArgs(
f *reflect.StructField,
mapNames func(string) string,
) (ph string, opt bool, err error) {
// TODO skip unsettable fields
mode, ph, err := parseTag(f.Tag.Get("goxic"))
switch mode {
case tagMand:
opt = false
case tagOpt:
opt = true
}
if err != nil {
return "", opt, err
}
if len(ph) == 0 && mapNames != nil {
ph = mapNames(f.Name)
}
return ph, opt, nil
}
package js
import (
"html/template"
"io"
"git.fractalqb.de/fractalqb/goxic"
)
type Escaper struct {
Cnt goxic.Content
}
func (hc Escaper) WriteTo(wr io.Writer) (int64, error) {
esc := goxic.EscGoTmpl{To: wr, Esc: template.JSEscape}
return hc.Cnt.WriteTo(&esc)
}
func EscWrap(cnt goxic.Content) goxic.Content { return Escaper{cnt} }
package goxic
// Copyright © Marcus Perlick
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"regexp"
"strings"
)
type Parser struct {
StartInlinePh string
EndInlinePh string
BlockPh *regexp.Regexp
PhNameRgxGrp int
PhLBrkRgxGrp int
PhTBrkRgxGrp int
StartSubTemplate *regexp.Regexp
StartNameRgxGrp int
StartLBrkRgxGrp int
EndSubTemplate *regexp.Regexp
EndNameRgxGrp int
EndTBrkRgxGrp int
Endl string
PrepLine func([]byte) []byte
// When not nil, placeholders of the form <name>\<tag> will get the content
// wrapper CxfMap[<tag>] for the placeholder <name>.
CxfMap map[string]CntXformer
}
func NewParser(inlineStart, inlineEnd, lcomStart, lcomEnd string) *Parser {
res := &Parser{
StartInlinePh: inlineStart,
EndInlinePh: inlineEnd,
BlockPh: regexp.MustCompile(
`^[ \t]*` +
lcomStart +
`(\\?) >>> ([a-zA-Z0-9_-]+) <<< (\\?)` +
lcomEnd +
`[ \t]*$`),
PhNameRgxGrp: 2,
PhLBrkRgxGrp: 1,
PhTBrkRgxGrp: 3,
StartSubTemplate: regexp.MustCompile(
`^[ \t]*` +
lcomStart +
`(\\?) >>> ([a-zA-Z0-9_-]+) >>> ` +
lcomEnd +
`[ \t]*$`),
StartNameRgxGrp: 2,
StartLBrkRgxGrp: 1,
EndSubTemplate: regexp.MustCompile(
`^[ \t]*` +
lcomStart +
` <<< ([a-zA-Z0-9_-]+) <<< (\\?)` +
lcomEnd +
`[ \t]*$`),
EndNameRgxGrp: 1,
EndTBrkRgxGrp: 2,
Endl: "\n"}
return res
}
func PrepTrimWS(line string) (trimmed string) {
return strings.Trim(line, " \t")
}
func pPush(stack []string, s []byte) (updStack []string, path string) {
updStack = append(stack, string(s))
path = pathStr(updStack)
return updStack, path
}
func top(stack []string) string {
if len(stack) == 0 {
panic("empty stack")
} else {
return stack[len(stack)-1]
}
}
func pPop(stack []string) (updStack []string, path string) {
updStack = stack[:len(stack)-1]
path = pathStr(updStack)
return updStack, path
}
const PathSep = '/'
func pathStr(path []string) string {
buf := bytes.NewBufferString("")
for i, s := range path {
if i > 0 {
buf.WriteRune(PathSep)
}
buf.WriteString(s)
}
return buf.String()
}
func tmplName(rootName string, path string) string {
if len(path) > 0 {
return rootName + string(PathSep) + path
} else {
return rootName
}
}
func needTemplate(t *Template, rootPath string, name string) (*Template, error) {
name = tmplName(rootPath, name)
if t == nil {
t = NewTemplate(name)
} else if t.Name != name {
return t, fmt.Errorf("template name mismatch '%s' ≠ '%s'",
t.Name,
name)
}
return t, nil
}
func storeTemplate(res map[string]*Template, t *Template, key string, dup DuplicateTemplates) bool {
if t == nil {
return true
}
if old, ok := res[key]; ok && old != t {
dup[key] = t
return false
} else {
res[key] = t
return true
}
}
func (p *Parser) phLBrk(match [][]byte) bool {
return len(match[p.PhLBrkRgxGrp]) == 0
}
func (p *Parser) phTBrk(match [][]byte) bool {
return len(match[p.PhTBrkRgxGrp]) == 0
}
func (p *Parser) startLBrk(match [][]byte) bool {
return len(match[p.StartLBrkRgxGrp]) == 0
}
func (p *Parser) endTBrk(match [][]byte) bool {
return len(match[p.EndTBrkRgxGrp]) == 0
}
type DuplicateTemplates map[string]*Template
func (err DuplicateTemplates) Error() string {
buf := bytes.NewBufferString("duplicate templates: ")
sep := ""
for nm := range err {
buf.WriteString(sep)
buf.WriteString(nm)
sep = ", "
}
return buf.String()
}
func (p *Parser) Parse(rd io.Reader, rootName string, into map[string]*Template) error {
var dup DuplicateTemplates = make(map[string]*Template)
scn := bufio.NewScanner(rd)
var (
path []string
pStr = ""
endl = ""
)
var curTmpl *Template = nil
for scn.Scan() {
line := scn.Bytes()
if match := p.StartSubTemplate.FindSubmatch(line); len(match) > 0 {
if p.startLBrk(match) {
var err error
curTmpl, err = needTemplate(curTmpl, rootName, pStr)
if err != nil {
return err
}
curTmpl.AddStr(endl)
}
storeTemplate(into, curTmpl, pStr, dup)
subtName := match[p.StartNameRgxGrp]
if bytes.IndexRune(subtName, PathSep) >= 0 {
return fmt.Errorf(
"sub-temlpate name '%s' contains path separator %c",
subtName,
PathSep)
}
path, pStr = pPush(path, subtName)
curTmpl = into[pStr]
endl = ""
} else if match := p.EndSubTemplate.FindSubmatch(line); len(match) > 0 {
subtName := match[p.EndNameRgxGrp]
if len(path) == 0 || top(path) != string(subtName) {
return fmt.Errorf(
"unexpected sub-template end '%s'",
subtName)
}
storeTemplate(into, curTmpl, pStr, dup)
path, pStr = pPop(path)
curTmpl = into[pStr]
if p.endTBrk(match) {
endl = p.Endl
} else {
endl = ""
}
} else if match := p.BlockPh.FindSubmatch(line); len(match) > 0 {
var err error
curTmpl, err = needTemplate(curTmpl, rootName, pStr)
if err != nil {
return err
}
if p.phLBrk(match) {
curTmpl.AddStr(endl)
}
phName := match[p.PhNameRgxGrp]
p.addPh(curTmpl, string(phName))
if p.phTBrk(match) {
endl = p.Endl
} else {
endl = ""
}
} else {
var err error
curTmpl, err = needTemplate(curTmpl, rootName, pStr)
if err != nil {
return err
}
curTmpl.AddStr(endl)
if p.PrepLine != nil {
line = p.PrepLine(line)
}
err = p.addLine(curTmpl, line)
if err != nil {
return err
}
endl = p.Endl
}
}
if len(path) > 0 {
return fmt.Errorf("end of input in nested template: %s",
strings.Join(path, ", "))
}
storeTemplate(into, curTmpl, pStr, dup)
if len(dup) > 0 {
return dup
} else {
return nil
}
}
// ph=="" && rest!="" => no end of placeholder
func (p *Parser) inlStart(line []byte) (pre, ph, rest []byte) {
start := []byte(p.StartInlinePh)
startLen := len(start)
end := []byte(p.EndInlinePh)
endLen := len(end)
pos := 0
for {
if tmp := bytes.Index(line[pos:], start); tmp < 0 {
break
} else {
pos += tmp + startLen
}
if phend := bytes.Index(line[pos:], end); phend > 0 {
phend += pos
return line[:pos-startLen],
line[pos:phend],
line[phend+endLen:]
} else if phend == 0 {
copy(line[pos:], line[pos+endLen:])
line = line[:len(line)-endLen]
} else {
return line, nil, line[pos-startLen:]
}
}
return line, nil, nil
}
func (p *Parser) addLine(t *Template, line []byte) error {
var ph, rest []byte
line, ph, rest = p.inlStart(line)
for len(ph) > 0 {
if len(line) > 0 {
t.AddFix(bytes.Repeat(line, 1))
}
err := p.addPh(t, string(ph))
if err != nil {
return err
}
line, ph, rest = p.inlStart(rest)
}
if len(rest) > 0 {
return fmt.Errorf(
"unexpected end of inline placeholder '%s' at %d",
line,
len(line)-len(rest))
}
if len(line) > 0 {
t.AddFix(bytes.Repeat(line, 1))
}
return nil
}
func (p *Parser) addPh(t *Template, name string) error {
if p.CxfMap == nil {
t.Ph(name)
} else if sep := strings.IndexByte(name, '\\'); sep >= 0 {
wrapTag := name[sep+1:]
name = name[:sep]
wrapr := p.CxfMap[wrapTag]
if wrapr == nil {
return fmt.Errorf("no content wrapper '%s' for placeholder '%s'",
wrapTag,
name)
}
t.PhWrap(name, wrapr)
} else {
t.Ph(name)
}
return nil
}
func (p *Parser) ParseFile(templateFile, rootName string, into map[string]*Template) error {
tFile, err := os.Open(templateFile)
if err != nil {
return err
}
defer tFile.Close()
//p.PrepLine = goxic.PrepTrimWS
err = p.Parse(tFile, rootName, into)
return err
}
func (p *Parser) ParseString(s, rootName string, into map[string]*Template) error {
rd := strings.NewReader(s)
return p.Parse(rd, rootName, into)
}
// Code generated by "stringer -type tagMode"; DO NOT EDIT.
package goxic
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[tagNone-0]
_ = x[tagMand-1]
_ = x[tagOpt-2]
_ = x[tagIgnore-3]
}
const _tagMode_name = "tagNonetagMandtagOpttagIgnore"
var _tagMode_index = [...]uint8{0, 7, 14, 20, 29}
func (i tagMode) String() string {
if i < 0 || i >= tagMode(len(_tagMode_index)-1) {
return "tagMode(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _tagMode_name[_tagMode_index[i]:_tagMode_index[i+1]]
}