| // 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 | |
| } |