|
|
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package present
import ( "bytes" "html" "html/template" "strings" "unicode" "unicode/utf8" )
/* Fonts are demarcated by an initial and final char bracketing a space-delimited word, plus possibly some terminal punctuation. The chars are _ for italic * for bold ` (back quote) for fixed width. Inner appearances of the char become spaces. For instance, _this_is_italic_! becomes <i>this is italic</i>! */
func init() { funcs["style"] = Style }
// Style returns s with HTML entities escaped and font indicators turned into
// HTML font tags.
func Style(s string) template.HTML { return template.HTML(font(html.EscapeString(s))) }
// font returns s with font indicators turned into HTML font tags.
func font(s string) string { if !strings.ContainsAny(s, "[`_*") { return s } words := split(s) var b bytes.Buffer Word: for w, word := range words { if len(word) < 2 { continue Word } if link, _ := parseInlineLink(word); link != "" { words[w] = link continue Word } const marker = "_*`" // Initial punctuation is OK but must be peeled off.
first := strings.IndexAny(word, marker) if first == -1 { continue Word } // Opening marker must be at the beginning of the token or else preceded by punctuation.
if first != 0 { r, _ := utf8.DecodeLastRuneInString(word[:first]) if !unicode.IsPunct(r) { continue Word } } open, word := word[:first], word[first:] char := word[0] // ASCII is OK.
close := "" switch char { default: continue Word case '_': open += "<i>" close = "</i>" case '*': open += "<b>" close = "</b>" case '`': open += "<code>" close = "</code>" } // Closing marker must be at the end of the token or else followed by punctuation.
last := strings.LastIndex(word, word[:1]) if last == 0 { continue Word } if last+1 != len(word) { r, _ := utf8.DecodeRuneInString(word[last+1:]) if !unicode.IsPunct(r) { continue Word } } head, tail := word[:last+1], word[last+1:] b.Reset() b.WriteString(open) var wid int for i := 1; i < len(head)-1; i += wid { var r rune r, wid = utf8.DecodeRuneInString(head[i:]) if r != rune(char) { // Ordinary character.
b.WriteRune(r) continue } if head[i+1] != char { // Inner char becomes space.
b.WriteRune(' ') continue } // Doubled char becomes real char.
// Not worth worrying about "_x__".
b.WriteByte(char) wid++ // Consumed two chars, both ASCII.
} b.WriteString(close) // Write closing tag.
b.WriteString(tail) // Restore trailing punctuation.
words[w] = b.String() } return strings.Join(words, "") }
// split is like strings.Fields but also returns the runs of spaces
// and treats inline links as distinct words.
func split(s string) []string { var ( words = make([]string, 0, 10) start = 0 )
// appendWord appends the string s[start:end] to the words slice.
// If the word contains the beginning of a link, the non-link portion
// of the word and the entire link are appended as separate words,
// and the start index is advanced to the end of the link.
appendWord := func(end int) { if j := strings.Index(s[start:end], "[["); j > -1 { if _, l := parseInlineLink(s[start+j:]); l > 0 { // Append portion before link, if any.
if j > 0 { words = append(words, s[start:start+j]) } // Append link itself.
words = append(words, s[start+j:start+j+l]) // Advance start index to end of link.
start = start + j + l return } } // No link; just add the word.
words = append(words, s[start:end]) start = end }
wasSpace := false for i, r := range s { isSpace := unicode.IsSpace(r) if i > start && isSpace != wasSpace { appendWord(i) } wasSpace = isSpace } for start < len(s) { appendWord(len(s)) } return words }
|