diff options
Diffstat (limited to 'markup/internal/attributes/attributes.go')
-rw-r--r-- | markup/internal/attributes/attributes.go | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/markup/internal/attributes/attributes.go b/markup/internal/attributes/attributes.go new file mode 100644 index 000000000..1cce7edd1 --- /dev/null +++ b/markup/internal/attributes/attributes.go @@ -0,0 +1,219 @@ +// Copyright 2022 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributes + +import ( + "fmt" + "strconv" + "strings" + "sync" + + "github.com/gohugoio/hugo/common/hugio" + "github.com/spf13/cast" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/util" +) + +// Markdown attributes used as options by the Chroma highlighter. +var chromaHightlightProcessingAttributes = map[string]bool{ + "anchorLineNos": true, + "guessSyntax": true, + "hl_Lines": true, + "lineAnchors": true, + "lineNos": true, + "lineNoStart": true, + "lineNumbersInTable": true, + "noClasses": true, + "nohl": true, + "style": true, + "tabWidth": true, +} + +func init() { + for k, v := range chromaHightlightProcessingAttributes { + chromaHightlightProcessingAttributes[strings.ToLower(k)] = v + } +} + +type AttributesOwnerType int + +const ( + AttributesOwnerGeneral AttributesOwnerType = iota + AttributesOwnerCodeBlock +) + +func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *AttributesHolder { + var ( + attrs []Attribute + opts []Attribute + ) + for _, v := range astAttributes { + nameLower := strings.ToLower(string(v.Name)) + if strings.HasPrefix(string(nameLower), "on") { + continue + } + var vv interface{} + switch vvv := v.Value.(type) { + case bool, float64: + vv = vvv + case []interface{}: + // Highlight line number hlRanges. + var hlRanges [][2]int + for _, l := range vvv { + if ln, ok := l.(float64); ok { + hlRanges = append(hlRanges, [2]int{int(ln) - 1, int(ln) - 1}) + } else if rng, ok := l.([]uint8); ok { + slices := strings.Split(string([]byte(rng)), "-") + lhs, err := strconv.Atoi(slices[0]) + if err != nil { + continue + } + rhs := lhs + if len(slices) > 1 { + rhs, err = strconv.Atoi(slices[1]) + if err != nil { + continue + } + } + hlRanges = append(hlRanges, [2]int{lhs - 1, rhs - 1}) + } + } + vv = hlRanges + case []byte: + // Note that we don't do any HTML escaping here. + // We used to do that, but that changed in #9558. + // Noww it's up to the templates to decide. + vv = string(vvv) + default: + panic(fmt.Sprintf("not implemented: %T", vvv)) + } + + if ownerType == AttributesOwnerCodeBlock && chromaHightlightProcessingAttributes[nameLower] { + attr := Attribute{Name: string(v.Name), Value: vv} + opts = append(opts, attr) + } else { + attr := Attribute{Name: nameLower, Value: vv} + attrs = append(attrs, attr) + } + + } + + return &AttributesHolder{ + attributes: attrs, + options: opts, + } +} + +type Attribute struct { + Name string + Value interface{} +} + +func (a Attribute) ValueString() string { + return cast.ToString(a.Value) +} + +type AttributesHolder struct { + // What we get from Goldmark. + attributes []Attribute + + // Attributes considered to be an option (code blocks) + options []Attribute + + // What we send to the the render hooks. + attributesMapInit sync.Once + attributesMap map[string]interface{} + optionsMapInit sync.Once + optionsMap map[string]interface{} +} + +type Attributes map[string]interface{} + +func (a *AttributesHolder) Attributes() map[string]interface{} { + a.attributesMapInit.Do(func() { + a.attributesMap = make(map[string]interface{}) + for _, v := range a.attributes { + a.attributesMap[v.Name] = v.Value + } + }) + return a.attributesMap +} + +func (a *AttributesHolder) Options() map[string]interface{} { + a.optionsMapInit.Do(func() { + a.optionsMap = make(map[string]interface{}) + for _, v := range a.options { + a.optionsMap[v.Name] = v.Value + } + }) + return a.optionsMap +} + +func (a *AttributesHolder) AttributesSlice() []Attribute { + return a.attributes +} + +func (a *AttributesHolder) OptionsSlice() []Attribute { + return a.options +} + +// RenderASTAttributes writes the AST attributes to the given as attributes to an HTML element. +// This is used by the default HTML renderers, e.g. for headings etc. where no hook template could be found. +// This performs HTML esacaping of string attributes. +func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) { + for _, attr := range attributes { + + a := strings.ToLower(string(attr.Name)) + if strings.HasPrefix(a, "on") { + continue + } + + _, _ = w.WriteString(" ") + _, _ = w.Write(attr.Name) + _, _ = w.WriteString(`="`) + + switch v := attr.Value.(type) { + case []byte: + _, _ = w.Write(util.EscapeHTML(v)) + default: + w.WriteString(cast.ToString(v)) + } + + _ = w.WriteByte('"') + } +} + +// Render writes the attributes to the given as attributes to an HTML element. +// This is used for the default codeblock renderering. +// This performs HTML esacaping of string attributes. +func RenderAttributes(w hugio.FlexiWriter, skipClass bool, attributes ...Attribute) { + for _, attr := range attributes { + a := strings.ToLower(string(attr.Name)) + if skipClass && a == "class" { + continue + } + _, _ = w.WriteString(" ") + _, _ = w.WriteString(attr.Name) + _, _ = w.WriteString(`="`) + + switch v := attr.Value.(type) { + case []byte: + _, _ = w.Write(util.EscapeHTML(v)) + default: + w.WriteString(cast.ToString(v)) + } + + _ = w.WriteByte('"') + } +} |