diff options
19 files changed, 2532 insertions, 2287 deletions
diff --git a/bridge/telegram/html.go b/bridge/telegram/html.go index 85547d7b..35b3f4f9 100644 --- a/bridge/telegram/html.go +++ b/bridge/telegram/html.go @@ -53,13 +53,16 @@ func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) { } func makeHTML(input string) string { - return string(blackfriday.Markdown([]byte(input), - &customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")}, - blackfriday.EXTENSION_NO_INTRA_EMPHASIS| - blackfriday.EXTENSION_FENCED_CODE| - blackfriday.EXTENSION_AUTOLINK| - blackfriday.EXTENSION_SPACE_HEADERS| - blackfriday.EXTENSION_HEADER_IDS| - blackfriday.EXTENSION_BACKSLASH_LINE_BREAK| - blackfriday.EXTENSION_DEFINITION_LISTS)) + extensions := blackfriday.NoIntraEmphasis | + blackfriday.FencedCode | + blackfriday.Autolink | + blackfriday.SpaceHeadings | + blackfriday.HeadingIDs | + blackfriday.BackslashLineBreak | + blackfriday.DefinitionLists + + renderer := &customHTML{blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ + Flags: blackfriday.UseXHTML | blackfriday.SkipImages, + })} + return string(blackfriday.Run([]byte(input), blackfriday.WithExtensions(extensions), blackfriday.WithRenderer(renderer))) } @@ -53,10 +53,11 @@ require ( github.com/pkg/errors v0.8.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a - github.com/russross/blackfriday v1.5.1 + github.com/russross/blackfriday v2.0.0+incompatible github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 // indirect github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991 + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect github.com/sirupsen/logrus v0.0.0-20180213143110-8c0189d9f6bb github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect @@ -105,14 +105,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a h1:UWKek6MK3K6/TpbsFcv+8rrO6rSc6KKSp2FbMOHWsq4= github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/russross/blackfriday v1.5.1 h1:B8ZN6pD4PVofmlDCDUdELeYrbsVIDM/bpjW3v3zgcRc= -github.com/russross/blackfriday v1.5.1/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 h1:Lx3BlDGFElJt4u/zKc9A3BuGYbQAGlEFyPuUA3jeMD0= github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI= github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991 h1:PQiUTDzUC5EUh0vNurK7KQS22zlKqLLOFn+K9nJXDQQ= github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991/go.mod h1:KwtnpMClmrXsHCKTbRui5xBUNt17n1GGrGhdiw2KcoY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v0.0.0-20180213143110-8c0189d9f6bb h1:eKjx20EiekBRT2tjZ0XEdKpftfPJQwiavtFshwTyqf0= github.com/sirupsen/logrus v0.0.0-20180213143110-8c0189d9f6bb/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 h1:lXQ+j+KwZcbwrbgU0Rp4Eglg3EJLHbuZU3BbOqAGBmg= diff --git a/vendor/github.com/russross/blackfriday/.travis.yml b/vendor/github.com/russross/blackfriday/.travis.yml index a1687f17..a4eb2577 100644 --- a/vendor/github.com/russross/blackfriday/.travis.yml +++ b/vendor/github.com/russross/blackfriday/.travis.yml @@ -1,30 +1,18 @@ -sudo: false +# Travis CI (http://travis-ci.org/) is a continuous integration service for +# open source projects. This file configures it to run unit tests for +# blackfriday. + language: go + go: - - 1.5.4 - - 1.6.2 - - tip -matrix: - include: - - go: 1.2.2 - script: - - go get -t -v ./... - - go test -v -race ./... - - go: 1.3.3 - script: - - go get -t -v ./... - - go test -v -race ./... - - go: 1.4.3 - script: - - go get -t -v ./... - - go test -v -race ./... - allow_failures: - - go: tip - fast_finish: true + - 1.5 + - 1.6 + - 1.7 + install: - - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). + - go get -d -t -v ./... + - go build -v ./... + script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d -s .) - - go tool vet . - - go test -v -race ./... + - go test -v ./... + - go test -run=^$ -bench=BenchmarkReference -benchmem diff --git a/vendor/github.com/russross/blackfriday/README.md b/vendor/github.com/russross/blackfriday/README.md index a6c94b79..2e0db355 100644 --- a/vendor/github.com/russross/blackfriday/README.md +++ b/vendor/github.com/russross/blackfriday/README.md @@ -1,6 +1,4 @@ -Blackfriday -[![Build Status][BuildSVG]][BuildURL] -[![Godoc][GodocV2SVG]][GodocV2URL] +Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) =========== Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It @@ -18,27 +16,27 @@ It started as a translation from C of [Sundown][3]. Installation ------------ -Blackfriday is compatible with any modern Go release. With Go and git installed: +Blackfriday is compatible with any modern Go release. With Go 1.7 and git +installed: - go get -u gopkg.in/russross/blackfriday.v2 + go get gopkg.in/russross/blackfriday.v2 -will download, compile, and install the package into your `$GOPATH` directory -hierarchy. +will download, compile, and install the package into your `$GOPATH` +directory hierarchy. Alternatively, you can achieve the same if you +import it into a project: + + import "gopkg.in/russross/blackfriday.v2" + +and `go get` without parameters. Versions -------- Currently maintained and recommended version of Blackfriday is `v2`. It's being -developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the -documentation is available at -https://godoc.org/gopkg.in/russross/blackfriday.v2. - -It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`, -but we highly recommend using package management tool like [dep][7] or -[Glide][8] and make use of semantic versioning. With package management you -should import `github.com/russross/blackfriday` and specify that you're using -version 2.0.0. +developed on its own branch: https://github.com/russross/blackfriday/v2. You +should install and import it via [gopkg.in][6] at +`gopkg.in/russross/blackfriday.v2`. Version 2 offers a number of improvements over v1: @@ -58,43 +56,9 @@ Potential drawbacks: v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for tracking. -If you are still interested in the legacy `v1`, you can import it from -`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found -here: https://godoc.org/github.com/russross/blackfriday - -### Known issue with `dep` - -There is a known problem with using Blackfriday v1 _transitively_ and `dep`. -Currently `dep` prioritizes semver versions over anything else, and picks the -latest one, plus it does not apply a `[[constraint]]` specifier to transitively -pulled in packages. So if you're using something that uses Blackfriday v1, but -that something does not use `dep` yet, you will get Blackfriday v2 pulled in and -your first dependency will fail to build. - -There are couple of fixes for it, documented here: -https://github.com/golang/dep/blob/master/docs/FAQ.md#how-do-i-constrain-a-transitive-dependencys-version - -Meanwhile, `dep` team is working on a more general solution to the constraints -on transitive dependencies problem: https://github.com/golang/dep/issues/1124. - - Usage ----- -### v1 - -For basic usage, it is as simple as getting your input into a byte -slice and calling: - - output := blackfriday.MarkdownBasic(input) - -This renders it with no extensions enabled. To get a more useful -feature set, use this instead: - - output := blackfriday.MarkdownCommon(input) - -### v2 - For the most sensible markdown processing, it is as simple as getting your input into a byte slice and calling: @@ -121,7 +85,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday: ```go import ( "github.com/microcosm-cc/bluemonday" - "gopkg.in/russross/blackfriday.v2" + "github.com/russross/blackfriday" ) // ... @@ -129,21 +93,11 @@ unsafe := blackfriday.Run(input) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) ``` -### Custom options, v1 - -If you want to customize the set of options, first get a renderer -(currently only the HTML output engine), then use it to -call the more general `Markdown` function. For examples, see the -implementations of `MarkdownBasic` and `MarkdownCommon` in -`markdown.go`. - -### Custom options, v2 +### Custom options If you want to customize the set of options, use `blackfriday.WithExtensions`, `blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. -### `blackfriday-tool` - You can also check out `blackfriday-tool` for a more complete example of how to use it. Download and install it using: @@ -163,22 +117,6 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that can be copied to wherever you need it without worrying about dependencies and library versions. -### Sanitized anchor names - -Blackfriday includes an algorithm for creating sanitized anchor names -corresponding to a given input text. This algorithm is used to create -anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The -algorithm has a specification, so that other packages can create -compatible anchor names and links to those anchors. - -The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names. - -[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to -create compatible links to the anchor names generated by blackfriday. -This algorithm is also implemented in a small standalone package at -[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients -that want a small package and don't need full functionality of blackfriday. - Features -------- @@ -246,7 +184,7 @@ implements the following extensions: and supply a language (to make syntax highlighting simple). Just mark it like this: - ``` go + ```go func getTrue() bool { return true } @@ -255,15 +193,6 @@ implements the following extensions: You can use 3 or more backticks to mark the beginning of the block, and the same number to mark the end of the block. - To preserve classes of fenced code blocks while using the bluemonday - HTML sanitizer, use the following policy: - - ``` go - p := bluemonday.UGCPolicy() - p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code") - html := p.SanitizeBytes(unsafe) - ``` - * **Definition lists**. A simple definition list is made of a single-line term followed by a colon and the definition for that term. @@ -289,10 +218,8 @@ implements the following extensions: * **Strikethrough**. Use two tildes (`~~`) to mark text that should be crossed out. -* **Hard line breaks**. With this extension enabled (it is off by - default in the `MarkdownBasic` and `MarkdownCommon` convenience - functions), newlines in the input translate into line breaks in - the output. +* **Hard line breaks**. With this extension enabled newlines in the input + translate into line breaks in the output. This extension is off by default. * **Smart quotes**. Smartypants-style punctuation substitution is supported, turning normal double- and single-quote marks into @@ -332,14 +259,14 @@ are a few of note: renders output as LaTeX. -TODO +Todo ---- * More unit testing -* Improve Unicode support. It does not understand all Unicode +* Improve unicode support. It does not understand all unicode rules (about what constitutes a letter, a punctuation symbol, etc.), so it may fail to detect word boundaries correctly in - some instances. It is safe on all UTF-8 input. + some instances. It is safe on all utf-8 input. License @@ -354,10 +281,3 @@ License [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" [6]: https://labix.org/gopkg.in "gopkg.in" - [7]: https://github.com/golang/dep/ "dep" - [8]: https://github.com/Masterminds/glide "Glide" - - [BuildSVG]: https://travis-ci.org/russross/blackfriday.svg?branch=master - [BuildURL]: https://travis-ci.org/russross/blackfriday - [GodocV2SVG]: https://godoc.org/gopkg.in/russross/blackfriday.v2?status.svg - [GodocV2URL]: https://godoc.org/gopkg.in/russross/blackfriday.v2 diff --git a/vendor/github.com/russross/blackfriday/block.go b/vendor/github.com/russross/blackfriday/block.go index 7fc731d5..d7da33f2 100644 --- a/vendor/github.com/russross/blackfriday/block.go +++ b/vendor/github.com/russross/blackfriday/block.go @@ -15,17 +15,26 @@ package blackfriday import ( "bytes" - "unicode" + "html" + "regexp" + + "github.com/shurcooL/sanitized_anchor_name" +) + +const ( + charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" + escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]" +) + +var ( + reBackslashOrAmp = regexp.MustCompile("[\\&]") + reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity) ) // Parse block-level data. // Note: this function and many that it calls assume that // the input buffer ends with a newline. -func (p *parser) block(out *bytes.Buffer, data []byte) { - if len(data) == 0 || data[len(data)-1] != '\n' { - panic("block input is missing terminating newline") - } - +func (p *Markdown) block(data []byte) { // this is called recursively: enforce a maximum depth if p.nesting >= p.maxNesting { return @@ -34,14 +43,14 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // parse out one block-level construct at a time for len(data) > 0 { - // prefixed header: + // prefixed heading: // - // # Header 1 - // ## Header 2 + // # Heading 1 + // ## Heading 2 // ... - // ###### Header 6 - if p.isPrefixHeader(data) { - data = data[p.prefixHeader(out, data):] + // ###### Heading 6 + if p.isPrefixHeading(data) { + data = data[p.prefixHeading(data):] continue } @@ -51,7 +60,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // ... // </div> if data[0] == '<' { - if i := p.html(out, data, true); i > 0 { + if i := p.html(data, true); i > 0 { data = data[i:] continue } @@ -62,9 +71,9 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // % stuff // % more stuff // % even more stuff - if p.flags&EXTENSION_TITLEBLOCK != 0 { + if p.extensions&Titleblock != 0 { if data[0] == '%' { - if i := p.titleBlock(out, data, true); i > 0 { + if i := p.titleBlock(data, true); i > 0 { data = data[i:] continue } @@ -86,7 +95,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // return b // } if p.codePrefix(data) > 0 { - data = data[p.code(out, data):] + data = data[p.code(data):] continue } @@ -100,8 +109,8 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // return n * fact(n-1) // } // ``` - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data, true); i > 0 { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data, true); i > 0 { data = data[i:] continue } @@ -115,9 +124,9 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // or // ______ if p.isHRule(data) { - p.r.HRule(out) + p.addBlock(HorizontalRule, nil) var i int - for i = 0; data[i] != '\n'; i++ { + for i = 0; i < len(data) && data[i] != '\n'; i++ { } data = data[i:] continue @@ -128,7 +137,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // > A big quote I found somewhere // > on the web if p.quotePrefix(data) > 0 { - data = data[p.quote(out, data):] + data = data[p.quote(data):] continue } @@ -138,8 +147,8 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // ------|-----|--------- // Bob | 31 | 555-1234 // Alice | 27 | 555-4321 - if p.flags&EXTENSION_TABLES != 0 { - if i := p.table(out, data); i > 0 { + if p.extensions&Tables != 0 { + if i := p.table(data); i > 0 { data = data[i:] continue } @@ -152,7 +161,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // // also works with + or - if p.uliPrefix(data) > 0 { - data = data[p.list(out, data, 0):] + data = data[p.list(data, 0):] continue } @@ -161,7 +170,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // 1. Item 1 // 2. Item 2 if p.oliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_ORDERED):] + data = data[p.list(data, ListTypeOrdered):] continue } @@ -173,55 +182,62 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // // Term 2 // : Definition c - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_DEFINITION):] + data = data[p.list(data, ListTypeDefinition):] continue } } // anything else must look like a normal paragraph - // note: this finds underlined headers, too - data = data[p.paragraph(out, data):] + // note: this finds underlined headings, too + data = data[p.paragraph(data):] } p.nesting-- } -func (p *parser) isPrefixHeader(data []byte) bool { +func (p *Markdown) addBlock(typ NodeType, content []byte) *Node { + p.closeUnmatchedBlocks() + container := p.addChild(typ, 0) + container.content = content + return container +} + +func (p *Markdown) isPrefixHeading(data []byte) bool { if data[0] != '#' { return false } - if p.flags&EXTENSION_SPACE_HEADERS != 0 { + if p.extensions&SpaceHeadings != 0 { level := 0 - for level < 6 && data[level] == '#' { + for level < 6 && level < len(data) && data[level] == '#' { level++ } - if data[level] != ' ' { + if level == len(data) || data[level] != ' ' { return false } } return true } -func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { +func (p *Markdown) prefixHeading(data []byte) int { level := 0 - for level < 6 && data[level] == '#' { + for level < 6 && level < len(data) && data[level] == '#' { level++ } i := skipChar(data, level, ' ') end := skipUntilChar(data, i, '\n') skip := end id := "" - if p.flags&EXTENSION_HEADER_IDS != 0 { + if p.extensions&HeadingIDs != 0 { j, k := 0, 0 - // find start/end of header id + // find start/end of heading id for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { } for k = j + 1; k < end && data[k] != '}'; k++ { } - // extract header id iff found + // extract heading id iff found if j < end && k < end { id = string(data[j+2 : k]) end = j @@ -241,45 +257,41 @@ func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { end-- } if end > i { - if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[i:end])) + if id == "" && p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[i:end])) } - work := func() bool { - p.inline(out, data[i:end]) - return true - } - p.r.Header(out, work, level, id) + block := p.addBlock(Heading, data[i:end]) + block.HeadingID = id + block.Level = level } return skip } -func (p *parser) isUnderlinedHeader(data []byte) int { - // test of level 1 header +func (p *Markdown) isUnderlinedHeading(data []byte) int { + // test of level 1 heading if data[0] == '=' { i := skipChar(data, 1, '=') i = skipChar(data, i, ' ') - if data[i] == '\n' { + if i < len(data) && data[i] == '\n' { return 1 - } else { - return 0 } + return 0 } - // test of level 2 header + // test of level 2 heading if data[0] == '-' { i := skipChar(data, 1, '-') i = skipChar(data, i, ' ') - if data[i] == '\n' { + if i < len(data) && data[i] == '\n' { return 2 - } else { - return 0 } + return 0 } return 0 } -func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) titleBlock(data []byte, doRender bool) int { if data[0] != '%' { return 0 } @@ -293,12 +305,17 @@ func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { } data = bytes.Join(splitData[0:i], []byte("\n")) - p.r.TitleBlock(out, data) - - return len(data) + consumed := len(data) + data = bytes.TrimPrefix(data, []byte("% ")) + data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1) + block := p.addBlock(Heading, data) + block.Level = 1 + block.IsTitleblock = true + + return consumed } -func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) html(data []byte, doRender bool) int { var i, j int // identify the opening tag @@ -310,17 +327,12 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { // handle special cases if !tagfound { // check for an HTML comment - if size := p.htmlComment(out, data, doRender); size > 0 { + if size := p.htmlComment(data, doRender); size > 0 { return size } // check for an <hr> tag - if size := p.htmlHr(out, data, doRender); size > 0 { - return size - } - - // check for HTML CDATA - if size := p.htmlCDATA(out, data, doRender); size > 0 { + if size := p.htmlHr(data, doRender); size > 0 { return size } @@ -395,60 +407,42 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { for end > 0 && data[end-1] == '\n' { end-- } - p.r.BlockHtml(out, data[:end]) + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) } return i } -func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int { - // html block needs to end with a blank line - if i := p.isEmpty(data[start:]); i > 0 { - size := start + i +func finalizeHTMLBlock(block *Node) { + block.Literal = block.content + block.content = nil +} + +// HTML comment, lax form +func (p *Markdown) htmlComment(data []byte, doRender bool) int { + i := p.inlineHTMLComment(data) + // needs to end with a blank line + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j if doRender { // trim trailing newlines end := size for end > 0 && data[end-1] == '\n' { end-- } - p.r.BlockHtml(out, data[:end]) + block := p.addBlock(HTMLBlock, data[:end]) + finalizeHTMLBlock(block) } return size } return 0 } -// HTML comment, lax form -func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int { - i := p.inlineHTMLComment(out, data) - return p.renderHTMLBlock(out, data, i, doRender) -} - -// HTML CDATA section -func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int { - const cdataTag = "<![cdata[" - const cdataTagLen = len(cdataTag) - if len(data) < cdataTagLen+1 { - return 0 - } - if !bytes.Equal(bytes.ToLower(data[:cdataTagLen]), []byte(cdataTag)) { - return 0 - } - i := cdataTagLen - // scan for an end-of-comment marker, across lines if necessary - for i < len(data) && !(data[i-2] == ']' && data[i-1] == ']' && data[i] == '>') { - i++ - } - i++ - // no end-of-comment marker - if i >= len(data) { +// HR, which is the only self-closing block tag considered +func (p *Markdown) htmlHr(data []byte, doRender bool) int { + if len(data) < 4 { return 0 } - return p.renderHTMLBlock(out, data, i, doRender) -} - -// HR, which is the only self-closing block tag considered -func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { return 0 } @@ -456,22 +450,31 @@ func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { // not an <hr> tag after all; at least not a valid one return 0 } - i := 3 - for data[i] != '>' && data[i] != '\n' { + for i < len(data) && data[i] != '>' && data[i] != '\n' { i++ } - - if data[i] == '>' { - return p.renderHTMLBlock(out, data, i+1, doRender) + if i < len(data) && data[i] == '>' { + i++ + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + return size + } } - return 0 } -func (p *parser) htmlFindTag(data []byte) (string, bool) { +func (p *Markdown) htmlFindTag(data []byte) (string, bool) { i := 0 - for isalnum(data[i]) { + for i < len(data) && isalnum(data[i]) { i++ } key := string(data[:i]) @@ -481,9 +484,11 @@ func (p *parser) htmlFindTag(data []byte) (string, bool) { return "", false } -func (p *parser) htmlFindEnd(tag string, data []byte) int { +func (p *Markdown) htmlFindEnd(tag string, data []byte) int { // assume data[0] == '<' && data[1] == '/' already tested - + if tag == "hr" { + return 2 + } // check if tag is a match closetag := []byte("</" + tag + ">") if !bytes.HasPrefix(data, closetag) { @@ -503,7 +508,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int { return i } - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { + if p.extensions&LaxHTMLBlocks != 0 { return i } if skip = p.isEmpty(data[i:]); skip == 0 { @@ -514,7 +519,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int { return i + skip } -func (*parser) isEmpty(data []byte) int { +func (*Markdown) isEmpty(data []byte) int { // it is okay to call isEmpty on an empty buffer if len(data) == 0 { return 0 @@ -526,10 +531,13 @@ func (*parser) isEmpty(data []byte) int { return 0 } } - return i + 1 + if i < len(data) && data[i] == '\n' { + i++ + } + return i } -func (*parser) isHRule(data []byte) bool { +func (*Markdown) isHRule(data []byte) bool { i := 0 // skip up to three spaces @@ -545,7 +553,7 @@ func (*parser) isHRule(data []byte) bool { // the whole line must be the char or whitespace n := 0 - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { switch { case data[i] == c: n++ @@ -561,8 +569,7 @@ func (*parser) isHRule(data []byte) bool { // isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, // and returns the end index if so, or 0 otherwise. It also returns the marker found. // If syntax is not nil, it gets set to the syntax specified in the fence line. -// A final newline is mandatory to recognize the fence line, unless newlineOptional is true. -func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) { +func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) { i, size := 0, 0 // skip up to three spaces @@ -604,7 +611,7 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional i = skipChar(data, i, ' ') if i >= len(data) { - if newlineOptional && i == len(data) { + if i == len(data) { return i, marker } return 0, "" @@ -649,33 +656,33 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional i = skipChar(data, i, ' ') if i >= len(data) || data[i] != '\n' { - if newlineOptional && i == len(data) { + if i == len(data) { return i, marker } return 0, "" } - return i + 1, marker // Take newline into account. } // fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. // If doRender is true, a final newline is mandatory to recognize the fenced code block. -func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { var syntax string - beg, marker := isFenceLine(data, &syntax, "", false) + beg, marker := isFenceLine(data, &syntax, "") if beg == 0 || beg >= len(data) { return 0 } var work bytes.Buffer + work.Write([]byte(syntax)) + work.WriteByte('\n') for { // safe to assume beg < len(data) // check for the end of the code block - newlineOptional := !doRender - fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional) + fenceEnd, _ := isFenceLine(data[beg:], nil, marker) if fenceEnd != 0 { beg += fenceEnd break @@ -697,24 +704,55 @@ func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) } if doRender { - p.r.BlockCode(out, work.Bytes(), syntax) + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = true + finalizeCodeBlock(block) } return beg } -func (p *parser) table(out *bytes.Buffer, data []byte) int { - var header bytes.Buffer - i, columns := p.tableHeader(&header, data) +func unescapeChar(str []byte) []byte { + if str[0] == '\\' { + return []byte{str[1]} + } + return []byte(html.UnescapeString(string(str))) +} + +func unescapeString(str []byte) []byte { + if reBackslashOrAmp.Match(str) { + return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar) + } + return str +} + +func finalizeCodeBlock(block *Node) { + if block.IsFenced { + newlinePos := bytes.IndexByte(block.content, '\n') + firstLine := block.content[:newlinePos] + rest := block.content[newlinePos+1:] + block.Info = unescapeString(bytes.Trim(firstLine, "\n")) + block.Literal = rest + } else { + block.Literal = block.content + } + block.content = nil +} + +func (p *Markdown) table(data []byte) int { + table := p.addBlock(Table, nil) + i, columns := p.tableHeader(data) if i == 0 { + p.tip = table.Parent + table.Unlink() return 0 } - var body bytes.Buffer + p.addBlock(TableBody, nil) for i < len(data) { pipes, rowStart := 0, i - for ; data[i] != '\n'; i++ { + for ; i < len(data) && data[i] != '\n'; i++ { if data[i] == '|' { pipes++ } @@ -726,12 +764,12 @@ func (p *parser) table(out *bytes.Buffer, data []byte) int { } // include the newline in data sent to tableRow - i++ - p.tableRow(&body, data[rowStart:i], columns, false) + if i < len(data) && data[i] == '\n' { + i++ + } + p.tableRow(data[rowStart:i], columns, false) } - p.r.Table(out, header.Bytes(), body.Bytes(), columns) - return i } @@ -744,10 +782,10 @@ func isBackslashEscaped(data []byte, i int) bool { return backslashes&1 == 1 } -func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) { +func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) { i := 0 colCount := 1 - for i = 0; data[i] != '\n'; i++ { + for i = 0; i < len(data) && data[i] != '\n'; i++ { if data[i] == '|' && !isBackslashEscaped(data, i) { colCount++ } @@ -759,7 +797,11 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns } // include the newline in the data sent to tableRow - header := data[:i+1] + j := i + if j < len(data) && data[j] == '\n' { + j++ + } + header := data[:j] // column count ignores pipes at beginning or end of line if data[0] == '|' { @@ -769,7 +811,7 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns colCount-- } - columns = make([]int, colCount) + columns = make([]CellAlignFlags, colCount) // move on to the header underline i++ @@ -785,27 +827,29 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 // and trailing | optional on last column col := 0 - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { dashes := 0 if data[i] == ':' { i++ - columns[col] |= TABLE_ALIGNMENT_LEFT + columns[col] |= TableAlignmentLeft dashes++ } - for data[i] == '-' { + for i < len(data) && data[i] == '-' { i++ dashes++ } - if data[i] == ':' { + if i < len(data) && data[i] == ':' { i++ - columns[col] |= TABLE_ALIGNMENT_RIGHT + columns[col] |= TableAlignmentRight dashes++ } - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } - + if i == len(data) { + return + } // end of column test is messy switch { case dashes < 3: @@ -816,12 +860,12 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns // marker found, now skip past trailing whitespace col++ i++ - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } // trailing junk found after last column - if col >= colCount && data[i] != '\n' { + if col >= colCount && i < len(data) && data[i] != '\n' { return } @@ -842,27 +886,31 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns return } - p.tableRow(out, header, columns, true) - size = i + 1 + p.addBlock(TableHead, nil) + p.tableRow(header, columns, true) + size = i + if size < len(data) && data[size] == '\n' { + size++ + } return } -func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) { +func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) { + p.addBlock(TableRow, nil) i, col := 0, 0 - var rowWork bytes.Buffer if data[i] == '|' && !isBackslashEscaped(data, i) { i++ } for col = 0; col < len(columns) && i < len(data); col++ { - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } cellStart := i - for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { + for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { i++ } @@ -871,42 +919,33 @@ func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header // skip the end-of-cell marker, possibly taking us past end of buffer i++ - for cellEnd > cellStart && data[cellEnd-1] == ' ' { + for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' { cellEnd-- } - var cellWork bytes.Buffer - p.inline(&cellWork, data[cellStart:cellEnd]) - - if header { - p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col]) - } else { - p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col]) - } + cell := p.addBlock(TableCell, data[cellStart:cellEnd]) + cell.IsHeader = header + cell.Align = columns[col] } // pad it out with empty columns to get the right number for ; col < len(columns); col++ { - if header { - p.r.TableHeaderCell(&rowWork, nil, columns[col]) - } else { - p.r.TableCell(&rowWork, nil, columns[col]) - } + cell := p.addBlock(TableCell, nil) + cell.IsHeader = header + cell.Align = columns[col] } // silently ignore rows with too many cells - - p.r.TableRow(out, rowWork.Bytes()) } // returns blockquote prefix length -func (p *parser) quotePrefix(data []byte) int { +func (p *Markdown) quotePrefix(data []byte) int { i := 0 - for i < 3 && data[i] == ' ' { + for i < 3 && i < len(data) && data[i] == ' ' { i++ } - if data[i] == '>' { - if data[i+1] == ' ' { + if i < len(data) && data[i] == '>' { + if i+1 < len(data) && data[i+1] == ' ' { return i + 2 } return i + 1 @@ -916,7 +955,7 @@ func (p *parser) quotePrefix(data []byte) int { // blockquote ends with at least one blank line // followed by something without a blockquote prefix -func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { +func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool { if p.isEmpty(data[beg:]) <= 0 { return false } @@ -927,7 +966,8 @@ func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { } // parse a blockquote fragment -func (p *parser) quote(out *bytes.Buffer, data []byte) int { +func (p *Markdown) quote(data []byte) int { + block := p.addBlock(BlockQuote, nil) var raw bytes.Buffer beg, end := 0, 0 for beg < len(data) { @@ -935,9 +975,9 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { // Step over whole lines, collecting them. While doing that, check for // fenced code and if one's found, incorporate it altogether, // irregardless of any contents inside it - for data[end] != '\n' { - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data[end:], false); i > 0 { + for end < len(data) && data[end] != '\n' { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data[end:], false); i > 0 { // -1 to compensate for the extra end++ after the loop: end += i - 1 break @@ -945,44 +985,47 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { } end++ } - end++ - + if end < len(data) && data[end] == '\n' { + end++ + } if pre := p.quotePrefix(data[beg:]); pre > 0 { // skip the prefix beg += pre } else if p.terminateBlockquote(data, beg, end) { break } - // this line is part of the blockquote raw.Write(data[beg:end]) beg = end } - - var cooked bytes.Buffer - p.block(&cooked, raw.Bytes()) - p.r.BlockQuote(out, cooked.Bytes()) + p.block(raw.Bytes()) + p.finalize(block) return end } // returns prefix length for block code -func (p *parser) codePrefix(data []byte) int { - if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { +func (p *Markdown) codePrefix(data []byte) int { + if len(data) >= 1 && data[0] == '\t' { + return 1 + } + if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { return 4 } return 0 } -func (p *parser) code(out *bytes.Buffer, data []byte) int { +func (p *Markdown) code(data []byte) int { var work bytes.Buffer i := 0 for i < len(data) { beg := i - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '\n' { i++ } - i++ blankline := p.isEmpty(data[beg:i]) > 0 if pre := p.codePrefix(data[beg:i]); pre > 0 { @@ -993,7 +1036,7 @@ func (p *parser) code(out *bytes.Buffer, data []byte) int { break } - // verbatim copy to the working buffeu + // verbatim copy to the working buffer if blankline { work.WriteByte('\n') } else { @@ -1013,122 +1056,183 @@ func (p *parser) code(out *bytes.Buffer, data []byte) int { work.WriteByte('\n') - p.r.BlockCode(out, work.Bytes(), "") + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = false + finalizeCodeBlock(block) return i } // returns unordered list item prefix -func (p *parser) uliPrefix(data []byte) int { +func (p *Markdown) uliPrefix(data []byte) int { i := 0 - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { + for i < len(data) && i < 3 && data[i] == ' ' { i++ } - - // need a *, +, or - followed by a space + if i >= len(data)-1 { + return 0 + } + // need one of {'*', '+', '-'} followed by a space or a tab if (data[i] != '*' && data[i] != '+' && data[i] != '-') || - data[i+1] != ' ' { + (data[i+1] != ' ' && data[i+1] != '\t') { return 0 } return i + 2 } // returns ordered list item prefix -func (p *parser) oliPrefix(data []byte) int { +func (p *Markdown) oliPrefix(data []byte) int { i := 0 // start with up to 3 spaces - for i < 3 && data[i] == ' ' { + for i < 3 && i < len(data) && data[i] == ' ' { i++ } // count the digits start := i - for data[i] >= '0' && data[i] <= '9' { + for i < len(data) && data[i] >= '0' && data[i] <= '9' { i++ } + if start == i || i >= len(data)-1 { + return 0 + } - // we need >= 1 digits followed by a dot and a space - if start == i || data[i] != '.' || data[i+1] != ' ' { + // we need >= 1 digits followed by a dot and a space or a tab + if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') { return 0 } return i + 2 } // returns definition list item prefix -func (p *parser) dliPrefix(data []byte) int { +func (p *Markdown) dliPrefix(data []byte) int { + if len(data) < 2 { + return 0 + } i := 0 - - // need a : followed by a spaces - if data[i] != ':' || data[i+1] != ' ' { + // need a ':' followed by a space or a tab + if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') { return 0 } - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } return i + 2 } // parse ordered or unordered list block -func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { +func (p *Markdown) list(data []byte, flags ListType) int { i := 0 - flags |= LIST_ITEM_BEGINNING_OF_LIST - work := func() bool { - for i < len(data) { - skip := p.listItem(out, data[i:], &flags) - i += skip + flags |= ListItemBeginningOfList + block := p.addBlock(List, nil) + block.ListFlags = flags + block.Tight = true - if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 { - break - } - flags &= ^LIST_ITEM_BEGINNING_OF_LIST + for i < len(data) { + skip := p.listItem(data[i:], &flags) + if flags&ListItemContainsBlock != 0 { + block.ListData.Tight = false } - return true + i += skip + if skip == 0 || flags&ListItemEndOfList != 0 { + break + } + flags &= ^ListItemBeginningOfList } - p.r.List(out, work, flags) + above := block.Parent + finalizeList(block) + p.tip = above return i } +// Returns true if block ends with a blank line, descending if needed +// into lists and sublists. +func endsWithBlankLine(block *Node) bool { + // TODO: figure this out. Always false now. + for block != nil { + //if block.lastLineBlank { + //return true + //} + t := block.Type + if t == List || t == Item { + block = block.LastChild + } else { + break + } + } + return false +} + +func finalizeList(block *Node) { + block.open = false + item := block.FirstChild + for item != nil { + // check for non-final list item ending with blank line: + if endsWithBlankLine(item) && item.Next != nil { + block.ListData.Tight = false + break + } + // recurse into children of list item, to see if there are spaces + // between any of them: + subItem := item.FirstChild + for subItem != nil { + if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) { + block.ListData.Tight = false + break + } + subItem = subItem.Next + } + item = item.Next + } +} + // Parse a single list item. // Assumes initial prefix is already removed if this is a sublist. -func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { +func (p *Markdown) listItem(data []byte, flags *ListType) int { // keep track of the indentation of the first line itemIndent := 0 - for itemIndent < 3 && data[itemIndent] == ' ' { - itemIndent++ + if data[0] == '\t' { + itemIndent += 4 + } else { + for itemIndent < 3 && data[itemIndent] == ' ' { + itemIndent++ + } } + var bulletChar byte = '*' i := p.uliPrefix(data) if i == 0 { i = p.oliPrefix(data) + } else { + bulletChar = data[i-2] } if i == 0 { i = p.dliPrefix(data) // reset definition term flag if i > 0 { - *flags &= ^LIST_TYPE_TERM + *flags &= ^ListTypeTerm } } if i == 0 { - // if in defnition list, set term flag and continue - if *flags&LIST_TYPE_DEFINITION != 0 { - *flags |= LIST_TYPE_TERM + // if in definition list, set term flag and continue + if *flags&ListTypeDefinition != 0 { + *flags |= ListTypeTerm } else { return 0 } } // skip leading whitespace on first line - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } // find the end of the line line := i - for i > 0 && data[i-1] != '\n' { + for i > 0 && i < len(data) && data[i-1] != '\n' { i++ } @@ -1148,7 +1252,7 @@ gatherlines: i++ // find the end of this line - for data[i-1] != '\n' { + for i < len(data) && data[i-1] != '\n' { i++ } @@ -1156,18 +1260,24 @@ gatherlines: // and move on to the next line if p.isEmpty(data[line:i]) > 0 { containsBlankLine = true - raw.Write(data[line:i]) line = i continue } // calculate the indentation indent := 0 - for indent < 4 && line+indent < i && data[line+indent] == ' ' { - indent++ + indentIndex := 0 + if data[line] == '\t' { + indentIndex++ + indent += 4 + } else { + for indent < 4 && line+indent < i && data[line+indent] == ' ' { + indent++ + indentIndex++ + } } - chunk := data[line+indent : i] + chunk := data[line+indentIndex : i] // evaluate how this line fits in switch { @@ -1177,15 +1287,7 @@ gatherlines: p.dliPrefix(chunk) > 0: if containsBlankLine { - // end the list if the type changed after a blank line - if indent <= itemIndent && - ((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) || - (*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) { - - *flags |= LIST_ITEM_END_OF_LIST - break gatherlines - } - *flags |= LIST_ITEM_CONTAINS_BLOCK + *flags |= ListItemContainsBlock } // to be a nested list, it must be indented more @@ -1199,93 +1301,89 @@ gatherlines: sublist = raw.Len() } - // is this a nested prefix header? - case p.isPrefixHeader(chunk): - // if the header is not indented, it is not nested in the list + // is this a nested prefix heading? + case p.isPrefixHeading(chunk): + // if the heading is not indented, it is not nested in the list // and thus ends the list if containsBlankLine && indent < 4 { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList break gatherlines } - *flags |= LIST_ITEM_CONTAINS_BLOCK + *flags |= ListItemContainsBlock // anything following an empty line is only part // of this item if it is indented 4 spaces // (regardless of the indentation of the beginning of the item) case containsBlankLine && indent < 4: - if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { + if *flags&ListTypeDefinition != 0 && i < len(data)-1 { // is the next item still a part of this list? next := i - for data[next] != '\n' { + for next < len(data) && data[next] != '\n' { next++ } for next < len(data)-1 && data[next] == '\n' { next++ } if i < len(data)-1 && data[i] != ':' && data[next] != ':' { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList } } else { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList } break gatherlines // a blank line means this should be parsed as a block case containsBlankLine: - *flags |= LIST_ITEM_CONTAINS_BLOCK + raw.WriteByte('\n') + *flags |= ListItemContainsBlock } - containsBlankLine = false + // if this line was preceded by one or more blanks, + // re-introduce the blank into the buffer + if containsBlankLine { + containsBlankLine = false + raw.WriteByte('\n') + } // add the line into the working buffer without prefix - raw.Write(data[line+indent : i]) + raw.Write(data[line+indentIndex : i]) line = i } - // If reached end of data, the Renderer.ListItem call we're going to make below - // is definitely the last in the list. - if line >= len(data) { - *flags |= LIST_ITEM_END_OF_LIST - } - rawBytes := raw.Bytes() + block := p.addBlock(Item, nil) + block.ListFlags = *flags + block.Tight = false + block.BulletChar = bulletChar + block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark + // render the contents of the list item - var cooked bytes.Buffer - if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { + if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 { // intermediate render of block item, except for definition term if sublist > 0 { - p.block(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) + p.block(rawBytes[:sublist]) + p.block(rawBytes[sublist:]) } else { - p.block(&cooked, rawBytes) + p.block(rawBytes) } } else { // intermediate render of inline item if sublist > 0 { - p.inline(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) + child := p.addChild(Paragraph, 0) + child.content = rawBytes[:sublist] + p.block(rawBytes[sublist:]) } else { - p.inline(&cooked, rawBytes) + child := p.addChild(Paragraph, 0) + child.content = rawBytes } } - - // render the actual list item - cookedBytes := cooked.Bytes() - parsedEnd := len(cookedBytes) - - // strip trailing newlines - for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' { - parsedEnd-- - } - p.r.ListItem(out, cookedBytes[:parsedEnd], *flags) - return line } // render a single paragraph that has already been parsed out -func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { +func (p *Markdown) renderParagraph(data []byte) { if len(data) == 0 { return } @@ -1296,27 +1394,29 @@ func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { beg++ } + end := len(data) // trim trailing newline - end := len(data) - 1 + if data[len(data)-1] == '\n' { + end-- + } // trim trailing spaces for end > beg && data[end-1] == ' ' { end-- } - work := func() bool { - p.inline(out, data[beg:end]) - return true - } - p.r.Paragraph(out, work) + p.addBlock(Paragraph, data[beg:end]) } -func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { +func (p *Markdown) paragraph(data []byte) int { // prev: index of 1st char of previous line // line: index of 1st char of current line // i: index of cursor/end of current line var prev, line, i int - + tabSize := TabSizeDefault + if p.extensions&TabSizeEight != 0 { + tabSize = TabSizeDouble + } // keep going until we find something to mark the end of the paragraph for i < len(data) { // mark the beginning of the current line @@ -1324,24 +1424,32 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { current := data[i:] line = i + // did we find a reference or a footnote? If so, end a paragraph + // preceding it and report that we have consumed up to the end of that + // reference: + if refEnd := isReference(p, current, tabSize); refEnd > 0 { + p.renderParagraph(data[:i]) + return i + refEnd + } + // did we find a blank line marking the end of the paragraph? if n := p.isEmpty(current); n > 0 { // did this blank line followed by a definition list item? - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if i < len(data)-1 && data[i+1] == ':' { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + return p.list(data[prev:], ListTypeDefinition) } } - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i + n } - // an underline under some text marks a header, so our paragraph ended on prev line + // an underline under some text marks a heading, so our paragraph ended on prev line if i > 0 { - if level := p.isUnderlinedHeader(current); level > 0 { + if level := p.isUnderlinedHeading(current); level > 0 { // render the paragraph - p.renderParagraph(out, data[:prev]) + p.renderParagraph(data[:prev]) // ignore leading and trailing whitespace eol := i - 1 @@ -1352,24 +1460,17 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { eol-- } - // render the header - // this ugly double closure avoids forcing variables onto the heap - work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool { - return func() bool { - pp.inline(o, d) - return true - } - }(out, p, data[prev:eol]) - id := "" - if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[prev:eol])) + if p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[prev:eol])) } - p.r.Header(out, work, level, id) + block := p.addBlock(Heading, data[prev:eol]) + block.Level = level + block.HeadingID = id // find the end of the underline - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { i++ } return i @@ -1377,74 +1478,72 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { } // if the next line starts a block of HTML, then the paragraph ends here - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - if data[i] == '<' && p.html(out, current, false) > 0 { + if p.extensions&LaxHTMLBlocks != 0 { + if data[i] == '<' && p.html(current, false) > 0 { // rewind to before the HTML block - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } } - // if there's a prefixed header or a horizontal rule after this, paragraph is over - if p.isPrefixHeader(current) || p.isHRule(current) { - p.renderParagraph(out, data[:i]) + // if there's a prefixed heading or a horizontal rule after this, paragraph is over + if p.isPrefixHeading(current) || p.isHRule(current) { + p.renderParagraph(data[:i]) return i } // if there's a fenced code block, paragraph is over - if p.flags&EXTENSION_FENCED_CODE != 0 { - if p.fencedCodeBlock(out, current, false) > 0 { - p.renderParagraph(out, data[:i]) + if p.extensions&FencedCode != 0 { + if p.fencedCodeBlock(current, false) > 0 { + p.renderParagraph(data[:i]) return i } } // if there's a definition list item, prev line is a definition term - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(current) != 0 { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + ret := p.list(data[prev:], ListTypeDefinition) + return ret } } // if there's a list after this, paragraph is over - if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { + if p.extensions&NoEmptyLineBeforeBlock != 0 { if p.uliPrefix(current) != 0 || p.oliPrefix(current) != 0 || p.quotePrefix(current) != 0 || p.codePrefix(current) != 0 { - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } } // otherwise, scan to the beginning of the next line - for data[i] != '\n' { - i++ + nl := bytes.IndexByte(data[i:], '\n') + if nl >= 0 { + i += nl + 1 + } else { + i += len(data[i:]) } - i++ } - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } -// SanitizedAnchorName returns a sanitized anchor name for the given text. -// -// It implements the algorithm specified in the package comment. -func SanitizedAnchorName(text string) string { - var anchorName []rune - futureDash := false - for _, r := range text { - switch { - case unicode.IsLetter(r) || unicode.IsNumber(r): - if futureDash && len(anchorName) > 0 { - anchorName = append(anchorName, '-') - } - futureDash = false - anchorName = append(anchorName, unicode.ToLower(r)) - default: - futureDash = true - } +func skipChar(data []byte, start int, char byte) int { + i := start + for i < len(data) && data[i] == char { + i++ + } + return i +} + +func skipUntilChar(text []byte, start int, char byte) int { + i := start + for i < len(text) && text[i] != char { + i++ } - return string(anchorName) + return i } diff --git a/vendor/github.com/russross/blackfriday/doc.go b/vendor/github.com/russross/blackfriday/doc.go index 9656c42a..5b3fa987 100644 --- a/vendor/github.com/russross/blackfriday/doc.go +++ b/vendor/github.com/russross/blackfriday/doc.go @@ -1,32 +1,18 @@ -// Package blackfriday is a Markdown processor. +// Package blackfriday is a markdown processor. // -// It translates plain text with simple formatting rules into HTML or LaTeX. +// It translates plain text with simple formatting rules into an AST, which can +// then be further processed to HTML (provided by Blackfriday itself) or other +// formats (provided by the community). // -// Sanitized Anchor Names +// The simplest way to invoke Blackfriday is to call the Run function. It will +// take a text input and produce a text output in HTML (or other format). // -// Blackfriday includes an algorithm for creating sanitized anchor names -// corresponding to a given input text. This algorithm is used to create -// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The -// algorithm is specified below, so that other packages can create -// compatible anchor names and links to those anchors. +// A slightly more sophisticated way to use Blackfriday is to create a Markdown +// processor and to call Parse, which returns a syntax tree for the input +// document. You can leverage Blackfriday's parsing for content extraction from +// markdown documents. You can assign a custom renderer and set various options +// to the Markdown processor. // -// The algorithm iterates over the input text, interpreted as UTF-8, -// one Unicode code point (rune) at a time. All runes that are letters (category L) -// or numbers (category N) are considered valid characters. They are mapped to -// lower case, and included in the output. All other runes are considered -// invalid characters. Invalid characters that preceed the first valid character, -// as well as invalid character that follow the last valid character -// are dropped completely. All other sequences of invalid characters -// between two valid characters are replaced with a single dash character '-'. -// -// SanitizedAnchorName exposes this functionality, and can be used to -// create compatible links to the anchor names generated by blackfriday. -// This algorithm is also implemented in a small standalone package at -// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients -// that want a small package and don't need full functionality of blackfriday. +// If you're interested in calling Blackfriday from command line, see +// https://github.com/russross/blackfriday-tool. package blackfriday - -// NOTE: Keep Sanitized Anchor Name algorithm in sync with package -// github.com/shurcooL/sanitized_anchor_name. -// Otherwise, users of sanitized_anchor_name will get anchor names -// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/esc.go b/vendor/github.com/russross/blackfriday/esc.go new file mode 100644 index 00000000..6385f27c --- /dev/null +++ b/vendor/github.com/russross/blackfriday/esc.go @@ -0,0 +1,34 @@ +package blackfriday + +import ( + "html" + "io" +) + +var htmlEscaper = [256][]byte{ + '&': []byte("&"), + '<': []byte("<"), + '>': []byte(">"), + '"': []byte("""), +} + +func escapeHTML(w io.Writer, s []byte) { + var start, end int + for end < len(s) { + escSeq := htmlEscaper[s[end]] + if escSeq != nil { + w.Write(s[start:end]) + w.Write(escSeq) + start = end + 1 + } + end++ + } + if start < len(s) && end <= len(s) { + w.Write(s[start:end]) + } +} + +func escLink(w io.Writer, text []byte) { + unesc := html.UnescapeString(string(text)) + escapeHTML(w, []byte(unesc)) +} diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go index c917c7d3..25fb185e 100644 --- a/vendor/github.com/russross/blackfriday/html.go +++ b/vendor/github.com/russross/blackfriday/html.go @@ -18,46 +18,62 @@ package blackfriday import ( "bytes" "fmt" + "io" "regexp" - "strconv" "strings" ) -// Html renderer configuration options. +// HTMLFlags control optional behavior of HTML renderer. +type HTMLFlags int + +// HTML renderer configuration options. const ( - HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks - HTML_SKIP_STYLE // skip embedded <style> elements - HTML_SKIP_IMAGES // skip embedded images - HTML_SKIP_LINKS // skip all links - HTML_SAFELINK // only link to trusted protocols - HTML_NOFOLLOW_LINKS // only link with rel="nofollow" - HTML_NOREFERRER_LINKS // only link with rel="noreferrer" - HTML_HREF_TARGET_BLANK // add a blank target - HTML_TOC // generate a table of contents - HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents) - HTML_COMPLETE_PAGE // generate a complete HTML page - HTML_USE_XHTML // generate XHTML output instead of HTML - HTML_USE_SMARTYPANTS // enable smart punctuation substitutions - HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS) - HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS) - HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES) - HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering - HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS) - HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source + HTMLFlagsNone HTMLFlags = 0 + SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks + SkipImages // Skip embedded images + SkipLinks // Skip all links + Safelink // Only link to trusted protocols + NofollowLinks // Only link with rel="nofollow" + NoreferrerLinks // Only link with rel="noreferrer" + HrefTargetBlank // Add a blank target + CompletePage // Generate a complete HTML page + UseXHTML // Generate XHTML output instead of HTML + FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source + Smartypants // Enable smart punctuation substitutions + SmartypantsFractions // Enable smart fractions (with Smartypants) + SmartypantsDashes // Enable smart dashes (with Smartypants) + SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) + SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering + SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants) + TOC // Generate a table of contents ) var ( - alignments = []string{ - "left", - "right", - "center", - } + htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag) +) - // TODO: improve this regexp to catch all possible entities: - htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`) +const ( + htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" + + processingInstruction + "|" + declaration + "|" + cdata + ")" + closeTag = "</" + tagName + "\\s*[>]" + openTag = "<" + tagName + attribute + "*" + "\\s*/?>" + attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)" + attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")" + attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")" + attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*" + cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>" + declaration = "<![A-Z]+" + "\\s+[^>]*>" + doubleQuotedValue = "\"[^\"]*\"" + htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->" + processingInstruction = "[<][?].*?[?][>]" + singleQuotedValue = "'[^']*'" + tagName = "[A-Za-z][A-Za-z0-9-]*" + unquotedValue = "[^\"'=<>`\\x00-\\x20]+" ) -type HtmlRendererParameters struct { +// HTMLRendererParameters is a collection of supplementary parameters tweaking +// the behavior of various parts of HTML renderer. +type HTMLRendererParameters struct { // Prepend this text to each relative URL. AbsolutePrefix string // Add this text to each footnote anchor, to ensure uniqueness. @@ -66,34 +82,34 @@ type HtmlRendererParameters struct { // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string // <sup>[return]</sup> is used. FootnoteReturnLinkContents string - // If set, add this text to the front of each Header ID, to ensure + // If set, add this text to the front of each Heading ID, to ensure // uniqueness. - HeaderIDPrefix string - // If set, add this text to the back of each Header ID, to ensure uniqueness. - HeaderIDSuffix string + HeadingIDPrefix string + // If set, add this text to the back of each Heading ID, to ensure uniqueness. + HeadingIDSuffix string + + Title string // Document title (used if CompletePage is set) + CSS string // Optional CSS file URL (used if CompletePage is set) + Icon string // Optional icon file URL (used if CompletePage is set) + + Flags HTMLFlags // Flags allow customizing this renderer's behavior } -// Html is a type that implements the Renderer interface for HTML output. +// HTMLRenderer is a type that implements the Renderer interface for HTML output. // -// Do not create this directly, instead use the HtmlRenderer function. -type Html struct { - flags int // HTML_* options - closeTag string // how to end singleton tags: either " />" or ">" - title string // document title - css string // optional css file url (used with HTML_COMPLETE_PAGE) +// Do not create this directly, instead use the NewHTMLRenderer function. +type HTMLRenderer struct { + HTMLRendererParameters - parameters HtmlRendererParameters + closeTag string // how to end singleton tags: either " />" or ">" - // table of contents data - tocMarker int - headerCount int - currentLevel int - toc *bytes.Buffer + // Track heading IDs to prevent ID collision in a single generation. + headingIDs map[string]int - // Track header IDs to prevent ID collision in a single generation. - headerIDs map[string]int + lastOutputLen int + disableTags int - smartypants *smartypantsRenderer + sr *SPRenderer } const ( @@ -101,850 +117,824 @@ const ( htmlClose = ">" ) -// HtmlRenderer creates and configures an Html object, which +// NewHTMLRenderer creates and configures an HTMLRenderer object, which // satisfies the Renderer interface. -// -// flags is a set of HTML_* options ORed together. -// title is the title of the document, and css is a URL for the document's -// stylesheet. -// title and css are only used when HTML_COMPLETE_PAGE is selected. -func HtmlRenderer(flags int, title string, css string) Renderer { - return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{}) -} - -func HtmlRendererWithParameters(flags int, title string, - css string, renderParameters HtmlRendererParameters) Renderer { +func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer { // configure the rendering engine closeTag := htmlClose - if flags&HTML_USE_XHTML != 0 { + if params.Flags&UseXHTML != 0 { closeTag = xhtmlClose } - if renderParameters.FootnoteReturnLinkContents == "" { - renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>` + if params.FootnoteReturnLinkContents == "" { + params.FootnoteReturnLinkContents = `<sup>[return]</sup>` } - return &Html{ - flags: flags, - closeTag: closeTag, - title: title, - css: css, - parameters: renderParameters, - - headerCount: 0, - currentLevel: 0, - toc: new(bytes.Buffer), + return &HTMLRenderer{ + HTMLRendererParameters: params, - headerIDs: make(map[string]int), + closeTag: closeTag, + headingIDs: make(map[string]int), - smartypants: smartypants(flags), + sr: NewSmartypantsRenderer(params.Flags), } } -// Using if statements is a bit faster than a switch statement. As the compiler -// improves, this should be unnecessary this is only worthwhile because -// attrEscape is the single largest CPU user in normal use. -// Also tried using map, but that gave a ~3x slowdown. -func escapeSingleChar(char byte) (string, bool) { - if char == '"' { - return """, true - } - if char == '&' { - return "&", true - } - if char == '<' { - return "<", true - } - if char == '>' { - return ">", true - } - return "", false +func isHTMLTag(tag []byte, tagname string) bool { + found, _ := findHTMLTagPos(tag, tagname) + return found } -func attrEscape(out *bytes.Buffer, src []byte) { - org := 0 - for i, ch := range src { - if entity, ok := escapeSingleChar(ch); ok { - if i > org { - // copy all the normal characters since the last escape - out.Write(src[org:i]) - } - org = i + 1 - out.WriteString(entity) +// Look for a character, but ignore it when it's in any kind of quotes, it +// might be JavaScript +func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { + inSingleQuote := false + inDoubleQuote := false + inGraveQuote := false + i := start + for i < len(html) { + switch { + case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: + return i + case html[i] == '\'': + inSingleQuote = !inSingleQuote + case html[i] == '"': + inDoubleQuote = !inDoubleQuote + case html[i] == '`': + inGraveQuote = !inGraveQuote } + i++ } - if org < len(src) { - out.Write(src[org:]) - } + return start } -func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) { - end := 0 - for _, rang := range skipRanges { - attrEscape(out, src[end:rang[0]]) - out.Write(src[rang[0]:rang[1]]) - end = rang[1] +func findHTMLTagPos(tag []byte, tagname string) (bool, int) { + i := 0 + if i < len(tag) && tag[0] != '<' { + return false, -1 } - attrEscape(out, src[end:]) -} - -func (options *Html) GetFlags() int { - return options.flags -} - -func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) { - text = bytes.TrimPrefix(text, []byte("% ")) - text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1) - out.WriteString("<h1 class=\"title\">") - out.Write(text) - out.WriteString("\n</h1>") -} - -func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) { - marker := out.Len() - doubleSpace(out) + i++ + i = skipSpace(tag, i) - if id == "" && options.flags&HTML_TOC != 0 { - id = fmt.Sprintf("toc_%d", options.headerCount) + if i < len(tag) && tag[i] == '/' { + i++ } - if id != "" { - id = options.ensureUniqueHeaderID(id) - - if options.parameters.HeaderIDPrefix != "" { - id = options.parameters.HeaderIDPrefix + id + i = skipSpace(tag, i) + j := 0 + for ; i < len(tag); i, j = i+1, j+1 { + if j >= len(tagname) { + break } - if options.parameters.HeaderIDSuffix != "" { - id = id + options.parameters.HeaderIDSuffix + if strings.ToLower(string(tag[i]))[0] != tagname[j] { + return false, -1 } - - out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id)) - } else { - out.WriteString(fmt.Sprintf("<h%d>", level)) } - tocMarker := out.Len() - if !text() { - out.Truncate(marker) - return + if i == len(tag) { + return false, -1 } - // are we building a table of contents? - if options.flags&HTML_TOC != 0 { - options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id) + rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') + if rightAngle >= i { + return true, rightAngle } - out.WriteString(fmt.Sprintf("</h%d>\n", level)) + return false, -1 } -func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) { - if options.flags&HTML_SKIP_HTML != 0 { - return +func skipSpace(tag []byte, i int) int { + for i < len(tag) && isspace(tag[i]) { + i++ } - - doubleSpace(out) - out.Write(text) - out.WriteByte('\n') -} - -func (options *Html) HRule(out *bytes.Buffer) { - doubleSpace(out) - out.WriteString("<hr") - out.WriteString(options.closeTag) - out.WriteByte('\n') + return i } -func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) { - doubleSpace(out) +func isRelativeLink(link []byte) (yes bool) { + // a tag begin with '#' + if link[0] == '#' { + return true + } - // parse out the language names/classes - count := 0 - for _, elt := range strings.Fields(lang) { - if elt[0] == '.' { - elt = elt[1:] - } - if len(elt) == 0 { - continue - } - if count == 0 { - out.WriteString("<pre><code class=\"language-") - } else { - out.WriteByte(' ') - } - attrEscape(out, []byte(elt)) - count++ + // link begin with '/' but not '//', the second maybe a protocol relative link + if len(link) >= 2 && link[0] == '/' && link[1] != '/' { + return true } - if count == 0 { - out.WriteString("<pre><code>") - } else { - out.WriteString("\">") + // only the root '/' + if len(link) == 1 && link[0] == '/' { + return true } - attrEscape(out, text) - out.WriteString("</code></pre>\n") -} + // current directory : begin with "./" + if bytes.HasPrefix(link, []byte("./")) { + return true + } -func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) { - doubleSpace(out) - out.WriteString("<blockquote>\n") - out.Write(text) - out.WriteString("</blockquote>\n") -} + // parent directory : begin with "../" + if bytes.HasPrefix(link, []byte("../")) { + return true + } -func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { - doubleSpace(out) - out.WriteString("<table>\n<thead>\n") - out.Write(header) - out.WriteString("</thead>\n\n<tbody>\n") - out.Write(body) - out.WriteString("</tbody>\n</table>\n") + return false } -func (options *Html) TableRow(out *bytes.Buffer, text []byte) { - doubleSpace(out) - out.WriteString("<tr>\n") - out.Write(text) - out.WriteString("\n</tr>\n") -} +func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string { + for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] { + tmp := fmt.Sprintf("%s-%d", id, count+1) -func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { - doubleSpace(out) - switch align { - case TABLE_ALIGNMENT_LEFT: - out.WriteString("<th align=\"left\">") - case TABLE_ALIGNMENT_RIGHT: - out.WriteString("<th align=\"right\">") - case TABLE_ALIGNMENT_CENTER: - out.WriteString("<th align=\"center\">") - default: - out.WriteString("<th>") + if _, tmpFound := r.headingIDs[tmp]; !tmpFound { + r.headingIDs[id] = count + 1 + id = tmp + } else { + id = id + "-1" + } } - out.Write(text) - out.WriteString("</th>") -} - -func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) { - doubleSpace(out) - switch align { - case TABLE_ALIGNMENT_LEFT: - out.WriteString("<td align=\"left\">") - case TABLE_ALIGNMENT_RIGHT: - out.WriteString("<td align=\"right\">") - case TABLE_ALIGNMENT_CENTER: - out.WriteString("<td align=\"center\">") - default: - out.WriteString("<td>") + if _, found := r.headingIDs[id]; !found { + r.headingIDs[id] = 0 } - out.Write(text) - out.WriteString("</td>") -} - -func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) { - out.WriteString("<div class=\"footnotes\">\n") - options.HRule(out) - options.List(out, text, LIST_TYPE_ORDERED) - out.WriteString("</div>\n") + return id } -func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { - if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { - doubleSpace(out) - } - slug := slugify(name) - out.WriteString(`<li id="`) - out.WriteString(`fn:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.Write(text) - if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 { - out.WriteString(` <a class="footnote-return" href="#`) - out.WriteString(`fnref:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.WriteString(options.parameters.FootnoteReturnLinkContents) - out.WriteString(`</a>`) - } - out.WriteString("</li>\n") +func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte { + if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { + newDest := r.AbsolutePrefix + if link[0] != '/' { + newDest += "/" + } + newDest += string(link) + return []byte(newDest) + } + return link } -func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) { - marker := out.Len() - doubleSpace(out) - - if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("<dl>") - } else if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("<ol>") - } else { - out.WriteString("<ul>") +func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string { + if isRelativeLink(link) { + return attrs } - if !text() { - out.Truncate(marker) - return + val := []string{} + if flags&NofollowLinks != 0 { + val = append(val, "nofollow") } - if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("</dl>\n") - } else if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("</ol>\n") - } else { - out.WriteString("</ul>\n") + if flags&NoreferrerLinks != 0 { + val = append(val, "noreferrer") } -} - -func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) { - if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) || - flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { - doubleSpace(out) + if flags&HrefTargetBlank != 0 { + attrs = append(attrs, "target=\"_blank\"") } - if flags&LIST_TYPE_TERM != 0 { - out.WriteString("<dt>") - } else if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("<dd>") - } else { - out.WriteString("<li>") - } - out.Write(text) - if flags&LIST_TYPE_TERM != 0 { - out.WriteString("</dt>\n") - } else if flags&LIST_TYPE_DEFINITION != 0 { - out.WriteString("</dd>\n") - } else { - out.WriteString("</li>\n") + if len(val) == 0 { + return attrs } + attr := fmt.Sprintf("rel=%q", strings.Join(val, " ")) + return append(attrs, attr) } -func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) { - marker := out.Len() - doubleSpace(out) - - out.WriteString("<p>") - if !text() { - out.Truncate(marker) - return - } - out.WriteString("</p>\n") +func isMailto(link []byte) bool { + return bytes.HasPrefix(link, []byte("mailto:")) } -func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) { - skipRanges := htmlEntity.FindAllIndex(link, -1) - if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL { - // mark it but don't link it if it is not a safe link: no smartypants - out.WriteString("<tt>") - entityEscapeWithSkip(out, link, skipRanges) - out.WriteString("</tt>") - return - } - - out.WriteString("<a href=\"") - if kind == LINK_TYPE_EMAIL { - out.WriteString("mailto:") - } else { - options.maybeWriteAbsolutePrefix(out, link) +func needSkipLink(flags HTMLFlags, dest []byte) bool { + if flags&SkipLinks != 0 { + return true } + return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) +} - entityEscapeWithSkip(out, link, skipRanges) +func isSmartypantable(node *Node) bool { + pt := node.Parent.Type + return pt != Link && pt != CodeBlock && pt != Code +} - var relAttrs []string - if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "nofollow") +func appendLanguageAttr(attrs []string, info []byte) []string { + if len(info) == 0 { + return attrs } - if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "noreferrer") + endOfLang := bytes.IndexAny(info, "\t ") + if endOfLang < 0 { + endOfLang = len(info) } - if len(relAttrs) > 0 { - out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) - } - - // blank target only add to external link - if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { - out.WriteString("\" target=\"_blank") - } - - out.WriteString("\">") + return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) +} - // Pretty print: if we get an email address as - // an actual URI, e.g. `mailto:foo@bar.com`, we don't - // want to print the `mailto:` prefix - switch { - case bytes.HasPrefix(link, []byte("mailto://")): - attrEscape(out, link[len("mailto://"):]) - case bytes.HasPrefix(link, []byte("mailto:")): - attrEscape(out, link[len("mailto:"):]) - default: - entityEscapeWithSkip(out, link, skipRanges) +func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { + w.Write(name) + if len(attrs) > 0 { + w.Write(spaceBytes) + w.Write([]byte(strings.Join(attrs, " "))) } - - out.WriteString("</a>") + w.Write(gtBytes) + r.lastOutputLen = 1 } -func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) { - out.WriteString("<code>") - attrEscape(out, text) - out.WriteString("</code>") +func footnoteRef(prefix string, node *Node) []byte { + urlFrag := prefix + string(slugify(node.Destination)) + anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID) + return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor)) } -func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("<strong>") - out.Write(text) - out.WriteString("</strong>") +func footnoteItem(prefix string, slug []byte) []byte { + return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug)) } -func (options *Html) Emphasis(out *bytes.Buffer, text []byte) { - if len(text) == 0 { - return - } - out.WriteString("<em>") - out.Write(text) - out.WriteString("</em>") +func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte { + const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>` + return []byte(fmt.Sprintf(format, prefix, slug, returnLink)) } -func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) { - if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { - out.WriteString(options.parameters.AbsolutePrefix) - if link[0] != '/' { - out.WriteByte('/') - } +func itemOpenCR(node *Node) bool { + if node.Prev == nil { + return false } + ld := node.Parent.ListData + return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0 } -func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { - if options.flags&HTML_SKIP_IMAGES != 0 { - return - } - - out.WriteString("<img src=\"") - options.maybeWriteAbsolutePrefix(out, link) - attrEscape(out, link) - out.WriteString("\" alt=\"") - if len(alt) > 0 { - attrEscape(out, alt) +func skipParagraphTags(node *Node) bool { + grandparent := node.Parent.Parent + if grandparent == nil || grandparent.Type != List { + return false } - if len(title) > 0 { - out.WriteString("\" title=\"") - attrEscape(out, title) - } - - out.WriteByte('"') - out.WriteString(options.closeTag) + tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0 + return grandparent.Type == List && tightOrTerm } -func (options *Html) LineBreak(out *bytes.Buffer) { - out.WriteString("<br") - out.WriteString(options.closeTag) - out.WriteByte('\n') -} - -func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - if options.flags&HTML_SKIP_LINKS != 0 { - // write the link text out but don't link it, just mark it with typewriter font - out.WriteString("<tt>") - attrEscape(out, content) - out.WriteString("</tt>") - return - } - - if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) { - // write the link text out but don't link it, just mark it with typewriter font - out.WriteString("<tt>") - attrEscape(out, content) - out.WriteString("</tt>") - return - } - - out.WriteString("<a href=\"") - options.maybeWriteAbsolutePrefix(out, link) - attrEscape(out, link) - if len(title) > 0 { - out.WriteString("\" title=\"") - attrEscape(out, title) - } - var relAttrs []string - if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "nofollow") - } - if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) { - relAttrs = append(relAttrs, "noreferrer") - } - if len(relAttrs) > 0 { - out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " "))) - } - - // blank target only add to external link - if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) { - out.WriteString("\" target=\"_blank") +func cellAlignment(align CellAlignFlags) string { + switch align { + case TableAlignmentLeft: + return "left" + case TableAlignmentRight: + return "right" + case TableAlignmentCenter: + return "center" + default: + return "" } - - out.WriteString("\">") - out.Write(content) - out.WriteString("</a>") - return } -func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) { - if options.flags&HTML_SKIP_HTML != 0 { - return - } - if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") { - return - } - if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") { - return - } - if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") { - return +func (r *HTMLRenderer) out(w io.Writer, text []byte) { + if r.disableTags > 0 { + w.Write(htmlTagRe.ReplaceAll(text, []byte{})) + } else { + w.Write(text) } - out.Write(text) + r.lastOutputLen = len(text) } -func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("<strong><em>") - out.Write(text) - out.WriteString("</em></strong>") +func (r *HTMLRenderer) cr(w io.Writer) { + if r.lastOutputLen > 0 { + r.out(w, nlBytes) + } } -func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) { - out.WriteString("<del>") - out.Write(text) - out.WriteString("</del>") -} +var ( + nlBytes = []byte{'\n'} + gtBytes = []byte{'>'} + spaceBytes = []byte{' '} +) -func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { - slug := slugify(ref) - out.WriteString(`<sup class="footnote-ref" id="`) - out.WriteString(`fnref:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`"><a href="#`) - out.WriteString(`fn:`) - out.WriteString(options.parameters.FootnoteAnchorPrefix) - out.Write(slug) - out.WriteString(`">`) - out.WriteString(strconv.Itoa(id)) - out.WriteString(`</a></sup>`) -} +var ( + brTag = []byte("<br>") + brXHTMLTag = []byte("<br />") + emTag = []byte("<em>") + emCloseTag = []byte("</em>") + strongTag = []byte("<strong>") + strongCloseTag = []byte("</strong>") + delTag = []byte("<del>") + delCloseTag = []byte("</del>") + ttTag = []byte("<tt>") + ttCloseTag = []byte("</tt>") + aTag = []byte("<a") + aCloseTag = []byte("</a>") + preTag = []byte("<pre>") + preCloseTag = []byte("</pre>") + codeTag = []byte("<code>") + codeCloseTag = []byte("</code>") + pTag = []byte("<p>") + pCloseTag = []byte("</p>") + blockquoteTag = []byte("<blockquote>") + blockquoteCloseTag = []byte("</blockquote>") + hrTag = []byte("<hr>") + hrXHTMLTag = []byte("<hr />") + ulTag = []byte("<ul>") + ulCloseTag = []byte("</ul>") + olTag = []byte("<ol>") + olCloseTag = []byte("</ol>") + dlTag = []byte("<dl>") + dlCloseTag = []byte("</dl>") + liTag = []byte("<li>") + liCloseTag = []byte("</li>") + ddTag = []byte("<dd>") + ddCloseTag = []byte("</dd>") + dtTag = []byte("<dt>") + dtCloseTag = []byte("</dt>") + tableTag = []byte("<table>") + tableCloseTag = []byte("</table>") + tdTag = []byte("<td") + tdCloseTag = []byte("</td>") + thTag = []byte("<th") + thCloseTag = []byte("</th>") + theadTag = []byte("<thead>") + theadCloseTag = []byte("</thead>") + tbodyTag = []byte("<tbody>") + tbodyCloseTag = []byte("</tbody>") + trTag = []byte("<tr>") + trCloseTag = []byte("</tr>") + h1Tag = []byte("<h1") + h1CloseTag = []byte("</h1>") + h2Tag = []byte("<h2") + h2CloseTag = []byte("</h2>") + h3Tag = []byte("<h3") + h3CloseTag = []byte("</h3>") + h4Tag = []byte("<h4") + h4CloseTag = []byte("</h4>") + h5Tag = []byte("<h5") + h5CloseTag = []byte("</h5>") + h6Tag = []byte("<h6") + h6CloseTag = []byte("</h6>") + + footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n") + footnotesCloseDivBytes = []byte("\n</div>\n") +) -func (options *Html) Entity(out *bytes.Buffer, entity []byte) { - out.Write(entity) +func headingTagsFromLevel(level int) ([]byte, []byte) { + switch level { + case 1: + return h1Tag, h1CloseTag + case 2: + return h2Tag, h2CloseTag + case 3: + return h3Tag, h3CloseTag + case 4: + return h4Tag, h4CloseTag + case 5: + return h5Tag, h5CloseTag + default: + return h6Tag, h6CloseTag + } } -func (options *Html) NormalText(out *bytes.Buffer, text []byte) { - if options.flags&HTML_USE_SMARTYPANTS != 0 { - options.Smartypants(out, text) +func (r *HTMLRenderer) outHRTag(w io.Writer) { + if r.Flags&UseXHTML == 0 { + r.out(w, hrTag) } else { - attrEscape(out, text) + r.out(w, hrXHTMLTag) } } -func (options *Html) Smartypants(out *bytes.Buffer, text []byte) { - smrt := smartypantsData{false, false} - - // first do normal entity escaping - var escaped bytes.Buffer - attrEscape(&escaped, text) - text = escaped.Bytes() - - mark := 0 - for i := 0; i < len(text); i++ { - if action := options.smartypants[text[i]]; action != nil { - if i > mark { - out.Write(text[mark:i]) - } - - previousChar := byte(0) - if i > 0 { - previousChar = text[i-1] +// RenderNode is a default renderer of a single node of a syntax tree. For +// block nodes it will be called twice: first time with entering=true, second +// time with entering=false, so that it could know when it's working on an open +// tag and when on close. It writes the result to w. +// +// The return value is a way to tell the calling walker to adjust its walk +// pattern: e.g. it can terminate the traversal by returning Terminate. Or it +// can ask the walker to skip a subtree of this node by returning SkipChildren. +// The typical behavior is to return GoToNext, which asks for the usual +// traversal to the next node. +func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { + attrs := []string{} + switch node.Type { + case Text: + if r.Flags&Smartypants != 0 { + var tmp bytes.Buffer + escapeHTML(&tmp, node.Literal) + r.sr.Process(w, tmp.Bytes()) + } else { + if node.Parent.Type == Link { + escLink(w, node.Literal) + } else { + escapeHTML(w, node.Literal) } - i += action(out, &smrt, previousChar, text[i:]) - mark = i + 1 } - } - - if mark < len(text) { - out.Write(text[mark:]) - } -} - -func (options *Html) DocumentHeader(out *bytes.Buffer) { - if options.flags&HTML_COMPLETE_PAGE == 0 { - return - } - - ending := "" - if options.flags&HTML_USE_XHTML != 0 { - out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") - out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n") - out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n") - ending = " /" - } else { - out.WriteString("<!DOCTYPE html>\n") - out.WriteString("<html>\n") - } - out.WriteString("<head>\n") - out.WriteString(" <title>") - options.NormalText(out, []byte(options.title)) - out.WriteString("</title>\n") - out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") - out.WriteString(VERSION) - out.WriteString("\"") - out.WriteString(ending) - out.WriteString(">\n") - out.WriteString(" <meta charset=\"utf-8\"") - out.WriteString(ending) - out.WriteString(">\n") - if options.css != "" { - out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"") - attrEscape(out, []byte(options.css)) - out.WriteString("\"") - out.WriteString(ending) - out.WriteString(">\n") - } - out.WriteString("</head>\n") - out.WriteString("<body>\n") - - options.tocMarker = out.Len() -} - -func (options *Html) DocumentFooter(out *bytes.Buffer) { - // finalize and insert the table of contents - if options.flags&HTML_TOC != 0 { - options.TocFinalize() - - // now we have to insert the table of contents into the document - var temp bytes.Buffer - - // start by making a copy of everything after the document header - temp.Write(out.Bytes()[options.tocMarker:]) - - // now clear the copied material from the main output buffer - out.Truncate(options.tocMarker) - - // corner case spacing issue - if options.flags&HTML_COMPLETE_PAGE != 0 { - out.WriteByte('\n') + case Softbreak: + r.cr(w) + // TODO: make it configurable via out(renderer.softbreak) + case Hardbreak: + if r.Flags&UseXHTML == 0 { + r.out(w, brTag) + } else { + r.out(w, brXHTMLTag) } - - // insert the table of contents - out.WriteString("<nav>\n") - out.Write(options.toc.Bytes()) - out.WriteString("</nav>\n") - - // corner case spacing issue - if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 { - out.WriteByte('\n') + r.cr(w) + case Emph: + if entering { + r.out(w, emTag) + } else { + r.out(w, emCloseTag) } - - // write out everything that came after it - if options.flags&HTML_OMIT_CONTENTS == 0 { - out.Write(temp.Bytes()) + case Strong: + if entering { + r.out(w, strongTag) + } else { + r.out(w, strongCloseTag) } - } - - if options.flags&HTML_COMPLETE_PAGE != 0 { - out.WriteString("\n</body>\n") - out.WriteString("</html>\n") - } - -} - -func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) { - for level > options.currentLevel { - switch { - case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")): - // this sublist can nest underneath a header - size := options.toc.Len() - options.toc.Truncate(size - len("</li>\n")) - - case options.currentLevel > 0: - options.toc.WriteString("<li>") + case Del: + if entering { + r.out(w, delTag) + } else { + r.out(w, delCloseTag) } - if options.toc.Len() > 0 { - options.toc.WriteByte('\n') + case HTMLSpan: + if r.Flags&SkipHTML != 0 { + break } - options.toc.WriteString("<ul>\n") - options.currentLevel++ - } - - for level < options.currentLevel { - options.toc.WriteString("</ul>") - if options.currentLevel > 1 { - options.toc.WriteString("</li>\n") + r.out(w, node.Literal) + case Link: + // mark it but don't link it if it is not a safe link: no smartypants + dest := node.LinkData.Destination + if needSkipLink(r.Flags, dest) { + if entering { + r.out(w, ttTag) + } else { + r.out(w, ttCloseTag) + } + } else { + if entering { + dest = r.addAbsPrefix(dest) + var hrefBuf bytes.Buffer + hrefBuf.WriteString("href=\"") + escLink(&hrefBuf, dest) + hrefBuf.WriteByte('"') + attrs = append(attrs, hrefBuf.String()) + if node.NoteID != 0 { + r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node)) + break + } + attrs = appendLinkAttrs(attrs, r.Flags, dest) + if len(node.LinkData.Title) > 0 { + var titleBuff bytes.Buffer + titleBuff.WriteString("title=\"") + escapeHTML(&titleBuff, node.LinkData.Title) + titleBuff.WriteByte('"') + attrs = append(attrs, titleBuff.String()) + } + r.tag(w, aTag, attrs) + } else { + if node.NoteID != 0 { + break + } + r.out(w, aCloseTag) + } } - options.currentLevel-- - } - - options.toc.WriteString("<li><a href=\"#") - if anchor != "" { - options.toc.WriteString(anchor) - } else { - options.toc.WriteString("toc_") - options.toc.WriteString(strconv.Itoa(options.headerCount)) - } - options.toc.WriteString("\">") - options.headerCount++ - - options.toc.Write(text) - - options.toc.WriteString("</a></li>\n") -} - -func (options *Html) TocHeader(text []byte, level int) { - options.TocHeaderWithAnchor(text, level, "") -} - -func (options *Html) TocFinalize() { - for options.currentLevel > 1 { - options.toc.WriteString("</ul></li>\n") - options.currentLevel-- - } - - if options.currentLevel > 0 { - options.toc.WriteString("</ul>\n") - } -} - -func isHtmlTag(tag []byte, tagname string) bool { - found, _ := findHtmlTagPos(tag, tagname) - return found -} - -// Look for a character, but ignore it when it's in any kind of quotes, it -// might be JavaScript -func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { - inSingleQuote := false - inDoubleQuote := false - inGraveQuote := false - i := start - for i < len(html) { - switch { - case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: - return i - case html[i] == '\'': - inSingleQuote = !inSingleQuote - case html[i] == '"': - inDoubleQuote = !inDoubleQuote - case html[i] == '`': - inGraveQuote = !inGraveQuote + case Image: + if r.Flags&SkipImages != 0 { + return SkipChildren } - i++ - } - return start -} - -func findHtmlTagPos(tag []byte, tagname string) (bool, int) { - i := 0 - if i < len(tag) && tag[0] != '<' { - return false, -1 - } - i++ - i = skipSpace(tag, i) - - if i < len(tag) && tag[i] == '/' { - i++ - } - - i = skipSpace(tag, i) - j := 0 - for ; i < len(tag); i, j = i+1, j+1 { - if j >= len(tagname) { + if entering { + dest := node.LinkData.Destination + dest = r.addAbsPrefix(dest) + if r.disableTags == 0 { + //if options.safe && potentiallyUnsafe(dest) { + //out(w, `<img src="" alt="`) + //} else { + r.out(w, []byte(`<img src="`)) + escLink(w, dest) + r.out(w, []byte(`" alt="`)) + //} + } + r.disableTags++ + } else { + r.disableTags-- + if r.disableTags == 0 { + if node.LinkData.Title != nil { + r.out(w, []byte(`" title="`)) + escapeHTML(w, node.LinkData.Title) + } + r.out(w, []byte(`" />`)) + } + } + case Code: + r.out(w, codeTag) + escapeHTML(w, node.Literal) + r.out(w, codeCloseTag) + case Document: + break + case Paragraph: + if skipParagraphTags(node) { break } - - if strings.ToLower(string(tag[i]))[0] != tagname[j] { - return false, -1 + if entering { + // TODO: untangle this clusterfuck about when the newlines need + // to be added and when not. + if node.Prev != nil { + switch node.Prev.Type { + case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule: + r.cr(w) + } + } + if node.Parent.Type == BlockQuote && node.Prev == nil { + r.cr(w) + } + r.out(w, pTag) + } else { + r.out(w, pCloseTag) + if !(node.Parent.Type == Item && node.Next == nil) { + r.cr(w) + } } + case BlockQuote: + if entering { + r.cr(w) + r.out(w, blockquoteTag) + } else { + r.out(w, blockquoteCloseTag) + r.cr(w) + } + case HTMLBlock: + if r.Flags&SkipHTML != 0 { + break + } + r.cr(w) + r.out(w, node.Literal) + r.cr(w) + case Heading: + openTag, closeTag := headingTagsFromLevel(node.Level) + if entering { + if node.IsTitleblock { + attrs = append(attrs, `class="title"`) + } + if node.HeadingID != "" { + id := r.ensureUniqueHeadingID(node.HeadingID) + if r.HeadingIDPrefix != "" { + id = r.HeadingIDPrefix + id + } + if r.HeadingIDSuffix != "" { + id = id + r.HeadingIDSuffix + } + attrs = append(attrs, fmt.Sprintf(`id="%s"`, id)) + } + r.cr(w) + r.tag(w, openTag, attrs) + } else { + r.out(w, closeTag) + if !(node.Parent.Type == Item && node.Next == nil) { + r.cr(w) + } + } + case HorizontalRule: + r.cr(w) + r.outHRTag(w) + r.cr(w) + case List: + openTag := ulTag + closeTag := ulCloseTag + if node.ListFlags&ListTypeOrdered != 0 { + openTag = olTag + closeTag = olCloseTag + } + if node.ListFlags&ListTypeDefinition != 0 { + openTag = dlTag + closeTag = dlCloseTag + } + if entering { + if node.IsFootnotesList { + r.out(w, footnotesDivBytes) + r.outHRTag(w) + r.cr(w) + } + r.cr(w) + if node.Parent.Type == Item && node.Parent.Parent.Tight { + r.cr(w) + } + r.tag(w, openTag[:len(openTag)-1], attrs) + r.cr(w) + } else { + r.out(w, closeTag) + //cr(w) + //if node.parent.Type != Item { + // cr(w) + //} + if node.Parent.Type == Item && node.Next != nil { + r.cr(w) + } + if node.Parent.Type == Document || node.Parent.Type == BlockQuote { + r.cr(w) + } + if node.IsFootnotesList { + r.out(w, footnotesCloseDivBytes) + } + } + case Item: + openTag := liTag + closeTag := liCloseTag + if node.ListFlags&ListTypeDefinition != 0 { + openTag = ddTag + closeTag = ddCloseTag + } + if node.ListFlags&ListTypeTerm != 0 { + openTag = dtTag + closeTag = dtCloseTag + } + if entering { + if itemOpenCR(node) { + r.cr(w) + } + if node.ListData.RefLink != nil { + slug := slugify(node.ListData.RefLink) + r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug)) + break + } + r.out(w, openTag) + } else { + if node.ListData.RefLink != nil { + slug := slugify(node.ListData.RefLink) + if r.Flags&FootnoteReturnLinks != 0 { + r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug)) + } + } + r.out(w, closeTag) + r.cr(w) + } + case CodeBlock: + attrs = appendLanguageAttr(attrs, node.Info) + r.cr(w) + r.out(w, preTag) + r.tag(w, codeTag[:len(codeTag)-1], attrs) + escapeHTML(w, node.Literal) + r.out(w, codeCloseTag) + r.out(w, preCloseTag) + if node.Parent.Type != Item { + r.cr(w) + } + case Table: + if entering { + r.cr(w) + r.out(w, tableTag) + } else { + r.out(w, tableCloseTag) + r.cr(w) + } + case TableCell: + openTag := tdTag + closeTag := tdCloseTag + if node.IsHeader { + openTag = thTag + closeTag = thCloseTag + } + if entering { + align := cellAlignment(node.Align) + if align != "" { + attrs = append(attrs, fmt.Sprintf(`align="%s"`, align)) + } + if node.Prev == nil { + r.cr(w) + } + r.tag(w, openTag, attrs) + } else { + r.out(w, closeTag) + r.cr(w) + } + case TableHead: + if entering { + r.cr(w) + r.out(w, theadTag) + } else { + r.out(w, theadCloseTag) + r.cr(w) + } + case TableBody: + if entering { + r.cr(w) + r.out(w, tbodyTag) + // XXX: this is to adhere to a rather silly test. Should fix test. + if node.FirstChild == nil { + r.cr(w) + } + } else { + r.out(w, tbodyCloseTag) + r.cr(w) + } + case TableRow: + if entering { + r.cr(w) + r.out(w, trTag) + } else { + r.out(w, trCloseTag) + r.cr(w) + } + default: + panic("Unknown node type " + node.Type.String()) } - - if i == len(tag) { - return false, -1 - } - - rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') - if rightAngle > i { - return true, rightAngle - } - - return false, -1 -} - -func skipUntilChar(text []byte, start int, char byte) int { - i := start - for i < len(text) && text[i] != char { - i++ - } - return i -} - -func skipSpace(tag []byte, i int) int { - for i < len(tag) && isspace(tag[i]) { - i++ - } - return i + return GoToNext } -func skipChar(data []byte, start int, char byte) int { - i := start - for i < len(data) && data[i] == char { - i++ +// RenderHeader writes HTML document preamble and TOC if requested. +func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) { + r.writeDocumentHeader(w) + if r.Flags&TOC != 0 { + r.writeTOC(w, ast) } - return i } -func doubleSpace(out *bytes.Buffer) { - if out.Len() > 0 { - out.WriteByte('\n') +// RenderFooter writes HTML document footer. +func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) { + if r.Flags&CompletePage == 0 { + return } + io.WriteString(w, "\n</body>\n</html>\n") } -func isRelativeLink(link []byte) (yes bool) { - // a tag begin with '#' - if link[0] == '#' { - return true - } - - // link begin with '/' but not '//', the second maybe a protocol relative link - if len(link) >= 2 && link[0] == '/' && link[1] != '/' { - return true - } - - // only the root '/' - if len(link) == 1 && link[0] == '/' { - return true - } - - // current directory : begin with "./" - if bytes.HasPrefix(link, []byte("./")) { - return true +func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { + if r.Flags&CompletePage == 0 { + return } - - // parent directory : begin with "../" - if bytes.HasPrefix(link, []byte("../")) { - return true + ending := "" + if r.Flags&UseXHTML != 0 { + io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") + io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n") + io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n") + ending = " /" + } else { + io.WriteString(w, "<!DOCTYPE html>\n") + io.WriteString(w, "<html>\n") } + io.WriteString(w, "<head>\n") + io.WriteString(w, " <title>") + if r.Flags&Smartypants != 0 { + r.sr.Process(w, []byte(r.Title)) + } else { + escapeHTML(w, []byte(r.Title)) + } + io.WriteString(w, "</title>\n") + io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") + io.WriteString(w, Version) + io.WriteString(w, "\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") + io.WriteString(w, " <meta charset=\"utf-8\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") + if r.CSS != "" { + io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"") + escapeHTML(w, []byte(r.CSS)) + io.WriteString(w, "\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") + } + if r.Icon != "" { + io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"") + escapeHTML(w, []byte(r.Icon)) + io.WriteString(w, "\"") + io.WriteString(w, ending) + io.WriteString(w, ">\n") + } + io.WriteString(w, "</head>\n") + io.WriteString(w, "<body>\n\n") +} + +func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) { + buf := bytes.Buffer{} + + inHeading := false + tocLevel := 0 + headingCount := 0 + + ast.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Heading && !node.HeadingData.IsTitleblock { + inHeading = entering + if entering { + node.HeadingID = fmt.Sprintf("toc_%d", headingCount) + if node.Level == tocLevel { + buf.WriteString("</li>\n\n<li>") + } else if node.Level < tocLevel { + for node.Level < tocLevel { + tocLevel-- + buf.WriteString("</li>\n</ul>") + } + buf.WriteString("</li>\n\n<li>") + } else { + for node.Level > tocLevel { + tocLevel++ + buf.WriteString("\n<ul>\n<li>") + } + } + + fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount) + headingCount++ + } else { + buf.WriteString("</a>") + } + return GoToNext + } - return false -} + if inHeading { + return r.RenderNode(&buf, node, entering) + } -func (options *Html) ensureUniqueHeaderID(id string) string { - for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] { - tmp := fmt.Sprintf("%s-%d", id, count+1) + return GoToNext + }) - if _, tmpFound := options.headerIDs[tmp]; !tmpFound { - options.headerIDs[id] = count + 1 - id = tmp - } else { - id = id + "-1" - } + for ; tocLevel > 0; tocLevel-- { + buf.WriteString("</li>\n</ul>") } - if _, found := options.headerIDs[id]; !found { - options.headerIDs[id] = 0 + if buf.Len() > 0 { + io.WriteString(w, "<nav>\n") + w.Write(buf.Bytes()) + io.WriteString(w, "\n\n</nav>\n") } - - return id + r.lastOutputLen = buf.Len() } diff --git a/vendor/github.com/russross/blackfriday/inline.go b/vendor/github.com/russross/blackfriday/inline.go index 4483b8f1..3d633106 100644 --- a/vendor/github.com/russross/blackfriday/inline.go +++ b/vendor/github.com/russross/blackfriday/inline.go @@ -22,6 +22,9 @@ import ( var ( urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+` anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`) + + // TODO: improve this regexp to catch all possible entities: + htmlEntityRe = regexp.MustCompile(`&[a-z]{2,5};`) ) // Functions to parse text within a block @@ -29,87 +32,89 @@ var ( // data is the complete block being rendered // offset is the number of valid chars before the current cursor -func (p *parser) inline(out *bytes.Buffer, data []byte) { - // this is called recursively: enforce a maximum depth - if p.nesting >= p.maxNesting { +func (p *Markdown) inline(currBlock *Node, data []byte) { + // handlers might call us recursively: enforce a maximum depth + if p.nesting >= p.maxNesting || len(data) == 0 { return } p.nesting++ - - i, end := 0, 0 - for i < len(data) { - // copy inactive chars into the output - for end < len(data) && p.inlineCallback[data[end]] == nil { - end++ - } - - p.r.NormalText(out, data[i:end]) - - if end >= len(data) { - break - } - i = end - - // call the trigger + beg, end := 0, 0 + for end < len(data) { handler := p.inlineCallback[data[end]] - if consumed := handler(p, out, data, i); consumed == 0 { - // no action from the callback; buffer the byte for later - end = i + 1 + if handler != nil { + if consumed, node := handler(p, data, end); consumed == 0 { + // No action from the callback. + end++ + } else { + // Copy inactive chars into the output. + currBlock.AppendChild(text(data[beg:end])) + if node != nil { + currBlock.AppendChild(node) + } + // Skip past whatever the callback used. + beg = end + consumed + end = beg + } } else { - // skip past whatever the callback used - i += consumed - end = i + end++ } } - + if beg < len(data) { + if data[end-1] == '\n' { + end-- + } + currBlock.AppendChild(text(data[beg:end])) + } p.nesting-- } // single and double emphasis parsing -func emphasis(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func emphasis(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] c := data[0] - ret := 0 if len(data) > 2 && data[1] != c { // whitespace cannot follow an opening emphasis; // strikethrough only takes two characters '~~' if c == '~' || isspace(data[1]) { - return 0 + return 0, nil } - if ret = helperEmphasis(p, out, data[1:], c); ret == 0 { - return 0 + ret, node := helperEmphasis(p, data[1:], c) + if ret == 0 { + return 0, nil } - return ret + 1 + return ret + 1, node } if len(data) > 3 && data[1] == c && data[2] != c { if isspace(data[2]) { - return 0 + return 0, nil } - if ret = helperDoubleEmphasis(p, out, data[2:], c); ret == 0 { - return 0 + ret, node := helperDoubleEmphasis(p, data[2:], c) + if ret == 0 { + return 0, nil } - return ret + 2 + return ret + 2, node } if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c { if c == '~' || isspace(data[3]) { - return 0 + return 0, nil } - if ret = helperTripleEmphasis(p, out, data, 3, c); ret == 0 { - return 0 + ret, node := helperTripleEmphasis(p, data, 3, c) + if ret == 0 { + return 0, nil } - return ret + 3 + return ret + 3, node } - return 0 + return 0, nil } -func codeSpan(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] nb := 0 @@ -131,7 +136,7 @@ func codeSpan(p *parser, out *bytes.Buffer, data []byte, offset int) int { // no matching delimiter? if i < nb && end >= len(data) { - return 0 + return 0, nil } // trim outside whitespace @@ -147,43 +152,36 @@ func codeSpan(p *parser, out *bytes.Buffer, data []byte, offset int) int { // render the code span if fBegin != fEnd { - p.r.CodeSpan(out, data[fBegin:fEnd]) + code := NewNode(Code) + code.Literal = data[fBegin:fEnd] + return end, code } - return end - + return end, nil } // newline preceded by two spaces becomes <br> -// newline without two spaces works when EXTENSION_HARD_LINE_BREAK is enabled -func lineBreak(p *parser, out *bytes.Buffer, data []byte, offset int) int { - // remove trailing spaces from out - outBytes := out.Bytes() - end := len(outBytes) - eol := end - for eol > 0 && outBytes[eol-1] == ' ' { - eol-- - } - out.Truncate(eol) - - precededByTwoSpaces := offset >= 2 && data[offset-2] == ' ' && data[offset-1] == ' ' - precededByBackslash := offset >= 1 && data[offset-1] == '\\' // see http://spec.commonmark.org/0.18/#example-527 - precededByBackslash = precededByBackslash && p.flags&EXTENSION_BACKSLASH_LINE_BREAK != 0 - - if p.flags&EXTENSION_JOIN_LINES != 0 { - return 1 +func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + origOffset := offset + for offset < len(data) && data[offset] == ' ' { + offset++ } - // should there be a hard line break here? - if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash { - return 0 + if offset < len(data) && data[offset] == '\n' { + if offset-origOffset >= 2 { + return offset - origOffset + 1, NewNode(Hardbreak) + } + return offset - origOffset, nil } + return 0, nil +} - if precededByBackslash && eol > 0 { - out.Truncate(eol - 1) +// newline without two spaces works when HardLineBreak is enabled +func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + if p.extensions&HardLineBreak != 0 { + return 1, NewNode(Hardbreak) } - p.r.LineBreak(out) - return 1 + return 0, nil } type linkType int @@ -202,27 +200,43 @@ func isReferenceStyleLink(data []byte, pos int, t linkType) bool { return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^' } +func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + +func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + // '[': parse a link or an image or a footnote -func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func link(p *Markdown, data []byte, offset int) (int, *Node) { // no links allowed inside regular links, footnote, and deferred footnotes if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { - return 0 + return 0, nil } var t linkType switch { // special case: ![^text] == deferred footnote (that follows something with // an exclamation point) - case p.flags&EXTENSION_FOOTNOTES != 0 && len(data)-1 > offset && data[offset+1] == '^': + case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^': t = linkDeferredFootnote // ![alt] == image - case offset > 0 && data[offset-1] == '!': + case offset >= 0 && data[offset] == '!': t = linkImg + offset++ // ^[text] == inline footnote // [^refId] == deferred footnote - case p.flags&EXTENSION_FOOTNOTES != 0: - if offset > 0 && data[offset-1] == '^' { + case p.extensions&Footnotes != 0: + if offset >= 0 && data[offset] == '^' { t = linkInlineFootnote + offset++ } else if len(data)-1 > offset && data[offset+1] == '^' { t = linkDeferredFootnote } @@ -235,7 +249,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { var ( i = 1 - noteId int + noteID int title, link, altContent []byte textHasNl = false ) @@ -244,8 +258,6 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { i++ } - brace := 0 - // look for the matching closing bracket for level := 1; level > 0 && i < len(data); i++ { switch { @@ -267,11 +279,12 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } if i >= len(data) { - return 0 + return 0, nil } txtE := i i++ + var footnoteNode *Node // skip any amount of whitespace or newline // (this is much more lax than original markdown syntax) @@ -279,8 +292,8 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { i++ } - switch { // inline style link + switch { case i < len(data) && data[i] == '(': // skip initial whitespace i++ @@ -291,27 +304,14 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { linkB := i - // look for link end: ' " ), check for new opening braces and take this - // into account, this may lead for overshooting and probably will require - // some fine-tuning. + // look for link end: ' " ) findlinkend: for i < len(data) { switch { case data[i] == '\\': i += 2 - case data[i] == '(': - brace++ - i++ - - case data[i] == ')': - if brace <= 0 { - break findlinkend - } - brace-- - i++ - - case data[i] == '\'' || data[i] == '"': + case data[i] == ')' || data[i] == '\'' || data[i] == '"': break findlinkend default: @@ -320,7 +320,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } if i >= len(data) { - return 0 + return 0, nil } linkE := i @@ -345,7 +345,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } if i >= len(data) { - return 0 + return 0, nil } // skip whitespace after title @@ -397,7 +397,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { i++ } if i >= len(data) { - return 0 + return 0, nil } linkE := i @@ -427,7 +427,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { // find the reference with matching id lr, ok := p.getRef(string(id)) if !ok { - return 0 + return 0, nil } // keep link and title from reference @@ -464,9 +464,10 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } } + footnoteNode = NewNode(Item) if t == linkInlineFootnote { // create a new reference - noteId = len(p.notes) + 1 + noteID = len(p.notes) + 1 var fragment []byte if len(id) > 0 { @@ -477,18 +478,18 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } copy(fragment, slugify(id)) } else { - fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteId))...) + fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...) } ref := &reference{ - noteId: noteId, + noteID: noteID, hasBlock: false, link: fragment, title: id, + footnote: footnoteNode, } p.notes = append(p.notes, ref) - p.notesRecord[string(ref.link)] = struct{}{} link = ref.link title = ref.title @@ -496,40 +497,26 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { // find the reference with matching id lr, ok := p.getRef(string(id)) if !ok { - return 0 + return 0, nil } - if t == linkDeferredFootnote && !p.isFootnote(lr) { - lr.noteId = len(p.notes) + 1 + if t == linkDeferredFootnote { + lr.noteID = len(p.notes) + 1 + lr.footnote = footnoteNode p.notes = append(p.notes, lr) - p.notesRecord[string(lr.link)] = struct{}{} } // keep link and title from reference link = lr.link // if inline footnote, title == footnote contents title = lr.title - noteId = lr.noteId + noteID = lr.noteID } // rewind the whitespace i = txtE + 1 } - // build content: img alt is escaped, link content is parsed - var content bytes.Buffer - if txtE > 1 { - if t == linkImg { - content.Write(data[1:txtE]) - } else { - // links cannot contain other links, so turn off link parsing temporarily - insideLink := p.insideLink - p.insideLink = true - p.inline(&content, data[1:txtE]) - p.insideLink = insideLink - } - } - var uLink []byte if t == linkNormal || t == linkImg { if len(link) > 0 { @@ -539,49 +526,54 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int { } // links need something to click on and somewhere to go - if len(uLink) == 0 || (t == linkNormal && content.Len() == 0) { - return 0 + if len(uLink) == 0 || (t == linkNormal && txtE <= 1) { + return 0, nil } } // call the relevant rendering function + var linkNode *Node switch t { case linkNormal: + linkNode = NewNode(Link) + linkNode.Destination = normalizeURI(uLink) + linkNode.Title = title if len(altContent) > 0 { - p.r.Link(out, uLink, title, altContent) + linkNode.AppendChild(text(altContent)) } else { - p.r.Link(out, uLink, title, content.Bytes()) + // links cannot contain other links, so turn off link parsing + // temporarily and recurse + insideLink := p.insideLink + p.insideLink = true + p.inline(linkNode, data[1:txtE]) + p.insideLink = insideLink } case linkImg: - outSize := out.Len() - outBytes := out.Bytes() - if outSize > 0 && outBytes[outSize-1] == '!' { - out.Truncate(outSize - 1) - } - - p.r.Image(out, uLink, title, content.Bytes()) + linkNode = NewNode(Image) + linkNode.Destination = uLink + linkNode.Title = title + linkNode.AppendChild(text(data[1:txtE])) + i++ - case linkInlineFootnote: - outSize := out.Len() - outBytes := out.Bytes() - if outSize > 0 && outBytes[outSize-1] == '^' { - out.Truncate(outSize - 1) + case linkInlineFootnote, linkDeferredFootnote: + linkNode = NewNode(Link) + linkNode.Destination = link + linkNode.Title = title + linkNode.NoteID = noteID + linkNode.Footnote = footnoteNode + if t == linkInlineFootnote { + i++ } - p.r.FootnoteRef(out, link, noteId) - - case linkDeferredFootnote: - p.r.FootnoteRef(out, link, noteId) - default: - return 0 + return 0, nil } - return i + return i, linkNode } -func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int { +func (p *Markdown) inlineHTMLComment(data []byte) int { if len(data) < 5 { return 0 } @@ -600,44 +592,75 @@ func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int { return i + 1 } +func stripMailto(link []byte) []byte { + if bytes.HasPrefix(link, []byte("mailto://")) { + return link[9:] + } else if bytes.HasPrefix(link, []byte("mailto:")) { + return link[7:] + } else { + return link + } +} + +// autolinkType specifies a kind of autolink that gets detected. +type autolinkType int + +// These are the possible flag values for the autolink renderer. +const ( + notAutolink autolinkType = iota + normalAutolink + emailAutolink +) + // '<' when tags or autolinks are allowed -func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] - altype := LINK_TYPE_NOT_AUTOLINK - end := tagLength(data, &altype) - if size := p.inlineHTMLComment(out, data); size > 0 { + altype, end := tagLength(data) + if size := p.inlineHTMLComment(data); size > 0 { end = size } if end > 2 { - if altype != LINK_TYPE_NOT_AUTOLINK { + if altype != notAutolink { var uLink bytes.Buffer unescapeText(&uLink, data[1:end+1-2]) if uLink.Len() > 0 { - p.r.AutoLink(out, uLink.Bytes(), altype) + link := uLink.Bytes() + node := NewNode(Link) + node.Destination = link + if altype == emailAutolink { + node.Destination = append([]byte("mailto:"), link...) + } + node.AppendChild(text(stripMailto(link))) + return end, node } } else { - p.r.RawHtmlTag(out, data[:end]) + htmlTag := NewNode(HTMLSpan) + htmlTag.Literal = data[:end] + return end, htmlTag } } - return end + return end, nil } // '\\' backslash escape var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~") -func escape(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func escape(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] if len(data) > 1 { + if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' { + return 2, NewNode(Hardbreak) + } if bytes.IndexByte(escapeChars, data[1]) < 0 { - return 0 + return 0, nil } - p.r.NormalText(out, data[1:2]) + return 2, text(data[1:2]) } - return 2 + return 2, nil } func unescapeText(ob *bytes.Buffer, src []byte) { @@ -663,7 +686,7 @@ func unescapeText(ob *bytes.Buffer, src []byte) { // '&' escaped when it doesn't belong to an entity // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; -func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int { +func entity(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] end := 1 @@ -679,25 +702,70 @@ func entity(p *parser, out *bytes.Buffer, data []byte, offset int) int { if end < len(data) && data[end] == ';' { end++ // real entity } else { - return 0 // lone '&' + return 0, nil // lone '&' } - p.r.Entity(out, data[:end]) + ent := data[:end] + // undo & escaping or it will be converted to &amp; by another + // escaper in the renderer + if bytes.Equal(ent, []byte("&")) { + ent = []byte{'&'} + } - return end + return end, text(ent) } func linkEndsWithEntity(data []byte, linkEnd int) bool { - entityRanges := htmlEntity.FindAllIndex(data[:linkEnd], -1) + entityRanges := htmlEntityRe.FindAllIndex(data[:linkEnd], -1) return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd } -func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int { - // quick check to rule out most false hits on ':' - if p.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' { - return 0 +// hasPrefixCaseInsensitive is a custom implementation of +// strings.HasPrefix(strings.ToLower(s), prefix) +// we rolled our own because ToLower pulls in a huge machinery of lowercasing +// anything from Unicode and that's very slow. Since this func will only be +// used on ASCII protocol prefixes, we can take shortcuts. +func hasPrefixCaseInsensitive(s, prefix []byte) bool { + if len(s) < len(prefix) { + return false + } + delta := byte('a' - 'A') + for i, b := range prefix { + if b != s[i] && b != s[i]+delta { + return false + } } + return true +} +var protocolPrefixes = [][]byte{ + []byte("http://"), + []byte("https://"), + []byte("ftp://"), + []byte("file://"), + []byte("mailto:"), +} + +const shortestPrefix = 6 // len("ftp://"), the shortest of the above + +func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) { + // quick check to rule out most false hits + if p.insideLink || len(data) < offset+shortestPrefix { + return 0, nil + } + for _, prefix := range protocolPrefixes { + endOfHead := offset + 8 // 8 is the len() of the longest prefix + if endOfHead > len(data) { + endOfHead = len(data) + } + if hasPrefixCaseInsensitive(data[offset:endOfHead], prefix) { + return autoLink(p, data, offset) + } + } + return 0, nil +} + +func autoLink(p *Markdown, data []byte, offset int) (int, *Node) { // Now a more expensive check to see if we're not inside an anchor element anchorStart := offset offsetFromAnchor := 0 @@ -708,8 +776,9 @@ func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int { anchorStr := anchorRe.Find(data[anchorStart:]) if anchorStr != nil { - out.Write(anchorStr[offsetFromAnchor:]) - return len(anchorStr) - offsetFromAnchor + anchorClose := NewNode(HTMLSpan) + anchorClose.Literal = anchorStr[offsetFromAnchor:] + return len(anchorStr) - offsetFromAnchor, anchorClose } // scan backward for a word boundary @@ -718,14 +787,14 @@ func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int { rewind++ } if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters - return 0 + return 0, nil } origData := data data = data[offset-rewind:] if !isSafeLink(data) { - return 0 + return 0, nil } linkEnd := 0 @@ -802,19 +871,17 @@ func autoLink(p *parser, out *bytes.Buffer, data []byte, offset int) int { } } - // we were triggered on the ':', so we need to rewind the output a bit - if out.Len() >= rewind { - out.Truncate(len(out.Bytes()) - rewind) - } - var uLink bytes.Buffer unescapeText(&uLink, data[:linkEnd]) if uLink.Len() > 0 { - p.r.AutoLink(out, uLink.Bytes(), LINK_TYPE_NORMAL) + node := NewNode(Link) + node.Destination = uLink.Bytes() + node.AppendChild(text(uLink.Bytes())) + return linkEnd, node } - return linkEnd - rewind + return linkEnd, nil } func isEndOfLink(char byte) bool { @@ -847,17 +914,17 @@ func isSafeLink(link []byte) bool { } // return the length of the given tag, or 0 is it's not valid -func tagLength(data []byte, autolink *int) int { +func tagLength(data []byte) (autolink autolinkType, end int) { var i, j int // a valid tag can't be shorter than 3 chars if len(data) < 3 { - return 0 + return notAutolink, 0 } // begins with a '<' optionally followed by '/', followed by letter or number if data[0] != '<' { - return 0 + return notAutolink, 0 } if data[1] == '/' { i = 2 @@ -866,11 +933,11 @@ func tagLength(data []byte, autolink *int) int { } if !isalnum(data[i]) { - return 0 + return notAutolink, 0 } // scheme test - *autolink = LINK_TYPE_NOT_AUTOLINK + autolink = notAutolink // try to find the beginning of an URI for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') { @@ -879,21 +946,20 @@ func tagLength(data []byte, autolink *int) int { if i > 1 && i < len(data) && data[i] == '@' { if j = isMailtoAutoLink(data[i:]); j != 0 { - *autolink = LINK_TYPE_EMAIL - return i + j + return emailAutolink, i + j } } if i > 2 && i < len(data) && data[i] == ':' { - *autolink = LINK_TYPE_NORMAL + autolink = normalAutolink i++ } // complete autolink test: no whitespace or ' or " switch { case i >= len(data): - *autolink = LINK_TYPE_NOT_AUTOLINK - case *autolink != 0: + autolink = notAutolink + case autolink != notAutolink: j = i for i < len(data) { @@ -908,24 +974,20 @@ func tagLength(data []byte, autolink *int) int { } if i >= len(data) { - return 0 + return autolink, 0 } if i > j && data[i] == '>' { - return i + 1 + return autolink, i + 1 } // one of the forbidden chars has been found - *autolink = LINK_TYPE_NOT_AUTOLINK + autolink = notAutolink } - - // look for something looking like a tag end - for i < len(data) && data[i] != '>' { - i++ - } - if i >= len(data) { - return 0 + i += bytes.IndexByte(data[i:], '>') + if i < 0 { + return autolink, 0 } - return i + 1 + return autolink, i + 1 } // look for the address part of a mail autolink and '>' @@ -944,14 +1006,13 @@ func isMailtoAutoLink(data []byte) int { nb++ case '-', '.', '_': - // Do nothing. + break case '>': if nb == 1 { return i + 1 - } else { - return 0 } + return 0 default: return 0 } @@ -1014,9 +1075,8 @@ func helperFindEmphChar(data []byte, c byte) int { if data[i] != '[' && data[i] != '(' { // not a link if tmpI > 0 { return tmpI - } else { - continue } + continue } cc := data[i] i++ @@ -1035,7 +1095,7 @@ func helperFindEmphChar(data []byte, c byte) int { return 0 } -func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int { +func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { i := 0 // skip one symbol if coming from emph3 @@ -1046,11 +1106,11 @@ func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int { for i < len(data) { length := helperFindEmphChar(data[i:], c) if length == 0 { - return 0 + return 0, nil } i += length if i >= len(data) { - return 0 + return 0, nil } if i+1 < len(data) && data[i+1] == c { @@ -1060,52 +1120,46 @@ func helperEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int { if data[i] == c && !isspace(data[i-1]) { - if p.flags&EXTENSION_NO_INTRA_EMPHASIS != 0 { + if p.extensions&NoIntraEmphasis != 0 { if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) { continue } } - var work bytes.Buffer - p.inline(&work, data[:i]) - p.r.Emphasis(out, work.Bytes()) - return i + 1 + emph := NewNode(Emph) + p.inline(emph, data[:i]) + return i + 1, emph } } - return 0 + return 0, nil } -func helperDoubleEmphasis(p *parser, out *bytes.Buffer, data []byte, c byte) int { +func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { i := 0 for i < len(data) { length := helperFindEmphChar(data[i:], c) if length == 0 { - return 0 + return 0, nil } i += length if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) { - var work bytes.Buffer - p.inline(&work, data[:i]) - - if work.Len() > 0 { - // pick the right renderer - if c == '~' { - p.r.StrikeThrough(out, work.Bytes()) - } else { - p.r.DoubleEmphasis(out, work.Bytes()) - } + nodeType := Strong + if c == '~' { + nodeType = Del } - return i + 2 + node := NewNode(nodeType) + p.inline(node, data[:i]) + return i + 2, node } i++ } - return 0 + return 0, nil } -func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, c byte) int { +func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) { i := 0 origData := data data = data[offset:] @@ -1113,7 +1167,7 @@ func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, for i < len(data) { length := helperFindEmphChar(data[i:], c) if length == 0 { - return 0 + return 0, nil } i += length @@ -1125,30 +1179,36 @@ func helperTripleEmphasis(p *parser, out *bytes.Buffer, data []byte, offset int, switch { case i+2 < len(data) && data[i+1] == c && data[i+2] == c: // triple symbol found - var work bytes.Buffer - - p.inline(&work, data[:i]) - if work.Len() > 0 { - p.r.TripleEmphasis(out, work.Bytes()) - } - return i + 3 + strong := NewNode(Strong) + em := NewNode(Emph) + strong.AppendChild(em) + p.inline(em, data[:i]) + return i + 3, strong case (i+1 < len(data) && data[i+1] == c): // double symbol found, hand over to emph1 - length = helperEmphasis(p, out, origData[offset-2:], c) + length, node := helperEmphasis(p, origData[offset-2:], c) if length == 0 { - return 0 - } else { - return length - 2 + return 0, nil } + return length - 2, node default: // single symbol found, hand over to emph2 - length = helperDoubleEmphasis(p, out, origData[offset-1:], c) + length, node := helperDoubleEmphasis(p, origData[offset-1:], c) if length == 0 { - return 0 - } else { - return length - 1 + return 0, nil } + return length - 1, node } } - return 0 + return 0, nil +} + +func text(s []byte) *Node { + node := NewNode(Text) + node.Literal = s + return node +} + +func normalizeURI(s []byte) []byte { + return s // TODO: implement } diff --git a/vendor/github.com/russross/blackfriday/latex.go b/vendor/github.com/russross/blackfriday/latex.go deleted file mode 100644 index 70705aa9..00000000 --- a/vendor/github.com/russross/blackfriday/latex.go +++ /dev/null @@ -1,332 +0,0 @@ -// -// Blackfriday Markdown Processor -// Available at http://github.com/russross/blackfriday -// -// Copyright © 2011 Russ Ross <russ@russross.com>. -// Distributed under the Simplified BSD License. -// See README.md for details. -// - -// -// -// LaTeX rendering backend -// -// - -package blackfriday - -import ( - "bytes" -) - -// Latex is a type that implements the Renderer interface for LaTeX output. -// -// Do not create this directly, instead use the LatexRenderer function. -type Latex struct { -} - -// LatexRenderer creates and configures a Latex object, which -// satisfies the Renderer interface. -// -// flags is a set of LATEX_* options ORed together (currently no such options -// are defined). -func LatexRenderer(flags int) Renderer { - return &Latex{} -} - -func (options *Latex) GetFlags() int { - return 0 -} - -// render code chunks using verbatim, or listings if we have a language -func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) { - if lang == "" { - out.WriteString("\n\\begin{verbatim}\n") - } else { - out.WriteString("\n\\begin{lstlisting}[language=") - out.WriteString(lang) - out.WriteString("]\n") - } - out.Write(text) - if lang == "" { - out.WriteString("\n\\end{verbatim}\n") - } else { - out.WriteString("\n\\end{lstlisting}\n") - } -} - -func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) { - -} - -func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) { - out.WriteString("\n\\begin{quotation}\n") - out.Write(text) - out.WriteString("\n\\end{quotation}\n") -} - -func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) { - // a pretty lame thing to do... - out.WriteString("\n\\begin{verbatim}\n") - out.Write(text) - out.WriteString("\n\\end{verbatim}\n") -} - -func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) { - marker := out.Len() - - switch level { - case 1: - out.WriteString("\n\\section{") - case 2: - out.WriteString("\n\\subsection{") - case 3: - out.WriteString("\n\\subsubsection{") - case 4: - out.WriteString("\n\\paragraph{") - case 5: - out.WriteString("\n\\subparagraph{") - case 6: - out.WriteString("\n\\textbf{") - } - if !text() { - out.Truncate(marker) - return - } - out.WriteString("}\n") -} - -func (options *Latex) HRule(out *bytes.Buffer) { - out.WriteString("\n\\HRule\n") -} - -func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) { - marker := out.Len() - if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("\n\\begin{enumerate}\n") - } else { - out.WriteString("\n\\begin{itemize}\n") - } - if !text() { - out.Truncate(marker) - return - } - if flags&LIST_TYPE_ORDERED != 0 { - out.WriteString("\n\\end{enumerate}\n") - } else { - out.WriteString("\n\\end{itemize}\n") - } -} - -func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) { - out.WriteString("\n\\item ") - out.Write(text) -} - -func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) { - marker := out.Len() - out.WriteString("\n") - if !text() { - out.Truncate(marker) - return - } - out.WriteString("\n") -} - -func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) { - out.WriteString("\n\\begin{tabular}{") - for _, elt := range columnData { - switch elt { - case TABLE_ALIGNMENT_LEFT: - out.WriteByte('l') - case TABLE_ALIGNMENT_RIGHT: - out.WriteByte('r') - default: - out.WriteByte('c') - } - } - out.WriteString("}\n") - out.Write(header) - out.WriteString(" \\\\\n\\hline\n") - out.Write(body) - out.WriteString("\n\\end{tabular}\n") -} - -func (options *Latex) TableRow(out *bytes.Buffer, text []byte) { - if out.Len() > 0 { - out.WriteString(" \\\\\n") - } - out.Write(text) -} - -func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) { - if out.Len() > 0 { - out.WriteString(" & ") - } - out.Write(text) -} - -func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) { - if out.Len() > 0 { - out.WriteString(" & ") - } - out.Write(text) -} - -// TODO: this -func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) { - -} - -func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) { - -} - -func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) { - out.WriteString("\\href{") - if kind == LINK_TYPE_EMAIL { - out.WriteString("mailto:") - } - out.Write(link) - out.WriteString("}{") - out.Write(link) - out.WriteString("}") -} - -func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) { - out.WriteString("\\texttt{") - escapeSpecialChars(out, text) - out.WriteString("}") -} - -func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("\\textbf{") - out.Write(text) - out.WriteString("}") -} - -func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) { - out.WriteString("\\textit{") - out.Write(text) - out.WriteString("}") -} - -func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) { - if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) { - // treat it like a link - out.WriteString("\\href{") - out.Write(link) - out.WriteString("}{") - out.Write(alt) - out.WriteString("}") - } else { - out.WriteString("\\includegraphics{") - out.Write(link) - out.WriteString("}") - } -} - -func (options *Latex) LineBreak(out *bytes.Buffer) { - out.WriteString(" \\\\\n") -} - -func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) { - out.WriteString("\\href{") - out.Write(link) - out.WriteString("}{") - out.Write(content) - out.WriteString("}") -} - -func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) { -} - -func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) { - out.WriteString("\\textbf{\\textit{") - out.Write(text) - out.WriteString("}}") -} - -func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) { - out.WriteString("\\sout{") - out.Write(text) - out.WriteString("}") -} - -// TODO: this -func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) { - -} - -func needsBackslash(c byte) bool { - for _, r := range []byte("_{}%$&\\~#") { - if c == r { - return true - } - } - return false -} - -func escapeSpecialChars(out *bytes.Buffer, text []byte) { - for i := 0; i < len(text); i++ { - // directly copy normal characters - org := i - - for i < len(text) && !needsBackslash(text[i]) { - i++ - } - if i > org { - out.Write(text[org:i]) - } - - // escape a character - if i >= len(text) { - break - } - out.WriteByte('\\') - out.WriteByte(text[i]) - } -} - -func (options *Latex) Entity(out *bytes.Buffer, entity []byte) { - // TODO: convert this into a unicode character or something - out.Write(entity) -} - -func (options *Latex) NormalText(out *bytes.Buffer, text []byte) { - escapeSpecialChars(out, text) -} - -// header and footer -func (options *Latex) DocumentHeader(out *bytes.Buffer) { - out.WriteString("\\documentclass{article}\n") - out.WriteString("\n") - out.WriteString("\\usepackage{graphicx}\n") - out.WriteString("\\usepackage{listings}\n") - out.WriteString("\\usepackage[margin=1in]{geometry}\n") - out.WriteString("\\usepackage[utf8]{inputenc}\n") - out.WriteString("\\usepackage{verbatim}\n") - out.WriteString("\\usepackage[normalem]{ulem}\n") - out.WriteString("\\usepackage{hyperref}\n") - out.WriteString("\n") - out.WriteString("\\hypersetup{colorlinks,%\n") - out.WriteString(" citecolor=black,%\n") - out.WriteString(" filecolor=black,%\n") - out.WriteString(" linkcolor=black,%\n") - out.WriteString(" urlcolor=black,%\n") - out.WriteString(" pdfstartview=FitH,%\n") - out.WriteString(" breaklinks=true,%\n") - out.WriteString(" pdfauthor={Blackfriday Markdown Processor v") - out.WriteString(VERSION) - out.WriteString("}}\n") - out.WriteString("\n") - out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n") - out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n") - out.WriteString("\\parindent=0pt\n") - out.WriteString("\n") - out.WriteString("\\begin{document}\n") -} - -func (options *Latex) DocumentFooter(out *bytes.Buffer) { - out.WriteString("\n\\end{document}\n") -} diff --git a/vendor/github.com/russross/blackfriday/markdown.go b/vendor/github.com/russross/blackfriday/markdown.go index 1722a738..ff61cb05 100644 --- a/vendor/github.com/russross/blackfriday/markdown.go +++ b/vendor/github.com/russross/blackfriday/markdown.go @@ -1,230 +1,200 @@ -// // Blackfriday Markdown Processor // Available at http://github.com/russross/blackfriday // // Copyright © 2011 Russ Ross <russ@russross.com>. // Distributed under the Simplified BSD License. // See README.md for details. -// - -// -// -// Markdown parsing and processing -// -// package blackfriday import ( "bytes" "fmt" + "io" "strings" "unicode/utf8" ) -const VERSION = "1.5" +// +// Markdown parsing and processing +// + +// Version string of the package. Appears in the rendered document when +// CompletePage flag is on. +const Version = "2.0" + +// Extensions is a bitwise or'ed collection of enabled Blackfriday's +// extensions. +type Extensions int // These are the supported markdown parsing extensions. // OR these values together to select multiple extensions. const ( - EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words - EXTENSION_TABLES // render tables - EXTENSION_FENCED_CODE // render fenced code blocks - EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked - EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~ - EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules - EXTENSION_SPACE_HEADERS // be strict about prefix header rules - EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks - EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four - EXTENSION_FOOTNOTES // Pandoc-style footnotes - EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block - EXTENSION_HEADER_IDS // specify header IDs with {#id} - EXTENSION_TITLEBLOCK // Titleblock ala pandoc - EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text - EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks - EXTENSION_DEFINITION_LISTS // render definition lists - EXTENSION_JOIN_LINES // delete newline and join lines - - commonHtmlFlags = 0 | - HTML_USE_XHTML | - HTML_USE_SMARTYPANTS | - HTML_SMARTYPANTS_FRACTIONS | - HTML_SMARTYPANTS_DASHES | - HTML_SMARTYPANTS_LATEX_DASHES - - commonExtensions = 0 | - EXTENSION_NO_INTRA_EMPHASIS | - EXTENSION_TABLES | - EXTENSION_FENCED_CODE | - EXTENSION_AUTOLINK | - EXTENSION_STRIKETHROUGH | - EXTENSION_SPACE_HEADERS | - EXTENSION_HEADER_IDS | - EXTENSION_BACKSLASH_LINE_BREAK | - EXTENSION_DEFINITION_LISTS + NoExtensions Extensions = 0 + NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words + Tables // Render tables + FencedCode // Render fenced code blocks + Autolink // Detect embedded URLs that are not explicitly marked + Strikethrough // Strikethrough text using ~~test~~ + LaxHTMLBlocks // Loosen up HTML block parsing rules + SpaceHeadings // Be strict about prefix heading rules + HardLineBreak // Translate newlines into line breaks + TabSizeEight // Expand tabs to eight spaces instead of four + Footnotes // Pandoc-style footnotes + NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block + HeadingIDs // specify heading IDs with {#id} + Titleblock // Titleblock ala pandoc + AutoHeadingIDs // Create the heading ID from the text + BackslashLineBreak // Translate trailing backslashes into line breaks + DefinitionLists // Render definition lists + + CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | + SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes + + CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | + Autolink | Strikethrough | SpaceHeadings | HeadingIDs | + BackslashLineBreak | DefinitionLists ) -// These are the possible flag values for the link renderer. -// Only a single one of these values will be used; they are not ORed together. -// These are mostly of interest if you are writing a new output format. -const ( - LINK_TYPE_NOT_AUTOLINK = iota - LINK_TYPE_NORMAL - LINK_TYPE_EMAIL -) +// ListType contains bitwise or'ed flags for list and list item objects. +type ListType int // These are the possible flag values for the ListItem renderer. // Multiple flag values may be ORed together. // These are mostly of interest if you are writing a new output format. const ( - LIST_TYPE_ORDERED = 1 << iota - LIST_TYPE_DEFINITION - LIST_TYPE_TERM - LIST_ITEM_CONTAINS_BLOCK - LIST_ITEM_BEGINNING_OF_LIST - LIST_ITEM_END_OF_LIST + ListTypeOrdered ListType = 1 << iota + ListTypeDefinition + ListTypeTerm + + ListItemContainsBlock + ListItemBeginningOfList // TODO: figure out if this is of any use now + ListItemEndOfList ) +// CellAlignFlags holds a type of alignment in a table cell. +type CellAlignFlags int + // These are the possible flag values for the table cell renderer. // Only a single one of these values will be used; they are not ORed together. // These are mostly of interest if you are writing a new output format. const ( - TABLE_ALIGNMENT_LEFT = 1 << iota - TABLE_ALIGNMENT_RIGHT - TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT) + TableAlignmentLeft CellAlignFlags = 1 << iota + TableAlignmentRight + TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) ) // The size of a tab stop. const ( - TAB_SIZE_DEFAULT = 4 - TAB_SIZE_EIGHT = 8 + TabSizeDefault = 4 + TabSizeDouble = 8 ) // blockTags is a set of tags that are recognized as HTML block tags. // Any of these can be included in markdown text without special escaping. var blockTags = map[string]struct{}{ - "blockquote": {}, - "del": {}, - "div": {}, - "dl": {}, - "fieldset": {}, - "form": {}, - "h1": {}, - "h2": {}, - "h3": {}, - "h4": {}, - "h5": {}, - "h6": {}, - "iframe": {}, - "ins": {}, - "math": {}, - "noscript": {}, - "ol": {}, - "pre": {}, - "p": {}, - "script": {}, - "style": {}, - "table": {}, - "ul": {}, + "blockquote": struct{}{}, + "del": struct{}{}, + "div": struct{}{}, + "dl": struct{}{}, + "fieldset": struct{}{}, + "form": struct{}{}, + "h1": struct{}{}, + "h2": struct{}{}, + "h3": struct{}{}, + "h4": struct{}{}, + "h5": struct{}{}, + "h6": struct{}{}, + "iframe": struct{}{}, + "ins": struct{}{}, + "math": struct{}{}, + "noscript": struct{}{}, + "ol": struct{}{}, + "pre": struct{}{}, + "p": struct{}{}, + "script": struct{}{}, + "style": struct{}{}, + "table": struct{}{}, + "ul": struct{}{}, // HTML5 - "address": {}, - "article": {}, - "aside": {}, - "canvas": {}, - "figcaption": {}, - "figure": {}, - "footer": {}, - "header": {}, - "hgroup": {}, - "main": {}, - "nav": {}, - "output": {}, - "progress": {}, - "section": {}, - "video": {}, + "address": struct{}{}, + "article": struct{}{}, + "aside": struct{}{}, + "canvas": struct{}{}, + "figcaption": struct{}{}, + "figure": struct{}{}, + "footer": struct{}{}, + "header": struct{}{}, + "hgroup": struct{}{}, + "main": struct{}{}, + "nav": struct{}{}, + "output": struct{}{}, + "progress": struct{}{}, + "section": struct{}{}, + "video": struct{}{}, } -// Renderer is the rendering interface. -// This is mostly of interest if you are implementing a new rendering format. +// Renderer is the rendering interface. This is mostly of interest if you are +// implementing a new rendering format. // -// When a byte slice is provided, it contains the (rendered) contents of the -// element. -// -// When a callback is provided instead, it will write the contents of the -// respective element directly to the output buffer and return true on success. -// If the callback returns false, the rendering function should reset the -// output buffer as though it had never been called. -// -// Currently Html and Latex implementations are provided +// Only an HTML implementation is provided in this repository, see the README +// for external implementations. type Renderer interface { - // block-level callbacks - BlockCode(out *bytes.Buffer, text []byte, lang string) - BlockQuote(out *bytes.Buffer, text []byte) - BlockHtml(out *bytes.Buffer, text []byte) - Header(out *bytes.Buffer, text func() bool, level int, id string) - HRule(out *bytes.Buffer) - List(out *bytes.Buffer, text func() bool, flags int) - ListItem(out *bytes.Buffer, text []byte, flags int) - Paragraph(out *bytes.Buffer, text func() bool) - Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) - TableRow(out *bytes.Buffer, text []byte) - TableHeaderCell(out *bytes.Buffer, text []byte, flags int) - TableCell(out *bytes.Buffer, text []byte, flags int) - Footnotes(out *bytes.Buffer, text func() bool) - FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) - TitleBlock(out *bytes.Buffer, text []byte) - - // Span-level callbacks - AutoLink(out *bytes.Buffer, link []byte, kind int) - CodeSpan(out *bytes.Buffer, text []byte) - DoubleEmphasis(out *bytes.Buffer, text []byte) - Emphasis(out *bytes.Buffer, text []byte) - Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) - LineBreak(out *bytes.Buffer) - Link(out *bytes.Buffer, link []byte, title []byte, content []byte) - RawHtmlTag(out *bytes.Buffer, tag []byte) - TripleEmphasis(out *bytes.Buffer, text []byte) - StrikeThrough(out *bytes.Buffer, text []byte) - FootnoteRef(out *bytes.Buffer, ref []byte, id int) - - // Low-level callbacks - Entity(out *bytes.Buffer, entity []byte) - NormalText(out *bytes.Buffer, text []byte) - - // Header and footer - DocumentHeader(out *bytes.Buffer) - DocumentFooter(out *bytes.Buffer) - - GetFlags() int + // RenderNode is the main rendering method. It will be called once for + // every leaf node and twice for every non-leaf node (first with + // entering=true, then with entering=false). The method should write its + // rendition of the node to the supplied writer w. + RenderNode(w io.Writer, node *Node, entering bool) WalkStatus + + // RenderHeader is a method that allows the renderer to produce some + // content preceding the main body of the output document. The header is + // understood in the broad sense here. For example, the default HTML + // renderer will write not only the HTML document preamble, but also the + // table of contents if it was requested. + // + // The method will be passed an entire document tree, in case a particular + // implementation needs to inspect it to produce output. + // + // The output should be written to the supplied writer w. If your + // implementation has no header to write, supply an empty implementation. + RenderHeader(w io.Writer, ast *Node) + + // RenderFooter is a symmetric counterpart of RenderHeader. + RenderFooter(w io.Writer, ast *Node) } // Callback functions for inline parsing. One such function is defined // for each character that triggers a response when parsing inline data. -type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int - -// Parser holds runtime state used by the parser. -// This is constructed by the Markdown function. -type parser struct { - r Renderer - refOverride ReferenceOverrideFunc - refs map[string]*reference - inlineCallback [256]inlineParser - flags int - nesting int - maxNesting int - insideLink bool +type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node) + +// Markdown is a type that holds extensions and the runtime state used by +// Parse, and the renderer. You can not use it directly, construct it with New. +type Markdown struct { + renderer Renderer + referenceOverride ReferenceOverrideFunc + refs map[string]*reference + inlineCallback [256]inlineParser + extensions Extensions + nesting int + maxNesting int + insideLink bool // Footnotes need to be ordered as well as available to quickly check for // presence. If a ref is also a footnote, it's stored both in refs and here // in notes. Slice is nil if footnotes not enabled. - notes []*reference - notesRecord map[string]struct{} + notes []*reference + + doc *Node + tip *Node // = doc + oldTip *Node + lastMatchedContainer *Node // = doc + allClosed bool } -func (p *parser) getRef(refid string) (ref *reference, found bool) { - if p.refOverride != nil { - r, overridden := p.refOverride(refid) +func (p *Markdown) getRef(refid string) (ref *reference, found bool) { + if p.referenceOverride != nil { + r, overridden := p.referenceOverride(refid) if overridden { if r == nil { return nil, false @@ -232,7 +202,7 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) { return &reference{ link: []byte(r.Link), title: []byte(r.Title), - noteId: 0, + noteID: 0, hasBlock: false, text: []byte(r.Text)}, true } @@ -242,9 +212,34 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) { return ref, found } -func (p *parser) isFootnote(ref *reference) bool { - _, ok := p.notesRecord[string(ref.link)] - return ok +func (p *Markdown) finalize(block *Node) { + above := block.Parent + block.open = false + p.tip = above +} + +func (p *Markdown) addChild(node NodeType, offset uint32) *Node { + return p.addExistingChild(NewNode(node), offset) +} + +func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node { + for !p.tip.canContain(node.Type) { + p.finalize(p.tip) + } + p.tip.AppendChild(node) + p.tip = node + return node +} + +func (p *Markdown) closeUnmatchedBlocks() { + if !p.allClosed { + for p.oldTip != p.lastMatchedContainer { + parent := p.oldTip.Parent + p.finalize(p.oldTip) + p.oldTip = parent + } + p.allClosed = true + } } // @@ -271,102 +266,27 @@ type Reference struct { // See the documentation in Options for more details on use-case. type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) -// Options represents configurable overrides and callbacks (in addition to the -// extension flag set) for configuring a Markdown parse. -type Options struct { - // Extensions is a flag set of bit-wise ORed extension bits. See the - // EXTENSION_* flags defined in this package. - Extensions int - - // ReferenceOverride is an optional function callback that is called every - // time a reference is resolved. - // - // In Markdown, the link reference syntax can be made to resolve a link to - // a reference instead of an inline URL, in one of the following ways: - // - // * [link text][refid] - // * [refid][] - // - // Usually, the refid is defined at the bottom of the Markdown document. If - // this override function is provided, the refid is passed to the override - // function first, before consulting the defined refids at the bottom. If - // the override function indicates an override did not occur, the refids at - // the bottom will be used to fill in the link details. - ReferenceOverride ReferenceOverrideFunc -} - -// MarkdownBasic is a convenience function for simple rendering. -// It processes markdown input with no extensions enabled. -func MarkdownBasic(input []byte) []byte { - // set up the HTML renderer - htmlFlags := HTML_USE_XHTML - renderer := HtmlRenderer(htmlFlags, "", "") - - // set up the parser - return MarkdownOptions(input, renderer, Options{Extensions: 0}) -} - -// Call Markdown with most useful extensions enabled -// MarkdownCommon is a convenience function for simple rendering. -// It processes markdown input with common extensions enabled, including: -// -// * Smartypants processing with smart fractions and LaTeX dashes -// -// * Intra-word emphasis suppression -// -// * Tables -// -// * Fenced code blocks -// -// * Autolinking -// -// * Strikethrough support -// -// * Strict header parsing -// -// * Custom Header IDs -func MarkdownCommon(input []byte) []byte { - // set up the HTML renderer - renderer := HtmlRenderer(commonHtmlFlags, "", "") - return MarkdownOptions(input, renderer, Options{ - Extensions: commonExtensions}) -} - -// Markdown is the main rendering function. -// It parses and renders a block of markdown-encoded text. -// The supplied Renderer is used to format the output, and extensions dictates -// which non-standard extensions are enabled. -// -// To use the supplied Html or LaTeX renderers, see HtmlRenderer and -// LatexRenderer, respectively. -func Markdown(input []byte, renderer Renderer, extensions int) []byte { - return MarkdownOptions(input, renderer, Options{ - Extensions: extensions}) -} - -// MarkdownOptions is just like Markdown but takes additional options through -// the Options struct. -func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { - // no point in parsing if we can't render - if renderer == nil { - return nil +// New constructs a Markdown processor. You can use the same With* functions as +// for Run() to customize parser's behavior and the renderer. +func New(opts ...Option) *Markdown { + var p Markdown + for _, opt := range opts { + opt(&p) } - - extensions := opts.Extensions - - // fill in the render structure - p := new(parser) - p.r = renderer - p.flags = extensions - p.refOverride = opts.ReferenceOverride p.refs = make(map[string]*reference) p.maxNesting = 16 p.insideLink = false - + docNode := NewNode(Document) + p.doc = docNode + p.tip = docNode + p.oldTip = docNode + p.lastMatchedContainer = docNode + p.allClosed = true // register inline parsers + p.inlineCallback[' '] = maybeLineBreak p.inlineCallback['*'] = emphasis p.inlineCallback['_'] = emphasis - if extensions&EXTENSION_STRIKETHROUGH != 0 { + if p.extensions&Strikethrough != 0 { p.inlineCallback['~'] = emphasis } p.inlineCallback['`'] = codeSpan @@ -375,116 +295,166 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { p.inlineCallback['<'] = leftAngle p.inlineCallback['\\'] = escape p.inlineCallback['&'] = entity - - if extensions&EXTENSION_AUTOLINK != 0 { - p.inlineCallback[':'] = autoLink - } - - if extensions&EXTENSION_FOOTNOTES != 0 { + p.inlineCallback['!'] = maybeImage + p.inlineCallback['^'] = maybeInlineFootnote + if p.extensions&Autolink != 0 { + p.inlineCallback['h'] = maybeAutoLink + p.inlineCallback['m'] = maybeAutoLink + p.inlineCallback['f'] = maybeAutoLink + p.inlineCallback['H'] = maybeAutoLink + p.inlineCallback['M'] = maybeAutoLink + p.inlineCallback['F'] = maybeAutoLink + } + if p.extensions&Footnotes != 0 { p.notes = make([]*reference, 0) - p.notesRecord = make(map[string]struct{}) } - - first := firstPass(p, input) - second := secondPass(p, first) - return second + return &p } -// first pass: -// - normalize newlines -// - extract references (outside of fenced code blocks) -// - expand tabs (outside of fenced code blocks) -// - copy everything else -func firstPass(p *parser, input []byte) []byte { - var out bytes.Buffer - tabSize := TAB_SIZE_DEFAULT - if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 { - tabSize = TAB_SIZE_EIGHT - } - beg := 0 - lastFencedCodeBlockEnd := 0 - for beg < len(input) { - // Find end of this line, then process the line. - end := beg - for end < len(input) && input[end] != '\n' && input[end] != '\r' { - end++ - } +// Option customizes the Markdown processor's default behavior. +type Option func(*Markdown) - if p.flags&EXTENSION_FENCED_CODE != 0 { - // track fenced code block boundaries to suppress tab expansion - // and reference extraction inside them: - if beg >= lastFencedCodeBlockEnd { - if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 { - lastFencedCodeBlockEnd = beg + i - } - } - } - - // add the line body if present - if end > beg { - if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks. - out.Write(input[beg:end]) - } else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 { - beg += refEnd - continue - } else { - expandTabs(&out, input[beg:end], tabSize) - } - } - - if end < len(input) && input[end] == '\r' { - end++ - } - if end < len(input) && input[end] == '\n' { - end++ - } - out.WriteByte('\n') - - beg = end +// WithRenderer allows you to override the default renderer. +func WithRenderer(r Renderer) Option { + return func(p *Markdown) { + p.renderer = r } +} - // empty input? - if out.Len() == 0 { - out.WriteByte('\n') +// WithExtensions allows you to pick some of the many extensions provided by +// Blackfriday. You can bitwise OR them. +func WithExtensions(e Extensions) Option { + return func(p *Markdown) { + p.extensions = e } - - return out.Bytes() } -// second pass: actual rendering -func secondPass(p *parser, input []byte) []byte { - var output bytes.Buffer - - p.r.DocumentHeader(&output) - p.block(&output, input) - - if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 { - p.r.Footnotes(&output, func() bool { - flags := LIST_ITEM_BEGINNING_OF_LIST - for i := 0; i < len(p.notes); i += 1 { - ref := p.notes[i] - var buf bytes.Buffer - if ref.hasBlock { - flags |= LIST_ITEM_CONTAINS_BLOCK - p.block(&buf, ref.title) - } else { - p.inline(&buf, ref.title) - } - p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags) - flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK - } - - return true +// WithNoExtensions turns off all extensions and custom behavior. +func WithNoExtensions() Option { + return func(p *Markdown) { + p.extensions = NoExtensions + p.renderer = NewHTMLRenderer(HTMLRendererParameters{ + Flags: HTMLFlagsNone, }) } +} - p.r.DocumentFooter(&output) - - if p.nesting != 0 { - panic("Nesting level did not end at zero") +// WithRefOverride sets an optional function callback that is called every +// time a reference is resolved. +// +// In Markdown, the link reference syntax can be made to resolve a link to +// a reference instead of an inline URL, in one of the following ways: +// +// * [link text][refid] +// * [refid][] +// +// Usually, the refid is defined at the bottom of the Markdown document. If +// this override function is provided, the refid is passed to the override +// function first, before consulting the defined refids at the bottom. If +// the override function indicates an override did not occur, the refids at +// the bottom will be used to fill in the link details. +func WithRefOverride(o ReferenceOverrideFunc) Option { + return func(p *Markdown) { + p.referenceOverride = o } +} - return output.Bytes() +// Run is the main entry point to Blackfriday. It parses and renders a +// block of markdown-encoded text. +// +// The simplest invocation of Run takes one argument, input: +// output := Run(input) +// This will parse the input with CommonExtensions enabled and render it with +// the default HTMLRenderer (with CommonHTMLFlags). +// +// Variadic arguments opts can customize the default behavior. Since Markdown +// type does not contain exported fields, you can not use it directly. Instead, +// use the With* functions. For example, this will call the most basic +// functionality, with no extensions: +// output := Run(input, WithNoExtensions()) +// +// You can use any number of With* arguments, even contradicting ones. They +// will be applied in order of appearance and the latter will override the +// former: +// output := Run(input, WithNoExtensions(), WithExtensions(exts), +// WithRenderer(yourRenderer)) +func Run(input []byte, opts ...Option) []byte { + r := NewHTMLRenderer(HTMLRendererParameters{ + Flags: CommonHTMLFlags, + }) + optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)} + optList = append(optList, opts...) + parser := New(optList...) + ast := parser.Parse(input) + var buf bytes.Buffer + parser.renderer.RenderHeader(&buf, ast) + ast.Walk(func(node *Node, entering bool) WalkStatus { + return parser.renderer.RenderNode(&buf, node, entering) + }) + parser.renderer.RenderFooter(&buf, ast) + return buf.Bytes() +} + +// Parse is an entry point to the parsing part of Blackfriday. It takes an +// input markdown document and produces a syntax tree for its contents. This +// tree can then be rendered with a default or custom renderer, or +// analyzed/transformed by the caller to whatever non-standard needs they have. +// The return value is the root node of the syntax tree. +func (p *Markdown) Parse(input []byte) *Node { + p.block(input) + // Walk the tree and finish up some of unfinished blocks + for p.tip != nil { + p.finalize(p.tip) + } + // Walk the tree again and process inline markdown in each block + p.doc.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell { + p.inline(node, node.content) + node.content = nil + } + return GoToNext + }) + p.parseRefsToAST() + return p.doc +} + +func (p *Markdown) parseRefsToAST() { + if p.extensions&Footnotes == 0 || len(p.notes) == 0 { + return + } + p.tip = p.doc + block := p.addBlock(List, nil) + block.IsFootnotesList = true + block.ListFlags = ListTypeOrdered + flags := ListItemBeginningOfList + // Note: this loop is intentionally explicit, not range-form. This is + // because the body of the loop will append nested footnotes to p.notes and + // we need to process those late additions. Range form would only walk over + // the fixed initial set. + for i := 0; i < len(p.notes); i++ { + ref := p.notes[i] + p.addExistingChild(ref.footnote, 0) + block := ref.footnote + block.ListFlags = flags | ListTypeOrdered + block.RefLink = ref.link + if ref.hasBlock { + flags |= ListItemContainsBlock + p.block(ref.title) + } else { + p.inline(block, ref.title) + } + flags &^= ListItemBeginningOfList | ListItemContainsBlock + } + above := block.Parent + finalizeList(block) + p.tip = above + block.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Paragraph || node.Type == Heading { + p.inline(node, node.content) + node.content = nil + } + return GoToNext + }) } // @@ -516,18 +486,56 @@ func secondPass(p *parser, input []byte) []byte { // // are not yet supported. -// References are parsed and stored in this struct. +// reference holds all information necessary for a reference-style links or +// footnotes. +// +// Consider this markdown with reference-style links: +// +// [link][ref] +// +// [ref]: /url/ "tooltip title" +// +// It will be ultimately converted to this HTML: +// +// <p><a href=\"/url/\" title=\"title\">link</a></p> +// +// And a reference structure will be populated as follows: +// +// p.refs["ref"] = &reference{ +// link: "/url/", +// title: "tooltip title", +// } +// +// Alternatively, reference can contain information about a footnote. Consider +// this markdown: +// +// Text needing a footnote.[^a] +// +// [^a]: This is the note +// +// A reference structure will be populated as follows: +// +// p.refs["a"] = &reference{ +// link: "a", +// title: "This is the note", +// noteID: <some positive int>, +// } +// +// TODO: As you can see, it begs for splitting into two dedicated structures +// for refs and for footnotes. type reference struct { link []byte title []byte - noteId int // 0 if not a footnote ref + noteID int // 0 if not a footnote ref hasBlock bool - text []byte + footnote *Node // a link to the Item node within a list of footnotes + + text []byte // only gets populated by refOverride feature with Reference.Text } func (r *reference) String() string { - return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}", - r.link, r.title, r.text, r.noteId, r.hasBlock) + return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}", + r.link, r.title, r.text, r.noteID, r.hasBlock) } // Check whether or not data starts with a reference link. @@ -535,7 +543,7 @@ func (r *reference) String() string { // (in the render struct). // Returns the number of bytes to skip to move past it, // or zero if the first line is not a reference. -func isReference(p *parser, data []byte, tabSize int) int { +func isReference(p *Markdown, data []byte, tabSize int) int { // up to 3 optional leading spaces if len(data) < 4 { return 0 @@ -545,18 +553,18 @@ func isReference(p *parser, data []byte, tabSize int) int { i++ } - noteId := 0 + noteID := 0 // id part: anything but a newline between brackets if data[i] != '[' { return 0 } i++ - if p.flags&EXTENSION_FOOTNOTES != 0 { + if p.extensions&Footnotes != 0 { if i < len(data) && data[i] == '^' { // we can set it to anything here because the proper noteIds will // be assigned later during the second pass. It just has to be != 0 - noteId = 1 + noteID = 1 i++ } } @@ -568,7 +576,11 @@ func isReference(p *parser, data []byte, tabSize int) int { return 0 } idEnd := i - + // footnotes can have empty ID, like this: [^], but a reference can not be + // empty like this: []. Break early if it's not a footnote and there's no ID + if noteID == 0 && idOffset == idEnd { + return 0 + } // spacer: colon (space | tab)* newline? (space | tab)* i++ if i >= len(data) || data[i] != ':' { @@ -599,7 +611,7 @@ func isReference(p *parser, data []byte, tabSize int) int { hasBlock bool ) - if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 { + if p.extensions&Footnotes != 0 && noteID != 0 { linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) lineEnd = linkEnd } else { @@ -612,11 +624,11 @@ func isReference(p *parser, data []byte, tabSize int) int { // a valid ref has been found ref := &reference{ - noteId: noteId, + noteID: noteID, hasBlock: hasBlock, } - if noteId > 0 { + if noteID > 0 { // reusing the link field for the id since footnotes don't have links ref.link = data[idOffset:idEnd] // if footnote, it's not really a title, it's the contained text @@ -634,15 +646,12 @@ func isReference(p *parser, data []byte, tabSize int) int { return lineEnd } -func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { +func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { // link: whitespace-free sequence, optionally between angle brackets if data[i] == '<' { i++ } linkOffset = i - if i == len(data) { - return - } for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { i++ } @@ -705,13 +714,13 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse return } -// The first bit of this logic is the same as (*parser).listItem, but the rest +// The first bit of this logic is the same as Parser.listItem, but the rest // is much simpler. This function simply finds the entire block and shifts it // over by one tab if it is indeed a block (just returns the line if it's not). // blockEnd is the end of the section in the input buffer, and contents is the // extracted text that was shifted over one tab. It will need to be rendered at // the end of the document. -func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { +func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { if i == 0 || len(data) == 0 { return } diff --git a/vendor/github.com/russross/blackfriday/node.go b/vendor/github.com/russross/blackfriday/node.go new file mode 100644 index 00000000..51b9e8c1 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/node.go @@ -0,0 +1,354 @@ +package blackfriday + +import ( + "bytes" + "fmt" +) + +// NodeType specifies a type of a single node of a syntax tree. Usually one +// node (and its type) corresponds to a single markdown feature, e.g. emphasis +// or code block. +type NodeType int + +// Constants for identifying different types of nodes. See NodeType. +const ( + Document NodeType = iota + BlockQuote + List + Item + Paragraph + Heading + HorizontalRule + Emph + Strong + Del + Link + Image + Text + HTMLBlock + CodeBlock + Softbreak + Hardbreak + Code + HTMLSpan + Table + TableCell + TableHead + TableBody + TableRow +) + +var nodeTypeNames = []string{ + Document: "Document", + BlockQuote: "BlockQuote", + List: "List", + Item: "Item", + Paragraph: "Paragraph", + Heading: "Heading", + HorizontalRule: "HorizontalRule", + Emph: "Emph", + Strong: "Strong", + Del: "Del", + Link: "Link", + Image: "Image", + Text: "Text", + HTMLBlock: "HTMLBlock", + CodeBlock: "CodeBlock", + Softbreak: "Softbreak", + Hardbreak: "Hardbreak", + Code: "Code", + HTMLSpan: "HTMLSpan", + Table: "Table", + TableCell: "TableCell", + TableHead: "TableHead", + TableBody: "TableBody", + TableRow: "TableRow", +} + +func (t NodeType) String() string { + return nodeTypeNames[t] +} + +// ListData contains fields relevant to a List and Item node type. +type ListData struct { + ListFlags ListType + Tight bool // Skip <p>s around list item data if true + BulletChar byte // '*', '+' or '-' in bullet lists + Delimiter byte // '.' or ')' after the number in ordered lists + RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering + IsFootnotesList bool // This is a list of footnotes +} + +// LinkData contains fields relevant to a Link node type. +type LinkData struct { + Destination []byte // Destination is what goes into a href + Title []byte // Title is the tooltip thing that goes in a title attribute + NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote + Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil. +} + +// CodeBlockData contains fields relevant to a CodeBlock node type. +type CodeBlockData struct { + IsFenced bool // Specifies whether it's a fenced code block or an indented one + Info []byte // This holds the info string + FenceChar byte + FenceLength int + FenceOffset int +} + +// TableCellData contains fields relevant to a TableCell node type. +type TableCellData struct { + IsHeader bool // This tells if it's under the header row + Align CellAlignFlags // This holds the value for align attribute +} + +// HeadingData contains fields relevant to a Heading node type. +type HeadingData struct { + Level int // This holds the heading level number + HeadingID string // This might hold heading ID, if present + IsTitleblock bool // Specifies whether it's a title block +} + +// Node is a single element in the abstract syntax tree of the parsed document. +// It holds connections to the structurally neighboring nodes and, for certain +// types of nodes, additional information that might be needed when rendering. +type Node struct { + Type NodeType // Determines the type of the node + Parent *Node // Points to the parent + FirstChild *Node // Points to the first child, if any + LastChild *Node // Points to the last child, if any + Prev *Node // Previous sibling; nil if it's the first child + Next *Node // Next sibling; nil if it's the last child + + Literal []byte // Text contents of the leaf nodes + + HeadingData // Populated if Type is Heading + ListData // Populated if Type is List + CodeBlockData // Populated if Type is CodeBlock + LinkData // Populated if Type is Link + TableCellData // Populated if Type is TableCell + + content []byte // Markdown content of the block nodes + open bool // Specifies an open block node that has not been finished to process yet +} + +// NewNode allocates a node of a specified type. +func NewNode(typ NodeType) *Node { + return &Node{ + Type: typ, + open: true, + } +} + +func (n *Node) String() string { + ellipsis := "" + snippet := n.Literal + if len(snippet) > 16 { + snippet = snippet[:16] + ellipsis = "..." + } + return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis) +} + +// Unlink removes node 'n' from the tree. +// It panics if the node is nil. +func (n *Node) Unlink() { + if n.Prev != nil { + n.Prev.Next = n.Next + } else if n.Parent != nil { + n.Parent.FirstChild = n.Next + } + if n.Next != nil { + n.Next.Prev = n.Prev + } else if n.Parent != nil { + n.Parent.LastChild = n.Prev + } + n.Parent = nil + n.Next = nil + n.Prev = nil +} + +// AppendChild adds a node 'child' as a child of 'n'. +// It panics if either node is nil. +func (n *Node) AppendChild(child *Node) { + child.Unlink() + child.Parent = n + if n.LastChild != nil { + n.LastChild.Next = child + child.Prev = n.LastChild + n.LastChild = child + } else { + n.FirstChild = child + n.LastChild = child + } +} + +// InsertBefore inserts 'sibling' immediately before 'n'. +// It panics if either node is nil. +func (n *Node) InsertBefore(sibling *Node) { + sibling.Unlink() + sibling.Prev = n.Prev + if sibling.Prev != nil { + sibling.Prev.Next = sibling + } + sibling.Next = n + n.Prev = sibling + sibling.Parent = n.Parent + if sibling.Prev == nil { + sibling.Parent.FirstChild = sibling + } +} + +func (n *Node) isContainer() bool { + switch n.Type { + case Document: + fallthrough + case BlockQuote: + fallthrough + case List: + fallthrough + case Item: + fallthrough + case Paragraph: + fallthrough + case Heading: + fallthrough + case Emph: + fallthrough + case Strong: + fallthrough + case Del: + fallthrough + case Link: + fallthrough + case Image: + fallthrough + case Table: + fallthrough + case TableHead: + fallthrough + case TableBody: + fallthrough + case TableRow: + fallthrough + case TableCell: + return true + default: + return false + } +} + +func (n *Node) canContain(t NodeType) bool { + if n.Type == List { + return t == Item + } + if n.Type == Document || n.Type == BlockQuote || n.Type == Item { + return t != Item + } + if n.Type == Table { + return t == TableHead || t == TableBody + } + if n.Type == TableHead || n.Type == TableBody { + return t == TableRow + } + if n.Type == TableRow { + return t == TableCell + } + return false +} + +// WalkStatus allows NodeVisitor to have some control over the tree traversal. +// It is returned from NodeVisitor and different values allow Node.Walk to +// decide which node to go to next. +type WalkStatus int + +const ( + // GoToNext is the default traversal of every node. + GoToNext WalkStatus = iota + // SkipChildren tells walker to skip all children of current node. + SkipChildren + // Terminate tells walker to terminate the traversal. + Terminate +) + +// NodeVisitor is a callback to be called when traversing the syntax tree. +// Called twice for every node: once with entering=true when the branch is +// first visited, then with entering=false after all the children are done. +type NodeVisitor func(node *Node, entering bool) WalkStatus + +// Walk is a convenience method that instantiates a walker and starts a +// traversal of subtree rooted at n. +func (n *Node) Walk(visitor NodeVisitor) { + w := newNodeWalker(n) + for w.current != nil { + status := visitor(w.current, w.entering) + switch status { + case GoToNext: + w.next() + case SkipChildren: + w.entering = false + w.next() + case Terminate: + return + } + } +} + +type nodeWalker struct { + current *Node + root *Node + entering bool +} + +func newNodeWalker(root *Node) *nodeWalker { + return &nodeWalker{ + current: root, + root: root, + entering: true, + } +} + +func (nw *nodeWalker) next() { + if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { + nw.current = nil + return + } + if nw.entering && nw.current.isContainer() { + if nw.current.FirstChild != nil { + nw.current = nw.current.FirstChild + nw.entering = true + } else { + nw.entering = false + } + } else if nw.current.Next == nil { + nw.current = nw.current.Parent + nw.entering = false + } else { + nw.current = nw.current.Next + nw.entering = true + } +} + +func dump(ast *Node) { + fmt.Println(dumpString(ast)) +} + +func dumpR(ast *Node, depth int) string { + if ast == nil { + return "" + } + indent := bytes.Repeat([]byte("\t"), depth) + content := ast.Literal + if content == nil { + content = ast.content + } + result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) + for n := ast.FirstChild; n != nil; n = n.Next { + result += dumpR(n, depth+1) + } + return result +} + +func dumpString(ast *Node) string { + return dumpR(ast, 0) +} diff --git a/vendor/github.com/russross/blackfriday/smartypants.go b/vendor/github.com/russross/blackfriday/smartypants.go index f25bd07d..3a220e94 100644 --- a/vendor/github.com/russross/blackfriday/smartypants.go +++ b/vendor/github.com/russross/blackfriday/smartypants.go @@ -17,11 +17,14 @@ package blackfriday import ( "bytes" + "io" ) -type smartypantsData struct { +// SPRenderer is a struct containing state of a Smartypants renderer. +type SPRenderer struct { inSingleQuote bool inDoubleQuote bool + callbacks [256]smartCallback } func wordBoundary(c byte) bool { @@ -118,7 +121,7 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote return true } -func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 2 { t1 := tolower(text[1]) @@ -127,7 +130,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt if len(text) >= 3 { nextChar = text[2] } - if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) { + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { return 1 } } @@ -152,7 +155,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt if len(text) > 1 { nextChar = text[1] } - if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) { + if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { return 0 } @@ -160,7 +163,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt return 0 } -func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 3 { t1 := tolower(text[1]) t2 := tolower(text[2]) @@ -185,7 +188,7 @@ func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te return 0 } -func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 2 { if text[1] == '-' { out.WriteString("—") @@ -202,7 +205,7 @@ func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text return 0 } -func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 3 && text[1] == '-' && text[2] == '-' { out.WriteString("—") return 2 @@ -216,13 +219,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, return 0 } -func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int { +func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int { if bytes.HasPrefix(text, []byte(""")) { nextChar := byte(0) if len(text) >= 7 { nextChar = text[6] } - if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) { + if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { return 5 } } @@ -235,18 +238,18 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte return 0 } -func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { var quote byte = 'd' if angledQuotes { quote = 'a' } - return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { - return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP) + return func(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) } } -func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 3 && text[1] == '.' && text[2] == '.' { out.WriteString("…") return 2 @@ -261,13 +264,13 @@ func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te return 0 } -func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { if len(text) >= 2 && text[1] == '`' { nextChar := byte(0) if len(text) >= 3 { nextChar = text[2] } - if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) { + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { return 1 } } @@ -276,7 +279,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, return 0 } -func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b // note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8) @@ -318,7 +321,7 @@ func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar b return 0 } -func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if text[0] == '1' && text[1] == '/' && text[2] == '2' { if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { @@ -346,27 +349,27 @@ func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te return 0 } -func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int { +func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { nextChar := byte(0) if len(text) > 1 { nextChar = text[1] } - if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) { + if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { out.WriteString(""") } return 0 } -func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { - return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd') +func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'd') } -func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { - return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a') +func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'a') } -func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int { +func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { i := 0 for i < len(text) && text[i] != '>' { @@ -377,54 +380,78 @@ func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, return i } -type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int +type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int -type smartypantsRenderer [256]smartCallback +// NewSmartypantsRenderer constructs a Smartypants renderer object. +func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { + var ( + r SPRenderer -var ( - smartAmpAngled = smartAmp(true, false) - smartAmpAngledNBSP = smartAmp(true, true) - smartAmpRegular = smartAmp(false, false) - smartAmpRegularNBSP = smartAmp(false, true) -) + smartAmpAngled = r.smartAmp(true, false) + smartAmpAngledNBSP = r.smartAmp(true, true) + smartAmpRegular = r.smartAmp(false, false) + smartAmpRegularNBSP = r.smartAmp(false, true) + + addNBSP = flags&SmartypantsQuotesNBSP != 0 + ) -func smartypants(flags int) *smartypantsRenderer { - r := new(smartypantsRenderer) - addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0 - if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 { - r['"'] = smartDoubleQuote + if flags&SmartypantsAngledQuotes == 0 { + r.callbacks['"'] = r.smartDoubleQuote if !addNBSP { - r['&'] = smartAmpRegular + r.callbacks['&'] = smartAmpRegular } else { - r['&'] = smartAmpRegularNBSP + r.callbacks['&'] = smartAmpRegularNBSP } } else { - r['"'] = smartAngledDoubleQuote + r.callbacks['"'] = r.smartAngledDoubleQuote if !addNBSP { - r['&'] = smartAmpAngled + r.callbacks['&'] = smartAmpAngled } else { - r['&'] = smartAmpAngledNBSP + r.callbacks['&'] = smartAmpAngledNBSP } } - r['\''] = smartSingleQuote - r['('] = smartParens - if flags&HTML_SMARTYPANTS_DASHES != 0 { - if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 { - r['-'] = smartDash + r.callbacks['\''] = r.smartSingleQuote + r.callbacks['('] = r.smartParens + if flags&SmartypantsDashes != 0 { + if flags&SmartypantsLatexDashes == 0 { + r.callbacks['-'] = r.smartDash } else { - r['-'] = smartDashLatex + r.callbacks['-'] = r.smartDashLatex } } - r['.'] = smartPeriod - if flags&HTML_SMARTYPANTS_FRACTIONS == 0 { - r['1'] = smartNumber - r['3'] = smartNumber + r.callbacks['.'] = r.smartPeriod + if flags&SmartypantsFractions == 0 { + r.callbacks['1'] = r.smartNumber + r.callbacks['3'] = r.smartNumber } else { for ch := '1'; ch <= '9'; ch++ { - r[ch] = smartNumberGeneric + r.callbacks[ch] = r.smartNumberGeneric } } - r['<'] = smartLeftAngle - r['`'] = smartBacktick - return r + r.callbacks['<'] = r.smartLeftAngle + r.callbacks['`'] = r.smartBacktick + return &r +} + +// Process is the entry point of the Smartypants renderer. +func (r *SPRenderer) Process(w io.Writer, text []byte) { + mark := 0 + for i := 0; i < len(text); i++ { + if action := r.callbacks[text[i]]; action != nil { + if i > mark { + w.Write(text[mark:i]) + } + previousChar := byte(0) + if i > 0 { + previousChar = text[i-1] + } + var tmp bytes.Buffer + i += action(&tmp, previousChar, text[i:]) + w.Write(tmp.Bytes()) + mark = i + 1 + } + } + if mark < len(text) { + w.Write(text[mark:]) + } } diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml b/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml new file mode 100644 index 00000000..93b1fcdb --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml @@ -0,0 +1,16 @@ +sudo: false +language: go +go: + - 1.x + - master +matrix: + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE b/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE new file mode 100644 index 00000000..c35c17af --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015 Dmitri Shuralyov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/README.md b/vendor/github.com/shurcooL/sanitized_anchor_name/README.md new file mode 100644 index 00000000..670bf0fe --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/README.md @@ -0,0 +1,36 @@ +sanitized_anchor_name +===================== + +[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name) + +Package sanitized_anchor_name provides a func to create sanitized anchor names. + +Its logic can be reused by multiple packages to create interoperable anchor names +and links to those anchors. + +At this time, it does not try to ensure that generated anchor names +are unique, that responsibility falls on the caller. + +Installation +------------ + +```bash +go get -u github.com/shurcooL/sanitized_anchor_name +``` + +Example +------- + +```Go +anchorName := sanitized_anchor_name.Create("This is a header") + +fmt.Println(anchorName) + +// Output: +// this-is-a-header +``` + +License +------- + +- [MIT License](LICENSE) diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/main.go b/vendor/github.com/shurcooL/sanitized_anchor_name/main.go new file mode 100644 index 00000000..6a77d124 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/main.go @@ -0,0 +1,29 @@ +// Package sanitized_anchor_name provides a func to create sanitized anchor names. +// +// Its logic can be reused by multiple packages to create interoperable anchor names +// and links to those anchors. +// +// At this time, it does not try to ensure that generated anchor names +// are unique, that responsibility falls on the caller. +package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name" + +import "unicode" + +// Create returns a sanitized anchor name for the given text. +func Create(text string) string { + var anchorName []rune + var futureDash = false + for _, r := range text { + switch { + case unicode.IsLetter(r) || unicode.IsNumber(r): + if futureDash && len(anchorName) > 0 { + anchorName = append(anchorName, '-') + } + futureDash = false + anchorName = append(anchorName, unicode.ToLower(r)) + default: + futureDash = true + } + } + return string(anchorName) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9a59b2ce..d622bd7b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -113,7 +113,7 @@ github.com/pkg/errors github.com/pmezard/go-difflib/difflib # github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a github.com/rs/xid -# github.com/russross/blackfriday v1.5.1 +# github.com/russross/blackfriday v2.0.0+incompatible github.com/russross/blackfriday # github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/saintfish/chardet @@ -121,6 +121,8 @@ github.com/saintfish/chardet github.com/shazow/rateio # github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991 github.com/shazow/ssh-chat/sshd +# github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 +github.com/shurcooL/sanitized_anchor_name # github.com/sirupsen/logrus v0.0.0-20180213143110-8c0189d9f6bb github.com/sirupsen/logrus # github.com/spf13/afero v0.0.0-20180211162714-bbf41cb36dff |