pax_global_header00006660000000000000000000000064145511021410014504gustar00rootroot0000000000000052 comment=b29470dbfa93de9a696929e3566a848744769f10 golang-github-johanneskaufmann-html-to-markdown-1.5.0/000077500000000000000000000000001455110214100227645ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/000077500000000000000000000000001455110214100243245ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001455110214100265075ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000010201455110214100311720ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "\U0001F41B Bug" labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **HTML Input** ```html

Title

``` **Generated Markdown** ````markdown # Title ```` **Expected Markdown** ````markdown # Title!!! ```` **Additional context** Add any other context about the problem here. For example, if you changed the default options or used a plugin. Also adding the version from the `go.mod` is helpful. golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/dependabot.yml000066400000000000000000000004251455110214100271550ustar00rootroot00000000000000# Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/workflows/000077500000000000000000000000001455110214100263615ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/.github/workflows/go.yml000066400000000000000000000015721455110214100275160ustar00rootroot00000000000000name: Go on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Set up Go 1.x uses: actions/setup-go@v2 with: go-version: ^1.13 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Get dependencies run: | go get -v -t -d ./... if [ -f Gopkg.toml ]; then curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh dep ensure fi - name: Build run: go build -v . - name: Test run: go test -v -race -coverprofile=coverage.txt -covermode=atomic . - name: Upload Coverage report to CodeCov uses: codecov/codecov-action@v2 with: token: ${{secrets.CODECOV_TOKEN}} file: ./coverage.txt golang-github-johanneskaufmann-html-to-markdown-1.5.0/.gitignore000066400000000000000000000003131455110214100247510ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out .DS_Store golang-github-johanneskaufmann-html-to-markdown-1.5.0/CONTRIBUTING.md000066400000000000000000000000001455110214100252030ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/LICENSE000066400000000000000000000020621455110214100237710ustar00rootroot00000000000000MIT License Copyright (c) 2018 Johannes Kaufmann 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. golang-github-johanneskaufmann-html-to-markdown-1.5.0/README.md000066400000000000000000000240701455110214100242460ustar00rootroot00000000000000# html-to-markdown [![Go Report Card](https://goreportcard.com/badge/github.com/JohannesKaufmann/html-to-markdown)](https://goreportcard.com/report/github.com/JohannesKaufmann/html-to-markdown) [![codecov](https://codecov.io/gh/JohannesKaufmann/html-to-markdown/branch/master/graph/badge.svg)](https://codecov.io/gh/JohannesKaufmann/html-to-markdown) ![GitHub MIT License](https://img.shields.io/github/license/JohannesKaufmann/html-to-markdown) [![GoDoc](https://godoc.org/github.com/JohannesKaufmann/html-to-markdown?status.png)](http://godoc.org/github.com/JohannesKaufmann/html-to-markdown) ![Gopher, the mascot of Golang, is wearing a party hat and holding a balloon. Next to the Gopher is a machine that converts characters associated with HTML to characters associated with Markdown.](/logo_five_years.png) Convert HTML into Markdown with Go. It is using an [HTML Parser](https://github.com/PuerkitoBio/goquery) to avoid the use of `regexp` as much as possible. That should prevent some [weird cases](https://stackoverflow.com/a/1732454) and allows it to be used for cases where the input is totally unknown. ## Installation ``` go get github.com/JohannesKaufmann/html-to-markdown ``` ## Usage ```go import ( "fmt" "log" md "github.com/JohannesKaufmann/html-to-markdown" ) converter := md.NewConverter("", true, nil) html := `Important` markdown, err := converter.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Println("md ->", markdown) ``` If you are already using [goquery](https://github.com/PuerkitoBio/goquery) you can pass a selection to `Convert`. ```go markdown, err := converter.Convert(selec) ``` ### Using it on the command line If you want to make use of `html-to-markdown` on the command line without any Go coding, check out [`html2md`](https://github.com/suntong/html2md#usage), a cli wrapper for `html-to-markdown` that has all the following options and plugins builtin. ## Options The third parameter to `md.NewConverter` is `*md.Options`. For example you can change the character that is around a bold text ("`**`") to a different one (for example "`__`") by changing the value of `StrongDelimiter`. ```go opt := &md.Options{ StrongDelimiter: "__", // default: ** // ... } converter := md.NewConverter("", true, opt) ``` For all the possible options look at [godocs](https://godoc.org/github.com/JohannesKaufmann/html-to-markdown/#Options) and for a example look at the [example](/examples/options/main.go). ## Adding Rules ```go converter.AddRules( md.Rule{ Filter: []string{"del", "s", "strike"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { // You need to return a pointer to a string (md.String is just a helper function). // If you return nil the next function for that html element // will be picked. For example you could only convert an element // if it has a certain class name and fallback if not. content = strings.TrimSpace(content) return md.String("~" + content + "~") }, }, // more rules ) ``` For more information have a look at the example [add_rules](/examples/add_rules/main.go). ## Using Plugins If you want plugins (github flavored markdown like striketrough, tables, ...) you can pass it to `Use`. ```go import "github.com/JohannesKaufmann/html-to-markdown/plugin" // Use the `GitHubFlavored` plugin from the `plugin` package. converter.Use(plugin.GitHubFlavored()) ``` Or if you only want to use the `Strikethrough` plugin. You can change the character that distinguishes the text that is crossed out by setting the first argument to a different value (for example "~~" instead of "~"). ```go converter.Use(plugin.Strikethrough("")) ``` For more information have a look at the example [github_flavored](/examples/github_flavored/main.go). --- These are the plugins located in the [plugin folder](/plugin) which you can use by importing "github.com/JohannesKaufmann/html-to-markdown/plugin". | Name | Description | | --------------------- | ------------------------------------------------------------------------------------------- | | GitHubFlavored | GitHub's Flavored Markdown contains `TaskListItems`, `Strikethrough` and `Table`. | | TaskListItems | (Included in `GitHubFlavored`). Converts `` checkboxes into `- [x] Task`. | | Strikethrough | (Included in `GitHubFlavored`). Converts ``, ``, and `` to the `~~` syntax. | | Table | (Included in `GitHubFlavored`). Convert a `` into something like this... | | TableCompat | | | | | | VimeoEmbed | | | YoutubeEmbed | | | | | | ConfluenceCodeBlock | Converts `` elements that are used in Atlassian’s Wiki "Confluence". | | ConfluenceAttachments | Converts `` elements. | These are the plugins in other repositories: | Name | Description | | ---------------------------- | ------------------- | | \[Plugin Name\]\(Your Link\) | A short description | I you write a plugin, feel free to open a PR that adds your Plugin to this list. ## Writing Plugins Have a look at the [plugin folder](/plugin) for a reference implementation. The most basic one is [Strikethrough](/plugin/strikethrough.go). ## Security This library produces markdown that is readable and can be changed by humans. Once you convert this markdown back to HTML (e.g. using [goldmark](https://github.com/yuin/goldmark) or [blackfriday](https://github.com/russross/blackfriday)) you need to be careful of malicious content. This library does NOT sanitize untrusted content. Use an HTML sanitizer such as [bluemonday](https://github.com/microcosm-cc/bluemonday) before displaying the HTML in the browser. ## Other Methods [Godoc](https://godoc.org/github.com/JohannesKaufmann/html-to-markdown) ### `func (c *Converter) Keep(tags ...string) *Converter` Determines which elements are to be kept and rendered as HTML. ### `func (c *Converter) Remove(tags ...string) *Converter` Determines which elements are to be removed altogether i.e. converted to an empty string. ## Escaping Some characters have a special meaning in markdown. For example, the character "\*" can be used for lists, emphasis and dividers. By placing a backlash before that character (e.g. "\\\*") you can "escape" it. Then the character will render as a raw "\*" without the _"markdown meaning"_ applied. But why is "escaping" even necessary? ```md Paragraph 1 - Paragraph 2 ``` The markdown above doesn't seem that problematic. But "Paragraph 1" (with only one hyphen below) will be recognized as a _setext heading_. ```html

Paragraph 1

Paragraph 2

``` A well-placed backslash character would prevent that... ```md Paragraph 1 \- Paragraph 2 ``` --- How to configure escaping? Depending on the `EscapeMode` option, the markdown output is going to be different. ```go opt = &md.Options{ EscapeMode: "basic", // default } ``` Lets try it out with this HTML input: | | | | -------- | ----------------------------------------------------- | | input | `

fake **bold** and real bold

` | | | | | | **With EscapeMode "basic"** | | output | `fake \*\*bold\*\* and real **bold**` | | rendered | fake \*\*bold\*\* and real **bold** | | | | | | **With EscapeMode "disabled"** | | output | `fake **bold** and real **bold**` | | rendered | fake **bold** and real **bold** | With **basic** escaping, we get some escape characters (the backlash "\\") but it renders correctly. With escaping **disabled**, the fake and real bold can't be distinguished in the markdown. That means it is both going to render as bold. --- So now you know the purpose of escaping. However, if you encounter some content where the escaping breaks, you can manually disable it. But please also open an issue! ## Issues If you find HTML snippets (or even full websites) that don't produce the expected results, please open an issue! ## Contributing & Testing Please first discuss the change you wish to make, by opening an issue. I'm also happy to guide you to where a change is most likely needed. _Note: The outside API should not change because of backwards compatibility..._ You don't have to be afraid of breaking the converter, since there are many "Golden File Tests": Add your problematic HTML snippet to one of the `input.html` files in the `testdata` folder. Then run `go test -update` and have a look at which `.golden` files changed in GIT. You can now change the internal logic and inspect what impact your change has by running `go test -update` again. _Note: Before submitting your change as a PR, make sure that you run those tests and check the files into GIT..._ ## Related Projects - [turndown (js)](https://github.com/domchristie/turndown), a very good library written in javascript. - [lunny/html2md](https://github.com/lunny/html2md), which is using [regex instead of goquery](https://stackoverflow.com/a/1732454). I came around a few edge case when using it (leaving some html comments, ...) so I wrote my own. ## License This project is licensed under the terms of the MIT license. golang-github-johanneskaufmann-html-to-markdown-1.5.0/SECURITY.md000066400000000000000000000003321455110214100245530ustar00rootroot00000000000000# Security Policy ## Reporting a Vulnerability Please report (suspected) security vulnerabilities to johannes@joina.de with the subject _"Security html-to-markdown"_ and you will receive a response within 48 hours. golang-github-johanneskaufmann-html-to-markdown-1.5.0/commonmark.go000066400000000000000000000266301455110214100254650ustar00rootroot00000000000000package md import ( "fmt" "unicode" "regexp" "strconv" "strings" "unicode/utf8" "github.com/JohannesKaufmann/html-to-markdown/escape" "github.com/PuerkitoBio/goquery" ) var multipleSpacesR = regexp.MustCompile(` +`) var commonmark = []Rule{ { Filter: []string{"ul", "ol"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { parent := selec.Parent() // we have a nested list, were the ul/ol is inside a list item // -> based on work done by @requilence from @anytypeio if (parent.Is("li") || parent.Is("ul") || parent.Is("ol")) && parent.Children().Last().IsSelection(selec) { // add a line break prefix if the parent's text node doesn't have it. // that makes sure that every list item is on its on line lastContentTextNode := strings.TrimRight(parent.Nodes[0].FirstChild.Data, " \t") if !strings.HasSuffix(lastContentTextNode, "\n") { content = "\n" + content } // remove empty lines between lists trimmedSpaceContent := strings.TrimRight(content, " \t") if strings.HasSuffix(trimmedSpaceContent, "\n") { content = strings.TrimRightFunc(content, unicode.IsSpace) } } else { content = "\n\n" + content + "\n\n" } return &content }, }, { Filter: []string{"li"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { if strings.TrimSpace(content) == "" { return nil } // remove leading newlines content = leadingNewlinesR.ReplaceAllString(content, "") // replace trailing newlines with just a single one content = trailingNewlinesR.ReplaceAllString(content, "\n") // remove leading spaces content = strings.TrimLeft(content, " ") prefix := selec.AttrOr(attrListPrefix, "") // `prefixCount` is not nessesarily the length of the empty string `prefix` // but how much space is reserved for the prefixes of the siblings. prefixCount, previousPrefixCounts := countListParents(opt, selec) // if the prefix is not needed, balance it by adding the usual prefix spaces if prefix == "" { prefix = strings.Repeat(" ", prefixCount) } // indent the prefix so that the nested links are represented indent := strings.Repeat(" ", previousPrefixCounts) prefix = indent + prefix content = IndentMultiLineListItem(opt, content, prefixCount+previousPrefixCounts) return String(prefix + content + "\n") }, }, { Filter: []string{"#text"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { text := selec.Text() if trimmed := strings.TrimSpace(text); trimmed == "" { return String("") } text = tabR.ReplaceAllString(text, " ") // replace multiple spaces by one space: dont accidentally make // normal text be indented and thus be a code block. text = multipleSpacesR.ReplaceAllString(text, " ") if opt.EscapeMode == "basic" { text = escape.MarkdownCharacters(text) } // if its inside a list, trim the spaces to not mess up the indentation parent := selec.Parent() next := selec.Next() if IndexWithText(selec) == 0 && (parent.Is("li") || parent.Is("ol") || parent.Is("ul")) && (next.Is("ul") || next.Is("ol")) { // trim only spaces and not new lines text = strings.Trim(text, ` `) } return &text }, }, { Filter: []string{"p", "div"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { parent := goquery.NodeName(selec.Parent()) if IsInlineElement(parent) || parent == "li" { content = "\n" + content + "\n" return &content } // remove unnecessary spaces to have clean markdown content = TrimpLeadingSpaces(content) content = "\n\n" + content + "\n\n" return &content }, }, { Filter: []string{"h1", "h2", "h3", "h4", "h5", "h6"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { if strings.TrimSpace(content) == "" { return nil } content = strings.Replace(content, "\n", " ", -1) content = strings.Replace(content, "\r", " ", -1) content = strings.Replace(content, `#`, `\#`, -1) content = strings.TrimSpace(content) insideLink := selec.ParentsFiltered("a").Length() > 0 if insideLink { text := opt.StrongDelimiter + content + opt.StrongDelimiter text = AddSpaceIfNessesary(selec, text) return &text } node := goquery.NodeName(selec) level, err := strconv.Atoi(node[1:]) if err != nil { return nil } if opt.HeadingStyle == "setext" && level < 3 { line := "-" if level == 1 { line = "=" } underline := strings.Repeat(line, len(content)) return String("\n\n" + content + "\n" + underline + "\n\n") } prefix := strings.Repeat("#", level) text := "\n\n" + prefix + " " + content + "\n\n" return &text }, }, { Filter: []string{"strong", "b"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { // only use one bold tag if they are nested parent := selec.Parent() if parent.Is("strong") || parent.Is("b") { return &content } trimmed := strings.TrimSpace(content) if trimmed == "" { return &trimmed } // If there is a newline character between the start and end delimiter // the delimiters won't be recognized. Either we remove all newline characters // OR on _every_ line we put start & end delimiters. trimmed = delimiterForEveryLine(trimmed, opt.StrongDelimiter) // Always have a space to the side to recognize the delimiter trimmed = AddSpaceIfNessesary(selec, trimmed) return &trimmed }, }, { Filter: []string{"i", "em"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { // only use one italic tag if they are nested parent := selec.Parent() if parent.Is("i") || parent.Is("em") { return &content } trimmed := strings.TrimSpace(content) if trimmed == "" { return &trimmed } // If there is a newline character between the start and end delimiter // the delimiters won't be recognized. Either we remove all newline characters // OR on _every_ line we put start & end delimiters. trimmed = delimiterForEveryLine(trimmed, opt.EmDelimiter) // Always have a space to the side to recognize the delimiter trimmed = AddSpaceIfNessesary(selec, trimmed) return &trimmed }, }, { Filter: []string{"img"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { src := selec.AttrOr("src", "") src = strings.TrimSpace(src) if src == "" { return String("") } src = opt.GetAbsoluteURL(selec, src, opt.domain) alt := selec.AttrOr("alt", "") alt = strings.Replace(alt, "\n", " ", -1) text := fmt.Sprintf("![%s](%s)", alt, src) return &text }, }, { Filter: []string{"a"}, AdvancedReplacement: func(content string, selec *goquery.Selection, opt *Options) (AdvancedResult, bool) { // if there is no href, no link is used. So just return the content inside the link href, ok := selec.Attr("href") if !ok || strings.TrimSpace(href) == "" || strings.TrimSpace(href) == "#" { return AdvancedResult{ Markdown: content, }, false } href = opt.GetAbsoluteURL(selec, href, opt.domain) // having multiline content inside a link is a bit tricky content = EscapeMultiLine(content) var title string if t, ok := selec.Attr("title"); ok { t = strings.Replace(t, "\n", " ", -1) // escape all quotes t = strings.Replace(t, `"`, `\"`, -1) title = fmt.Sprintf(` "%s"`, t) } // if there is no link content (for example because it contains an svg) // the 'title' or 'aria-label' attribute is used instead. if strings.TrimSpace(content) == "" { content = selec.AttrOr("title", selec.AttrOr("aria-label", "")) } // a link without text won't de displayed anyway if content == "" { return AdvancedResult{}, true } if opt.LinkStyle == "inlined" { md := fmt.Sprintf("[%s](%s%s)", content, href, title) md = AddSpaceIfNessesary(selec, md) return AdvancedResult{ Markdown: md, }, false } var replacement string var reference string switch opt.LinkReferenceStyle { case "collapsed": replacement = "[" + content + "][]" reference = "[" + content + "]: " + href + title case "shortcut": replacement = "[" + content + "]" reference = "[" + content + "]: " + href + title default: id := selec.AttrOr("data-index", "") replacement = "[" + content + "][" + id + "]" reference = "[" + id + "]: " + href + title } replacement = AddSpaceIfNessesary(selec, replacement) return AdvancedResult{Markdown: replacement, Footer: reference}, false }, }, { Filter: []string{"code", "kbd", "samp", "tt"}, Replacement: func(_ string, selec *goquery.Selection, opt *Options) *string { code := getCodeContent(selec) // Newlines in the text aren't great, since this is inline code and not a code block. // Newlines will be stripped anyway in the browser, but it won't be recognized as code // from the markdown parser when there is more than one newline. // So limit to code = multipleNewLinesRegex.ReplaceAllString(code, "\n") fenceChar := '`' maxCount := calculateCodeFenceOccurrences(fenceChar, code) maxCount++ fence := strings.Repeat(string(fenceChar), maxCount) // code block contains a backtick as first character if strings.HasPrefix(code, "`") { code = " " + code } // code block contains a backtick as last character if strings.HasSuffix(code, "`") { code = code + " " } // TODO: configure delimeter in options? text := fence + code + fence text = AddSpaceIfNessesary(selec, text) return &text }, }, { Filter: []string{"pre"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { codeElement := selec.Find("code") language := codeElement.AttrOr("class", "") language = strings.Replace(language, "language-", "", 1) code := getCodeContent(selec) fenceChar, _ := utf8.DecodeRuneInString(opt.Fence) fence := CalculateCodeFence(fenceChar, code) text := "\n\n" + fence + language + "\n" + code + "\n" + fence + "\n\n" return &text }, }, { Filter: []string{"hr"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { // e.g. `## --- Heading` would look weird, so don't render a divider if inside a heading insideHeading := selec.ParentsFiltered("h1,h2,h3,h4,h5,h6").Length() > 0 if insideHeading { return String("") } text := "\n\n" + opt.HorizontalRule + "\n\n" return &text }, }, { Filter: []string{"br"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { return String("\n\n") }, }, { Filter: []string{"blockquote"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { content = strings.TrimSpace(content) if content == "" { return nil } content = multipleNewLinesRegex.ReplaceAllString(content, "\n\n") var beginningR = regexp.MustCompile(`(?m)^`) content = beginningR.ReplaceAllString(content, "> ") text := "\n\n" + content + "\n\n" return &text }, }, { Filter: []string{"noscript"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { // for now remove the contents of noscript. But in the future we could // tell goquery to parse the contents of the tag. // -> https://github.com/PuerkitoBio/goquery/issues/139#issuecomment-517526070 return nil }, }, } golang-github-johanneskaufmann-html-to-markdown-1.5.0/commonmark_test.go000066400000000000000000000146111455110214100265200ustar00rootroot00000000000000package md_test import ( "bytes" "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" "testing" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" "github.com/sebdah/goldie/v2" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" ) type Variation struct { Options *md.Options Plugins []md.Plugin } type GoldenTest struct { Name string Domain string DisableGoldmark bool Variations map[string]Variation } func runGoldenTest(t *testing.T, test GoldenTest, variationKey string) { variation := test.Variations[variationKey] g := goldie.New(t) // testdata/TestCommonmark/name/input.html p := path.Join(t.Name(), "input.html") // get the input html from a file input, err := ioutil.ReadFile(path.Join("testdata", p)) if err != nil { t.Error(err) return } if test.Domain == "" { test.Domain = "example.com" } conv := md.NewConverter(test.Domain, true, variation.Options) conv.Keep("keep-tag").Remove("remove-tag") for _, plugin := range variation.Plugins { conv.Use(plugin) } markdown, err := conv.ConvertBytes(input) if err != nil { t.Error(err) } // testdata/TestCommonmark/name/output.default.golden p = path.Join(t.Name(), "output."+variationKey) g.Assert(t, p, markdown) gold := goldmark.New(goldmark.WithExtensions(extension.GFM)) var buf bytes.Buffer if err := gold.Convert(markdown, &buf); err != nil { t.Error(err) } if !test.DisableGoldmark { // testdata/TestCommonmark/name/goldmark.golden p = path.Join(t.Name(), "goldmark") g.Assert(t, p, buf.Bytes()) } } func RunGoldenTest(t *testing.T, tests []GoldenTest) { // loop through all test cases that were added manually dirs := make(map[string]struct{}) for _, test := range tests { name := test.Name name = strings.Replace(name, " ", "_", -1) dirs[name] = struct{}{} } // now add all tests that were found on disk to the tests slice err := filepath.Walk(path.Join("testdata", t.Name()), func(p string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { return nil } // skip folders that don't contain an input.html file if _, err := os.Stat(path.Join(p, "input.html")); os.IsNotExist(err) { return nil } parts := strings.SplitN(p, string(os.PathSeparator), 3) p = parts[2] // remove "testdata/TestCommonmark/" from "testdata/TestCommonmark/..." _, ok := dirs[p] if ok { return nil } // add the folder from disk to the tests slice, since its not it there yet tests = append(tests, GoldenTest{ Name: p, }) return nil }) if err != nil { t.Error(err) return } for _, test := range tests { if len(test.Variations) == 0 { test.Variations = map[string]Variation{ "default": {}, } } t.Run(test.Name, func(t *testing.T) { if strings.Contains(t.Name(), "#") { fmt.Println("the name", test.Name, t.Name(), "seems too be used for multiple tests") return } for variationKey := range test.Variations { runGoldenTest(t, test, variationKey) } }) } } func TestCommonmark(t *testing.T) { var tests = []GoldenTest{ { Name: "link", DisableGoldmark: true, Variations: map[string]Variation{ "relative": { Options: &md.Options{ GetAbsoluteURL: func(selec *goquery.Selection, rawURL string, domain string) string { return rawURL }, }, }, "inlined": { Options: &md.Options{LinkStyle: "inlined"}, }, "referenced_full": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "full"}, }, "referenced_collapsed": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "collapsed"}, }, "referenced_shortcut": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "shortcut"}, }, }, }, { Name: "heading", Variations: map[string]Variation{ "atx": { Options: &md.Options{HeadingStyle: "atx"}, }, "setext": { Options: &md.Options{HeadingStyle: "setext"}, }, }, }, { Name: "italic", Variations: map[string]Variation{ "asterisks": { Options: &md.Options{EmDelimiter: "*"}, }, "underscores": { Options: &md.Options{EmDelimiter: "_"}, }, }, }, { Name: "bold", Variations: map[string]Variation{ "asterisks": { Options: &md.Options{StrongDelimiter: "**"}, }, "underscores": { Options: &md.Options{StrongDelimiter: "__"}, }, }, }, { Name: "pre_code", Variations: map[string]Variation{ "indented": { Options: &md.Options{CodeBlockStyle: "indented"}, }, "fenced_backtick": { Options: &md.Options{CodeBlockStyle: "fenced", Fence: "```"}, }, "fenced_tilde": { Options: &md.Options{CodeBlockStyle: "fenced", Fence: "~~~"}, }, }, }, { Name: "list", Variations: map[string]Variation{ "asterisks": { Options: &md.Options{BulletListMarker: "*"}, }, "dash": { Options: &md.Options{BulletListMarker: "-"}, }, "plus": { Options: &md.Options{BulletListMarker: "+"}, }, }, }, { Name: "list_nested", DisableGoldmark: true, Variations: map[string]Variation{ "asterisks": { Options: &md.Options{BulletListMarker: "*"}, }, "dash": { Options: &md.Options{BulletListMarker: "-"}, }, "plus": { Options: &md.Options{BulletListMarker: "+"}, }, }, }, // + all the test on disk that are added automatically } RunGoldenTest(t, tests) } func TestRealWorld(t *testing.T) { var tests = []GoldenTest{ { Name: "blog.golang.org", Domain: "blog.golang.org", Variations: map[string]Variation{ "inlined": { Options: &md.Options{LinkStyle: "inlined"}, }, "referenced_full": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "full"}, }, "referenced_collapsed": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "collapsed"}, }, "referenced_shortcut": { Options: &md.Options{LinkStyle: "referenced", LinkReferenceStyle: "shortcut"}, }, "emphasis_asterisks": { Options: &md.Options{EmDelimiter: "*", StrongDelimiter: "**"}, }, "emphasis_underscores": { Options: &md.Options{EmDelimiter: "_", StrongDelimiter: "__"}, }, }, }, { Name: "golang.org", Domain: "golang.org", }, // + all the test on disk that are added automatically } RunGoldenTest(t, tests) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/escape/000077500000000000000000000000001455110214100242245ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/escape/escape.go000066400000000000000000000035221455110214100260150ustar00rootroot00000000000000// Package escape escapes characters that are commonly used in // markdown like the * for strong/italic. package escape import ( "regexp" "strings" ) var backslash = regexp.MustCompile(`\\(\S)`) var heading = regexp.MustCompile(`(?m)^(#{1,6} )`) var orderedList = regexp.MustCompile(`(?m)^(\W* {0,3})(\d+)\. `) var unorderedList = regexp.MustCompile(`(?m)^([^\\\w]*)[*+-] `) var horizontalDivider = regexp.MustCompile(`(?m)^([-*_] *){3,}$`) var blockquote = regexp.MustCompile(`(?m)^(\W* {0,3})> `) var link = regexp.MustCompile(`([\[\]])`) var replacer = strings.NewReplacer( `*`, `\*`, `_`, `\_`, "`", "\\`", `|`, `\|`, ) // MarkdownCharacters escapes common markdown characters so that // `

**Not Bold**

ends up as correct markdown `\*\*Not Strong\*\*`. // No worry, the escaped characters will display fine, just without the formatting. func MarkdownCharacters(text string) string { // Escape backslash escapes! text = backslash.ReplaceAllString(text, `\\$1`) // Escape headings text = heading.ReplaceAllString(text, `\$1`) // Escape hr text = horizontalDivider.ReplaceAllStringFunc(text, func(t string) string { if strings.Contains(t, "-") { return strings.Replace(t, "-", `\-`, 3) } else if strings.Contains(t, "_") { return strings.Replace(t, "_", `\_`, 3) } return strings.Replace(t, "*", `\*`, 3) }) // Escape ol bullet points text = orderedList.ReplaceAllString(text, `$1$2\. `) // Escape ul bullet points text = unorderedList.ReplaceAllStringFunc(text, func(t string) string { return regexp.MustCompile(`([*+-])`).ReplaceAllString(t, `\$1`) }) // Escape blockquote indents text = blockquote.ReplaceAllString(text, `$1\> `) // Escape em/strong * // Escape em/strong _ // Escape code _ text = replacer.Replace(text) // Escape link & image brackets text = link.ReplaceAllString(text, `\$1`) return text } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/000077500000000000000000000000001455110214100246025ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/add_rules/000077500000000000000000000000001455110214100265445ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/add_rules/main.go000066400000000000000000000025541455110214100300250ustar00rootroot00000000000000package main import ( "fmt" "log" "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) func main() { html := `Good soundtrack and cake.` // -> `Good soundtrack ~~and cake~~.` /* We want to add a rule when a `span` tag has a class of `bb_strike`. Have a look at `plugin/strikethrough.go` to see how it is implemented normally. */ strikethrough := md.Rule{ Filter: []string{"span"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { // If the span element has not the classname `bb_strike` return nil. // That way the next rules will apply. In this case the commonmark rules. // -> return nil -> next rule applies if !selec.HasClass("bb_strike") { return nil } // Trim spaces so that the following does NOT happen: `~ and cake~`. // Because of the space it is not recognized as strikethrough. // -> trim spaces at begin&end of string when inside strong/italic/... content = strings.TrimSpace(content) return md.String("~~" + content + "~~") }, } conv := md.NewConverter("", true, nil) conv.AddRules(strikethrough) // -> add 1+ rules to the converter. the last added will be used first. markdown, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Printf("\n\nmarkdown:'%s'\n", markdown) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/custom_tag/000077500000000000000000000000001455110214100267475ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/custom_tag/main.go000066400000000000000000000022001455110214100302140ustar00rootroot00000000000000package main import ( "fmt" "log" "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) func main() { html := `https://youtu.be/1SoMeViD https://youtu.be/2SoMeViD https://youtu.be/3SoMeViDhttps://youtu.be/4SoMeViD https://youtu.be/5SoMeViD ` videoRule := md.Rule{ // We want to add a rule for a `my_video` tag. Filter: []string{"my_video"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { text := "click to watch video" // in this case, the content inside the tag is the url href := strings.TrimSpace(content) // format it, so that its `[click to watch video](https://youtu.be/1SoMeViD)\n\n` md := fmt.Sprintf("[%s](%s)\n\n", text, href) return &md }, } conv := md.NewConverter("", true, nil) conv.AddRules(videoRule) // -> add 1+ rules to the converter. the last added will be used first. markdown, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Printf("\n\nresult:'%s'\n", markdown) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/escaping/000077500000000000000000000000001455110214100263735ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/escaping/main.go000066400000000000000000000016461455110214100276550ustar00rootroot00000000000000package main import ( "fmt" "log" md "github.com/JohannesKaufmann/html-to-markdown" ) func main() { html := `

fake **bold** and real bold

` // With "basic" we get: // "fake \*\*bold\*\* and real **bold**" // which would render as: // "

fake **bold** and real bold

" // With "none" we get: // "fake **bold** and real **bold**" // which would render as: // "

fake bold and real bold

" opt := &md.Options{ EscapeMode: "basic", // default } conv := md.NewConverter("", true, opt) markdown1, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } // - - - - // opt = &md.Options{ EscapeMode: "disabled", } conv = md.NewConverter("", true, opt) markdown2, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Println("with basic:", markdown1) fmt.Println("with disabled:", markdown2) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/github_flavored/000077500000000000000000000000001455110214100277465ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/github_flavored/main.go000066400000000000000000000010701455110214100312170ustar00rootroot00000000000000package main import ( "fmt" "log" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/JohannesKaufmann/html-to-markdown/plugin" ) func main() { html := `
  • Checked!
  • Check Me!
` /* - [x] Checked! - [ ] Check Me! */ conv := md.NewConverter("", true, nil) // Use the `GitHubFlavored` plugin from the `plugin` package. conv.Use(plugin.GitHubFlavored()) markdown, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Println(markdown) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/goquery/000077500000000000000000000000001455110214100262755ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/goquery/main.go000066400000000000000000000006561455110214100275570ustar00rootroot00000000000000package main import ( "fmt" "log" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) func main() { url := "https://blog.golang.org/godoc-documenting-go-code" doc, err := goquery.NewDocument(url) if err != nil { log.Fatal(err) } content := doc.Find("#content") conv := md.NewConverter(md.DomainFromURL(url), true, nil) markdown := conv.Convert(content) fmt.Println(markdown) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/options/000077500000000000000000000000001455110214100262755ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/examples/options/main.go000066400000000000000000000006311455110214100275500ustar00rootroot00000000000000package main import ( "fmt" "log" md "github.com/JohannesKaufmann/html-to-markdown" ) func main() { html := `Bold Text` // -> `__Bold Text__` // instead of `**Bold Text**` opt := &md.Options{ StrongDelimiter: "__", // default: ** } conv := md.NewConverter("", true, opt) markdown, err := conv.ConvertString(html) if err != nil { log.Fatal(err) } fmt.Println(markdown) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/from.go000066400000000000000000000274401455110214100242650ustar00rootroot00000000000000// Package md converts html to markdown. // // converter := md.NewConverter("", true, nil) // // html = `Important` // // markdown, err := converter.ConvertString(html) // if err != nil { // log.Fatal(err) // } // fmt.Println("md ->", markdown) // Or if you are already using goquery: // markdown, err := converter.Convert(selec) package md import ( "bytes" "errors" "fmt" "io" "log" "net/http" "net/url" "regexp" "strconv" "strings" "sync" "time" "github.com/PuerkitoBio/goquery" ) type simpleRuleFunc func(content string, selec *goquery.Selection, options *Options) *string type ruleFunc func(content string, selec *goquery.Selection, options *Options) (res AdvancedResult, skip bool) // BeforeHook runs before the converter and can be used to transform the original html type BeforeHook func(selec *goquery.Selection) // Afterhook runs after the converter and can be used to transform the resulting markdown type Afterhook func(markdown string) string // Converter is initialized by NewConverter. type Converter struct { mutex sync.RWMutex rules map[string][]ruleFunc keep map[string]struct{} remove map[string]struct{} before []BeforeHook after []Afterhook domain string options Options } func validate(val string, possible ...string) error { for _, e := range possible { if e == val { return nil } } return fmt.Errorf("field must be one of %v but got %s", possible, val) } func validateOptions(opt Options) error { if err := validate(opt.HeadingStyle, "setext", "atx"); err != nil { return err } if strings.Count(opt.HorizontalRule, "*") < 3 && strings.Count(opt.HorizontalRule, "_") < 3 && strings.Count(opt.HorizontalRule, "-") < 3 { return errors.New("HorizontalRule must be at least 3 characters of '*', '_' or '-' but got " + opt.HorizontalRule) } if err := validate(opt.BulletListMarker, "-", "+", "*"); err != nil { return err } if err := validate(opt.CodeBlockStyle, "indented", "fenced"); err != nil { return err } if err := validate(opt.Fence, "```", "~~~"); err != nil { return err } if err := validate(opt.EmDelimiter, "_", "*"); err != nil { return err } if err := validate(opt.StrongDelimiter, "**", "__"); err != nil { return err } if err := validate(opt.LinkStyle, "inlined", "referenced"); err != nil { return err } if err := validate(opt.LinkReferenceStyle, "full", "collapsed", "shortcut"); err != nil { return err } return nil } var ( attrListPrefix = "data-converter-list-prefix" ) // NewConverter initializes a new converter and holds all the rules. // - `domain` is used for links and images to convert relative urls ("/image.png") to absolute urls. // - CommonMark is the default set of rules. Set enableCommonmark to false if you want // to customize everything using AddRules and DONT want to fallback to default rules. func NewConverter(domain string, enableCommonmark bool, options *Options) *Converter { conv := &Converter{ domain: domain, rules: make(map[string][]ruleFunc), keep: make(map[string]struct{}), remove: make(map[string]struct{}), } conv.before = append(conv.before, func(selec *goquery.Selection) { selec.Find("a[href]").Each(func(i int, s *goquery.Selection) { // TODO: don't hardcode "data-index" and rename it to avoid accidental conflicts s.SetAttr("data-index", strconv.Itoa(i+1)) }) }) conv.before = append(conv.before, func(selec *goquery.Selection) { selec.Find("li").Each(func(i int, s *goquery.Selection) { prefix := getListPrefix(options, s) s.SetAttr(attrListPrefix, prefix) }) }) conv.after = append(conv.after, func(markdown string) string { markdown = strings.TrimSpace(markdown) markdown = multipleNewLinesRegex.ReplaceAllString(markdown, "\n\n") // remove unnecessary trailing spaces to have clean markdown markdown = TrimTrailingSpaces(markdown) return markdown }) if enableCommonmark { conv.AddRules(commonmark...) conv.remove["script"] = struct{}{} conv.remove["style"] = struct{}{} conv.remove["textarea"] = struct{}{} } // TODO: put domain in options? if options == nil { options = &Options{} } if options.HeadingStyle == "" { options.HeadingStyle = "atx" } if options.HorizontalRule == "" { options.HorizontalRule = "* * *" } if options.BulletListMarker == "" { options.BulletListMarker = "-" } if options.CodeBlockStyle == "" { options.CodeBlockStyle = "indented" } if options.Fence == "" { options.Fence = "```" } if options.EmDelimiter == "" { options.EmDelimiter = "_" } if options.StrongDelimiter == "" { options.StrongDelimiter = "**" } if options.LinkStyle == "" { options.LinkStyle = "inlined" } if options.LinkReferenceStyle == "" { options.LinkReferenceStyle = "full" } if options.EscapeMode == "" { options.EscapeMode = "basic" } // for now, store it in the options options.domain = domain if options.GetAbsoluteURL == nil { options.GetAbsoluteURL = DefaultGetAbsoluteURL } conv.options = *options err := validateOptions(conv.options) if err != nil { log.Println("markdown options is not valid:", err) } return conv } func (conv *Converter) getRuleFuncs(tag string) []ruleFunc { conv.mutex.RLock() defer conv.mutex.RUnlock() r, ok := conv.rules[tag] if !ok || len(r) == 0 { if _, keep := conv.keep[tag]; keep { return []ruleFunc{wrap(ruleKeep)} } if _, remove := conv.remove[tag]; remove { return nil // TODO: } return []ruleFunc{wrap(ruleDefault)} } return r } func wrap(simple simpleRuleFunc) ruleFunc { return func(content string, selec *goquery.Selection, opt *Options) (AdvancedResult, bool) { res := simple(content, selec, opt) if res == nil { return AdvancedResult{}, true } return AdvancedResult{Markdown: *res}, false } } // Before registers a hook that is run before the conversion. It // can be used to transform the original goquery html document. // // For example, the default before hook adds an index to every link, // so that the `a` tag rule (for "reference" "full") can have an incremental number. func (conv *Converter) Before(hooks ...BeforeHook) *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() for _, hook := range hooks { conv.before = append(conv.before, hook) } return conv } // After registers a hook that is run after the conversion. It // can be used to transform the markdown document that is about to be returned. // // For example, the default after hook trims the returned markdown. func (conv *Converter) After(hooks ...Afterhook) *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() for _, hook := range hooks { conv.after = append(conv.after, hook) } return conv } // ClearBefore clears the current before hooks (including the default before hooks). func (conv *Converter) ClearBefore() *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() conv.before = nil return conv } // ClearAfter clears the current after hooks (including the default after hooks). func (conv *Converter) ClearAfter() *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() conv.after = nil return conv } // AddRules adds the rules that are passed in to the converter. // // By default it overrides the rule for that html tag. You can // fall back to the default rule by returning nil. func (conv *Converter) AddRules(rules ...Rule) *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() for _, rule := range rules { if len(rule.Filter) == 0 { log.Println("you need to specify at least one filter for your rule") } for _, filter := range rule.Filter { r, _ := conv.rules[filter] if rule.AdvancedReplacement != nil { r = append(r, rule.AdvancedReplacement) } else { r = append(r, wrap(rule.Replacement)) } conv.rules[filter] = r } } return conv } // Keep certain html tags in the generated output. func (conv *Converter) Keep(tags ...string) *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() for _, tag := range tags { conv.keep[tag] = struct{}{} } return conv } // Remove certain html tags from the source. func (conv *Converter) Remove(tags ...string) *Converter { conv.mutex.Lock() defer conv.mutex.Unlock() for _, tag := range tags { conv.remove[tag] = struct{}{} } return conv } // Plugin can be used to extends functionality beyond what // is offered by commonmark. type Plugin func(conv *Converter) []Rule // Use can be used to add additional functionality to the converter. It is // used when its not sufficient to use only rules for example in Plugins. func (conv *Converter) Use(plugins ...Plugin) *Converter { for _, plugin := range plugins { rules := plugin(conv) conv.AddRules(rules...) // TODO: for better performance only use one lock for all plugins } return conv } // Timeout for the http client var Timeout = time.Second * 10 var netClient = &http.Client{ Timeout: Timeout, } // DomainFromURL returns `u.Host` from the parsed url. func DomainFromURL(rawURL string) string { rawURL = strings.TrimSpace(rawURL) u, _ := url.Parse(rawURL) if u != nil && u.Host != "" { return u.Host } // lets try it again by adding a scheme u, _ = url.Parse("http://" + rawURL) if u != nil { return u.Host } return "" } // Reduce many newline characters `\n` to at most 2 new line characters. var multipleNewLinesRegex = regexp.MustCompile(`[\n]{2,}`) // Convert returns the content from a goquery selection. // If you have a goquery document just pass in doc.Selection. func (conv *Converter) Convert(selec *goquery.Selection) string { conv.mutex.RLock() domain := conv.domain options := conv.options l := len(conv.rules) if l == 0 { log.Println("you have added no rules. either enable commonmark or add you own.") } before := conv.before after := conv.after conv.mutex.RUnlock() // before hook for _, hook := range before { hook(selec) } res := conv.selecToMD(domain, selec, &options) markdown := res.Markdown if res.Header != "" { markdown = res.Header + "\n\n" + markdown } if res.Footer != "" { markdown += "\n\n" + res.Footer } // after hook for _, hook := range after { markdown = hook(markdown) } return markdown } // ConvertReader returns the content from a reader and returns a buffer. func (conv *Converter) ConvertReader(reader io.Reader) (bytes.Buffer, error) { var buffer bytes.Buffer doc, err := goquery.NewDocumentFromReader(reader) if err != nil { return buffer, err } buffer.WriteString( conv.Convert(doc.Selection), ) return buffer, nil } // ConvertResponse returns the content from a html response. func (conv *Converter) ConvertResponse(res *http.Response) (string, error) { doc, err := goquery.NewDocumentFromResponse(res) if err != nil { return "", err } return conv.Convert(doc.Selection), nil } // ConvertString returns the content from a html string. If you // already have a goquery selection use `Convert`. func (conv *Converter) ConvertString(html string) (string, error) { doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) if err != nil { return "", err } return conv.Convert(doc.Selection), nil } // ConvertBytes returns the content from a html byte array. func (conv *Converter) ConvertBytes(bytes []byte) ([]byte, error) { res, err := conv.ConvertString(string(bytes)) if err != nil { return nil, err } return []byte(res), nil } // ConvertURL returns the content from the page with that url. func (conv *Converter) ConvertURL(url string) (string, error) { // not using goquery.NewDocument directly because of the timeout resp, err := netClient.Get(url) if err != nil { return "", err } if resp.StatusCode < 200 || resp.StatusCode > 299 { return "", fmt.Errorf("expected a status code in the 2xx range but got %d", resp.StatusCode) } doc, err := goquery.NewDocumentFromResponse(resp) if err != nil { return "", err } domain := DomainFromURL(url) if conv.domain != domain { log.Printf("expected '%s' as the domain but got '%s' \n", conv.domain, domain) } return conv.Convert(doc.Selection), nil } golang-github-johanneskaufmann-html-to-markdown-1.5.0/from_test.go000066400000000000000000000361731455110214100253270ustar00rootroot00000000000000package md import ( "bytes" "errors" "io/ioutil" "log" "net/http" "net/http/httptest" "os" "strings" "sync" "testing" "github.com/PuerkitoBio/goquery" ) func TestConvertReader(t *testing.T) { input := `Bold` expected := `**Bold**` converter := NewConverter("", true, nil) res, err := converter.ConvertReader(strings.NewReader(input)) if err != nil { t.Error(err) } if !bytes.Equal(res.Bytes(), []byte(expected)) { t.Error("the result is different that expected") } } type ErrReader struct{ Error error } // -> https://stackoverflow.com/a/57452918 func (e *ErrReader) Read([]byte) (int, error) { return 0, e.Error } func TestConvertReader_Error(t *testing.T) { reader := &ErrReader{ Error: errors.New("we got an error"), } converter := NewConverter("", true, nil) res, err := converter.ConvertReader(reader) if err != reader.Error { t.Error("expected an error") } if res.Len() != 0 { t.Error("expected an empty buffer") } } func TestConvertBytes(t *testing.T) { input := `Bold` expected := `**Bold**` converter := NewConverter("", true, nil) res, err := converter.ConvertBytes([]byte(input)) if err != nil { t.Error(err) } if !bytes.Equal(res, []byte(expected)) { t.Error("the result is different that expected") } } func TestConvertBytes_Empty(t *testing.T) { converter := NewConverter("", true, nil) res, err := converter.ConvertBytes(nil) if err != nil { t.Error(err) } if !bytes.Equal(res, []byte("")) { t.Error("the result is different that expected") } } func TestConvertResponse(t *testing.T) { input := `Bold` expected := `**Bold**` converter := NewConverter("", true, nil) res, err := converter.ConvertResponse(&http.Response{ StatusCode: 200, Body: ioutil.NopCloser(bytes.NewBufferString(input)), Request: &http.Request{}, }) if err != nil { t.Error(err) } if res != expected { t.Error("the result is different that expected") } } func TestConvertResponse_Error(t *testing.T) { expectedErr := errors.New("custom error reader") converter := NewConverter("", true, nil) res, err := converter.ConvertResponse(&http.Response{ StatusCode: 200, Body: ioutil.NopCloser(&ErrReader{ Error: expectedErr, }), Request: &http.Request{}, }) if err != expectedErr { t.Error(err) } if res != "" { t.Error("the result is different that expected") } } func TestConvertString(t *testing.T) { input := `Bold` expected := `**Bold**` converter := NewConverter("", true, nil) res, err := converter.ConvertString(input) if err != nil { t.Error(err) } if res != expected { t.Error("the result is different that expected") } } func TestConvertSelection(t *testing.T) { input := `Bold` expected := `**Bold**` doc, err := goquery.NewDocumentFromReader(strings.NewReader(input)) if err != nil { t.Error(err) } converter := NewConverter("", true, nil) res := converter.Convert(doc.Selection) if res != expected { t.Error("the result is different that expected") } } func TestConvertURL(t *testing.T) { input := `Bold` expected := `**Bold**` // Start a local HTTP server server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Write([]byte(input)) })) // Close the server when test finishes defer server.Close() // override the client used in `ConvertURL` netClient = server.Client() converter := NewConverter(server.URL, true, nil) res, err := converter.ConvertURL(server.URL) if err != nil { t.Error(err) } if res != expected { t.Error("the result is different that expected") } } func TestConvertURL_Error(t *testing.T) { url := "abc https://example.com" converter := NewConverter("", true, nil) res, err := converter.ConvertURL(url) if err == nil { t.Error("expected an error") } if res != "" { t.Error("the result is different that expected") } } func TestConvertURL_ErrorStatusCode(t *testing.T) { // Start a local HTTP server server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusNotFound) rw.Write([]byte("404 Not Found")) })) // Close the server when test finishes defer server.Close() // override the client used in `ConvertURL` netClient = server.Client() converter := NewConverter(server.URL, true, nil) res, err := converter.ConvertURL(server.URL) if err == nil { t.Error("expected an error") } if res != "" { t.Error("the result is different that expected") } } // - - - - - - - - - - - - // func TestNewConverter_NoRules(t *testing.T) { var buf bytes.Buffer log.SetOutput(&buf) log.SetFlags(0) defer func() { // reset the options back to the defaults log.SetOutput(os.Stderr) log.SetFlags(3) }() input := `Bold` expected := `` // disable commonmark converter := NewConverter("", false, nil) res, err := converter.ConvertString(input) if err != nil { t.Error(err) } if res != expected { t.Error("the result is different that expected") } if strings.TrimSuffix(buf.String(), "\n") != "you have added no rules. either enable commonmark or add you own." { t.Error("expected a different log message") } } func TestNewConverter_ValidateOptions(t *testing.T) { var buf bytes.Buffer log.SetOutput(&buf) log.SetFlags(0) defer func() { // reset the options back to the defaults log.SetOutput(os.Stderr) log.SetFlags(3) }() input := `Bold` expected := `====Bold====` converter := NewConverter("", true, &Options{ StrongDelimiter: "====", }) res, err := converter.ConvertString(input) if err != nil { t.Error(err) } if res != expected { t.Error("the result is different that expected") } if strings.TrimSuffix(buf.String(), "\n") != "markdown options is not valid: field must be one of [** __] but got ====" { t.Error("expected a different log message") } } func TestNewConverter_ValidateOptions_All(t *testing.T) { var tests = []struct { name string options *Options input string expected string }{ { name: "HeadingStyle", options: &Options{ HeadingStyle: "invalid", }, input: `

Heading

`, expected: `# Heading`, }, { name: "HorizontalRule", options: &Options{ HorizontalRule: "--", }, input: `
`, expected: `--`, }, { name: "BulletListMarker", options: &Options{ BulletListMarker: "^", }, input: `
  • Test
`, expected: `^ Test`, }, { name: "CodeBlockStyle", options: &Options{ CodeBlockStyle: "invalid", }, input: `test`, expected: "`test`", }, { name: "Fence", options: &Options{ Fence: "^^^", }, input: `
test
`, expected: "^^^\ntest\n^^^", }, { name: "EmDelimiter", options: &Options{ EmDelimiter: "-", }, input: `test`, expected: "-test-", }, { name: "LinkStyle", options: &Options{ LinkStyle: "invalid", }, input: `link`, expected: `[link][1] [1]: example.com`, }, { name: "LinkReferenceStyle", options: &Options{ LinkReferenceStyle: "invalid", }, input: `link`, expected: "[link](example.com)", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var buf bytes.Buffer log.SetOutput(&buf) log.SetFlags(0) defer func() { // reset the options back to the defaults log.SetOutput(os.Stderr) log.SetFlags(3) }() converter := NewConverter("", true, test.options) res, err := converter.ConvertString(test.input) if err != nil { t.Error(err) } if res != test.expected { t.Errorf("expected '%s' but got '%s'", test.expected, res) } logOutput := strings.TrimSuffix(buf.String(), "\n") if !strings.Contains(logOutput, "markdown options is not valid: ") { t.Errorf("expected a different log message but got '%s'", logOutput) } }) } } func BenchmarkFromString(b *testing.B) { converter := NewConverter("www.google.com", true, nil) strongRule := Rule{ Filter: []string{"strong"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { return nil }, } var wg sync.WaitGroup convert := func(html string) { defer wg.Done() _, err := converter.ConvertString(html) if err != nil { b.Error(err) } } add := func() { defer wg.Done() converter.AddRules(strongRule) } for n := 0; n < b.N; n++ { wg.Add(2) go add() go convert("Bold") } wg.Wait() } func TestAddRules_ChangeContent(t *testing.T) { expected := "Some other Content" var wasCalled bool rule := Rule{ Filter: []string{"p"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { wasCalled = true if content != "Some Content" { t.Errorf("got wrong `content`: '%s'", content) } if !selec.Is("p") { t.Error("selec is not p") } return String(expected) }, } conv := NewConverter("", true, nil) conv.AddRules(rule) md, err := conv.ConvertString(`

Some Content

`) if err != nil { t.Error(err) } if md != expected { t.Errorf("wanted '%s' but got '%s'", expected, md) } if !wasCalled { t.Error("rule was not called") } } func TestAddRules_Fallback(t *testing.T) { // firstExpected := "Some other Content" expected := "Totally different Content" var firstWasCalled bool var secondWasCalled bool firstRule := Rule{ Filter: []string{"p"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { firstWasCalled = true if secondWasCalled { t.Error("expected first rule to be called before second rule. second is already called") } if content != "Some Content" { t.Errorf("got wrong `content`: '%s'", content) } if !selec.Is("p") { t.Error("selec is not p") } return nil }, } secondRule := Rule{ Filter: []string{"p"}, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { secondWasCalled = true if !firstWasCalled { t.Error("expected first rule to be called before second rule. first is not called yet") } if content != "Some Content" { t.Errorf("got wrong `content`: '%s'", content) } if !selec.Is("p") { t.Error("selec is not p") } return String(expected) }, } conv := NewConverter("", true, nil) conv.AddRules(secondRule, firstRule) md, err := conv.ConvertString(`

Some Content

`) if err != nil { t.Error(err) } if md != expected { t.Errorf("wanted '%s' but got '%s'", expected, md) } if !firstWasCalled { t.Error("first rule was not called") } if !secondWasCalled { t.Error("second rule was not called") } } func TestAddRules_NoRules(t *testing.T) { var buf bytes.Buffer log.SetOutput(&buf) log.SetFlags(0) defer func() { // reset the options back to the defaults log.SetOutput(os.Stderr) log.SetFlags(3) }() var wasCalled bool rule := Rule{ Filter: []string{ /* nothing */ }, Replacement: func(content string, selec *goquery.Selection, opt *Options) *string { wasCalled = true return nil }, } conv := NewConverter("", true, nil) conv.AddRules(rule) md, err := conv.ConvertString(`

Some Content

`) if err != nil { t.Error(err) } if md != "Some Content" { t.Error("got different markdown result") } logOutput := strings.TrimSuffix(buf.String(), "\n") if logOutput != "you need to specify at least one filter for your rule" { t.Errorf("expected a different log message but got '%s'", logOutput) } if wasCalled { t.Error("the rule should not have been called") } } func TestBefore(t *testing.T) { var firstWasCalled bool var secondWasCalled bool firstHook := func(selec *goquery.Selection) { firstWasCalled = true if secondWasCalled { t.Error("the second hook should not be called yet") } } secondHook := func(selec *goquery.Selection) { secondWasCalled = true if !firstWasCalled { t.Error("the first hook should already be called") } } conv := NewConverter("", true, nil) conv.Before(firstHook, secondHook) _, err := conv.ConvertString(`Link`) if err != nil { t.Error(err) } if !firstWasCalled || !secondWasCalled { t.Error("not all hooks were called") } } func TestAfter(t *testing.T) { var firstWasCalled bool var secondWasCalled bool firstHook := func(md string) string { firstWasCalled = true if secondWasCalled { t.Error("the second hook should not be called yet") } return md + " first" } secondHook := func(md string) string { secondWasCalled = true if !firstWasCalled { t.Error("the first hook should already be called") } return md + " second" } conv := NewConverter("", true, nil) conv.After(firstHook, secondHook) md, err := conv.ConvertString(`base`) if err != nil { t.Error(err) } if md != `base first second` { t.Errorf("expected different markdown result but got '%s'", md) } if !firstWasCalled || !secondWasCalled { t.Error("not all hooks were called") } } func TestClearBefore(t *testing.T) { var wasCalled bool hook := func(selec *goquery.Selection) { wasCalled = true } conv := NewConverter("", true, nil) conv.ClearBefore() if len(conv.before) != 0 { t.Error("the before hook array should be of length 0") } conv.Before(hook) _, err := conv.ConvertString(`Link`) if err != nil { t.Error(err) } if !wasCalled { t.Error("the hook should have been called") } } func TestClearAfter(t *testing.T) { var wasCalled bool hook := func(markdown string) string { wasCalled = true return "my new value" } conv := NewConverter("", true, nil) conv.ClearAfter() if len(conv.after) != 0 { t.Error("the after hook array should be of length 0") } conv.After(hook) md, err := conv.ConvertString(`Link`) if err != nil { t.Error(err) } if md != "my new value" { t.Error("the result was different then expected") } if !wasCalled { t.Error("the hook should have been called") } } func TestDomainFromURL(t *testing.T) { var tests = []struct { input string expected string }{ { input: "example.com", expected: "example.com", }, { input: "https://example.com", expected: "example.com", }, { input: "https://www.example.com", expected: "www.example.com", }, { input: "http://example.com/index.html", expected: "example.com", }, { input: "http://example.com?page=home", expected: "example.com", }, { input: "http://example.com#page", expected: "example.com", }, { input: "http://example.com:3000", expected: "example.com:3000", }, { // not so happy about this :( input: "example", expected: "example", }, { input: "https://developer.mozilla.org/en-US/docs/Web/API/URL/host", expected: "developer.mozilla.org", }, { input: " http://example.com", expected: "example.com", }, { // invalid url input: "abc http://example.com", expected: "", }, } for _, test := range tests { t.Run(test.input, func(t *testing.T) { res := DomainFromURL(test.input) if res != test.expected { t.Errorf("for '%s' expected '%s' but got '%s'", test.input, test.expected, res) } }) } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/go.mod000066400000000000000000000004261455110214100240740ustar00rootroot00000000000000module github.com/JohannesKaufmann/html-to-markdown go 1.13 require ( github.com/PuerkitoBio/goquery v1.8.1 github.com/sebdah/goldie/v2 v2.5.3 github.com/sergi/go-diff v1.2.0 // indirect github.com/yuin/goldmark v1.6.0 golang.org/x/net v0.19.0 gopkg.in/yaml.v2 v2.4.0 ) golang-github-johanneskaufmann-html-to-markdown-1.5.0/go.sum000066400000000000000000000154251455110214100241260ustar00rootroot00000000000000github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y= github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= golang-github-johanneskaufmann-html-to-markdown-1.5.0/logo_five_years.png000066400000000000000000005415151455110214100266610ustar00rootroot00000000000000PNG  IHDRGiCCPsRGB IEC61966-2.1(u+DQ?1Q IXȏ(#$Q7Լ{OU/`"ReMl橑̹{>{9{."YE5z@͙zx,䛋<ϸi-1І')kwT&`*_K$ *jM7Dž'WLmf%K u~9z$<Fa_Uay9jvYdnvFbxacQFˠA-+K"*:K`uY'%DOȲjo_TS>'z6-в GPR~E,i]˒߁ h}bz(UR)x=(4]Cӳ}!&_u{%罋 0g pHYs   IDATxw|չ7Lٝղ-ْLK $@z^ kI( )ܔK =`nl{-Yd}!=]J䟰hwv49<n@!D`k@7L_u_ Du\!m9Klyo /nzup]q82BY &pRbjD,r`TUFN 25߾ttի1c,DžB(w8k!JB]{1W"`}2YDGٍ*@lqdL=PK<"B!K yV ItW XPG' ϴPzV,ٿ~ #C!0Z#>[ˤu&<נt 2 :HN.}8o:Us`!NG!$uWLnvX9=C6(3(l|ZcLx81m+lny7v$x!EBe(3d:P W'Z7(!`1]Oz*GoPo8.to8v^'h!&(!LHTiW4W@L.JtoFEpqc7|6B`q{h׀֯<ύ!&&{@!SJmwX[;TMi82{l*&[ro:ދkBhbhB(yΩeXcHE PL>U9lO/_Be B('ϽVՒ{W3T:ViF ïw 5]A v$-GBhhB(Թc*=v@ɉfe'FN O*mܶÖ"s܎[BB!runÆJJ; @艦9S!˂`s x DkBY]fլ;cZL1pd([0 //;sjAާi=>BFkBc~W5'6c jL6s{_$YmЫ5G-Q 18!!PN+ԍpVԓ{W˪ 48B 0Bӵ-w3h ۺTK՗A܆!P!\qJ5A&CJn0KRM$ 0L(XMncs=$Bq5Bd1Nǰ$O.۲/N˅!FkB'SuaWͰ8Pթs@'30%{|m4}dK!/0Z#Y'dw8fpV=Ivy\6;P c gV9Ti6?@dYdz|!!rlEV[e@:dz,>Q~󧻎MweynOsBe !rXsn FaA[?? tO8vN P.hB(y%32ۛaQ e݆zٯzD 7L)2@r*5YVWqeB !&Rm髺{Zz"&< sEpƊǑtN6]$~QM7s\:]:B1!DQz5)f^b*,CK,##*a9t=fTS*l8)]^NU[5ΠB8UDv'=Fx;pLr(/+ m6jGBWBM gv3[P.gLs (BB!=0Z#$p8AEͮӥLOϚ{ʙc}Ɏջa@7c+ !FkBͲ&4G'%ruZ4ЛBՃҶ~vL|@ riZQ|7Be!E>ז@$Won@˓'6.z3@$zng,]W& YmЩjb<7!=!H|j12_f)k蠷<+bՃtѾ[Z7f麦^70_m֥KܘBhhB}#ӝw-GӈGJb:[rfsL|@ Iοڠ9tcυ!3! evsCQW5ŠzbKf76Nʞ畨:zЁ4icgOq&Gyg!P0Є4)tzP7ԍN6(GsWJIdD3\ZSVY]㯳;/!B2AVxNٸE9 &L1n"'ꚏGeVks+O"JzUHT9Dƨ(O5, 8I9<vn#WZCT6`_v ɭ*EyvtmF;}l~s5@Z/ækB຋K }ujgwqq2B) 1֞;r4ɥ 3{/ZoHL+.I9'dϹv du4ȓ]DZΟe5'`鱣pȜ şN,iQv)Vl FKbÞ#D!4"B/l{2h~BYn D^ocm9s\!3ioT擪$ͮ;﯊om5^'Y9fzlvWfK 2a,e`:B w=ǀИtW(vLV4_! DP;sdJ RG}uqy]/.:1o8mýڲi/:z Jƞ%|Y4Ga]+lj&ݚ^jBFk4iwbE[pBB/<jO՞@)Cދ}E"V϶kU|xۧ.8.ᗔptUͿi9ƑUv;2p|!R=tcUC⏑hյmHd+e JI=c]B1)_ uZY9tMQ[d [nDžͮ]qm:dTr%?3\<[ 3<|BFk4+}"I;׌Uc:a1%S\G#]"1.oʯBq%vv#]Xf^:$Cc7,%I}_Ib%ܔIv[9dza_83Oc>/Fp!2'bg(-d}wZyƖ@Wm==jy*Im9_Os< d Eh-1MOƑK>W,ЍYLHz'h뿾=eC/HK2]5}Ǻ'^(<\[YBhaF-]isOwIiJXgK ?8`VTRm瑒t@tXSm{3=m'uZƙ]w 7\H&O|vn,6yzwY37w>$vR7uucrïŹq!F9]=ҮHBٮPBm2,F]/v\V-p,r\ Mb/ KLG(cU޳|vER)3: 퍾j;Y.»(7Ü*RNbR[\ȣWI/c*kKOG/š|ol]x39W W(%>]#hrepoc{WܥSO!~bp\j yԔ@"PwJX(Bx 8U:>'6&5)#*sRqI\ EO^U^~?m+MWK5mCMY'tYcWzҵúƙ|+C:FGt]Ul*>ʔmbG.uQW_h@|75N/To1Oy튶*0*E) ~jO`-ʓ~홛\`Ƞ.H$ VYֆ N;6`c]k7;wt%h̸6b`(C"bjמh(nA ]R66>sJkVmoT擪vhsZ)kg6WWs?Q7̗#pLpmOOᠱr&]O-WV([/:B (7m D7MB_뫭ey[Mnq^K=#]ه~6|zAeէ9Zy頇 'O[QhdIAa_K,qu$ eLY5l"'$u'PrߓUUě%Y6>tQ;Ν:|Rb)*Ra r׉bBh`Fi@ȴ5҅R{b6rljqh$6Eb;9靻bG鄗>\.da{QkˡH]`?\jtmB@"nemo%y}C `s zQYfv\;aY-I@dՐlۯe2k -A}U#X'>R--<)_>(?u[_'h"7M.aL;$:Se'۟U,a#_iQ-мWIC{7y, ,ޒ3^=I?;GXB(-pL^@Ov/V;l 96u釳x\qHy%kL1`GZ ǻCay+y ]qm D8jIRЌ1EO}n{^ۚW~vL!w&g 5,- wQ~}x?[^/d`cדl3kvۯ1>(5BeZ䲨>[75RѫZKk+ZvqѾH 2>k5k\np'Ne,4>{5VY9ݠ!561v3ՃV~IImH䱫]%άdݥ 28=w,}ƮMX\ת#p!2GQnOb~}ʹ'n]LؽGٱ+(W!COW#sSWX>۳ao(Naw\ yn۔0@Nܕ\٧y7#QˇݲJM?^Vn F}Bgݣ5t` άMmqʙ†FH&];[I'T_~AN?{X9K>;= IDATHp6gJl՘!&(7E 쾶'>1OL]VY޳cPD~2v1G|G{iu*=Bxihp?ooHRw;*S6'mE6u IeAr 3b7>#Mt8hZoNV#&S>p=WO>FF7!8k0YzuyxZsͮnQWQ!5ME6nz c+f$7pM|oZjDRv_SGP7uKd9s520'k;oo@<]G 7XO аf(׵fM"Ev#d\ UڨAa~u-zԬ{'n~RqfU(pP_V/xRdnQc΂9IY8nQ.`rA&B!hrHlΜO*H "K3}[,sqnԔ8:k"|g`D6 ^h$J3z̢5e ToE3>n|:\=hw 92wJz,7b.[(rɹCdX__|*3v|u^Pͣ*k̯8jȂ18HMՐl<ǍEo~F0=1Nl=bzsl,򸯝{rF}*m5VkIHzuO?u阮Bhd0Z\V%cA}mFk/kG{#;_S_ڦ;~$oe~&0҇vj Ns C맔ma Rv.6[u+?,FzlJv~Ԟ4t[uC6esl~ʚ.zsiD'r%xG /gy+ kY-9Lד%[:ATaFuY%A;No267;#46LtR.U vw9Ri`>*#hfOul5? D3b[6vhr;,B[?4;9ocLѨ'StȘTդ)Ƕӛ#{@?hSQG'2yEu5å"0iO!8B`@dbcjlC5qAxS3x4O;Y>T'‰hB`Qb>!"_XOM,٭VNp-ƝǔQmnQxp|t]8w92ˍ/B(iQ.퇣JY$ O(s{1ӗ>q:,rv1{SdfV9|?`:}8{lT%D`jݭPG -LөdT~Ըr,^4V˸!Fm9_5(} 5 j2 GxaB޵fwt3)u;J FkBqOs R,~Mtio_<;oY0dن.,1E?"Sq[S2 VI7sJhGYIvWRu >8+8l=tM;Ϭ+ R DvsrSw2dz2|QI <’z=8@DE^'ΎAaFɯ9~t5 #nRή6ۿb0gRF~h[i`ĈrSs4o2Vh0ToFg71ۚ`d:ݠ2ɖtΟe5KjNFn%yd9k/ȶ4id{_):{eAIUB3g)43}n*B hrP}EN[ +F4k2-#kχLX'=XՈMueWjJ`y8t(su%ӈ+Y3(c`OS^w@_=S#p%KLSBv6!]MK|^qt I~4nvl4qUD\6lȖpKܶ{RBc55ݪ~7H@rCP+J:y f䇊8s>OZ'u `GE>|Ⓧ8Bvyx5H+&4k ګ?ŢwzpyἩNawKBv1/} !Ԭ|\peL.ص L'êşsgˡp bv`ti[pB 5)vב]֍t^HDݤ6R @<D 0sIS8:~ӫ|yN C!vwIYRTLhd@7JO].F@șýH"d8H;T7w?|Q۵pxN:- Z3`~ct=̥W7wgpq痊8gjkn̚Ti]\ߎhoЍw>w@0ZѦwn3 r"sR5bSi|u+}~ά-p= Zա";mrïcks'n({l=bdX4-_eU ﴚw.ٸyɦe YupBYhr1Y\/fG2BEEl{i%9>Mv9׍=f!CK:2eY#{kBVkS:E5$ХhϑdKk^-p3Zd:b #My+]z_=.MΞ%}}|eqz.ҟ^Bt- 伹ޖn۲Fҵ9nGQ&: PN3(]3|g6+4/ u4Ȧ~‹k Iɲ[Џ^\*u əjKolי8F Ɣ 3|R<%ii]rbsGꮿve\{SVT߹>ATwNY7. 0 ++/Ygasv!45zݪ>x<2=0,egq^0jUk+psr+3puظB4ʔ~&\g X, ̟F@c7P'%}b˪! rRmL/I])-IeXX^/=I?H"IgĻks}Yk6yl~}a JE`Dp7!tGex6+)MB'h_7:n8T'!(L`5N;G <lԕ%gED- <4k+)Ow&VvӖ$u9\bׇ̬65=˧q}Ur>f,{ "?p#FGt`ek|C6y"_y!Q;W g)IN>kOn6tf3X?q: r"ZUx┳]M&J eyX*LJ~`b2p6x}Aӗ> ܙ W~b IeA;nKϚE~SA4"=)Y6>0L9/Wl?\V%O\(xۛ{裯 _(1`̯N{p0zzTWX7p,b^'^vBhFkt:ڹ3d]L#dyH%O ex?AZmsL :;mϕ -Bh0Z?ֽ)`~?@,K1Wj)U;w4EkO̬f`VYȥ0 \WH/l2_q3XqhTp,s=U$(M0{I aB:-)/pǯjJ__Yc;h]HW<TvO*Uwl<:!=tQWzNR/.w j#Qeik= PA u[ϚӃȢ^fn(Ev||C)ʻ[1x+ mO^?,ތmVUBhh=?vـW_c.'j: *ZϪlϭA#&fT{z)6y"|]| $O7h_lR|g_sTJGD~{ͯc*rsͱ ^3Vo=2&1=iT~~9Sm5ﰮB9Z?֞8.2כ~ckm~v -aaq=7{qlHiPvn˹RsNDS1&!b1\⹅>U[ai-(cNDWK넎5L(*~zT\HΞ%>fiN^wAӚ˷^j&߶hk,esd$1 в'ەf)E ~ |י/R JFk D9'0/QQVvt)$zUf{G0ɷBKKlb.ǡד/l2x7( >g")r5֭~r'2,hF_kʦq9! ƉUqg@J12inM>ƴ3!"| !~Z)5R{<~l<\}Y,=N _S&%A35^5'|eؾK &h;S~a5\Pl#a@ٞ}cQnUtDZה "Zw(Zr2[ ~KHp' }ά=9]וs_-9c+O|L_lM dpۥ61C _SᲲ_oyJG;KBeܽ E9_ӿa!y"x9]K_fBEĠ[h kKGrB y4c#cKE^SJ[wX 'E~yiA͉l7kՒ1N}]^k]pp's5ez)}NO/ͿK|Kߺ~tӪB,c);=j1Gg"Wp_) ~v:äAnrEw2{@0{M)ƻwm m;iѸ3};|(We.PRm`i-9^yEf,O.LW2(\Zh]/Y*E{C ֡1GUU<g_Ͷ7P4=jmܡio}66ة.`VWڟ~R˞5}kBY 5_Zd[X׺`@uս) ZJ Ԗq~ݮ~Y^fE5 7թߎb,Ԩ IDAT}Lzb(l~uYMG#p3vbC<*n+adkob]3PNhn|#w5;aBI13{U+m80l1ihC,ʹBp6NxO,hsH=5e3\ƚM٘[m%SW'B=qK:s` ڥjz?@5oV쿧X-Wԫ6m bpPhП:Ӳ+\@ǭ?RO >r5FYMͶb7s/#mFFM|`z~"qe ؕ\Jm55<6^ݮ;gbi';oO.J\`pP.h@'ǺF۝\l/psMrk֧'D!3C풸%7qD2Y}zYeGYw0jTZ3?{[3X>ii ]v36JۑL-\1]>Bk=kK#6l<.%7mX6!0Zt("?ԑ?'K;.uѯH,E*,pz7d4G'!zU׈h=9۬'Yo(".]S]y1?Ϸ]0Kc˷6O#pv1L:^P[ >{Ju,U-]Mli~=ˆ#Fk4fTʾԑ_72w~w35S9dH e5!܆e2'N]i^ainǽ5u <"WҸJ׌#/+dx\L|%+VQ};٭:忏H-yo]Rl@䎆VMBhh =>DX9z?nqmf҉`F% O`qߤ8ĉ@O~wfƹ3<{jʭ^`Gt[;3x$,c 6d]Gq~zC7y xi2|:gBsgU{-[Y=E鍻]?4h[JfO"Yӌ0$[ZczwVȂҐ񐮟^ nT['y=ß{\lOmMS7ذݞ^E%={㷼M@J*BhLaFc)<ڝȖey޼o8VԧCk*Fe4 ~>i9j@'9}ji2m,±Mߢ=&Ϭ"Wa=.m nQ/oo{N<4$S$SzS=)GA:9N>1O͎?+g)qIoэCXZ+xnLh ߕJD>Fi`'4-s<4NBl郇^bUt.od0Wϝ`,=W|T[y9$q;~+q@uOuSlozcLE>*c]փ3*^#vEr~HE. ;rzouc"wxR39=,RVt㗒I+=~]-i{l^Gҕ9[=B*[zuOOe3N;ՓX^OoapP/MjL!͝2փfu"Z 5j ē勱 9%q6h삇_arJ0Qc(͌x߬"H.{rU7`Y*Pvuvӱ4Z7bL\Vze,`^|D>gjU!we9Ɛ՜pCYZy5j(dx'ԳdBO*xUXt'9]!T>Uϙ>|q@?ʯn(:ϛҺLe3 59І.BFk4JzU.{|I`Ov`4Ͷn)׍O(.Z8>\nmF..}(cԲkucrj(gϕ4l/V{)ơ[$$@*-!  &nz3L4U^{cMt߁u%<=R2sIgxNRz= +Dny njw+WZ+4_jC7B*pZfT4pkꚉ `d$nys-'%]64j`Ou? a8guC:+q8VGZ|3/-oF#u\\uSxR2?&Jcz],4tfP[ WMϞFrcybȆ?| K$ ߯kt9^|+’C.ub= Qd /?ΆI\TuaAflk?}DS-06 / =t]#v(}xW.bK'{^Ҥ/n׸,,%C6Oƺ.}ÅZd[L;{wm" VHg.߯ƩHM/ȳhgC"$gG|Q_~ X sGyxz%In[ƨ:$)y>?"H鬒yUS/ @-?@~wr/3? lxsD3111IXk2b.=/ s Lm6mɬ$*XO먂Pk@IO#9 * Ʀ5kCAxϫONDI7x"B&o**pˢ_n>;v\#|mlH4gqJgɣ$c QQ,̲ώ{T|OC祅Yde YkA~dG'szW+ )~ؚT\}(GzJfCnmyZUW[uLrsdDyZj#FO ܵׄ-iTiIfg%yQ^r7iuXH,$UMΟg3ۍx5r߄=mih&3:􋂲z0%rks5x%N|.Oה-:Ey $I,/cHWzS|}i[L:lQ~ebPgAP pڴuA6K2G.4b{_czbv"&`NC{@R`RI27(B0,4Ә_}/G[ iәȜv뗧Q PXdL7/}wӆ9!OWJ,PCNfK](B:oP$dJU)cfb0 !7uMQo8+mҦzUH% TbEe6aAƤMj˯=\`t0&2rFz~^A8gĂF| ^.wCeZaqX}BZhĕ>n5ArrA)$‡;5]r5S綜4-cok-$5b>LLL3615?['Swϧϣ XSy 鏤m8y#e:9nTeGn |'7(Oc^ϱXfl~刢>"6F4e6V:tÈ?E6" /5jsI(@`褢k\{^1T×Zc66Ggu&Ǎg4{Y%=?#tb.Yh}{*nyIlvU WdV%c̜E *bxhWNڢuvU@.k0MLL361VAKQmC_auZRZۉyK;}j7,*l 7B9,u 6,*{~ C4L= wX;teQ6kք6,H1n 04KV0$4gAOVS]۸Y;^)_:xẆ~oom 1ps,w~ȧ>kuncO~ˈF)4o(ݴ?El:i3[MLL ZBku(w ?!SZs<ʬؚpZl@#nYzT1;în-P3ʆb *& t j?4LuTRԽE6t^L~v J"])ʪ' &]O-%?Cp/VIɷ}"j17㊝vsf[],_+o<`@wss­p QV(Ƨ-,+}y{[_4kUtkEAhbb̂L(c[{uaѫVqCD\ kHZ>_OdMHxX]<5cԜL4FC+CuPŧfpQEpV)) Qh ,CzJ#B%Ak\]L灰-fn;<6+]H2r L_9NtL5V kQ _,7Fg?~F_@S%e/|"?BJB-!_gSػ%eVQT ׅnjfLwK5(jOF4`ևg_DsLH } a\m㇬)w6t'o:#R;oWըd'z mԌ 4Յr9]T`)R-N[9a_Uwh5)ntDPVG]^uaF^Ij0-t;"bu`nUѵ(B>Ӯ{JEn|Q%o1H(,PB ڐDv"4+fZfZ)xKK݃tS'Vx'ZFlfSI IDAT86zo[pAUQ;Em12s8w4=h] m􅦹.#dMLLL L~ x'7 /+4t:mTלFv{WP.\x Uکhe'"E9gH.neFǎ[ _c0HWgUfҜ16 0(흢ܢ? V$n;L(c,hd8retrة=ʲfYΞuҏ?ecW3buEk$1úZYDU@ (iȍZj0P³żQZtwcʁ@eF#)k=IkvSpSweE^۴T{}+ߔ&w{I'B؊fP3ݨDMHm Y0Z֪ F 0&H dxrEٙ&&B2=эV%] k N̮4+pǫLZ}r۾;[_SKdT恋gWӎ}]LWvMxN6H BTC&6zPLZOLrXGzI1C봳/$ZѝwBLw樢)g̫pY].`r;W{[j5lG>߻v.7nԷ8 /zC'rC>a'5ǍJmɤM*4"m5m|uHC >X2y(G|M~8*g& y>YiPs!D0RTk^^/?J+٪WH:7 ḽ]O\\&~ne|1aFT B0auZ}SmHw=H]Xa3=>LLLRLh}]B|ռQ1wVż9e)Zn$ղmX 6w Yfn;uݶLeEiur]'NikNR*cx](%~6$_4y@C[`VP!,(Α+fv]3D /F1V^ In; 9oJP1a)WJ/̯buJ(ɦȋ}T w[<؝> ɯ^9=nd 2bk}Z/(ŎemecfIY4KUL[,gZv/):Şo"du9 V;ḪcmpmO0*HTV*!6td\KxA-}ćWR3ڨhj~ɂ ]`iNgxRY> Z+;v(@Ȉ W1I8ԜS$] օu0hU( GB *gMgWʻRJYKx+0?^4-'Nbs5K#iU^[qClW;ɜ*ƀ),m)lf%32wk㺰7 BB(>pMUXJWmf!3{%5͚Bw;X}&h}zQxs]/F(ʄFI @Y. #WOS gWҏ^䖗i4*ʤUS/毑}.1bGⲁkcyG/Ɖ`_r%'dbbrcސpSWQTŒUQ>IYUW_>UTzuPV$[Rz$ePV}Ps 8l܊֝~zw0R[ْ0ӧ.8(Taf$aaO CP$[PB0/`?aL`?l{mv#v(2!?$zu:DNSCX al!>cĵ[c+j4W&C=AFրۚ`v%%)*PCPxM}feJ'oG/#W/ =i' .a[&y]Ig =ivxڀH(^[Y ״D6ڐⴙ&&&`ޚn72cW^w4_-RWQ,_8{|% -t8ȟ h6;9nTvCkF;&aCu>I~XKF_(~MҴ A6<_"eBVz0c!?oB<;FO}$k\y,sըz_X&9,vtc[afy2 *P*?٫1%} Լ1m*?2ms&]Af:δ2bb\'BssC~5xd@˘&&&11Ck#Y=x{P(rV^wuTU1C4_ bNvŜB*@sbXi4Nvq_2LHd:$tTJB*~-!!O8ÕFKQЮl64EM@põ2 ^h _V"`^#;ي K+A(&8I;:FL;kc>wj2|t۬]49L.׍#2leȕԣ`(on?ޝj^'Mhmh ~|\V=>r.a>5(̊ԟP b7lܒB*oHt͒&&&#S!TB~}Vo`sr(,I=5mJ[͈ج,פI3gXI)P˹3-Km0/wK7A[!흼!%B#K/4!)=-qw_ѯ.Y߬!"M󐍃G.40;Lk$T ,{6&@ g!$9Ճ[z+ 7%rr)*e OBˎ$wMyݴȩt {< *<;Neϙm@eA\Pq}&ځ|Чƺ }}i SX5[Mʎ ޓXhtR}טAK%nOQN?v_O ])ϣN1Su!5\Ӑ#8mc1RD!:. YSabb:fh*!>f^F;zZYio{z3&OkX55GUBF>$1jMplvJ =" ^h Ρ'1^]RJE5]^Yo0#brcW6[e%_d+/' 0LI (jIj@ULT,3g ;v`rShD(P!gY j= y鵹%Ycyy3eCuRkQss%H/[-86Þk{n3g+zoH/HliU&ۉ NBFُa\~8 e?Ρǯ5r]蔩Nܭ5&E_:]R *bxاeq\1eu@ cbZx:%[zM^u%FV_?*A# n%tMQdHCOuAYy[E rqq"ŧA !%ES0 1v.4҇wic P_PJ\'Vǎxj1IPE푔JTBXJ79l֌:![3q񓦘g#b6?ik&,4Z57L(e)tkyactп%v{s3]#"V̳U63o0Jhƍ#9Ub^[m辋fR.bZGQ)-蔩 ClwM-5"bb_*AUd` zo[؍[#0C֣5wn欙$ ]q f-, 4"‚M=ȶ0c<!SRәOL0#{0 AR`t6K3kDF#ܑ93W0:ktZB|ˊ8=MtDL| BVpYxLlk;<=%uŀ_sGRTC;mG^2vey0Pb@tQhꊢYY`_ME2:I1A Xt[IAwsSFӏ]W;fԇFc-gRCfWeyԦzzą-ҌeS,N/d\)"'حYъZm4PW0v{Wذ!C 9jW𲙙=5%܌ M!lYZ !.?.$G6t o/^2(=EoHU|FnwHB] ]jguu5?qTV.U~IVMNJY,]j5߮R,vʷK!b fό1ڒ6ΐQ}rNSGؑ_QlO3K 9T3oP(C]΂J֦)X4Uzxkp-p>[vUK̮o:3^\j8z[#G^Fz1j:P\<4T;ƗXօ-A<)j= G!O}i7i Qo~VXCEfMwS)Nm}A-})@hpȾ 0DԊ*eush.-r;ުh*KMu З=zɚ;+GbBTU |T')2+E}J1ܵWQ XKFj/$txuZUz$C"BH¬iNf i8Y8c:3lT|iAVÁoWCk?̮D7H"3+\^M̬ˊ?k%ŝHnX.j'Or$0>-ʴdƓB][6O,_9)32;Ůh.`{ \lцYb__`>}bo tGGfgsyy|aa֜\^c>IR}CW^mh=g9 4ڥXV:xr%vFOQƢ)RO$鵃f[3tLNĶP>حq&2+:uM;4acp;;m(PLu>CxMmu}.kq IDAT#s _-%e xm2X$.WUݏF_W0t_ eGlbbN{4.m{4B ӦiFk$'dh^&rBHkv>AJr B͵R9s,y8gJSmΰJqwnq5ž5rχjk~5"|N}}Oׅi= AY(ն:E%S4m* !g[1$5)ʯ+7JGucb珀- jkJr &0mbWgX}. ٮF8q0Bc?ӟt T".{TV &w6*:PU,wLz(/&%-7ͭJr!RJO(7PŸGӸcxǓ8jP0`@$d4619Z0CY5Я%c5gZ]=5ig Dɜ1]r nb >jd%FfeѢ q?8QN+Ƞ)䜼/2aŋ:3b'M RwPckx"հJ+W}`z=;! 8 QJ @r(*r mcj ߼$u".U0 B~u&R7bxmIdYmᆕri lkYȖTZQM쉕iG'3;tW;K GJKq'ڒ0*:a"5tS8<7òt#7a|:FڿIt:IB*~Os(X~!/Ym8ᨪ|=HgWiU͚7WB&7AS\`'&aCQ80* ]~,3,>WEpZ̳tc댽reXz'DQ:z7Sٝٲ/(I*p04]o' a-=3J+Wbp:mkX! F!ѥ|~Mb~NzG&A Ma+B /eW3Caq^Q`)*e^W/2[YYWA<U ߟ9{6e+Q *(-ݷjE6T(*Yf[m NUk@J\oSnᠩ{*R'Zc򨋏M1_u✌iɰ˄ JQ(*\T>&D0RTz~EI!{]O=~%?ȇ{ە|BCȇ;ղ\jtNϜ\:y Sہ{|_?=u0,)4wclu끐.╃oMm3Z'KݞVM`[\]{ih ,gU1sYjJ$j#*i m^72$@󨟜޽_2uYJNzv^#qּyѭ@4<Ε%^o)wgڭH{4FNǹ.,e `W+* ݘGl[zǫRjDk\ [ߧzlfjYb=z*bKr˵8T iNGݼ&3h1BQe:;}t5Y_Fj9ms߅|ȟZkpUXGQ L+KʢS0 cX0ac,/'8!t{zVxÚaj,: &47F}7ݶ[߄/,pyєFV*!}-d" ˁΞmyya벢i̦!zq5[ :I}Ya'ݝ9sơJ`PVs֬=]aHӂʱQlke;r vXz֗IG-pfPR.;Il+_?:iΩ.B&8,gpZQn=V4 4MYEq'2[To8gqW[_  iaW ׁUҤK!SEΡ:=䁋y6OK/X,_nSfYh G_ G~ᰆ11110B*Vt*ᰦ.TPvqM(*BFDxdt~8n<:o$6MvaPIr%^&DQ{NOmhQLkv\_q^~晹ъ>{pFÚ=yD"i1MumZi/8!y!p5 2m0&olm֞fqAd~]^E-!@xb7{~zx\٫L1uCzg#ѵd$٫vn؇H"ݠv+|e^PJQKh~ab_05RjoHim{o8i2*tDyY-6l;`KWƤ1W{IMBI. G'f 3C7**)_oMUQUڡ`DLg+ɋ%^ A><B UTP=b#  :5s$ܰ_;,Y貙<0i{j#;(LtB H47pdMgrq>MQ𾔊ۑ#B/x3+}%n>ĸELwG}gNɑpU'>qD -,TAКdJt&BrdsN&ؾxx:< Mg~!{|CjDQ62Ȅ4Gı4*i!e;hփlo;??F𥁰lD0S$"߾-r]pR]H(q]i DdBä|_{ֳw?z *J9v67SG#1/X[ aTܝ!Hʠd:De/;RzSJqiZo}t"\e;3~U neC|fisܓlұ=FI@֫sN:&&&)`ʘ%î@dVA'^lLtX3,!>E1\u- .~p̨rp8|ߵ~ 7El\AڮzZU'OK,AI}/N#$efw_sO (j E%8Uasӝ^IFjRPG(ժ_=ObHK\ +w+㋩4ҿ.+56--(aHD9J;nF"|KhB K%Y ]xfmṀXD4r8 ۂ =O0O!YK5&&&b'^I*pð3Ğ4H\>g]9Q 9B$uZmG'Nb<86I,7ܖ̶04u EǤd HNkYx}q0o;8sg]&[r )Ԅ $77H(pSHr7%܋ܻ%KյN9? fV;~kΜsDӠAZG;׭@>^+¸1 *CA=1Sӓ:<[ !-o$c6wmܤյ"qkV%IT0ʤCEѳmGgpv7pHq` ]^.ǡOEF-?wISO)gr/?u{*GMEh' >2plm*0Z=a)3j7.V;3IzpoN+|]rSqV#cЩ5,zdѧ{]/&ZpCzf6ߞv3oXX0 `GLc 5⋫}oK 19/ld zxaCoޏ1gT&Dz6Ѳu`1K_Ns/ ~1(M ~xb)~T.`Tِ.Y>)qc'FK'<]ceSʡ2ᅉ`z!6 Pzxo?.UoH >S6f~K)!C!sj2gsŰ-X:׮MA ,qm聕; fD2rDxK;A (+t3ls Û[ fU!^ǐz6 D #[-qzwZ+p]>[?] Oǟ'VwUa%)i6EC8i?5_jg7uZˍz4x.ũ'?Uh|@%l52|F'o+Tz57ˢ(A_魏ٯP mFe*[ZZNu% <++zvzu,6S=J GeWC4KD;,셍 _\K/sGQp-=|# fŬ) vxNwI>e ⣋BG7p7O}>bcǗ=V"CKLh=A"[c吝FUYZ6u̘޺M $BC~ׇ0N$# ;f1ӫ$Oxm}􋃂'vtGR} +L 7.u CV=ot܉GaaXw0s C˶ٓ0h7z⾖zpIkir lʇsFuWsi8ٿ0w^e\,bx,3Ix/gˏVJ; Mdg-VդD˙O̓rUrQp:J)s9,}dLqx ,_7a~._?>GuJOU@ &Z{=Q,?R>_:z4j19!a=U.aR uR*|D +jۅĚ,*5Aqq_wp"Ʈ+)Ek\V_Nw6>-V(φ0erÎDE_Jk76㏨0ayQvIO`e`(mWQ6_7ϴSFH;w:L\*q ](pCDjqwᮙH[t,RsX텏J[bf%uG3=s 7B.f I'CMqDxװDH1muBRqR鵟xc?2*W{?,.2fȐfdBku_jrGKT2/o&΁2'fS0W7=={6lt!(?pJc TG=tc- IDAT|hsF[S8 D"l=:c[sjP y6pϏ=!Zf(ah꒎J'Zc-҉V)U4-1?(rJuzIKaf?${,#Յ Q/V=nuW@xg EN,6ɴb^D7 ~hFkQ-\ ߉$ ϯ/^@T D_{&=uhZp贊&9\g)bUon͟1`ىcMKrl78.Aa ҖLh/Q&^]ް>ܒS; qϮ]}#qeݳʂؙ}]tQ15P> ~ BoyUǢ{خNU-S2z vSqNOHea)E@l`7CKC "o{¾pgatklőH>opۻ-n'sj4uuUQH"͉N10j{Oʯ1Ɗ̈́H/O?%ů9-sx:/DMWgLl&6)}"ax}ݛ~}3k`bޓ/ߧ{mdGBwNp׫8IX e[nxqYkwz6{?,nRݸ!CT.t|_;W0"6'D$Yrǝ_xA&uh99mjᅦ[Z"mmIy E4r45enm.Q=L!̺maxw+뢻`1ξ7_rny<zl1>k-FXsaG:6#j$58o?h>|izg~ߛ2dpкB鎍=F29F깰97,>ۉVkѭ[ঢ়eBRdRH22zLzQ}8Fc6vҳSeGZ3IޞuagY'% c" )tIBk^VI.ƖW#JUcŢ&] ud VXvRݎR#륖vN렁Uavh/Zڔ;|-@C4?ċ"R|LƔ(:YlE񿫣j6X YgѧuԅgZ3qNxq/81x{vgw>MyeYaQR ߙe FZ!!NBP6ڬ]47xR~8 2 xc=nYsPYgtWwL/QNˍd=MNZ#Z<;ʹЦ}{lJI]p$; #͉.i VPS+n=G5B@o.f6f WKvhZb"CЄrQvsJLNwIOȥb/r;K&C'DBvHnz]*POnJh?&2/p;XO_3`09pSB^6whd[G#6'Rwa,,KTG.<,:=*&L>Fi']>&'\ԌᔁM jB5xԍumb_BcI~h}0Z@=YPX?եb 9)rr"|ȕPtx'{+0i>%1zw+PJ~t5[lN𣫙Bpv#z&]JF2uZm5(K:<ț"GkL-ԇgdno>Vܧ;.]xӐ!z_۸ʊO>qoF4mܰ:rdlI㢞XKȻf3Uڴ^pzuÂ?@|Tu?dlhݬZʾ\)"+b\<dU d@c1fpC8E"|3ՖvYmy\jfw]W۵:i]=VH]H5 W;O)Myj1|ujz^km5sZGDz' 9v'CTG .@L$}t N/IĂqª0Wʳ߉ r 4UY4<Rۀ"H<1cmkM._\u KG_ֵʽ8&O.X|}Tm:]3zvDi0{uuxN#ب;'W`%`;.;YN*oEj+dƺ\ä’MUUx@2I ؄ EN}mɃ:|S#I0'I ]. D`Q5MJ]݈zs$6tku*%#/*X0X"Wjp%ƔcjZ j:#fY4sVRE([Eh bQHw?o$[$ep.}_|\fli^ 0;[#azFCU:?{?` ("It_w2-+C>۪`'LHi.WUs >ozz C8,+ueY("|OIdg#MRBkJ TTb#4O9}ܫ0k3&n@CҭO&Q|E!WF=(C@H ۻ.'E2r@\;|&* E=vk\?gʯk*zQ/]/tϳ榷_Hk*[A%Q&n3c6pF>NbҢGަ{u}_~U|)i)NSRq q8 *f`D ?j`"M\ږi6ӽNg:Efp6{7Ǩ]nnwrr2bBPdhd@X@GB8.PJ ('n@vy%HG*Ohm5>~|HΝ HL$1ʉu>)Z!El.A_*iz&}TS|VN>oM% I0RAvKR׵ۿ_gm*1/ -|dXQUwx8,,@pLeGP4ڊWE6.f2 |2"|CeA_veg9ܧvd[Ш~c|}T]z-G!z'%q]-.2dLh w{uLTpbX>CcM|/Š*ݏ?# 4B#s"' U(bwm,htu1dCz2ٛ*&=7/p|A[mqIs@<PJ\;8$-TKx"MGCVSR4R$s{LfA~]GѣƦ堩kN¯t}ȕϐ! %ObcG (fĉvJIG Ht׶'e$Qts+ n<;R eeHK,)=R4ڹ~,l&aX~i{O:-r6hɳ\j4 #_;iuxjX$Tq<e1 tJxIкBT+gc9c-ݪ_uUt *)򼿜\ݥhÁ1m=͓5r}%lfb$gx>3r6b[_̩Б7xR/O6x_` .,Pfit\j7>mj3[t ҘKv|:V\Mt=w++R2 R.}m椨4BEzTbΟ 3BuPz;+ٖ#b?{6! ~˚FK AQG!Ի`X-.#Wc^Z"ѵ]ޕ]~NZ'Q+Sᕾ/~bB52¬W֋U;$lTJt|rh>yfA}~]5HM E9-,δz8;a@W?ihÀ=NiA70ɛtomVMA,"<<>wI\&-+SBNbH0@r~­G%-_ׇ_ϐ!I\uC8l}[ X^ޏ'ج,ٿ==-R_^]{@H(GrX(5I~uc2+?v2eL)^1X:֮=d"]|tQa ˇ 靐i`?Mo'-Qa|U1f1Қ[<E_\j4yU=Ln1E ~JfYƯ ܒp1;7<`1 \4W¼ bQۦ_qTmVY{z')#UQ~c(xp>Kdg KhD~JzȠwsmQ ҵwʐ!)?>B9 4JJR { xPBY f)Y$J 1N7uD$h!-!1qB7nm=wZc Bk^2ziohSc&]ЪۆOSvtlkFQNKnn?^Y?!tPc8j*Ma)̽=|FU:*s8[rEXy,-whǟ6H<)Hb#ȵ3S ,rJ!SEc2,qz¨ .*] !5ۉ9#.h_F.KLa 1.b,jny?Zx80YA"Ve$Ɩ}߷~QZ?u!2dͥZG$鱺fe[nЃvD RuͲ9 &}5V(3À,"Ҋc"MB6$EN#)RTMRP8~ ,/va3񇼂ggM00'RAo& ^A9Į$8Ha ͋'pjWEL<߉1=%ǖ2HQBP]^dM:SFCİ?gS̐!B뿷VN-?|&+gG1l>&H:͒on{u=_.Y n"&P)۷Aefw{uUlnekGuyvn3!ru5E*_4a~g(+tY#u=c/UR6xJG q15Ñ&|m>!zU}Hin>!^kw˪0<<Y_mdZO Tx1qv3a7dPU9uD܉}ï.q V_NoH4eQk$ $1a>+Bl=Y&TFH QOjK㡶M^+NL.D3Y4ײ(2J^ZϣJêۏ?#XP& S! 4pe7v}122h 7ެ E0̠S񄛛c<]xTSJ&QdQ?|]/@Fb UIMa̷ 6;;s UW`]F41;,= ̫X{f\I^tTngQŪ IDATJ̭ԝ"oGՆFnHt{TE(/ qcm`pY^y_j6˕a$v-uLqli6cO)/"HpYZV#Y a&Qy.yU5=}(I!8!)^rq+ܾc@9, fQ!l` ,$a]4w4uE5K^OG/mCM')%O;g/>{ l=.oD(!D0&=9geDHc>\ϐ!n.кkQDLCIC'x7c!j`7Cr*otK]=|k8*6b+ Wi(.6!Ӑ!ƒPi1wWϢ(we;}1nx5]$3)eP}8^p(HOu:׊T_N5a)y P*CnQ[]kqI Ř{m rqUc)-d?流cnuZij*ʵ/Ӣ!bSXb3v<=q(Ť]c[b0ii6_קn_:b].G0cNFLfT\RJ˿Mhim3J D؃ׇ~Ĭ͚6F5~5u >?i0Fe0.+uʎ(0"H ?ڕc!C INYwE ]n_2S 膉e#T6πNuHzGsij23bOA^o𱓭NaC<]gH7A~kC ,\]]nM?݀OC+ح'Щ ie9._HL'l'- Kk\pMO_@Q[/PU0g2|X7&"/W ]V$&u:?|v:.i%?*!2 D.к#*H͆exewxEC׸>9"Fi=$>AԴ@zT5^{ʿ:׬YpF}ҹv]˲eJG] bŪ!0khIYy~INzWg|8SJnx.~)!LJuŐWNw|T9|2:7v-Ҥcy V};5'!jz05^Za.aKIBJi2ؘrԆ'Z<14 ~% lV#ci rD132_m҃oʎ3/DZS56Xlw yJ ZKWw_fv|eD)k9leĵCi\N2;F_mCǚdc}ۭ@_lOmI9<'Ig$dZNI%|vaUcRX ]'1p(1wgMJ]Q  ]'EDxnFY(; mAZI\qEK?Z4*Q::j|8;L.rYuP{T hzAKKy}g_K02pD꿮aI#}܊=BSJK\g/ȶ2M}=rgeq4u;k/l(t_+E;BzvNuͨYĢލWsm^Moy'I*zP&]_WkTIsa b#JV%:v8FRN Jk JHyv%{9[{!ѷ#^} HxUD脿m,ɀzø,fd% qܟ>A'*1 $f0pȳ5m b=_d.2hwLɫԀrXiHXǂDC,!gpg?SJYI#˘W_ `0ǛdUnr WIZd#OWR JNXQ#lZ-YewZLƣr%ߛC2C]h2 یQTNMu U2u?n $s}`Wt(??] څ8^Xέޟ$0wɅ`3AAkiY>ܟcxe]S#MF5씡I#h؁(c> %،wg& 4sֺ.Ľ?P=ى\, e(* <)Ebb31JD]ٳNIs)S0χz!# v4Ԅ4}>$Af,nCQQY!B \ޓ`5f 64SgWt"хN["WWcEײGRNKJ{W><P;ޝ`qvdM]wl l \ftZa7/(,PV:|t7 [kn/Ӓ } FN-Ggэc-ζCYu voH" ;[FƏ4A7"I&j@?Y>p%s&ra) aD2讝b3>lチ.K6#ڒ 1 noݦS@VcdRce4ʻj^'xBq e4ܸ`Tzȕ)AA<{( o'YLtR=+{ bBJboS䤅1:-.jEzE!:..{nBsE>#ȖBh 60qXZ UHs3$0'2Ij*31T#4B ͭo6gQyU$|._1o En*^aE cPEA2eBHVCEe켈O4Gi=$pTI?CTsq; #Ʋ ҭ:7@JюέЯkZ! =ţeMZKK5Ʋr=JMaI NMZ=r]x]M%Zм4Fx>ṏmNtOa~ukܠ M}.|urj ?D8 ?7ɯ-tKC]&1(iyPsBd(ȳ'YOt1aźpVn1%Ȩ9As5`` Y+jV iZo6NDoW6#'xÜV įndoB[d(w։#H[R(@$"b0*' ԷM@/TԷ!6.<q%w8|JRWWS&Sq 6["HO $6pG偮(Y苊ʾsOԩ=&0x:$I[6q`ܥT.KRŠ٪D)Kjh7cbO,-'yJY۹LsRQC s]VI\yR>*g#,=niqD3]e/!Vc27nK&ZfSM`H.rW׃r)C]u̙"k/hl.pLy ju%oz}Eٿ8_?\-H,Nb?1 bD[}8Q.;.^X? -m63dHahB2g|> %s.(ى#tp1@W ]^|"^ur՚57.al2,Cqg^ (mLLt;M}m48A˦:qxW䏶3`tT!bƎܕity%_WG;2/ii[zf/pYGt(RT`.@PJ+5 aF]Iٌ ؍,[&4o uCjRZI<-+j͕su$Z6x6壨F7??0m7.K2~,VdP#J'۸dhZ8~ lf9bb 1s >u]w 4WTf͢mHkVcuncKLD}눺WQ k)fCIv4u:E޽(mLG U N^u"ƚԅּlwGDw@u+cQIά,k #+ljV xi|N ~@(WLsf$C*Zxq.HÐDC3>CKOwB~fuB,B3 ^3|$q\?wjҊWGr{[yȢcc0'ڤ>6m: J9qZ-vҸ֢3|V}{ ]L wǘ"bdu¦c29ތ$9lXmg#S7[kŶ.i:9 E^Ԇh$m;h2ykL5I q|r{/ 蒁Z OIwN.3*g*EE y3{wn8 fH"4և#uNtıH4p0G!4f.&.LD=wSF풼΀B_X>upN]7!K@6/y zxcAۋ'aD -LVր*]N.7׻Rt ~AJDIӟfQ&LQh-l%㒜+@$`Nwz7M[e?%+ϳm&x *19@"QTuxBmӾOͲB";KvȜB<NjREYz'#:a@ 9#M=ꆯeA^scPy{ >B  m^Mõ W1hp}  ,!@Ä!aK; a8}ׇjF]gq}xʜ:Mze+=DarVKeUtӪ5SZ/k7a=b E#̆5Eh=*=`?B>cQge븙b3}% sӫ , IDATQ>g``7 =٧oC%*Y*Z abRo+@g;f+G$'b7|t4<~[YH!\b ;Л^7*?]V{a / _Mc%׼,H'zirl#743UۏXd{e51 . Oh>[X_Ps mXۘ1#G<55ރ$.ʾ0 3.:fM?(~-٣Q ?}frv3cz=A*"9t "]QeBuÒ2]/lm'cC: Ck$˷UP*ƇQ&>yQ% ׾ħஙHq/b52;n$zH.{ܙp_ݤ|,stzY ;gU} ˳<~tJ4eeAٌzJ7Ltiyj yvFΪ*ѭ.^.ӫ z/ 6N1[pE <;;ȷZXr Yem0jKx89.MwΤ҃ix#(q}>OB6G]﷘NA5ZM٥rk ^Vζ# :].&N7]vBzJQ/tg3(c,\#j=aÛ.8? ajD7aq Zla [O,,<`s<[m$AwBLu+FQ97܀@cMtI'f-]xzRc*e,(2[PW]##`Bk ˋI9*[`EGsJ ~~QkXZNA x??a7vY_hv+sf[`w54ڇ1`5(΁0n,/X!k 疒؂Wor)j]11)oͥz߾=#v/H˖Ju x7xh]AA:}$Nm2l_fu95} px;&#ImqX`YٳcǤyVjpvd2/?œntG+ͅ z|B0 R׺}(t@ qptjrCܙ&|>VӿZMt9ɚ!*E6p[~Z|N?R)Ӓq:iwo6w2y)WMCZ6z}ַ)nHOש7Lef23i]b `m6wCklIV33e,6 ~,==>Tt^k{0[ZM`kZ0Z_PfϢv;tpjWI RI.UNˏj~Q]BEth- Pto1vX8 [2FB 8e:{}Äe3oS#r^iRڐ%ē5Wg\7M]e=|Wz} |}2J /^A8nҍ FjKJ%%$LG>g42&k ?s2Ր-23e>Dt#Gg6vm!{7i\`)d⡵Z%CV6rNrAubs`v`v QR^>j_WiJ>R#ͣɌ\19~پ4""fǻ "^-zs > ڬ+.hApj 6q(0<{b1ty-X(?h4 ~]2k6as! H$556㮓= [o +קgD"Nc#1B 'Xg ^0^ #pFQ8-d21d\N%%jSA ed22glmN@H`>ѸxO Rĵi9ID pNԹsMop:.E!oG\Y]! nʠW U_w}C;반U>7 #dW=^+:aװ<}C_1,#Wc'UҌJ "I=3I#-z}K_O?ܔsq"z}Z\뭀Zwɶ ֘Is򠼚[_9ߣ֭/ 41C m⮙ 5@FZ%ÛL]xQ QS4+u?* ^SS#!A"ϷU؂:Eqm~n&B $"C&3k|l=zU{g@[z͌(zftbq$. Kc34V>B;{ؑ^ 4G8rY%ĜbBKú.iqέF;/)(,_BL R|~T$M VoMgt/U oJ>y`4aHsnE"nL5+LJ%@![> ˂]8^xL뵗{"fBi"}Ŵ[c#/6 baj9>ezC5m=] ýdGD=]߽mj.}bX3Oc[4_L9Νkߴ]C aqΡz{, "H|9fQNbR;HA9^ Uf5i2E߽": \ٱhkn.GF2")l9Ʈ.\!0IbQfAciztzFd5`oAy5W6$~>"_IwNi3,\(z\6]Jd#Fi7Up?B# #LNҫU)slR]i>WGŒpjE|+(YrRY0o.Ky:W}M"o#t8P0ڸ;MZX.SA0M^ K bWZ!aSۭ@#qnjjY*L5pЀy9Yy9yYYAe%\nOCSKcSkCSK}c˹NQMa2-J.q'tu1]LW'p^F101/_8W%>$7(C@e#J:l1v\M⊳IC5< w[};J\R qS*ק|Sp n}\&RX^_ !0BmF>^O_-GLD.mݕwqT*IÚ؆C车-&+e*[nPB^MGP=X\=mݓm~s%5*_虽\e$܊]5-`l@J4U7N1όo9Ugk-˻vE~_Rei,ǎ*]:\k"[=qdNWг:B\r~̘KyUWttވdFOԺZ=vXrbQ$S^WbsNumFRZ*zs͢V>,Oq6[.^,l=mbw=4[:X"Vf3!t9ڡA Iι/׵n' *TrR¢GE$@?~%'RpQхH.2Hj$ -NYE:_]z $'Ck@V;߈1" Xew'{$~W̽,/'+r alOk!Iϝ2r'_Q rzx*f405GŢOGBz1@'KK6k7Vp?B0|AFovm" E? [EGr(R]{WBļQd~Z~ra1vM9IbqfZ&}q_8]Ϳ΂{8u**.[̦AgXtk !G^2}/c44I۴jdMapA$UhdKqZ~buUQQ/n}K NlZe+rLtq؈NN׶{Yo+`xZZ-GG-JS]nPO]vchSYRu>:+ kSPwI2_4;\1ݍv;i}B 7 |lݗ7M'bR7nʒ0rT64-OLJ("VeсslEcb50M_w'pq 31L(2kS%kȒTU].q՜٥4TZ_`}lDNfH5$ዜ7eU{.op'0^\ֿ'* !C7:~4;.}=)g# B&$ Ah߰=q|MH*DAq׾O$RGp)B5ÆEz`gtnU+.d˨kt3εͼ]$wzU.07 =/K`ꨃB@t#($A"kzѷ.YafsG-xRYCTe~躥W$" ܥf76[m"K?(@H9p Lܶ$ ƝXjlmfAov=$:BS#Y3^Q,J讽g}^ !d M'}I) MMJ\9LSfr 3R%sU9hqOR{A]rΊ2#Ip:Q[n "U}b JZnZ^;1E̐n}9;T1*قd -NAl1o֗k pL(oQ Ye*>y&'˃>wIY'vExm>Tbe/jH楱uiS 2jY>?XN=#D bhݵcg56sA bV)]gM{7\gԼ΀ggGmx}no[rHCGU2|<B(iM_,Gc'*?]qf.sndW7Jg\~?ۢ.KLOԹۍfGPU--\\6>R_~*bg{z:tE#򂛫jEjb=ޒs$U'Uΐ*KU0I}y&[;0/I3@N\ Tz4У `v" rFIq5хTDje>ijݳq5 SKن[}ΐ4Z|`~L=`uB I5ºR_qǼ-\=]nvl7;FKSu4}$Q5nme$?σ^B}㦎-[L[AEC>~/^ϿGU K9`@4 A0ݾiS~Zsf͘ =KjrҜSΜ6 7r0% :ػDz Xiou=YV(1{x{%NJ֞wz[,nKR 'ǹǹ)pxrpBn&r\7Éym XReQZa=v?m~5~tI[ *e!K;}8A/\  ¡}XEN륯^xDt ur,n<k#44V|'k?1u3ĭY@VȽ/'UڬY?2fC uN5wF(S0<6^ґD&bP);bwsUw͠4/kD.oִ?<5WHOK BdM8v!u j!9wxfbA õH[(e;K7ɺU/L=h ɰl_97$a}_Z:IQ Rt1gma $UK5$Z <2\MMuBkx''bh.VsAK$St%%"}0v|ri*ev~45dy;;{̤bwAuqY.Qd8V/Lі(e\ T;VwahŰYXKejS;ޙ%%n*vz|ڂ!WݛAJH 88jw#N6F#(̓dxHYNI}_"'WB` | Y^\HezI=9n;Hp.`VĊ˦rRV Bb0e$?OBKՈnaw8EtĭAg_KG yOO4z<[g_r8trG&T)>L52RWS'ʳVvYKRusZX# aN~#vw6Iy͜s3\( 㚞j'?q~vy)*hpD2ƹဘ<Heqz*"xQV[U UmBhj+Rre"[qRf^kcrtxlI p:5EGNU&Gڂj=DBXw-ˀ=q%52_ʳ 2vy%_N:]S@wϢ">1@!P@j]CU~PLdmMp ,SdEvu |~'=&|5>fy\LaڻoAH nq-/nչhw (&}K5E2vZNF GO<--,dzZ5nL{)Zy)^:[vlxS's23Q;a|TحkaڐbFMLh`~F/unW߅J% M!ò%Ld13 WU 9Qӛ @ΙF T^300vKp ^rztYk= ~]} +6yvfĈ|B̺v6ok~n/?WͫCC2x=vUURB\c,+ & n\Ə)6d wY9~VRh58X)$}0555G/49]tyNPv԰M$2fypQ\"3w̌#;zP%lJ8(]۲"fyp}M2 { bdmcu3K5; mfNC &~]} :] {E8@u&A9Yi݊xUWg0L-+ J|OϘ:!zHvVeS?T@Up]U |QVKB_uBHIؙ=Pج$݁||s8,uJx(@es2$_nE:#x'{񧛅\\B(]SOom>ua| RL$&b0P]6 .[k¯XCj =ăE`9[r[.7'Ѩ&\T9SQEӍWg)Io}]V͞1؉JoiWcCqY指q  %BpVNwfPo% oB$Ha|c`@|d>^Qǻ- tFv9ǁdlRV ZpHVpjd|C B1 12&Y+'' Qq'l IlPN#.ͷ>l1+SL;7i}J-ᐜUU5oKx_Jxţ)R3}Ջw^lCt2By2;0썢xt}Of_2b|Gc52R QLhb<^(}aTM.^kC!1yX:,OJe|4ztN8/=r:Š=\$nJ<6O.eA0a|*'W[0*H(”:,8BsH,&%’Q$.Tr ڧI%xew7Ir>\.𳜘kulݚpaqj\ezЀ^x_ǟ}.g#;xe#6/T'[\@ٚE Pd|a`n08ՌnZ@lgTrR%zp8y5s.0箦#W ]Jɫm])<4q5`5_I-(t/PARf`|r|ʮ;h]jq׉ΰ_K9IIZUiȂzyVV?DBp2Ky&M 鲞dd[ 1 E_0;o7okvfۯ>JYg/`6}:Dwq|SNG kI^KF ‹$B>LsbqL#*pfLN Hpi WIù%Z5=cIWǃ"Ս6Vp(ãWdksKj. pI|3`AjoԇT2| S u:|>kΰZO2qZ:<"aCe= J$s8>q7sү6tpvmLf_ۚ?&[/:T>Vč٪Wl4UH bDhH 3. <8XLNP Ì6ps3z03@ 3􂱺% NXfXj<BMciΐqZﱺ"fpYzz?b$"-}nWޚ:i}wܜ 2 IDATpu|" {LWW_3ڿ'xEdr!'+#ʣ5Hlʄ{s:LթbJ,:eizL&B~K"(plA䅪\.pf@2܃Jb|5*#~_zk =[NpVTӑImv#x= 6l,&jpQ +uЫCTWO5N+kq-gNkۋC|z$Zz* 1*[]%WZ \&nlL0tx4dt!?l%R 4x}4qlXO bDhW Qɩ pz3&ഘ0`UWԋ[s= GUBŽ.¦c\HL1:Уz۬X\@m6r8( !D)$'85vB:keؽVrZ0B'.CG #rkӖDxZӍ˿Lna)Kl6[v (ܢXnnRSDTnllk[j\8IA{@\נedB4}L%B~K"ʨzu*BD#9P^&5 LXsPmN=;V&'zv%^Ϸ yvmq>6Z p`_?Ow `X#ԋ'ԄYObr3M}V'P:M\_@ wc|cZj//`w8:`EK[d1[l?d'tٙS&8v\7q_ڹ}aĸN\#Ahkb6dЯ%C}?ĩaqFpUq|5'H He~aYMqFJtlXAnu*inLuFETRa,+4nR/<()@,e4;xh2M.h$lЧ$R I{0!|;ϝkht?n5=Y5_4KE $/poJ7T)^kh_֢#`,'FI*da=~jUVan֋YDo ^[k6G$dMfp1/56a0^DAs# 0*H(Obeˊ(mocۡs`Sc>`9>]w6z;e};t>+oX(ޓ}ރ #Z߹GȑRGha}KZjr4 v;kϡs՗neo$ cKO7e~{ys4C{ٻ5A*W A2L%{gH;{{n8*[Ѓ`Fxv ZNuYE=? L<$94>'|_c%lGLJ\Μk5U)E1> Y!p; 8D3Dcs[66v}9~MuM@N_WMӫ( Y&.{71cH]|X[m{EfguFZ(^Y߽Ʊg98^a\6h@>0Ra6Έfá,(2m߸US#idЇU\7>szG*Nv=| ͭe\kh B.gJb?]>n!vK6,vZXތUjDuT*"9܁̀n +a^Jxq< OX8L]|%,]&3\Dm$tSL8%P_2'NHWR;pNx4VX:0a co r1gm(JvE KRJM_1[ aYqil@ɠx}(;0xAcL~^5DSj/V\Ͽ@bUSz&;|%.BkWqТA؏d򈙖E2%9>nID/"֏4Z[u0HAO֩*]^k9^G@[kEX,On8e=cqI*p4)F>V_I& ϶+uWF]WN ~x^\(5ڼ;dpUf(e;? Bx ^mWPEa)B qZD9sB|%cQip.D!ܫ BPRx꺌տ#t#2ϵm8YfcgG2׷կK[5 Z^.A3sjFߏh@r)[G+yD+E_QL 3+)9IpQO8umlnv>4[GtĐQ%D?dsֶ:DAx bI0(󶬤nSX-yFQumM!j wATl?Q숈RDDz PBI l;~Dy#23s^+4Ln78{F wT0tͫSz6ݠ x|H]0ohx; :jRfhU踼Ogw^=/bVv[_ጴܯr2'Ui6iɓƎQ#IU~=?Hr{vGe<^T|q88w}$U5E(~)npy'ͺg*ۛ8|ǝ&Hh`JLj&+>v2pZ>Tz?%yF<)?ӭf$V_ϷOѱ*PmD’lj7@X%)B9kl?~8ylPJ3 *?}*%;!kl7 Licg'DZ{6=휻ƌd?ytj KN Ho (0 Os76qh/F$V}k lvvL?X]{u.h6 V^Z:gIt4Z ^ V?(5 ;.Fvnr)-d{4Δs'BN=,ل>(7 CuHĎ.WA 0W8E@t:- ú+v/3z%\YYTKx͏KFY^— )3oR^Y?m隝>x@^ݢ"B-Vԙ OfF)~/ ,4<5dO\#(rsIʚK"HpHPdxEMMFwKqά.M}Ǥi"eMkedd-'D4 Q^ 3ClkF$¼B`)gOT;a.Az\89L%Q|jd:7fxL i>ov8 i (+UL(4JtD=Bl8e VaEUiumSF5(,h`>U RZ6ߢ=^(j i| 箱xk <"*2|㳆 W?gV5;n>qOr.{MWlE~HZ.ɗ2n>z6Ec{_~/N{u{}C߼2~W{E^S¶FIk>39!\FFƏ̿\w!* HFI 1Jrq\bgC1jUȓT+F\1iN"<|~j. wr=&IHl,ocRzk j#Ndgl1/ƼPuʼd]UY9N6XR  r{dDZkO}SKFd+]} =r98:a#cyEpP]S|b!>6_|9# h1ёoxxFm0̇ ^08<`}]w LVL=6,x,F+aW*|pVK̒ЙäLL떈.wjԂlLWiRhͅ/iHw-v.MMѢDWAbP=6i@;Rf'i]:Z_o|Bⲅvϧ^! 'sJkz"̴X᢯W;6<ĬpuNT>|ߴ_M3_-_#x0>aAiho?D}=cFxw,bYOAn|_}?æM%y@劭;'Yxm+j娵@{;Etl﬇kAI7m0Y\|RUJoJ ?B#ڜ0u2)JQy։LQMt*fvӿ+x+yg |v7@{$+cXĒ'SV)ifKKw*[pnъT[\i Hi x^Ĺ=-+>{̓aɷ%N4vwvD'vŷ'XK? n|/F [kZO6uӳ G_C|\̇5+sIMIL<5Pu@ލeddCzk?Nw~`1ZX/Eē֎x4S{ƠιY=k_EG5jݬW(܍ Fb玭h?;udeZ訁I9:jIgdd|;nWV;ҚmMf aF@NlXkҴ[ۖ56;6^؃w$9&aC<}]Z_ ,%߭eU2ӻ<|4^aثj9*jjߴǎ'-^0w&ۘ>! ى|S\~ņS-av6K~|}K)"`bB#H iM-}r{y0b{Ntca0~gsCSɅҖwNJ|mMesM@$E8+i|T#/ܮ#cuPNR҄2[NcT (u"ǡ ~ߎ^b^ظK ʔu$ 0Xt6*֣XJ?HҀ@ظH3tM_Jg%\˫g)lՍlxeG`JkFXڽ2=y"$9N+dzDGx>#c=s{zjj-@s{@nbN͗}A{sSKl.##PI/wwy+5WIDͰf\tB_kWɨ7y Ieq -t{5Zֳb4,_o=^5冀 zhDx_q% zyZ^I#E}@8S]s+).Jݞf+ M 9=Ͻvö́9G;/ HAjE a/t8*z'[}F>v2v>k||"F&|/o^/MȢ[{ ۃtT?N"_`U4TQlZFE}Ĉvo;E\v7̋p O)14u\ؿ%E!>2Ls̬Ŏw%ۛ o_q0v Ϲ-n2x計pN-/^s['i)D`ޑ=ddd:Q*Ż G,qT^n:!G òfd%E[طjlD" 7{TR;%;Q=_*.f-ZCl5/ _EO6MV?A핐0@ZFЭћcVMDǻU;@XՍO+E GL%6'' E2[Ds C#2wz%u .ht9gR4ȹ+3=Żc61et0r䙖1ё!&1U޳Q*x-!Ya IDATlE\a!O&@0-.WnzQ?HQv&(64HEZߠM1q}g.0-r<|rRKu T>nZ)q<-&]d6L4͂G?OPSN tviB`Z HWūUD--6_qQl0m6#X7D7DX0e!pz &%! 3Ζm+uU MM EYFb64Pڠih9Po^ p+"~ʾ:)| @K߼3 dddL^0;VG/3 #I\ZV蔨gfq1Q FiC)2aWb靓\"^\),0Xik,j-###=MlyINw£QH^S; F%\*`(krh`Yn][8UZwjzW5~KZ,3}Vu (rbbb]r lf'uP|VL(t"5vDb­9bs6\B%AV֓Z"(cu>Ǜ9.o<Қ'3-b+KlVU:?&5a ~Wh?!l<8}KV|-^ !ke<'QZxO|*QF:8;Qnl:v׎/DY CZ4;]iB(*0@ETHW5yco ]u;nöhj訅 bj%2[wvZj'1,"9Zx 8`zSeOXZe8j}쏑tw5 v%;w_h7O!}т56y͞p#tyXx(fù k?hS<47߰iú$ÏTTmj56ʧiL=τp A U ӮDw$Ho@iq}C^+wUs"8[ "YV'!zgIi3sT)~q6rYѾ4YT#F$A?#jzZE#Df<(UzodI@Qh*J+0OO[bɃ笇Z K-[ (ub}E@N3H+XרD{?!H!I8mYܮ׻[!xw/ynhl'_ӳZ^9!|C rt*y w;w?;9' <:/ wkU2>dzJ ԟd%M w95pvH+$m m[OlɩjbY;!ds+%~=4Z5,)®?J5xY kV{'`3X P&訅JH4B\ҺSj&z%N*G 9S],*}BՏMgm VZ̛OꍨpoH|j/^y?q6"jKTd3'2)tPTtEz gm-Z '1O\{xb PuE0Nf[w[m|?ZRQYV9ZJCHCۆ &dA5j/&qXXk.b~.ϰˇEôrPw#<;۪MF x{='>QKq$(+uQo/[#T>,]Oߤ ׻u/pJM{$cMP;<"DVl(#!AQ%wm9z `L(Ո{K##vyۡsCf+83ۏ MHlQ9ZCBIr?K6Z0?wvHܜn>qĶ}f3]ڐdC0αCW4&@eHU@I&4HaTb] 6Q^Dž!7j^ ?^*FTuJVCo_KȺ5G.2.$:2, X , 0_-XJk‹v;!ﮎ6R[DWgg֣_Lh]F3ِ0sw՜'9cFCCiesj fנS(~'0;9BXxkjo:jڎ[]n}Ae/r]Y^Znɸ:80< -AcU|n8tAz'Ʃ,EOqҺ_:&)zKιjoo~@8UQ I#`E@z採ݟHyh|L^^%]զO>SS`\)cJo:c<6B*b!"<,';ף>]ʧc}v絛Lkj6z bP>B%x;o`̀>?yu6>s' K;ضbsv ɺZƋ)Sw6YlpӁa/`h$5Ɂ)U*';*f+>((x7"?'a{J/1'+'uTple 5FCWjb_X %KB `ZiL#2Hdmd=##I(d+h;SlpJ$ME2t,&[ҫ̔$k im5BU)hZvu^G> /O~(f~ }__ X:͢0QJA!!5ZP|v! ^) &CZ|BS]:kCi`TDp ''Z*~z)I'im4Z"Ds|4S^iťهn䞋|;:/{rʝBQE !껝@*'ynvcZx6@UsG@rtiYc${^p^pv *`aQlRd5Y:M^UP$tWZ< "%T& <'FQtڔ}= !R>Cx-j 2 ߯RUouD| kL$ ˴չ[2#Jx+&* h)|d4/MLNlw{+d`l$ |xU!!l3I57f6)E 7vKB=/HJ)jvu&%'~iبM`M*_VP (fI@ uo@T:pky8.ږ3ՂT(/{\φ՞L?:9G!+Qk bI3.&ףcF  ׅ | f(!5f>PQfOy@Бjmv|^7ݻ׆$# Q s[ik=PLSְld楻$2yN͛w#DFga#q<& _n%mlAs%H;l^u+,qnOn q(]2U|nOO]l꯯dzW-:EHOCxގVw;{D{w\MV_B |W5y]#R|`(Y3^\Z7S CU| ϸR"j? ߺ=1!ʕuIoF}"];l)sWS#n7Xv~|37sOlEi~4 Vf~z1*f%IN}Wb`J'7rFU9_ex!ihDmJG+F@ֈ'39N5`Ч:=QC=}U{0m-@<UQMB+<IqДD_&O6~8-DxJP":fF%!~*ݱuoͩC_x#׆$#(Ɔupo0wKv@tKyV` JԜ>X0I M|GnPEsj^=^J2X;눧ꮝ'bD"P9mz2G2vM^x[n h+ 1oC~@qa/O?<6gr܈!!:Q@IN`p|Ga=ZhHJHB|]OEǼ:&s;sP}ij53Ono"I#C$9]~CR5i'6ֽ69VP^SRg:[IvvēJR>K^ yψQKwx;ZT<&ui`yy2sL^|Ucnh% k@%exWBG$Mx 9Z5i`3~{-g<>!vPNZ!q-VEFpnpw^. Tt<Ay%RGr}&7ȕZ =۷cIa!m-X$\ZtDDjkꝪka]C=hm9+Kia F*jF wĆ/Nv>nfB]ߌqQHߨZKJa1jߤ?JݳqbW@!iXd1D#NH:뷣 XP&A[% e0u eN Lи55I-@Hu4:*$=y|׊A_|:%9'gZ]h?2joHFdvF뷴|"|53sߚK |ì- [=8/p8{R9?]O[؝VP/!Ȉ!IPBʍ ๕:*A%SN#Y#UJ᪶қOoqɮ?㻝.\AZNgNWJq .VcCGJ:_Zϊl.QUh0A@bjlg&}toe-|cu+hig.|Z\Q%<8L'y{JDÔ>Ua*t+%:*WISNqʵrh2[ ܯyԺ&_}Cg/UP(I?lo[`u5Cԋ=r8((;g; nnY=4c0f({3 fS~oqz:mе+ۋ%Vڀ '7fQdƻ)(F:q˴ojH6 ~=ED{vMĵ7f$9,GVsN Kk˭U/O͞9f1)w1uk*T;^G]n]pⴗG"!=fy/TռsAzν'g!܉G޼ߧ(PVY_xTdᢌV}z'j$^|'#ቤe๕p.YJ4pqkQ̝橵,:4=qnk&r+ E@R4A3cXPV:j!i͜D*GyKVWOuy8!nh3SEEqn7[brz1 {'wpBs=OZa6x $ <i8~7Wj ҙbܞFm^{×1jaNT 㬨 4-L fK@8B.j`\T}C[[c'`t^-fJ"`Ju}7cP ̛l5CAt5ÂvJi>s:u})Mr$JC X +mPxKPdT*9OpԲ, yPJrןӣ[cjru3.w1dcZp?dLB{ ,lu/8guoٷVb۶粢hB8FqS ,F/<$,oM h.^k߼Cn5_(*g^ZKxA}Yڶ^A:?eì-)AA+w ;胅D;#jm4[+$Hgz>#j0}K+ ۚ\U3,(8xuN$WjTbg_+pV~KJ4)݇+J{[?eDSET K2fQT9qUQx,'uOhK`)HY@U)xbWk-Ikk}Ef$uROT*ny'8vq;""ѨNm¸"2iC{ʭ*nݣ Oi5Gt[NW~{-rfXK==#}E@7;MZS$#G_瘡A +#RkdC 6[ +"*t9EJʳCu`{G?vAlṔHO$r曝ԉq 3E̚Tbzc^X?2[`2;'v<7,$YB K9V뒓m/UXtFBi݂V1!i9Y|ªfCSscaR HJ?/';@ߜ5X JOH| [Z/ME@N[-/G/+ov${w*(ˤ=* $D5$XY|w e$j}z:>Vx޻nv:(X^nWf CC:R;MfBk?Aoogρ#%tNJxi7ry՞G:VU]٩E  ͹+/XS9My-CBUPS'PIi5AP26f&˶'H'e'dQFТICÃ>-qՉFv=#0BᔃC*ZHRU,(=~GSg)(DaE7عv?/n!__NiMQԶ]'2C) )ɓgN IdhjV*ёQ"K͋Ϝ!7FMjyOLp;{|vpRq'>]tvhپ@5{(+͛+i?N^Ykѥ>;W ^X '[ Jp4TR*"|nA E޸C}(Ð|hFV[V)CwUjGZǫ;i xkp5C;-6@44o u PȬҴD2,Xw]+CJL =}p#qx;]7 QcTZUg r7 NYkU\be7ق ͂#J.wc{1YY Kqi5Verlݹ_eܤ]ݻyy0ˆ׏s{ye=y\*+߽[HGIsN4(3- 4Ih=PPEwτfK]=%{ S22S`Bڍi ~RN͸!jR$ر| FZB*cp@tBO'T^ľx+Te2WҚ#N8)XYU#2g^<aX^/[Vu˲Ukvl=W+}}ǨNq3ٽbs?^bIM~( OPeC2d]-#/05&lqNg7gW]gl4#NR%MLT'jĸ;1L9[9*yyYv`nƗ"%T-ԙ Ľ8؛ YUMR~r?U%NԺXڱNҔ{lސpۉb+NJ&>N/ E0HuTanvHpP^; D ,\j4qt ѝ/oJ%33̩tN+/sW^3-.q#` jGQQ(y4ik jT[kjI^dq$E;BUfzsT22CwѪNpR,J'Yw. i.qay}qǁS]=#BsWZ%#i>v.f 6[&3A33ײ;о0I}Ek"$TKR7(-Υ;|l`)-7V$Er VK" [kص([ZK-W)u܅q8 #5%cڍ|ד/Ϩr6g_~"~psWVznBP#;di?Pl/>URZכ7w8I?SX/R3=s؛=n=[i0fps+NDXU~}(X6HNۭW5CB$~R xe ^Tq li@b)j0On@q'z&;8wi: n1|w8|줗XT-_ox? Y7R4gH%D, }ť] |Pghv#5wtu(bTWR_F* ^x6ovqyAqUOf,l6*pu6E2k2tdNWk%p0o+(L{ уsc'hYO 8wŌͷcbF ¥V1E e 'DoBi6x@~q۟s2fEhԪ`<|ₓSچ]t˺ZFF -I={/pvYM6`)GK\z`&rH.0gڜN^0OtF"k[Z |Vo!}c~O]\/GBƍSsr'V=~s EK+*{ `j#hY;&@aƭWچ]N2OIIdd:sWIb=r$_Nu@T SݽȚuܦ3nZ"LZc?Ldpgq &i Hچ&纚a臣S9*L擬ܳ75Nom"o@&XjE32UFxi:>c@rZY,%I |&Ō \/Z!ͲQzb-AAyy<ҭ[hϞ|{XAEQr7j$kTc¼<$TeinaK{f18A((i2rI5M94hLЭe]-#ӎ`29&lqN!n]1۠IҊ.NLt aҔajAkvv0aʁ2w{1-at Ll8OɽF:Gxf\|"^#5Z͝5 8+KrƿvNQn_"(Nj G *;8Im2N9c_5l6ǯ}^yc?I +Zw$uPڝh uu]K]x\DoJF%Fx5]{3s` H[J3lh4b =E`8U6tEc Y5X9k$+x ;%a:䣙gnV jD)}Õ_=K[oG)\:7j-"D AnϷs٪_Mf1@ q\wν pRE ʰۧmljK<~}yNwnAZop{? pY+t_\u]ç<z2.$U]lIb\;q`'=5DpfZ `KUm0? n):5{?B tuu3+UPG:s\[̻U={b)tXi-.j +k]Vm*ޢЖr~88N||{'L4o] Ϗ4o沈²,A6s@G!I3E\7 A[nD.VKO//)n!xIZWպOŇޔ;㑑iEg%D-NJny#x}-b+4JA ͸FY (N@Ζ$~;Fՙ$[4J'c2z ya.dӒ&cBS+C.‰˲<nIm2qa_r<їO\VȹKѯ$LMM,>{n0 IvnQBB>rgyE a lnJ;aU)s~ g}s5']<{ުZÞ#Euae]-#K5^Ht/?|o n>)q~8ІS vt8(p S`^)ӎ'SF 7VxR8sU/Sӎu*|lOE4eJ &L֞Қ7!\"7*utt4}ltfҝKm=~߼oo) Rr}w!~Ӷezl=Ͳ,MAQVe=w5 n׿y\Z^maYgʊñS* ᕵ=GN9 E6.1G"##AK&O wgAR'ְdQz3 %L|jIvի~*10rSF%EsaL|4SNT'n%#TrG`vͪf5q8Mx|o*7vҧ49D]8A\R9jBH4˲(:?S[')qDQC{yH4qƂ.W[IJ,0EQ0 B Z$IZ~ ,~:@voȑ^3ZO83󛕹=8jQwfs͖0 lX5( 5 ;&Iu@`аJ%]> L 3ш*!N2$*0äyCVQ+P :)W{>Ʒ fZqί8!g~{~5N K6ǮxЪ$w 3LF.tnahu.|8{E>L _f7w=<{@Z;Re@T4lJ)TIYꜧq ȱąDFb';e;㠮4 慝B{lgu1mbu=)a|1B);Զ kP1C݌~(Ư E=q_Vf+ܺ8g;o;7.#yٗތ]S5=:^Aoƭ??9}"-J^OZǃ<᥅3X_ע(?\qZCW+ɾ$?7`0 e~RU<_DO@_u8L.!ѭcw ς*RA3d} F\3$Jmح"-V[ vc=1ߊJ$XL ~lǣz" f`jOk[w@Xz]јA+q{̙avA~W}JYOr|.DХ `( f9|8u-P]KkG5 okGa)?(s,úI= Kl?M)Y-?\F`qsodyB4\:zkƻZTńz{P+$4c{!OL859okb| b=H꘨F1TE;ʿCQ#fix9{妊dI@,eVw?ȎRzͥdWWA]䔒VM_<}EFAtŨ;G{W1ajfmJZcS/`xØ"A1>wPM.RpkW5OE_F2єA0WU ?TAA=cjԡ2쐺k=ζNyMŷߞү1j )S44-v=TwqrUYUr_/y51q,g{BCkմ wފ'+ ZzpC#ΈzBMarqgus:b?w,yiȞ%BP-J4WO:e_>^B0_;$Te5ݧXrz+gG_{|IrG]{hiu@}bu_[4\VSgF:H#n__PTaI2 GPw͛]bmb{9Ï62YY_27) pՅϤxw2". ш;<ؓ.i;]gV-SppI%^/Gܾl[ַq-m{ \(j &}q̕ >^ҠzG̓\d;Q(Eĵh :$q,i|X!j-?'s[5ݏU@eQeos\'^)ݤ)P05s =IX^a Z99⪫WtcKOybonpNGOī)k_edJ Lez$KCB<ݼinǖzMFgEj=Pb`H^RՎ".m{/s2-"#Կ^`sH ӧֽo,]hEނl1n a1̴bҪ {:P<ī(AA$ME,IL/!IyЙ=0* o40eZςcsO˶a:uE;Oŧ3tZJ@k_Y8b𱛀n/=&mk<#g:["u }:Vd9lcmFv.Ѡ(C0n 5,ԩQX52iRppR$}y9gxZm}Nԟ~9T7{,55I` @r}|Yuf9Va6? 㜁ٸe뎽.w }if ԓ{DE$a4Sl6-%h8 J]}L B2kug fgEw| Z8P. nOO'\w5W3etNA:\Js>,$0{ZB3g΄u=./tM 2[Ő!=#=?b>(sϾv6+W*&3Ӿ`;6{6uag0ڂu ՝~~?6o%kXa>:#chB/ 2;#M<# &ՙ)4hֳ"mu D!>e5LfK+a%6g1zյƚ|AI6v6h2 *l~Z 17Y `ي/?p]6@}tE%CeYQ5pQ[<aCrK/ $B$˜?qD2AJ&4e2~[֒oo }`򒙵53UNl2!,('O7;qS["'0ǫJ*1y>:ԺfHQ//s߾#rBH@P( H[[7t|t,\<182eydh3U AQtI,MZt56[%َbC @@pߪnM=3+ΊIn5ĩ%Q7߻QkёXZ:鿾1o OS_M-g_{{# ? 0?b6(zގήގގΞ(tHQ9kV^M|N1k)K]ZGr§cp=:A(̤D{tKtomL`Rif懲 =p3~>\ ? F$K TD)W*%Kc[^H|m0>7%V1cE%m[Bq@,?Hܻ7nZ_?k?wdD}aG d?169";?%뗲,j`}]R(+2 ,Θ9wӦ={ݹAuvvvp 083p,DzAAyymg_}5ZSBō9% (~=o/O0Hvin̚2o ~[S:Ş  &i@dYq7c{^CK`A2`,e6dl68N Jjw{6u^UK}k)͛ f.`(f޾u1AF:Iʋ7$.NE,jso$HEn% [&/}羇)7_$I~-ucY`{h<99|^K/]AAY%IFHCHFO sO$I ?I$AP0"`a47`ML7ޘ{eع3R,{I2sBǪlB!RgQk)9-a4:{`F>1~j  )Ⱥ2Js 8܊VM7/p :Q d(R[| $g-h jU``$;0vg/߼}MޮN-rA38O4 $Ti?c dod j&3};Qqy6m58ZSŦZ}fCs~M[wklnNWb,-M|>z}><A y .:npy>^=^3<r  9ͨv"^B$-AM94%X dgdP㖋n13,oQ&D- `1֍ DI:HkA[I\ٔaBg1b(,Nu74Yrv[|Kryg^׶lgU?mKRa7'\ DQ d-Yoe*/w,[ɂ ="jLee9kVqvZBؿe9v&D;oI4:$(U+K.7B Q<34Ю"{?چ-1&r:嵸xJ2O KA"nAQ"[( Φg#,WL 1B@X>d*@4UTd.XPpW_m"rUܽ[h|mv5ncϰGaszNd2Yrm}[;Riy]R`U1uÙ^#- `0 MYLGZ<NL}*  ]2q|:F4Cw.gVE0x4~%_Z  oZSdY׉~G:ؘ*s?&Pb^ele{y>ʛӧN|Kq bl޾{]}ڟe?)SRӷeK2ϨFofHZGU9W!Y{:yo AKK9W+}}-+ L&`s#as,5o^&VJ`(w)jiͪa0LD؟TqlkZ5j$Y<~b9&MWϡ_'GcLXFGdsv.lKwo`{GOo\{/d.:LkPtQJ$sV͚+S5>ڴuDZ3VϞ9ue%MG9Zr6ҎߡW{e$ HnUc~Ґ[@HgdIm,r 2 AQ Cr0QZ5X#V#˲ `f[jۇvxJ2x |x ]7 q;%P$a6Dhprԋ j^x}U:i&ZND Bۜi{mvmߵ:O r  » IDATch|>y>ԙo[K11^yzr2.f.\qРwiڏZF-dP:Y: feY8&.?xv'Z8^:5#RڧfU̺%3ۃijvHƭƸ9-s#;܃O.':\}O XVf<\=XR(Ϸ=^w{|OO5s܁JځJ0rRYy/&\OܦzIfrL\ ` K[sWf[_^?8UwPe.u!Q( ^h2S`:#k4;Ʋf ҉LEYche}[r 4trtFgg>p P)Z$:GkP|ö sRS\Zi$NW`0 Bd]c{HPlxU[iE ̆83q &7/:߿|zp<tY+5lcr7S;OuHZd?PXבP 2v;coW|@y9,˒,KpɲǒGp6;! ʓN"zO<9Vq5ҿT7?d7"9AglhB1Q{܊z!ic>|sgX0̄Ffߘc{ހKKln7Ѵ"pBX!E#G Tdӟ9NҺ$|*i)1.Y.?znԺtc1Gcke&'˲_prB~ |OOsϗElZ؃gsM-~p0f(Q@J $2i"9z6%mu%`BX lKtt/>xڂ="wD_E޶S.8^M|~q+\ !}VJc:n˭1:_{.{W.w{G_y¤ n^߹ wMtL-iBk&.Y ;ޣ`0lZqSSKdYkcm6Dy/ 9#`eӋ'c po1x&鿟8 lK<'E=E lx!>|5ꊍeKz?~|- khΨ*d =U h4߱vܼfɌj!KS'^vҦagy[pyy\^^F[F绺.~|Q¨]ÞS^cǃy{ ^^Bs2{ f"b&Ev[56t-u}eŒI$##؋'SkS#>CKkؚ1&.?zfGr!kɻ.ٌ"K}qs>/$W昢O,Œ1 ]ofV/s+#ٶ[W/u"@@h؋ 9Bf߼^xsΏԞʫ ]tjM4`0R*]gWЮ =8 >jF'G? "K&ņwLޱ娉WUe h|9sg No|Nڄ #}ZkkD~bӻqcij)l*,g|{E9[zn˻jmǦT8!`0cSwx{h߈WS{&4&" HA)V%~v;W!uKZRM}0"M/"@e.q%>c. }&|jQkQZy&?q ٫\lH搴,>`KFڲ[q۳-$O7(TsZgMʠ I,CazZk   ݃]JNSMavZ < }exZ{m trDxH夅Ӛ^7i&g§!r0E%xz E*;+.%ݔ{#=$``Qe)/\Qz)ja4%jl,|A_0r {~%9I[$S 5Y, y VL`zP>V.X6JǠK:քp,uGppp7923uB)3|AHx?$,zv'NdCF'(jKd sgS`L'4gĬN!˴eHb33xᨵ>zTm7\α B2棘 DvYLۯY$׉KZhC IYj͆_L*iUAIC Eڃ܉t:uNw0(- I=5CkLRzzD@l#g6}]FHkN%!$Fi ˊV- 8CH4H>\rQnIPuORZc0 &̲\ʂJ/"X8Ip__\PZAsiA@DflI\4L:Iku*,vDڪkVSBk74Ez奊Oyy(EI%U53;oa0 &̳W]2V ^o_'ziobS(;݁aNjcq4sCXNsܨzM+G|Pk_W-։$}5K U_աe15S,̞ { ONA5` kJ.+ lu:ӟ!Os. %&ZEM+$;&8"V͚ZG65pA։"}5QL9XZ aX{͢SBJ:, H>W(2i6Rl) z1K~>sY0/]մrZ?>QkS^aDYgf,? PSdڬ;&9)Vj *f,1 PH>7_@[ꀐƩAQri9jcwf@2OK_'ZΫӒVj2J9GtSD6,H!AdJN-m-"WY1i`0 ud\e}w`^KԪ{߶WZ`2T!3W0r#n_\<) by>->˱48%;+Wdy IYI"I+idb5$_qcR, !f5+nNƛTZ"*8`&,o̱Ͷ?V {Лe`Lh3%p2l//4i2D%n7o_~E>.Csc^-i5YTָVQĔd@8ESgBkFڌ1 3!\ߧ~$P8o_mIk@8%G%d2yЫ9Ĝr&Z 3-C/jsӣ}v@p\ A I&2$1V/p)pl]Σi dBjS18]!YZDMMg;LTt>!Y6GcklnWeBj-V:>v%>CR䅇qw)b0w+\0 I""8fUcqcdžJ#pY} 33 plxժHr(KXdg9뻸QX+(tUS'$ Zu XZEŮ>E'<⌡դ(# jPZZϷvz܌h tfa0Q  0  Ztuڒ"r;,bǝo ;0kbzF߶W3e۩ D_@diQzI-_*q"f τqk ei=W_j64 [td utnuPqCqIrjS+j?~c//pH43u_z3%~eRJ F(򞂬[sީk^ڋ:Ԃܩ+,q0L{;Т)HH7ZV01cb,.V||y_oԹ K!ӯyeLcKNoRZ' }ƞC;I$Î2[7|1w| 0パ$nʱ}Z郑wZ|YVԝN],>'? *)SIt\=iY'!ZPZT^i )-j8pȯv-I@5$l8]?>S͊Q& CͷC1kz9~6Cc.D0L:@)z %^e]i7ywk|(m`{=_ +ܩ %Pɠ%NKS߬kIim1q9%ͤY XZhϝ7o-{\t˖Mbi]K6>-.jŋaz-=Ι*6jG֟D Za0b"fu&Up0C@f^l3_uvEЩ@38Ќk%(! R-aHK#k<Օ+|sۇaFZM)T8j7u(nv=O-&Akua'ʽlM/Tjz٘>bLc04_ȯ>+ {80f kΝtu./ķd>Al$r$dl vn‹&580Tn]?fА3Rߩ%wAz]H0zOF'p x1RiA"e$II! &dqGF($n׮7sj* < jrg>D/Ԁ˧{ByQ3KO&O6>-==ؓ@=-l_Ɓ֔ivWFW2 s:$NJKL8M%!\{xԣ>31#gYLG1C,ͱ4$:$ahu~BQ?t{=ΠfhHe {?p6OLL$WgA.l<6CӋ3ଲdJ 6K\Zm=,l|x f&j9VZmbO(f>I檪Q'9dYTJ@َ33y[nMU BH%)6SVaР77+Ը~zO͙g07F([rވSx;8ގr)%n b9'iEĴ"k:#xM[ײK'КI+i!bSH+_> :6)u;$,A5!<Ր5{B剓H[$\EInneI3Kn 0;ӚiQSr{vu?E6f|G* 83NL1č9kۇܯw"/FK'+g’ Sd<~\%v`,j,}PxT<"5tQ,%G-BaӲq'&7bB_b-[ɼ z4V# IDATt,YB07IL?>;p{GeYI84eYdYx_uF0 `[=Vg 9w(=ܧA,U}/YX\!= ,9so i%$B)u $E;}M=<ƾ|)Mq8oMC6fn+DځK}((ɫ(Υ&Zfbr8v"XzԊƶޡ0{puUqA҆IE:^!͝B'P k5)/!//x s9$@#@6#,$ 듴jQX@ᛢna,.'qՕr&׏oacEX E j࿭;FQ\]50I9BJ`1ͪv:v#]CUd5$@Pk6 lr4*p X~P?;mc9V|H%JZ'!`pcrxXαΝA_x~oD?)n")W!I}\qSٽ E&ߐ,yVʨI[3j^GfqL*./I0]]0i\hG{jP Z&IRf`Z츧 A׆zFEy<*s3*e/ "MF2j$^,{])'7xPH,C 8YI$,)H \R$=qd+>Rͷ|*Wc6g7N4QFk?lLJc &ے$l եVH]3f|L0I0jo5uwG6 ]~GୃhA%|)H 6ǐ JZ'\k R$YXBb _4

q@$A pQvgyӦ@X,IGLSR2TCvݷ(4fm0ϟQES $E4,PZ0݇N 4ɕIf\(d/eUuȽY& f+f%, bxPdiVHVZLKL܁8fgXGP]]>574]RR(rF"c6>U$C~!Mz٣;oz%Lb{Ll3&9|Jmל| )cA\qUv1}oDFJgS[ ʩ0'O|P"!DqV:ͷ0LI3Z 2z}Cr&ŭI̟Qiu[`RE*՞J;a\zVax<*i1̴z»M.5/REx]kW&sIXR&S[S-${K{מAm@$Fa$j5LBI!fLzJ jQ4$lZV0.V/CqѰ8**Jxzڀ1c}[%3 nni6díjkʩО*0ZҤ8F iKU0L[\njQqj%}E*Q$9,?`0!$r(-(XMIj[_I`0$#_ԖSDt;G^@?|miLJ {Ca.NiJ&Hc-ImJpV$t,Iъ*Krq- fjif=)nhr}oIbI2 5~SmwC1?#4I) Q? "IC9JnS"U } "Z 2Rf0ܿY(Mx0C l=lɥxIѯ4¤ $Kl%6Y>d+a1$YhcƎ?EZB`.&=~ɪ!k2M**J $+c0!$$!SBiBH@u+ֹͶYLW!긩wKkJ9ŎdmrmtFr;>0\:)I ,wNcX*],OIkY-s;Ƌ;N`Hd0 n{(eے<  z>K03,Vۃ˄Yc0$F?y_i= BHCxp;D C>r0H2ng!QĠƎ֪ ֨5vOQԖTPxC,^(=}%EavU|*7Ɯj%θq8*B!zM\$Zm(P ´UjRR* B+SGRLm4Esv9U[[:Q8٘ iC͘`+5* Y>Q-;.iS?TkNۋ0B,(H}8k?C%b%9ly;}1˶V/ߪ!15qJf)!KIrQoL)LZ`KA9%)SgwA'vrblSIx"gT6K>j}TIeY ~xv?pftzhW A1$KX${uyBclܸ59,lV$ 'q'x6vMzBti-Ƨ#!"WHIJtoP !Bx1c=q> !e˙1b N f8QJ-OqrLC‰J)O'=Fp@vˢu7(x|7 (i5xxz]^xzh ?m??B :c۳*qB8>T ƫ2[EcȤuF_pAfB _0%ԵvyoQk!k?7ߊ#]S<ȒX_) XcTg܎Ys8݂]k_ RhK뾄RjAՕF(nNW0,Zɍ~(VI :NX>QʽZmfTMӊMwd.`Wg8>g,yIK VFU%jnť53H Jz>ƥuHO]D]fGߕfYԓ~8J+Ï1U҅-N*Y{3]P0.(³`yҾ  +X3ki@B-K"%69!& eh&Z1'wTM!!^ <iv}MgUWOO+fNK%n̖%̲@[Қ's8+\}r%HQ7G}AH54∑8NJ?gÊuI$u&Ӽ*sTw0uZղnЕΖ`g2/pgPyi g[g3נ\3!9aP43d-L?`>Á%4Uʲ,ba%r5 HT\5c5$ă#(*KՖHq Bh$oV bA-볊Qd9`>'~!n" 'ڞE>c[*˫] 0< w=FuGe~ٽL;aEyB83@$.$f5jjPԏ 8V0K'#Df#-/5pBg3sa|:Zcnts>#8EkU蔕`v=0mxB838$Nz)WԚ-#sXlN{7R`U ]my-BQRZ8=$Q>"Q ;IYsQFfj8]\r2(kfa5QB)X]!,H0U̲dĽFeMfŚ"{6@đIx$_ b{yBfY>7[aaŬ$"ɲԢ54%Կq8dC=Pߵb̊-QRqYQ`K2)`T1#j]賎V8ֽl%)KHި)Ba9Zc>ih!EQf{w7*^R\aqX7vm8LCeCǮaRJiEզY(ҴB(gJv~ƉJxDUFM 8[,iۏh/`8 N^6-1zn53$vfHQ#8ɮVdTmFkHʭ7RRYD0*fYU6dGq(0 2BsQV$Mg7$Ib~McT8.[MIh芓օlcLZξ*`Ȓlg9Kj٨OȓrWq5,Hg4왆{0ji9ڴ/3"xȤ5#Ihg`Zgru`rq1nFTB$*$KRs, ]5tDqFA56 K.7 2­q,fi|Ou:'fFM)u9#5l"KRrf90B"+$Ib1ƚ"kL)F BrǻIU]SJYqi~(n*7 N-[N5-a4SF5e+6^'aGq"=3|P~2WfYjɦd.BEnwm6l xn0SB\RrI!tlQf'v^iڴ6,L)1pY?MJqRfi[l@6{eX3kalX[3#jV=!d Weݰ Pַ *c?hmYΙ&@}_@n:IHj94E^30mge%DQQCEך(IB):{6}PDRr=dBk"j}>#8QWN8#4~_)n>k>®6$ּ]Ƀ3AԺS;M cM]lW Y@)VQe=һ Mٲ )rFsZaTx*mv$Q ”łs8d*pui*K q lpr%?H!MZ/+!/  {9;`6GZ3Z䝥]FoW{WoǞ]z^k'j09jjͪm lisb U0J%&8o[wgA)lf*/qEql9TߢL(*}z}=B(î Ma rTcWe+{2YK&!bS3]~i8ÌBi efLWri= =bvҠ-vO"&hhC3UKƾxn-oݺ!i~Nf5yrszf|vô-QF0@#F=b&I5,/;(.53j݈m]Z,۞ԯHqpZ@S,ѻJ2ũj^EuCk7DZWO0{c\:?^}cםneVgǎm_Jv-}™"޻o&'ɋJ37 OqtC ɮd$ e 2f 5a5NuJ!C2,{StkCPU!`@psKs:PH(M($@;3YuEwTҳ'dqݒJi,I<㔅"K"V%ח_XBHDYeAGQAMZtUSM` ?jM(Y> ,I$vbÙ4CqVuv9wFBN6ERhm7w#~Gp?}c9z]UD$0+Lؗ(% d›;7AT_u[&qE߲-;!7qz+WsFW5vQk1B(UQH/!pfS>[5RWfvނZ%ۄ|fdaL6ioh ܳIIdϿ[lYK.C@HBHDHSH7s0XM) ) X6icV告 NV˫r yuBN3NnB)If>*ELaʼn,wطilI8?SCЩTY[S39BX݅Ki Fq[ !.gEdi#N^:'TүUid >VS0p[4$vz۩yFZwX11_bI3nM}QNcPUV!1I07jX8qIk*;;;~]B?  \/pC ( *"8N$IRͬ&RJ ! %GwJSŊz %f@q<Xv=klE$%8@j~{8I83aD~z~ IDATAT0?GWq㷘G@)橥r ! c!5(nXiF0bf؇wyZCT[~R|Fr?zVnuwv㗣RˣQȋ}AiS9̤'/+ YŎ jG*5e>HFk7+/p$F|7 UAZEY+!q,A΢ve 6N?GQa&Qz&bUJ{\j8`Uyj/]*[nzn?T/a՟REvƊZ/m^= wM:YUh}> IB2Qy?Ӝ*[$.t8 X \7u@&2*Њe+u!l9]e即 XNV) eY o)i'nBdguX]+jjkjL,uÌr{ifAG$q0HkV> YSB82NM _ {8I>+?'nJm_Xc*ncCDh lV ׾zp;?0R/X;\qڕ]gJv!oΡ0tՈE!ٶ] G.%JMbI 9V6;SVNMYZKtfh¡IyHcs/b%Mu.,uDѮwÿx>gx屈$\. (YqMn9p JbiOBmw4d]-R-[N!EL]+Ѽ9>->7FѼY9CS:`nv;\PmP,A'NHrk&71ɛ٧tHD`z~xAvvAySk"'BSjtNpFfY,LծxoI:Ժ/tKPHkVBxqW+@ǘ=}V{/?S2ڍ[ј$0*QIɬlSOKsOްqUz? \?"RcC֖9Ws7yS+*( ` HYM]-SA Y;S%NQrk WCS;9EQ(r|H g]?kMWMm.ZBΨ!ڮY^dO7ĒLim.#E:Udk;N[ iLo[Ժ-]'4zjwJVzi׸MW'@'!p^F(/ >Tz6xPt*9y.;B$Vj%c#@" CSEAהJͿ"U)&OZ;^`>˩ B3L:IE 9":Ur5 n7sIY:WAr/BZHԚ[Νzq[w87:9~Wm8v >u3i{B G$a8 R_/^pyl\ri}m \; yќwV$MjLUdUY%IDIEQAv F!G$h!9 lFAkq!]GrR|cP(LY:&!  $$ʒȒ*˪"kGԏ08(vS1㱂z2ȣ#&cv LŕQFrTÙ?i'PA}\qhd眎^lןYtFQ=p|Ժ-N橧Lv84S˛nޱaUrV,S)1Җ- VVk\TB@hDVB¡[K}}xF">;Ln=X&Ō0_+wC!=3Kͭt4 &rfB6 {)$Za+mٮ(F/Չ(RJP0OkC)C]rAxY 9]zXű\⴫;Ƹkݚ)+\Zcy |#b_.{biDf | dxiSZbx[=*|>wiS;om3?[ش;ec/pn}n~W~~D4֏%"d x B(/ ^BZπ+Qs.8u+{>k_$qrBC5>^ZOGq aն H|\"Y0? 8n8?o'^ܽpw(r cC@mI XThaY،m;*UKF y/V_Ospk00{Df N Č[;67x֒(vm/8oj뇌-BHvG mXm/$J8N͐ ʛڎTYU$\7hiRU-(4&ʖ=|US}7l1QOje1y nw|Mx K~?B>}m;L ]u|U?1 (z_1' nHT$+.N3Ns]?cm8j _Wurȑm7~l (!bCxhͼ}#nj BN\|"],Z-LKGs>'֬uŴ( ӑsPFQv6b xH4Ga됮w|dhX~$ojR3.ocwNWWEat$/a8kM >9] 3qm7ed`(;{vϦE% ^Z' ,n 1 QΕUS ߸vﯻ22&7^sRF|޶EhD!=chԵo',}qk_nNh3 .P3dZKbIk=tkˆf80ێNM2Hƃ_a.b2ޏ f}b4?>+䌜險*Ⱥ7BnXa$ojܵ gPF79O['_ؿ0Q ^nBnڗR35g^ܿw>pOYfZS>0^qUb9n(^ԭO>YCzұ^iz+- &a`%Wc7 6שfʙy$~;8XdTg$ $V:}u7'070(,Yը7x ˆeU%BFiGD:8!smCd'҂u;p(+3U,[1J) pTiW?A)m]$O+Ų5g}XJ"Bou[U0|tbJos Om;=*>aqœpx'O \ U)}shN_4ӾUݬ[ 4d,RLR0VqUj1kd8 Kk b(> r ={Rвn Rs8}KLYhݯfs%ڢwZ[J!!߽l{oyf Iţ):?~?AYc[` TTApM'l,}‚W}m?{hr:%ʵG'=%0 :q;T(-ӘQ #z"#| )r{=$)[ySOIr{\Pe}bBWy^T>ed'%{w-!S%s5gD^{xF{bfughoH0KkQeڢOIvi`Ea2|ZAS=ʁdr&2[T9̜N#x雮 é;P ߺ_è #Y5G3>᱓O-*Fnqu+'F3Zrǫ\^ޖ-Cŏ`Jq603YaZJfʲ$$RoK" B(6_!(aA+I qhFqCӢ $d)e]R5}$Q #@޿|b՟|&^|~&|J_>{r<ɛi@ļ^ ?~2`c뫕3վ3 'gU,t?z$~Gf 闩@sLYN{ZwA%gWO;{X}%3=Z ଷ%眲l,=]垩tYmw. :OF8l9SRWfW,]!|!,"Oq(A-/)!7-!R!rzk"Qk_|?M]͛hUNŐ f)"|ʕ/sW2\ǾsFDZ>3#x*0|'wSI] ; 5n[u0ط3vz0J.+]+ug;^ӼUuW> ICD2X99ႀb#Jw'!1I|70(6؞Y͒&GYE]iRQWzArJ,_0d4sF -}@bRJ۔RBhg(SBN+A쑼7uE1ƪ" ȏ/(|*3KNmmĻ`f;~|f}vzZ򽙦bu}Kk9*~Z=.˖e˖-;6M|޼)/I.l`l!˶eH:mcñ^{sfFg^3y~Ot]s,3dCXZyΤ5}OLg?=ŵF[Z6>Nq.LbMOwJ}} IDAT \_)%ÙBsxdX;5V]J$eUc/k6u']*bAX mmDQWA bKɸJ鐟m֬w\o6WOg SL0+f\qz6?5[槳BŴ8^tX"BLWS iem}`_~9ƮFeHcZﺑ''C`ύs95D4D<;J`Lcvlzþ'#RtMpN6yu-_akEc?7xˊ~߭~dZ!(=q)g^˯ ڥ;S>.eTbE.DDAH CS&\{6_t@+5ז+k\ 誒JKn#bB=1A9Zzby'+xRm#M\vȻ*y!8/p^[pLMj =J_~Q|Թ|Yo1*-qٙddvH}RkN@?]hNGC$j"G,]v+Zn9S5-ܫ7y+czߵ8E䗾~ɹkZ=:$bz=>3νߗYBFĭv6ݴ>ƘxFQnؼ?[LCԫ'?-9;Rj$UTExsT$[E?A)R׌1*@CŒBҰ($r#C)`1V B!DA("Ine+!(eŒYLQ_M04s@>0[ݺo_LOEWl'\Vf.kFR/o5/4S{3Čs(g8z"!&_&  2U?eqTѴ$Ttu]cLs .? %KHQF{U}7]r^Y{>m61J_E& Dtf#o&=mK1(w]M-cƾz@WLzȪ"S\WW\J eBNf[1! /1)V0V` Pp=ln"TZZ!+€UeSH(\2$ [B(X] lيO_$+tzn.9_ܩ|\goY|̻S4/kWd5QKйq\3Ǧ}ߕ5}޸%!~zeSyKҤ(OdfQn=TP3(ÿsG x/}?Ont ⟽]ɚ9.4S)^362#qy}I31eK$wY?@Tmkhj] ØSB`85̋Ԫ&-wT}AהtHuMPW3Jt{Yptk5oPj\py-z g}^pGu5w%7#ie/~|@;tyN(.c@U'_}~?o.Ɨ:qJёtđnyBY@t&ū_ n%}/c~5P$~]wR:cNkN֥|nP=Ts}7Ipnr(+VᄱorLA:46GAX :/~VN00>PZoETk@uMC`.6* Q#]<{A_}7-֏7\Ue}aɻF2CJod9wyi'z9G9:oiUĢO7֨5IcBʁP ȯ^嘦~.#qc?{b t&I埿fi쬺`.9//RzxP|,?z31H/O6Vx- 4L@KE\ˮ]Y!:",ה#{,C !;WK[zҝlMuAp0 _W#z'9ӉTЭPpd(;G],bSEU't=GbӅw[jO}{?kX|7,3϶ޯ6b➽!ly`)iDw\۔~o*)];%^Z|_j M@E=GK!Rēe /p8ekQNxlN3We>M]~'מ-lENwiT=OsoSlûu6/,zDzW_/j}s[22y!F0OrÌ׍O|[r~-^,xĎ|V9|=B0/'p(.<-vnKDjg(cK(V\)+;M{Bfyᘡ THdLOōt"3%cvE9o8݀胀J|~70C$WjQwT`K(8#|H grU[Dm:_Nˣ֋Vk=5FBs m{*ܾv&&?'VRqu+m?3[fq5-Z%.~'f|7 i( Yw<֖wA-{>`!$ B,0fr:me믦 BP( (T?*XI1rf%U _1^Rڎt Rwߤ4,w5Nf XO LcioXThݵtك dn𶋀 u@}J4'gN׸.\Ⱥx̢f1(m{peS1wS^EnxwSK@yΑּȲڕb/n<RL/$S6xXŲgr\lZ&q=O/ Z-} cb9Rq tPiv݂GW׼>#o-yş>@L@?ZN3~T?4Ǟ-O<9_qZpcB>DsJJk^j{%0Jw2ȗ* uX+X7[ԒI%[h=ǣ[v|{%NNũN&m' 1&=yLf\t}d^SUYNʁ89ҭҺy1fMtm,ю]-J*Π604]=O-0KES{ap_rU:6֣Gz]V@5~c;oy"kh [%W^ blڎ9WX2[Ih'CB,UP˦U(!)xܾ0{$BM孹hu qG<1Ûg~VӰ}`2?Η?sl}/}C?27R4 i+zMk8 DҺkVim(nGPq,z*&}z{ӣ~{P׭ R薝!Z^1gላ ! \;zed^fb %}w޸h3A\Ρ-J! xtM떇Q)mC'wrOTeR^cf*8RF+ӗ`i./EY$酔RA}l>z.Px/' ʒPUz+6e5wb3!骦ʶ9/H~,Y,"XΟ:{ho.~"[|h0PD`rW+qs-9tӅoMn`oM7f8P&Zr e0) Mcr+eKhGҭܨu;R:j]E%G5vkGіy9-ǎ0J(C89[x~(=l{@ 6uK!"Z\vt׵q=_V\XBLoAئfŴk*bɲ^i\/_J͓ 0vJd_56mX6u*D_@Дt5%|HN*r*a uȕ#_;E *wp'uKVuc ~?y׬Ze;pEcjRWZ1pdlNȺ&Ә|u¿hĒӭ Z6$wJ<(E I\pk<8v>[v/}C095_w*;E O @?|zg-v=/8'|8S}DQ/B"HϡXP\V )Wټ"KbGo֪:}UzȒ`^Mp H +0!eX*z= |SՒfM]ێ1 b[,K$ Ȳ[WRS5wM:Zz%L~-=8S8ҐCilZ-lZ-8Iv%$[5՚mK|!\ }00KgˍOCDILCIcخbv-w(i{JǹYͳɅm)S5dODÐNֹl{J6%e}N淿t³ܹm;1<87׆REfќ̪b͠dɘRſlSvz@ ˲Ɗ=CZN53YjȒ(K"cL!qC+k !$H(K\o3ɇ 8K8P2Sx,W5E+НHW>=@eeWeO]G乣uGh뉺w] Su3Q()jAЭ($~͠NV֓.}_p>zFL[®1vM$n$.poO>-ѧw^vڲimA o '2JmEtT3 |cR 5 1^lֲ]:{[B7$MreIl{Ųr=g  cL4b-Ltu=T3%QĀg(/V=vc_#}i{>l:zljȩInVYƕƕ½׀NGo^"O샫.Xk*' k\Ϋ/e/{Վ33!|%jZmRWEL/?!d΅O]ӎE(cSk]ؾzs3QE.ߔ~b&oQ4܉NpՕM#:ve3MN\XbYmv\Xt,IF{BA_5`ejUoJ8!' J`XUmOjla:r"ʉ1-$A]Uq= M3NW&ݝ>ˆ!c uCoa*&,‹׈A>(F\t 6 B?"sb,oT#*=B:_kt ؆yWҢ/E߫^PX·w{g>uZ~*n'fkt4J{a>$;zwcӇΆ1VXQIJfrʦZm+ mcY[,[M~jBhb|FI3X2n$FßTT@oGFzT"d<`'Rj5idIKGC2ٔ~^vsj3.|KVzI nhUN54wքp:jȖ%h[9̃|_`ާ{[V <O^kQ"NvTt+qE6"s>_XNЍٟ={Z(xzV}.8f*~ IDATf}r7%N[3z|7 fL ?-'|zD? Ywi:?́JƍDt2/Vj `ʨR (!!DA!c2F)<#qC^DU*(Rkۮ)u к$eIKK~@B Q}1]W l)r, MyYU/uƞٱjvLhoGު=`F c Waq;AqwA HiЦU†H dCTH[F)'\b}I]v`ӏ<8>‰܇z{n״Ǟ?;/6ջÈ`jf-%Dak|U: 5{n:@] ߾٫SXBWWc /6LP $jIƋJp{*IZz.S][6-Y>iuaTLP`& lC\$Q c`.ws[^%|EG'R ?ۅ N_%$T wTYr2G?vS,oGvGvp^Npa賕S7uF$#"֙% #QdYsYǥJ O~^o=O|nbcX7/m{շߊ[Ts[u[|wu;G*/k.Xƫ.%Lڶ#~,i(CDt+U4UZ|˂J'cŲi/$%FpkA&FŲ+f`5Ųk骊*˶+t2+ |.Py^2yg2(X ~}G¸,g;5~a|\ ދś`hE7:z-snNw}V׬6֏ U{aIwGDt P/HSt A\uSWy=bY/([?|y%W]pv@8Љm{ᱧ{Yw߅d f$$Ž7A F2_msϺc99;->Ԏwں5^(%´ !񘮩v9nXʖrh:VÈ$ ecBZACS˦#øbڱ *V*KSF#WY><߰4.?}7gnrd'x;ivp) 8o]V8p*:"WZ/CxHkP9WI @{'-\,^s??]rc},d܉}yt1`8)S#l&;R?ϕ"fV$?$2-!R6ViGC,c]3Z@(0[L/&(lZi9m{@] ]m_zQds\7j\EK4tMs׭۶clߊ113cH:XĮt x%ڼZ^8*[" z5J蔰(zrI}vrg^fζ=y3ˑn-.:+鍊2A5^"_5g&*QOa!g+Z\8bQ,%);GaVAtMSi[G1]YӻNŋ%ᤫTJf_ (*}xB$$_{CN=eqn;@6yk엻ཧ¦p? 2z;h9t4 Σ[; CE_A -a}?^u<Ȅa]W5ʦ3NwD#0V!t[O .SoH>v4RIC@vtR' Gl5uѱJ@(q]kU/OƍUk(cJ ;(aP!_ K?: X\uEEtGpk6[.m,~nx] ڮ3i@[cD/__6)hv6KITe-wO>`e1.xk$E~vb(nߜ0R!4rUѿ:p"絠#! vtMTb=0Bj7DAZ0&9E|")951tUB (8DŽPʂDAT$Q;gNW{M 07tEyREt<(\x+Fұ5_O('oţ)D }WZGD5gzKk^W ? U>~-&M<[z^OavwߊZ!9VkgYG+ f銡Դ` iՆJ1&RB)FYu5BH(]*dYʤbRF)5U0-FM[U&.ƔTJC$Il,c,I+x3k.;tn}Ϭf?=ֶxZc>H%͎ @xyϑ$t9. PЭPIE: V`'2`-7^q9o;<1V$ OХI#R5#lC@?a]BA@ $c!ADL2>+NQ!!cxSJ -`B'T$"2&W\]u@u_/t7ɏZN?®heeK'wO=_eGP[yW)Yp丸ݦ!6N觿蠈HDKV\aWZUM|(qe)j%\ukgwf c7GЎZ,kMLn!E p[tƒSp$ސ1JK]،C% 0Kg+" yI89Z6;00.WlM ]m2=Un[=pݪc;wpI˞(d>,~U;ty:~y:Ųdn)9JHHu?Twq1_jP>L=--t\ HBҚ,_TFgr\ߑx]}%Wf+Q>"sHZ7MQ^Ap`}z`Զ|]Z[ (:]&ׄhh8oZxŲU%$]@~q1UM?XŴM (7xe;ku4DB+lS9:{Y6cv?ؗ "wх[+5OZ[d~AiPK| S%ρ{(M8=U"Vt^[5`@u>-[c1B{$iB%$)Z4,g2}٥;;vSS/VFFOw@&r;(k MGzn]5WR(BxYҸ,ilJ\lSRFHK¨:  ?VIq\+Sl#}M$MtqWi;[Tnj 4q oe,ziA 0VÜ^ID.KK՚n7qIudG5dYp+e\u0eEK:W RYBc'|Q: 4Uv\pʦ%5Zcvm^Brr:tlA˘c1!Y@E4UC}86i r1BhUK rgGb-P5E=Y0*iBIkƘyilA@ 4O;Y1646/?B~'LOaԹyȤ>W}p_|CؚX;-|@:T(קcxcDWJM9<|ҚNJkPEMs }'!zqٿ DB/H߲*={W%IcK dYm+T3MŵPJsr:y&Bzdَk;(*.U,n/kQbL*,}}!τ1U &CY%r܀]JM,B(ZbE!1DA8UkV<'}1W,K/t7Oe9*j9N+t._j9Ҏ1('g@eTr&zPTKo6'~ Yc/#$exLP,5uuhPΰl~Ջ )UlPU8*kBiVHR̷(cZH<qC㝄 c?PBIl,h]Sܼᖫ/Τuqqkgeгr RP \ēD (35ՉQkM!t$_GSkùz@kChaF(Q06"&N,~mɓ6Ĵ(d<B o̴\1/@"?<>z /I]tQ;(#뿺dYQ,6] a,AѤ3LYPv{Y#~o4jJDQ9 YպmY1mɘcQb٬jYZh-^׾! (2/ya>5a ԌKI-qq)b<5z54fex s ?4}!%*6==%Uiai1Ԛk4, ]/7,^fݙY-33p=AYV ymǦ7ĆHUU2 $ٔyIuA+adҞ=T,x"k@YyfZ($bZbXXU%fh]XN50f "Ḟ6:0U"/Yh,I;. @A@vnM(>U`> Ɏ=8Z;x|+ _q]cpC;whk 8Һ?0j͋'-0-֓Zspa+t9H)QY}vt\l[nh]w$I$O!RJ#RB$@Q$w%0VDEP4WY\wTח#«Ċ,.ՈUu]Rv\ӉXoI'0ne5EWeö7:#[ jZ.n-\@25նLnߙNM|`_C˲y㺳Vm۹TN^.[ Xē,'d_8&QԺW| sϱ`۵;XQ@A@$dĔxBMdJ%M۔eM"'"-|>@E1=UEiQ1;ҤPxLUV{2'cz*nFW}ҍ.xΪeO*81kVekZ/Ņkx#F"{w2oO(Dl<|X_G' B A{`89 $ee;|zAs)s)v-=F*1e?l`߼=DAư!PE_'3LȒ,=y>&BQU;-ØR֤Y!s<uߑ.Ƅ`BB twhU %׵p Ϊ+F{E[}k2l}7fg8DЊ4'<'z cU@<5_9wD#QkɘY=^3*ض[GՈ80H#&ƔUy$ iLǕy[3F n YWK%Y1cq˦5/0*T%^Ji7h0i3,W(giَ1 eRInhU7rET$GԇS[>EI+H7kj3儬WX6zyx](l+,cvIF3^V]dOTF㥄Rcq{oRؒd@F$M5ta^>S2/IZ kV,ص˜?+=U(=k0-w:tuiNz$Єpt:Z=[֔3q)_޷/vً0 ⺌1Q=j"_6z- T%7 yh^L`0altA=}BlPkd#"/| 'ێ۰q=vC o(&%Q$rzZ,U7,O(-Vե)ekZ!bu [fkÉ"HBCHiڵ+FS35_P_'=U:]z#zGk5>mjsMqZé q|_!z?wn|ƚ5\PDCJ⅒;"6mg8sǟ?"Ke"g;XlMcŴUEn̅K/J1Yi-I)ȊԖ˜JcR(qC 騌 )US9=Yt{K3.x'gˉk/xr*m0v>Ќ5 - uonl j$y pD/i?qn3-N@zMZ_`*nf٧ < F#9cm]ooN/x""~6xMq[UL5m;7pE(1o)aYDńBhxW'Ô9$|J %SU"vJ帎bdICt |BsMGK [M~åMx /`>/?slsxm_" o_*S_ђid"zP8ϹYn1 LNDi ZB,?Bhh,U9)UȊ,ɒPSm.ϝ+nwZC5wTDLuqy&m,E5H:Ct{֊75Gz,g4?ϕ/)rO:2m|agjj13q]ϬNFD4.Y F㫇Lj3nu-˦K-BPN\+<P/"11D]t0(fqnw10E,!d;27$=<#μRUv1p ׇeevX>6ƌ {ljWU:Uϸw*ZHEޑU7C}P/D7Q{jT^+뉟6׫qkiVZ#ppdbz%[fBOaf2wם6S&UJpZy曦HnՔ, N/elmv8o@82LF t2)JH(=kc ]< x_=LJ< бց{>7mпsE &ߘaA%bXQaeiѯNCthkDZfiZh"u)R!@%QpLL6-&yU$qF `UEVd)_2b/ ] _AJ9sn `rtz0Ƈ&kZ1>7<}ܵ[.TE\7|%t^oh4PhgiWF賩˗}c?QG1C ݹ&bfctf?Z89xʧʊ,1ꆡɔB(PtjbZ5/yAsyͩ4-N׍'WМ0F!OU|bYL 0-f1FB$A^ŦĺnMwߧ?8vݚyu~S(=yxbrt;n/h,74|Al_Xpr0_oGzAMēC}9P_Ld2KnwQNpLY&&{8b+z4zaayiGl䨀Jq7j醓W6\7|Ac H'朧9HWTċR41MKUT\J Μ j`Vj;\ (7SSO"+$ 'XsZ1M&7t68Sm}cvqU7jO=zŚ!PoOgPM;͕SS`e0l?4Efh{Zd!{SZk*ô *!Sj+ZY#!IJxр3ӟsnV&OeiګҲ sQB;B53>B(4鍘%Kb_p½&G:;fVK,K ThS|J4rZ!1-kaect%}@oɑH8Ofv b_ [_&_Z#:ES^Mf>-+3u=xU=pc a 7驩?+_d_|O7Ef, ,V\A3-sD=19OsN *S+sB Ѵcfkҙ=<$Lpg_wc :8s&5Bȧ]ZcC-l< Ƹ+<0>hXV4*a˫>"MC~MK;_t3!O' iɫqס(͛ljkֺc,# 3^ye/>K@OaC<ޔ֔R0qQj\^u1^µ[U.C0fq<˧yMM5LqЯz-jF95S*g(9ݘ(X5B(. <𱉑t(%C}H8+aoZ41>WJ"p31D)]ʣeq{EZc΄|g٤r禧CǏ{Nsf#pCW~j0}M! Gsn_?snqn0w6#I-U  D;W(4NJk0,vmGpz,Ve]`rwFI4p2 7 -Q+Bd{m#Qn;"?ȉW$M $RCP.鱡>8B=F{wSA_"BgBff27G[k!ܲ KL?^(O F~tu;87-K,òL,,۾,vx_KkXDI8G)բȒ.b9B.EA*R.hMsná܀1e1VHk"KceŒkO>rlEaexrB(s{90 ѡޕCdm]A's|Fz2{>1.V66<|uoā)L3n؆bѱ{+jò)!GhoJkB, $b,ZDӍ*K>1Š$|"K@ vڶ>j!`Y#FPF")%ttgEL =vH_KƸ#-;mK Bo9,dI鏧2iW3wGBUn{KZ#"p/;qB!ujo\bg:1 $~'>iJ@FME]/qimݖI$0rQ,I5^ͫ]z`*RЯHnO5*߷KC't6+hiRB*VuHeL>/hI )m& i2abim# H(*TDIUE;z`ᓇ"r*^& u Z^ q(%}DI] +Ha{4O >:{3N'>9?7r`e &ˆDB)hl n_bKo% 99@Y0u˪aJcȢ"a4\Y1ƽl'v/r,^OF5 t6q9tC I>M,tƖi&RUK)pKE5D:z"-T"MM}QAWCn\.z41=29:PR\ >w$XOkbk;I |~68}+Cc{u \􍍙?T~qf#Mȴ( GH,]H i10 V֍ș\yPBa B+5¡-h:4P,pmf2-F,iȒ*$ UE8a(Β/havE{&/a~À sfauzn٩u֭O=49<96(s8~h\Ӎw>{jr15|A41UUURe yA3-_8G"ʕ%Qx걓x\+mڴv5F]c4a,9fp\/{[IcD /~oˮ[*#T0rӿTFʽ&غac+JR$h:50¢HEADڢSO"tT<%XUHZ&W( #cYdY,WcۊnZ`ZV,UߛSB9Vss~{v;N+A[~G&Gw_6D4yƍiY˫ըc59=<нjpעŕh&|ACS_F­Ejt˦@@o@PE+?򥢗x#Rtj_f|u#ҷGudd~T4ځJ@(m_!uڮ[+oGz%W]82?V<l;~WPݚZqS9!D( @[eNVM72R˂j][Kend6>Uqc8 VKPGgr\ԛ UKveq5vݝkO؇I*{X)'">t}x4s-}ܯ,v•3n>ɱ'ZB`gr7fw.lEđ7 4ݚ=Wφrxr .qM_9SEB0[Ǟ1A2gJ3J] pFVwYCNΣ/|,PG{| "Yi[l܃1HȒ,@*)"Tv_%Qu^#G,O2N8BaJ4y2-14 !P c!W00k 0 IDATN6f\v->ox!qU<|A3 چY sY{]MW6}Қ{GX"}G7 M|~*HE-œ[3nD!VL-?嵘Gz,y{fQHg׊to,=hy`3gKkи*/'t=ybotE;KsƘi2`Cv^}66fS/w?p'A{`ۀp +,P* D)*Pe/15-VdeIXFPp ^!7 'iky鹕D* Ib tnğ}b[a-F:\5tuL-Cvrc4A`ncP 7_</+s.D>ϸ2Ͻ~ҵi7mX-Fs@_},J%tixəWƃF!Y:P_Jf;繙)uhH-=m:3 ,8p6}nS76yK8𛓃}iLZ$ X %|ަ(5fD i-+>P*uwY3Ԋ\/^(@oM5DŅ"]7zJs_zڍjF7{D/,~+^0MJtv47iYϽrivqēՍ`5p`f~1LzG$>ῘΥK.XlnV _ 0 Kיer!B?TEM@)?7/m`ku<$ Q؎{ؒgs7 pҠFFeY M3 + c|u=|MvL(ywh.7x_}{v"tcm#115مZ |~y=WMUל]+=OeƇ\~0Ɗ,/|*c'; i}gWh/h|5ehHlN8 Y֦Nk;^C[[[&%ݒ'mʷlAZ^c,Q;m qoS0s .Bdf+˟_4p 74X8NFniޚl\t$uw%!8/ĊY3Ó#E`!=+6*H?rыޮ&WnܼK3Wy.w7gfwK±'@Z ';ӻ,Y\y3Tڲ]l}/mqsk73YW?ꪛ A 4g 50Ʋ$&R9xC"K,x9.ܧc9=:~];#2\tup/mR&t7,?3ƺ:Adz3ݕ*&|JgG3WYƾ6⩗^۽LYǒ}aT&Ofv>U`}O;AZo#t, (9}yay9s1eh\q~/{|SlۥP0=>{! 0uV@ZŽ]K%6#iB(PE4xl0A(mN<#IJM;*B)[Csj:CǢ$SɱAi3 EIQ|wHm{hi?or&Ç{0]_; qf陌Y[ Y&g̥-+v?|kdm0{kGjn]z`>DQ$ ᠯt']Ne(t%›>UHv1Ӳ.MM?2TS_3_ߝ[qy/x2H<;_u\pnUhՐߩ˫ߘ^j/qK]Yh"?nPڑ>I ;'3^~۷sۅ٬vmY{@UӴ矟4d4fI\4JSOͱb>Mr.~J[fq-!Ow5WUlyn:+oxhnjIu]wSdjoݜSkؕ3s gr[M)q5O4}lvmj7PayeӟQ"O>:rA5&q10!FCE7Bd6^xa'/_*,b @%R/hvo!ITYrũjvr,Pt]7F@}ܘ@S˺hػ羻lԑ}7|Lcu_y߈%d#C m2iYGymBW>~򗿼3]?m"]La |po,GRRWU?1}o$gL$PJ!| Qk !`13Ι8ڭhiB7#=vk2 oݺ}ЉtfHv#X/VLe6uwҀIM:?fa%0i]X[֢ ZU5D[AY ;`N?n|N|P(nfA솕%/]N?_X)#GM1f'!ΧtAXJj{U?:j1m2g_4yP9Y u*݊a lk5x xdƑ%6qj@XC_ds}f"l} ٳgNt)^fO~|[D8,τb&#Pb8:}_^OJڻ]^7|D P%iրhʋע-Z_BqHY%~7߯Cǎ|7>@D ~SJةPE@MiW_SJCo "} ~z)O/ί:o놽5C]ZoJ0LX*' 3 B,E k@iঙt9yqTQ4LSSײ"}x 8g^ RPU3EJ`h% {O6ƫo}ݝ !B(Eu2si㌜C[W(mbm], 6t㰉 s5K*4gx91=>1]C܊H^z)K>H/&vϖe]1sgv̱:];Y_GeBHkim8D.FncK$P`}v"-Xחv}&8YEqe ڌO[OyomzTBqĄobBY|>ww&7s7{Z$Ax' r-ݝ O!K,#~w1\1kIL_dacڅTڵ <]G韺=W=L ɒrhx\AC_wX'^Fs'ܘK4P_k) uP$]yo/|[ԕ+WBB eظc󹙙ݙݻUT@?phLy բ[p`0ްZ|3R4bO}sFOyZ@. N.]7 !C}jNȲ>^C"AURO$\KyMw"pFH1>0w`ߡ>;jvhf2ׯ^"( (Р20(`irayDN#щǏ\B @Ƙ-h3F>cƎLְ[GpbڧwI[o))OZO6&ޛz@oIc7^^+ !|oғ5@j4C=?z;)M3FW'pXD}uGHD !] ƌTJFXLr&dN=:>xrȉ}>h6dXT  .m6Wuw>t@8~(}t}jhtA_MU! ᱁󡙕蝥ثWo/6тs#7ۛaJN[fJ[ `|{[H豘aXTƌx[ĻѣN9<6 VPl1eY6㡐'J,"tڳ?>x>Yvkp#p?4e !Ofbm8<}5a;86T\"oR(~?29:pgnur.jmҺ` >qO~֢\}ŵ:,}cC> J@@`C!!z›sKӬtft̤Lʤ͌ό"u#K%Qk}3r]#Ȝ[wgVO881\e!A0_%9slu5JSXġd*3T\8ߧMHԣ'jN)y걓kCL)%\8~{آCvcn] HcG#1F#}D:{ezʝ+w6WnYF2i$?*DE&B(w Q*+D`J10L_`Js9爱{CsƘ3]gti:ӵ{O f&cf2ܬc/@{pvC??tw/ﺽn~ V`PknT딒O(mf-@odcG6Y#5+|G_3G"[%mS\ է=)vgv٩OHk (~V'Nz!P,zw;ϟVqa4 UwÄJM|-[٥Gz" (F-!w'~-W|`D擋UB7=G~磯\*yݷDc'r3J3^>=Rnxѓ ~>|Z5kJg p`w]Uf j @`~`<y=W+񂋪ŕjH5\"SDzZio3`_PesӴn9 =]RG;Kצo&F9uHFVƆz~W. ]ԣ'#2$Qxc'/MM_5WUо3;3dzxG jHCF>L^ tR'}}PzA`{ ?৖Ċ{}ms>u{nz~ɱF':;6_%ף\^c pUʛ]RvuÓ#cC}Oߙ]v;IW8;rsBQB>vpo>:lY >:*ʻN^xҺTc|isڝ6_su褷g}>y*ɓ- & h?Dō.jqw:q;\Teʧ]~gS"c*ң;066%;WGzFzz"Eǽ$h՛sKk eI9~pS'>vz<)Y8쨰PCUz:%Y eR~Ɠ< G(iZKcI5cI35}I3t`~IAYYP$2mh;8l,h!ػx2Ϗ>plWA_>?}#Lc~聱 r=_c"W`US9}S7bɅh&|AYU_tuT`!84qD.e\A4ΑOUE *ՙU*?%f5uGbPX=xlE s^͘aF 3fX{3\&_$O%O%SۯdL1F߿֧q-$-r9Miiwva5Մb>6qd豃ce,וO9 f,UA0=pOSe*vx K6 z#`ҺE#ݵBOt$wbqӊVܴ潟&kQMhXh@JZ(ѽ{9A[BO D b;Y[,vc# ainϕ>yd%_О?{Y[>㹵 ͇Tܒ3Fq%JF s1;Y,xbY1ɑɹŷPF2 % !* IUhr]{p c1FTE$D/G+hyڍ 8Dkf/\@oɑFhLz$MAZ7*}yUND;ZAGm*mt0"D0&;>+M&Ж.ÆZ;" IDAT;4g)릭lR_ܾсG'UY޾vkYY(\ror9p?Kn2 FbD1F +fU^Q)1ȒXi1]7*wdd(Ӳtl#beiKr`(%T#}F=J֓{znyniÓ#0 wfJlW@?{Dz@in2NB|;Yt浪p,˜)xA3~E+ji)J#SЌ\L=dtj1 TЌ_=ж)١GBX8iZݹ=#=u[xcqe6 G: YX}T>J~c߀6id pCMEWmM )F4x>33ar AedX[jtt-YkK]gs#SQyy&[|{gYxX~sr\* dro}¡@91pyj(8sl_hc ~y}l0B4?Tn8g< րmmIǴG^ɯH9cV 61L79e, &6<)W䑫V[GιaZgul#ê92F{fXEH!&c{K|zs+$מH#5ԫoē6폟).ônMݚ3]~f.Gqn1pU z#}2gO56(;!ЩXdmXM_׌KdθL;Q)fUx|de1TQ_.UNTos*[9t*=7zսT0Po].Ǿ9[[:t/--q$|[NW5,Ʀg/_}+^uHkd+gsn*T,nB5cS6:*.K]x9gG5CcmZM+.½-߲\@~{Cq%w+kX8849<)_.Nϭܝ_.)η=zrjt~y}i5jˡ=!,ɞ֛y~P;6d7-^!5lPjN356M)j+Y4#I}}^Jd>He^RSmGJ0!1l2MT&>m>t F6 |Al^[݈+0%<7iimGG īF;ٙINl/l5YiKu*-;$cno#pKkϯ B%_k;ADJmw 4!0ĥo`þehCi3Qs+ -i6F^4">!Ju֊")rCk0^,hQvd|,ԑtMoȑQjxN yN c~W kl|w$_ϥ^iZ((ə&>ɿ\f?hWHRk\ZLZUvwM]{d̎iy&gET39-Wkn^|AkXL ߻t;EFhM7ZqG&)Y'F+NȬl$>noMt|_O7X&!ChD'V)f! {0Qߗd_gP$t-n,Y^ soiiY"qF㝬⍴?z$l,Ww}YL;"-eQsò,Ƭ. @H+7Ԝ2۶==L_Χc ^3G!0>О4#`dWN/,۳wtW&Kah-F'G;=wixsh߻c;m\C Ԑ=vq[nY!R ౐b؉TV7r+q Yٕd6xUlpfY?21VWoO F~j0UYB^ hDDKQUA_K'~;Y- S=\wj!@ԇy^xzV/>*sѦ̶x!1ϑq7KU3~dxN֊ 4Vkq'ecfGAi[I)idimg}5E h p7Cءf%O3Χ5+8go i-܇R-=+V>K VBLoEz V1I]2-Օ =U1Zbkl1ݲ !bPiʪ)MS*g.̵yx=Å v qv Og k"@ƸX&bL q9`1ƛamBp(2`(A+!LQ(!V,:=_xFHk14fխ\U* IʉZ{EqY (I=U2p9e5i2EثĶ3dHEQ8BV;jXHGTLic))T{/@dpd6&kBnxʥY(^p Ʋ7N|h){ɔk|uXGq:Ub5Rz=QܠsJkf]۴,|Ƞ-n[Ќ9@ oK sMtJ#R!wEu^&ceYQ/Ufm.rmv=!.ܜ)xH,Nj\JK6zujM 9nϭ2@a`f,#{VU/V6#nϻle#o3@_{0vʦiD: )cf5x8H8E-m S,o8hX_s]3h.59u>䵗^~?b/;H3apTY^3r'eE%'pX՜_982 q,c0"b;S7ܠ6#]qzfmp X@ө,+^eE^ qys_ӄ`9zs@/pe{Lg}jB۱Q)6lkV GLGd:w͹&ތr;r@ ƹwﶜ@cG 65Kmi!H3yOR2};;5]}-a?϶bPP}.QDR_x܏]wF1Z&W(!Y;[Ř* 籃?{eZPwCXZ|POGԃB )ع+N\z_#jXaH[wēU#v.ʠ$~3)H ]EeiJWÿӳ C[ʢopȲ$W+Ͼpzge.82,K%.FSMUQJn=Xwu[`U·{k{\xm'2ʥ> a/9|75օu,sB\nX+}CBI2Jd}WKr:/:|l;]y2˩l@dUE!˯>ai=P lbLJkhM'n[SD:{B5lv .N{So:8PTK7ҸBر}Enk|jfY{O"=w->;uWNOUEx:|׮s(<ń*}+u*[^9v(~i1k~{aC^:ahF<\A_"A;)jܕitN2š]Eش8˿V/ ]w?\&?|lb0 tTY7ϝfaZt6L_3ŵһ}}@FkŻm:`W eέQnRXWoNjo#EQA*1^tFW/ۈ=uh+hب(*q߼|kֱϖs'3$ 6*ia?Z4~{| $Mڣri[^s/ 衠o:vA XX"+Bc tCue`D{GZ#d72ϯG f@;kq?,dro]P=8?{7GNx y0L#Xj22+OpzJTuU}@]=]AIBx! }L~e#HfrnLi=0ApT= kƷO$u8&t> dFoSf>ESai]sk\ƸP8ꈄ"V@Qݖa9 3-D\kYo]oVJGuҺ`PkžfgʓpH}Wkoy\Z#uk;=.-;ز5x.4* GhOf z̰⦕4OI@;:$Q {yB:?qӊVYicBhH񁀺OD!|zB}BmF&>:9r&ҹX9ZFcy\#(nF0nJV|dAFKHk߿v󬎅jC63C=0x46xUeXBŵK0BB1@ ܯ n-Dءa՛B^X9vbgzǏ-,5LE&XP!Qsߚ3 5{D2Bp৏)`L0klVUH,]`TQT:1֩_-^ S׺"?8:jj 1B>Q,&ĮhE2Ɨ.b2'7Wg Rk(`SMqJ$9`/G+/6W[냦[?ɸ?^~a(}GwK96%9ǁ https://developer.mozilla.org/de/docs/Web/HTML/Inline_elemente "b", "big", "i", "small", "tt", "abbr", "acronym", "cite", "code", "dfn", "em", "kbd", "strong", "samp", "var", "a", "bdo", "br", "img", "map", "object", "q", "script", "span", "sub", "sup", "button", "input", "label", "select", "textarea", } // IsInlineElement can be used to check wether a node name (goquery.Nodename) is // an html inline element and not a block element. Used in the rule for the // p tag to check wether the text is inside a block element. func IsInlineElement(e string) bool { for _, element := range inlineElements { if element == e { return true } } return false } // String is a helper function to return a pointer. func String(text string) *string { return &text } // Options to customize the output. You can change stuff like // the character that is used for strong text. type Options struct { // "setext" or "atx" // default: "atx" HeadingStyle string // Any Thematic break // default: "* * *" HorizontalRule string // "-", "+", or "*" // default: "-" BulletListMarker string // "indented" or "fenced" // default: "indented" CodeBlockStyle string // ``` or ~~~ // default: ``` Fence string // _ or * // default: _ EmDelimiter string // ** or __ // default: ** StrongDelimiter string // inlined or referenced // default: inlined LinkStyle string // full, collapsed, or shortcut // default: full LinkReferenceStyle string // basic, disabled // default: basic EscapeMode string domain string // GetAbsoluteURL parses the `rawURL` and adds the `domain` to convert relative (/page.html) // urls to absolute urls (http://domain.com/page.html). // // The default is `DefaultGetAbsoluteURL`, unless you override it. That can also // be useful if you want to proxy the images. GetAbsoluteURL func(selec *goquery.Selection, rawURL string, domain string) string // GetCodeBlockLanguage identifies the language for syntax highlighting // of a code block. The default is `DefaultGetCodeBlockLanguage`, which // only gets the attribute x from the selection. // // You can override it if you want more results, for example by using // lexers.Analyse(content) from github.com/alecthomas/chroma // TODO: implement // GetCodeBlockLanguage func(s *goquery.Selection, content string) string } // DefaultGetAbsoluteURL is the default function and can be overridden through `GetAbsoluteURL` in the options. func DefaultGetAbsoluteURL(selec *goquery.Selection, rawURL string, domain string) string { if domain == "" { return rawURL } u, err := url.Parse(rawURL) if err != nil { // we can't do anything with this url because it is invalid return rawURL } if u.Scheme == "data" { // this is a data uri (for example an inline base64 image) return rawURL } if u.Scheme == "" { u.Scheme = "http" } if u.Host == "" { u.Host = domain } return u.String() } // AdvancedResult is used for example for links. If you use LinkStyle:referenced // the link href is placed at the bottom of the generated markdown (Footer). type AdvancedResult struct { Header string Markdown string Footer string } // Rule to convert certain html tags to markdown. // md.Rule{ // Filter: []string{"del", "s", "strike"}, // Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { // // You need to return a pointer to a string (md.String is just a helper function). // // If you return nil the next function for that html element // // will be picked. For example you could only convert an element // // if it has a certain class name and fallback if not. // return md.String("~" + content + "~") // }, // } type Rule struct { Filter []string Replacement func(content string, selec *goquery.Selection, options *Options) *string AdvancedReplacement func(content string, selec *goquery.Selection, options *Options) (res AdvancedResult, skip bool) } var leadingNewlinesR = regexp.MustCompile(`^\n+`) var trailingNewlinesR = regexp.MustCompile(`\n+$`) var newlinesR = regexp.MustCompile(`\n+`) var tabR = regexp.MustCompile(`\t+`) var indentR = regexp.MustCompile(`(?m)\n`) func (conv *Converter) selecToMD(domain string, selec *goquery.Selection, opt *Options) AdvancedResult { var result AdvancedResult var builder strings.Builder selec.Contents().Each(func(i int, s *goquery.Selection) { name := goquery.NodeName(s) rules := conv.getRuleFuncs(name) for i := len(rules) - 1; i >= 0; i-- { rule := rules[i] content := conv.selecToMD(domain, s, opt) if content.Header != "" { result.Header += content.Header } if content.Footer != "" { result.Footer += content.Footer } res, skip := rule(content.Markdown, s, opt) if res.Header != "" { result.Header += res.Header + "\n" } if res.Footer != "" { result.Footer += res.Footer + "\n" } if !skip { builder.WriteString(res.Markdown) return } } }) result.Markdown = builder.String() return result } golang-github-johanneskaufmann-html-to-markdown-1.5.0/markdown_test.go000066400000000000000000000020631455110214100261750ustar00rootroot00000000000000package md import "testing" func TestDefaultGetAbsoluteURL_NoDomain(t *testing.T) { input := "/page.html?key=val#hash" expected := input res := DefaultGetAbsoluteURL(nil, input, "") if res != expected { t.Errorf("expected '%s' but got '%s'", expected, res) } } func TestDefaultGetAbsoluteURL_Domain(t *testing.T) { input := "/page.html?key=val#hash" expected := "http://test.com" + input res := DefaultGetAbsoluteURL(nil, input, "test.com") if res != expected { t.Errorf("expected '%s' but got '%s'", expected, res) } } func TestDefaultGetAbsoluteURL_DataURI(t *testing.T) { input := "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" expected := input res := DefaultGetAbsoluteURL(nil, input, "test.com") if res != expected { t.Errorf("expected '%s' but got '%s'", expected, res) } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/000077500000000000000000000000001455110214100242625ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/confluence_attachment_block.go000066400000000000000000000012101455110214100323060ustar00rootroot00000000000000package plugin import ( "fmt" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // ConfluenceAttachments converts `` elements // [Contributed by @Skarlso] func ConfluenceAttachments() md.Plugin { return func(c *md.Converter) []md.Rule { return []md.Rule{ { Filter: []string{"ri:attachment"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { if v, ok := selec.Attr("ri:filename"); ok { formatted := fmt.Sprintf("![][%s]", v) return md.String(formatted) } return md.String("") }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/confluence_code_block.go000066400000000000000000000025561455110214100311060ustar00rootroot00000000000000package plugin import ( "fmt" "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // ConfluenceCodeBlock converts `` elements that are used in Atlassian’s Wiki “Confluence”. // [Contributed by @Skarlso] func ConfluenceCodeBlock() md.Plugin { return func(c *md.Converter) []md.Rule { character := "```" return []md.Rule{ { Filter: []string{"ac:structured-macro"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { for _, node := range selec.Nodes { if node.Data == "ac:structured-macro" { // node's last child -> . We don't want to filter on that // because we would end up with structured-macro around us. // ac:plain-text-body's last child is [CDATA which has the actual content we are looking for. data := strings.TrimPrefix(node.LastChild.LastChild.Data, "[CDATA[") data = strings.TrimSuffix(data, "]]") // content, if set, will contain the language that has been set in the field. var language string if content != "" { language = content } formatted := fmt.Sprintf("%s%s\n%s\n%s", character, language, data, character) return md.String(formatted) } } return md.String(character + content + character) }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/frontmatter.go000066400000000000000000000025101455110214100271540ustar00rootroot00000000000000package plugin import ( "fmt" md "github.com/JohannesKaufmann/html-to-markdown" yaml "gopkg.in/yaml.v2" ) // type frontMatterCallback func(selec *goquery.Selection) map[string]interface{} // TODO: automatically convert to formats (look at hugo) // EXPERIMENTALFrontMatter was an experiment to add certain data // from a callback function into the beginning of the file as frontmatter. // It not really working right now. // // If someone has a need for it, let me know what your use-case is. Then // I can create a plugin with a good interface. func EXPERIMENTALFrontMatter(format string) md.Plugin { return func(c *md.Converter) []md.Rule { data := make(map[string]interface{}) d, err := yaml.Marshal(data) if err != nil { panic(err) } fmt.Println(string(d)) /* add rule for `head` - get title - return AdvancedResult{ Header: formated_yaml }, skip -> added to head -> others rules can apply */ title := "" // c.Find("head title").Text() var text string switch format { case "toml": // +++ text = fmt.Sprintf(` +++ title = "%s" +++ `, title) case "yaml": // --- text = fmt.Sprintf(` --- title: %s --- `, title) case "json": // { } text = fmt.Sprintf(` { "title": "%s" } `, title) default: panic("unknown format") } _ = text // c.AddLeading(text) return nil } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/gfm.go000066400000000000000000000007161455110214100253660ustar00rootroot00000000000000// Package plugin contains all the rules that are not // part of Commonmark like GitHub Flavored Markdown. package plugin import md "github.com/JohannesKaufmann/html-to-markdown" // GitHubFlavored is GitHub's Flavored Markdown func GitHubFlavored() md.Plugin { return func(c *md.Converter) (rules []md.Rule) { rules = append(rules, Strikethrough("")(c)...) rules = append(rules, Table()(c)...) rules = append(rules, TaskListItems()(c)...) return } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/iframe_vimeo.go000066400000000000000000000104321455110214100272530ustar00rootroot00000000000000package plugin import ( "encoding/json" "fmt" "net/http" "regexp" "strings" "time" "unicode/utf8" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // Timeout for the http client var Timeout = time.Second * 10 var netClient = &http.Client{ Timeout: Timeout, } type vimeoVideo struct { Type string `json:"type"` Version string `json:"version"` ProviderName string `json:"provider_name"` ProviderURL string `json:"provider_url"` Title string `json:"title"` AuthorName string `json:"author_name"` AuthorURL string `json:"author_url"` IsPlus string `json:"is_plus"` AccountType string `json:"account_type"` HTML string `json:"html"` Width int `json:"width"` Height int `json:"height"` Duration int `json:"duration"` Description string `json:"description"` ThumbnailURL string `json:"thumbnail_url"` ThumbnailWidth int `json:"thumbnail_width"` ThumbnailHeight int `json:"thumbnail_height"` ThumbnailURLWithPlayButton string `json:"thumbnail_url_with_play_button"` UploadDate string `json:"upload_date"` VideoID int `json:"video_id"` URI string `json:"uri"` } var vimeoID = regexp.MustCompile(`video\/(\d*)`) type vimeoVariation int // Configure how the Vimeo Plugin should display the video in markdown. const ( VimeoOnlyThumbnail vimeoVariation = iota VimeoWithTitle VimeoWithDescription ) // VimeoEmbed registers a rule (for iframes) and // returns a markdown compatible representation (link to video, ...). func VimeoEmbed(variation vimeoVariation) md.Plugin { return func(c *md.Converter) []md.Rule { getVimeoData := func(id string) (*vimeoVideo, error) { u := fmt.Sprintf("http://vimeo.com/api/oembed.json?url=https://vimeo.com/%s", id) resp, err := netClient.Get(u) if err != nil { return nil, err } defer resp.Body.Close() var res vimeoVideo err = json.NewDecoder(resp.Body).Decode(&res) if err != nil { return nil, err } return &res, nil } cleanDescription := func(html string) (string, error) { text, err := c.ConvertString(html) if err != nil { return "", err } text = strings.Replace(text, "\n", " ", -1) text = strings.Replace(text, "\t", " ", -1) before := utf8.RuneCountInString(text) text = summary(text, 70) after := utf8.RuneCountInString(text) if after != before { text += "..." } return text, nil } return []md.Rule{ { Filter: []string{"iframe"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { src := selec.AttrOr("src", "") if !strings.Contains(src, "vimeo.com") { return nil } parts := vimeoID.FindStringSubmatch(src) if len(parts) != 2 { return nil } id := parts[1] video, err := getVimeoData(id) if err != nil { panic(err) } // desc, err := cleanDescription(video.Description) // if err != nil { // panic(err) // } // [![Little red ridning hood](http://i.imgur.com/7YTMFQp.png)](https://vimeo.com/3514904 "Little red riding hood - Click to Watch!") // text := fmt.Sprintf("[![%s](%s) ](%s)", desc, video.ThumbnailURLWithPlayButton, "https://vimeo.com/"+video.URI) text := fmt.Sprintf(`[![](%s)](https://vimeo.com/%d)`, video.ThumbnailURLWithPlayButton, video.VideoID) switch variation { case VimeoOnlyThumbnail: // do nothing case VimeoWithTitle: duration := time.Duration(video.Duration) * time.Second text += fmt.Sprintf("\n\n'%s' by ['%s'](%s) (%s)", video.Title, video.AuthorName, video.AuthorURL, duration.String()) case VimeoWithDescription: desc, err := cleanDescription(video.Description) if err != nil { panic(err) } text += "\n\n" + desc } return &text }, }, } } } // truncate func summary(text string, limit int) string { result := text chars := 0 for i := range text { if chars >= limit { result = text[:i] break } chars++ } return result } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/iframe_youtube.go000066400000000000000000000017321455110214100276330ustar00rootroot00000000000000package plugin import ( "fmt" "regexp" "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) var youtubeID = regexp.MustCompile(`youtube\.com\/embed\/([^\&\?\/]+)`) // YoutubeEmbed registers a rule (for iframes) and // returns a markdown compatible representation (link to video, ...). func YoutubeEmbed() md.Plugin { return func(c *md.Converter) []md.Rule { return []md.Rule{ { Filter: []string{"iframe"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { src := selec.AttrOr("src", "") if !strings.Contains(src, "youtube.com") { return nil } alt := selec.AttrOr("title", "") parts := youtubeID.FindStringSubmatch(src) if len(parts) != 2 { return nil } id := parts[1] text := fmt.Sprintf("[![%s](https://img.youtube.com/vi/%s/0.jpg)](https://www.youtube.com/watch?v=%s)", alt, id, id) return &text }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/movefrontmatter.go000066400000000000000000000042111455110214100300430ustar00rootroot00000000000000package plugin import ( "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) const moveFrontmatterAttr = "movefrontmatter" // EXPERIMENTALMoveFrontMatter moves a frontmatter block at the beginning // of the document to the top of the generated markdown block, without touching (and escaping) it. func EXPERIMENTALMoveFrontMatter(delimiters ...rune) md.Plugin { return func(c *md.Converter) []md.Rule { if len(delimiters) == 0 { delimiters = []rune{'+', '$', '-', '%'} } var delimitersList []string for _, c := range delimiters { delimitersList = append(delimitersList, strings.Repeat(string(c), 3)) } isDelimiter := func(line string) bool { for _, delimiter := range delimitersList { if strings.HasPrefix(line, delimiter) { return true } } return false } c.Before(func(selec *goquery.Selection) { selec.Find("body").Contents().EachWithBreak(func(i int, s *goquery.Selection) bool { text := s.Text() // skip empty strings if strings.TrimSpace(text) == "" { return true } var frontmatter string var html string = text // if there is no frontmatter, keep the text lines := strings.Split(text, "\n") for i := 0; i < len(lines); i++ { if isDelimiter(lines[i]) { if i == 0 { continue } // split the frontmatter f := lines[:i+1] frontmatter = strings.Join(f, "\n") // and the html content AFTER the frontmatter h := lines[i+1:] html = strings.Join(h, "\n") break } } s.SetAttr(moveFrontmatterAttr, frontmatter) s.SetText(html) // the front matter must be the first thing in the file. So we break out of the loop return false }) }) return []md.Rule{ { Filter: []string{"#text"}, AdvancedReplacement: func(content string, selec *goquery.Selection, opt *md.Options) (md.AdvancedResult, bool) { frontmatter, exists := selec.Attr(moveFrontmatterAttr) if !exists { return md.AdvancedResult{}, true } return md.AdvancedResult{ Header: frontmatter, Markdown: content, }, false }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/plugin_test.go000066400000000000000000000041351455110214100271510ustar00rootroot00000000000000package plugin import ( "testing" md "github.com/JohannesKaufmann/html-to-markdown" ) func TestConfluenceCodeBlock(t *testing.T) { conv := md.NewConverter("", true, nil) conv.Use(ConfluenceCodeBlock()) input := ` some other stuff sql` expected := "```" + ` FOR stuff IN imdb_vertices FILTER LIKE(stuff.description, "%good%vs%evil%", true) RETURN stuff.description ` + "```" + ` some other stuff ` + "```sql" + ` FOR stuff IN imdb_vertices FILTER LIKE(stuff.description, "%good%vs%evil%", true) RETURN stuff.description ` + "```" markdown, err := conv.ConvertString(input) if err != nil { t.Error(err) } if markdown != expected { t.Errorf("got '%s' but wanted '%s'", markdown, expected) } } func TestConfluenceAttachments(t *testing.T) { conv := md.NewConverter("", true, nil) conv.Use(ConfluenceAttachments()) input := `

Here’s an image:

Another one

` expected := `Here’s an image: ![][image.png] Another one ![][image.jpg]` markdown, err := conv.ConvertString(input) if err != nil { t.Error(err) } if markdown != expected { t.Errorf("got '%s' but wanted '%s'", markdown, expected) } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/strikethrough.go000066400000000000000000000014631455110214100275170ustar00rootroot00000000000000package plugin import ( "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // Strikethrough converts ``, ``, and `` elements func Strikethrough(character string) md.Plugin { return func(c *md.Converter) []md.Rule { if character == "" { character = "~~" } return []md.Rule{ { Filter: []string{"del", "s", "strike"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { // trim spaces so that the following does NOT happen: `~ and cake~` content = strings.TrimSpace(content) content = character + content + character // always have a space to the side to recognize the delimiter content = md.AddSpaceIfNessesary(selec, content) return &content }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/table.go000066400000000000000000000112431455110214100257010ustar00rootroot00000000000000package plugin import ( "regexp" "strings" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // TableCompat is a compatibility plugin for environments where // only commonmark markdown (without Tables) is supported. // // Note: In an environment that supports "real" Tables, like GitHub's Flavored Markdown // use `plugin.Table()` instead. func TableCompat() md.Plugin { return func(c *md.Converter) []md.Rule { return []md.Rule{ { Filter: []string{"td", "th"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { content = strings.TrimSpace(content) if content == "" { return &content } next := selec.Next() nextIsEmpty := strings.TrimSpace(next.Text()) == "" if (next.Is("td") || next.Is("th")) && !nextIsEmpty { content = content + " · " } return &content }, }, { Filter: []string{"tr"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { content = content + "\n\n" return &content }, }, } } } // Table converts a html table (using hyphens and pipe characters) to a // visuall representation in markdown. // // Note: This Plugin overrides the default compatibility rules from `commonmark.go`. // Only use this Plugin in an environment that has extendeded the normal syntax, // like GitHub's Flavored Markdown. func Table() md.Plugin { return func(c *md.Converter) []md.Rule { c.Before(func(selec *goquery.Selection) { selec.Find("caption").Each(func(i int, s *goquery.Selection) { parent := s.Parent() if !parent.Is("table") { return } // move the caption from inside the table to after the table parent.AfterSelection(s) }) }) return []md.Rule{ { Filter: []string{"table"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { noHeader := selec.Find("thead").Length() == 0 && selec.Find("th").Length() == 0 if noHeader { var maxCount int selec.Find("tr").Each(func(i int, s *goquery.Selection) { count := s.Children().Length() if count > maxCount { maxCount = count } }) // add an empty header, so that the table is recognized. header := "|" + strings.Repeat(" |", maxCount) divider := "|" + strings.Repeat(" --- |", maxCount) content = header + "\n" + divider + content } content = "\n\n" + content + "\n\n" return &content }, }, { // TableCell Filter: []string{"th", "td"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { return md.String(getCellContent(content, selec)) }, }, { // TableRow Filter: []string{"tr"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { borderCells := "" if isHeadingRow(selec) { selec.Children().Each(func(i int, s *goquery.Selection) { border := "---" if align, ok := s.Attr("align"); ok { switch align { case "left": border = ":--" case "right": border = "--:" case "center": border = ":-:" } } borderCells += getCellContent(border, s) }) } text := "\n" + content if borderCells != "" { text += "\n" + borderCells } return &text }, }, } } } // A tr is a heading row if: // - the parent is a THEAD // - or if its the first child of the TABLE or the first TBODY (possibly // following a blank THEAD) // - and every cell is a TH func isHeadingRow(s *goquery.Selection) bool { parent := s.Parent() if goquery.NodeName(parent) == "thead" { return true } isTableOrBody := parent.Is("table") || isFirstTbody(parent) everyTH := true s.Children().Each(func(i int, s *goquery.Selection) { if goquery.NodeName(s) != "th" { everyTH = false } }) if parent.Children().First().IsSelection(s) && isTableOrBody && everyTH { return true } return false } func isFirstTbody(s *goquery.Selection) bool { firstSibling := s.Siblings().Eq(0) // TODO: previousSibling if s.Is("tbody") && firstSibling.Length() == 0 { return true } return false } var newLineRe = regexp.MustCompile(`(\r?\n)+`) func getCellContent(content string, s *goquery.Selection) string { content = strings.TrimSpace(content) if s.Find("table").Length() == 0 { // nested tables not found content = newLineRe.ReplaceAllString(content, "
") } index := -1 for i, node := range s.Parent().Children().Nodes { if s.IsNodes(node) { index = i break } } prefix := " " if index == 0 { prefix = "| " } return prefix + content + " |" } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin/task_list.go000066400000000000000000000012321455110214100266040ustar00rootroot00000000000000package plugin import ( md "github.com/JohannesKaufmann/html-to-markdown" "github.com/PuerkitoBio/goquery" ) // TaskListItems converts checkboxes into task list items. func TaskListItems() md.Plugin { return func(c *md.Converter) []md.Rule { return []md.Rule{ { Filter: []string{"input"}, Replacement: func(content string, selec *goquery.Selection, opt *md.Options) *string { if !selec.Parent().Is("li") { return nil } if selec.AttrOr("type", "") != "checkbox" { return nil } _, ok := selec.Attr("checked") if ok { return md.String("[x] ") } return md.String("[ ] ") }, }, } } } golang-github-johanneskaufmann-html-to-markdown-1.5.0/plugin_test.go000066400000000000000000000031341455110214100256510ustar00rootroot00000000000000package md_test import ( "testing" md "github.com/JohannesKaufmann/html-to-markdown" "github.com/JohannesKaufmann/html-to-markdown/plugin" ) func TestPlugins(t *testing.T) { var tests = []GoldenTest{ { Name: "strikethrough", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.Strikethrough(""), }, }, }, }, { Name: "checkbox", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.TaskListItems(), }, }, }, }, { Name: "table", DisableGoldmark: true, Variations: map[string]Variation{ "default": {}, "table": { Plugins: []md.Plugin{ plugin.Table(), }, }, "tablecompat": { Plugins: []md.Plugin{ plugin.TableCompat(), }, }, }, }, { Name: "movefrontmatter/simple", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.EXPERIMENTALMoveFrontMatter(), }, }, }, }, { Name: "movefrontmatter/not", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.EXPERIMENTALMoveFrontMatter(), }, }, }, }, { Name: "movefrontmatter/jekyll", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.EXPERIMENTALMoveFrontMatter('-', '+'), }, }, }, }, { Name: "movefrontmatter/blog", Variations: map[string]Variation{ "default": { Plugins: []md.Plugin{ plugin.EXPERIMENTALMoveFrontMatter(), }, }, }, }, } RunGoldenTest(t, tests) } golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/000077500000000000000000000000001455110214100245755ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/README.md000066400000000000000000000015421455110214100260560ustar00rootroot00000000000000## Testdata _The "testdata" folder holds various combinations of inputs and expected outputs to check that the library still works._ **You found a problematic HTML snippet? Great!** Add your problematic HTML snippet to one of the `input.html` files in the `testdata` folder. Then run `go test -update` and have a look at which `.golden` output files changed in GIT. _Note:_ Adding big block of HTML is not that helpful, so please: 1. narrow it down to the basic HTML structure that still causes the problem, 2. remove any unnecessary attributes (e.g. data-attributes) and 3. generalise the content (e.g. update links, replace text with lorem ipsum). **=> Make sure it has been changed enough to be considered your own work!** You can rerun `go test -update` until you are happy with the test case that you want to commit. Thanks for expanding the test cases! golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/000077500000000000000000000000001455110214100275405ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/blockquote/000077500000000000000000000000001455110214100317105ustar00rootroot00000000000000goldmark.golden000066400000000000000000000012351455110214100346240ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/blockquote

Some Quote Next Line

Allowing an unimportant mistake to pass without comment is a wonderful social grace.

Ideological differences are no excuse for rudeness.

This is the first level of quoting.

This is a paragraph in a nested blockquote.

Back to the first level.

This is a header.

  1. This is the first list item.
  2. This is the second list item.

A code block:

return 1 < 2 ? shell_exec('echo $input | $markdown_script') : 0;
golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/blockquote/input.html000066400000000000000000000015311455110214100337350ustar00rootroot00000000000000
Some Quote Next Line

Allowing an unimportant mistake to pass without comment is a wonderful social grace.

Ideological differences are no excuse for rudeness.

This is the first level of quoting.

This is a paragraph in a nested blockquote.

Back to the first level.

This is a header.

  1. This is the first list item.
  2. This is the second list item.

A code block:

return 1 < 2 ? shell_exec('echo $input | $markdown_script') : 0;
output.default.golden000066400000000000000000000007401455110214100360070ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/blockquote> Some Quote > Next Line > Allowing an unimportant mistake to pass without comment is a wonderful social grace. > > Ideological differences are no excuse for rudeness. > This is the first level of quoting. > > > This is a paragraph in a nested blockquote. > > Back to the first level. > ## This is a header. > > 1. This is the first list item. > 2. This is the second list item. > > A code block: > > ``` > return 1 < 2 ? shell_exec('echo $input | $markdown_script') : 0; > ```golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/bold/000077500000000000000000000000001455110214100304605ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/bold/goldmark.golden000066400000000000000000000005771455110214100334630ustar00rootroot00000000000000

Some Text

Some Text

Text

Some Text

Some Text.

Some Text Content

Some Text.

Some Text

首付 19,8万 / 月供 6339元X24

**Not Strong** **Still Not Strong**

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/bold/input.html000066400000000000000000000012541455110214100325070ustar00rootroot00000000000000

Some Text

Some Text

Text

SomeText

Some Text.

SomeTextContent

SomeText.

Some Text

首付19,8 / 月供6339元X24

**Not Strong** **Still Not Strong**

output.asterisks.golden000066400000000000000000000003261455110214100351430ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/boldSome **Text** Some **Text** **Text** Some **Text** Some **Text.** Some **Text** Content Some **Text.** Some **Text** #### 首付 _19,8万_ / 月供 _6339元X24_ \*\*Not Strong\*\* \*\*Still Not Strong\*\*output.underscores.golden000066400000000000000000000003261455110214100354670ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/boldSome __Text__ Some __Text__ __Text__ Some __Text__ Some __Text.__ Some __Text__ Content Some __Text.__ Some __Text__ #### 首付 _19,8万_ / 月供 _6339元X24_ \*\*Not Strong\*\* \*\*Still Not Strong\*\*golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/br_element/000077500000000000000000000000001455110214100316545ustar00rootroot00000000000000goldmark.golden000066400000000000000000000002071455110214100345660ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/br_element

1. xxx

2. xxxx

3. xxx

4. golang

a. xx

b. xx

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/br_element/input.html000066400000000000000000000002301455110214100336740ustar00rootroot00000000000000

1. xxx
2. xxxx
3. xxx


4. golang
a. xx
b. xx

output.default.golden000066400000000000000000000001211455110214100357440ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/br_element1\. xxx 2\. xxxx 3\. xxx ![](http://example.com/xxx) 4\. golang a. xx b. xxgolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/heading/000077500000000000000000000000001455110214100311375ustar00rootroot00000000000000goldmark.golden000066400000000000000000000010201455110214100340430ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/heading

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Heading 7

Heading with Whitespace

Header Containing Newlines

Heading One

Heading 2

#hashtag

not title ------

not title

not title

not title

not title \-\-\-

More posts from around the site:

More posts from around the site:

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/heading/input.html000066400000000000000000000024011455110214100331610ustar00rootroot00000000000000

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6
Heading 7

Heading with Whitespace

Header Containing Newlines

Heading

One

Heading 2

#hashtag

not title ------

not title -

not title

-

not title =

not title \-\-\-


More posts from around the site:


More posts from around the site:

output.atx.golden000066400000000000000000000006211455110214100344040ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/heading# Heading 1 ## Heading 2 ### Heading 3 #### Heading 4 ##### Heading 5 ###### Heading 6 Heading 7 ## Heading with Whitespace ## Header Containing Newlines # Heading One [**Heading 2**](http://example.com/page.html) # \#hashtag not title \-\-\---- not title - not title - not title = not title \\-\\-\\- #### More posts from around the site: #### More posts from around the site:output.default.golden000066400000000000000000000003071455110214100352350ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/heading# Heading 1 ## Heading 2 ### Heading 3 #### Heading 4 ##### Heading 5 ###### Heading 6 ## Heading with Whitespace ## Header Containing Newlines # Heading One [**Heading 2**](/page.html)output.setext.golden000066400000000000000000000007431455110214100351310ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/headingHeading 1 ========= Heading 2 --------- ### Heading 3 #### Heading 4 ##### Heading 5 ###### Heading 6 Heading 7 Heading with Whitespace ----------------------- Header Containing Newlines --------------------------- Heading One ============== [**Heading 2**](http://example.com/page.html) \#hashtag ========= not title \-\-\---- not title - not title - not title = not title \\-\\-\\- #### More posts from around the site: #### More posts from around the site:golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/hr/000077500000000000000000000000001455110214100301515ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/hr/goldmark.golden000066400000000000000000000000561455110214100331440ustar00rootroot00000000000000

Some Content


Other Content

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/hr/input.html000066400000000000000000000000601455110214100321720ustar00rootroot00000000000000

Some Content


Other Content

output.default.golden000066400000000000000000000000421455110214100342430ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/hrSome Content * * * Other Contentgolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/image/000077500000000000000000000000001455110214100306225ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/image/goldmark.golden000066400000000000000000000014251455110214100336160ustar00rootroot00000000000000

website favicon

alt "attribute"

alt  description

![](invalid%zz

zzz)

star

website favicon

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/image/input.html000066400000000000000000000022151455110214100326470ustar00rootroot00000000000000 website favicon
alt "attribute"
alt

description


star
website favicon output.default.golden000066400000000000000000000012051455110214100347160ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/image![website favicon](http://commonmark.org/help/images/favicon.png) ![alt "attribute"](http://commonmark.org/help/images/favicon.png) ![alt description](http://commonmark.org/help/images/favicon.png) ![](http://example.com/image.png) ![](invalid%zz zzz) ![star](data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7) ![website favicon](http://commonmark.org/help/images/favicon.png)golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/italic/000077500000000000000000000000001455110214100310055ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/italic/goldmark.golden000066400000000000000000000007721455110214100340050ustar00rootroot00000000000000

Some Text

Some Text

Some Text

DoubleItalic

Some 19,80€ Text

Content and no space afterward.

_Not Italic_

First

Second

Text und Fotos: Max Mustermann

Original veröffentlicht am 01.01.2021 auf Zeitung

Übersetzung: Max Mustermann

veröffentlicht am 02.02.2021

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/italic/input.html000066400000000000000000000012371455110214100330350ustar00rootroot00000000000000

Some Text

Some Text

Some Text

DoubleItalic

Some 19,80 Text

Content and no space afterward.

_Not Italic_

First
Second

Text und Fotos: Max Mustermann
Original veröffentlicht am 01.01.2021 auf
Zeitung
Übersetzung: Max Mustermann
veröffentlicht am 02.02.2021

output.asterisks.golden000066400000000000000000000005151455110214100354700ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/italicSome *Text* Some *Text* Some *Text* *DoubleItalic* Some *19,80€* Text *Content* and no space afterward. \_Not Italic\_ *First* *Second* *Text und Fotos: Max Mustermann* *Original veröffentlicht am 01.01.2021 auf* [Zeitung](http://example.com/posts/100979) *Übersetzung: Max Mustermann* *veröffentlicht am 02.02.2021*output.underscores.golden000066400000000000000000000005151455110214100360140ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/italicSome _Text_ Some _Text_ Some _Text_ _DoubleItalic_ Some _19,80€_ Text _Content_ and no space afterward. \_Not Italic\_ _First_ _Second_ _Text und Fotos: Max Mustermann_ _Original veröffentlicht am 01.01.2021 auf_ [Zeitung](http://example.com/posts/100979) _Übersetzung: Max Mustermann_ _veröffentlicht am 02.02.2021_golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/keep_remove_tag/000077500000000000000000000000001455110214100326745ustar00rootroot00000000000000goldmark.golden000066400000000000000000000001631455110214100356070ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/keep_remove_tag

Content

input.html000066400000000000000000000013401455110214100346400ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/keep_remove_tag

Content

Content

output.default.golden000066400000000000000000000000431455110214100367670ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/keep_remove_tag

Content

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link/000077500000000000000000000000001455110214100304755ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link/input.html000066400000000000000000000046511455110214100325300ustar00rootroot00000000000000 Simple Absolute Link
Simple Relative Link
Link with Space
Link with Title
Link with multiline Title
Broken Link

First Text

Second Text



first top second below


first left second right


BeforecloseAfter


Heading A

Heading B


DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"

Die App WDR aktuell begleitet Sie durch den Tag

Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.  |  mehr


output.inlined.golden000066400000000000000000000023751455110214100346000ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link[Simple Absolute Link](http://simple.org/) [Simple Relative Link](http://example.com/page.html) [Link with Space](http://space.org/) [Link with Title](http://title.org/ "Some Title") [Link with multiline Title](http://title.org/ "Some Long Title") [Broken Link](http://broken.com/test.html%5Cr) [First Text\ ![](http://example.com/xxx)\ Second Text](http://multi.org/) - [First Text\ \ Second Text](http://list.org/) [GitHub](https://github.com "GitHub") [first top](http://first_under.com) [second below](http://second_under.com) [first left](http://first_next.com) [second right](http://second_next.com) Before [close](http://example_close.com) After [**Heading A** **Heading B**](http://example.com/page.html) [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"](http://example.com/page.html "\"Vermögenssteuer ist aus wirtschaftlicher Sicht klug\"") [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**](http://example.com/nachrichten/wdr-aktuell-app-stores-100.html "Die App WDR aktuell begleitet Sie durch den Tag ")output.referenced_collapsed.golden000066400000000000000000000036531455110214100373060ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link[Simple Absolute Link][] [Simple Relative Link][] [Link with Space][] [Link with Title][] [Link with multiline Title][] [Broken Link][] [First Text\ ![](http://example.com/xxx)\ Second Text][] - [First Text\ \ Second Text][] [GitHub][] [first top][] [second below][] [first left][] [second right][] Before [close][] After [**Heading A** **Heading B**][] [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"][] [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**][] [Simple Absolute Link]: http://simple.org/ [Simple Relative Link]: http://example.com/page.html [Link with Space]: http://space.org/ [Link with Title]: http://title.org/ "Some Title" [Link with multiline Title]: http://title.org/ "Some Long Title" [Broken Link]: http://broken.com/test.html%5Cr [First Text\ ![](http://example.com/xxx)\ Second Text]: http://multi.org/ [First Text\ \ Second Text]: http://list.org/ [GitHub]: https://github.com "GitHub" [first top]: http://first_under.com [second below]: http://second_under.com [first left]: http://first_next.com [second right]: http://second_next.com [close]: http://example_close.com [**Heading A** **Heading B**]: http://example.com/page.html [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"]: http://example.com/page.html "\"Vermögenssteuer ist aus wirtschaftlicher Sicht klug\"" [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**]: http://example.com/nachrichten/wdr-aktuell-app-stores-100.html "Die App WDR aktuell begleitet Sie durch den Tag "output.referenced_full.golden000066400000000000000000000026051455110214100362760ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link[Simple Absolute Link][1] [Simple Relative Link][2] [Link with Space][3] [Link with Title][4] [Link with multiline Title][5] [Broken Link][6] [First Text\ ![](http://example.com/xxx)\ Second Text][7] - [First Text\ \ Second Text][8] [GitHub][9] [first top][10] [second below][11] [first left][12] [second right][13] Before [close][14] After [**Heading A** **Heading B**][15] [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"][16] [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**][17] [1]: http://simple.org/ [2]: http://example.com/page.html [3]: http://space.org/ [4]: http://title.org/ "Some Title" [5]: http://title.org/ "Some Long Title" [6]: http://broken.com/test.html%5Cr [7]: http://multi.org/ [8]: http://list.org/ [9]: https://github.com "GitHub" [10]: http://first_under.com [11]: http://second_under.com [12]: http://first_next.com [13]: http://second_next.com [14]: http://example_close.com [15]: http://example.com/page.html [16]: http://example.com/page.html "\"Vermögenssteuer ist aus wirtschaftlicher Sicht klug\"" [17]: http://example.com/nachrichten/wdr-aktuell-app-stores-100.html "Die App WDR aktuell begleitet Sie durch den Tag "output.referenced_shortcut.golden000066400000000000000000000036111455110214100372050ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link[Simple Absolute Link] [Simple Relative Link] [Link with Space] [Link with Title] [Link with multiline Title] [Broken Link] [First Text\ ![](http://example.com/xxx)\ Second Text] - [First Text\ \ Second Text] [GitHub] [first top] [second below] [first left] [second right] Before [close] After [**Heading A** **Heading B**] [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"] [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**] [Simple Absolute Link]: http://simple.org/ [Simple Relative Link]: http://example.com/page.html [Link with Space]: http://space.org/ [Link with Title]: http://title.org/ "Some Title" [Link with multiline Title]: http://title.org/ "Some Long Title" [Broken Link]: http://broken.com/test.html%5Cr [First Text\ ![](http://example.com/xxx)\ Second Text]: http://multi.org/ [First Text\ \ Second Text]: http://list.org/ [GitHub]: https://github.com "GitHub" [first top]: http://first_under.com [second below]: http://second_under.com [first left]: http://first_next.com [second right]: http://second_next.com [close]: http://example_close.com [**Heading A** **Heading B**]: http://example.com/page.html [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"]: http://example.com/page.html "\"Vermögenssteuer ist aus wirtschaftlicher Sicht klug\"" [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**]: http://example.com/nachrichten/wdr-aktuell-app-stores-100.html "Die App WDR aktuell begleitet Sie durch den Tag "output.relative.golden000066400000000000000000000022401455110214100347600ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/link[Simple Absolute Link](http://simple.org/) [Simple Relative Link](/page.html) [Link with Space](http://space.org/) [Link with Title](http://title.org/ "Some Title") [Link with multiline Title](http://title.org/ "Some Long Title") [Broken Link](http://broken.com/test.html\r) [First Text\ ![](xxx)\ Second Text](http://multi.org/) - [First Text\ \ Second Text](http://list.org/) [GitHub](https://github.com "GitHub") [first top](http://first_under.com) [second below](http://second_under.com) [first left](http://first_next.com) [second right](http://second_next.com) Before [close](http://example_close.com) After [**Heading A** **Heading B**](/page.html) [DIW-Chef zum Grünen-Programm "Vermögenssteuer ist aus wirtschaftlicher Sicht klug"](/page.html "\"Vermögenssteuer ist aus wirtschaftlicher Sicht klug\"") [**Die App WDR aktuell begleitet Sie durch den Tag**\ \ Sie möchten eine App, die Sie so durch den Tag in NRW begleitet, dass Sie jederzeit mitreden können? Die App WDR aktuell bietet Ihnen dafür immer die passenden Nachrichten.\  \| \ **mehr**](/nachrichten/wdr-aktuell-app-stores-100.html "Die App WDR aktuell begleitet Sie durch den Tag ")golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list/000077500000000000000000000000001455110214100305135ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list/goldmark.golden000066400000000000000000000052571455110214100335160ustar00rootroot00000000000000
  • Some Thing
  • Another Thing
  1. First Thing

  2. Second Thing

  3. 1

  4. 2

  5. 3

  6. 4

  7. 5

  8. 6

  9. 7

  10. 8

  11. 9

  12. 10

  13. 11

  14. 12

  15. 13

  16. 14

  17. 15

  18. 16

  19. 17

  20. 18

  21. 19

  22. 20

  23. 22

  24. 10

  25. 11

  26. 12

  27. 13

  28. 1

  29. 2

  30. 3

  31. 4

  32. 1

  33. 2

  34. 3

  35. 4

  36. 0

  37. 1

  38. 2

  39. 3

  40. Echo the word "foo"

echo('foo')
  1. Now echo "bar"
echo('bar')
  1. Link: example works

  2. Link: example works

  3. First Thing

    • Some Thing
    • Another Thing
  4. Second Thing

  • foo

  • bar

  • List items

  • Ending with

  • A space

  • Indent First Thing

    Second Thing

  • Third Thing

- Not List

1. Not List 1. Not List 1. Not List

  1. A paragraph with two lines.

    indented code
    

    A block quote.

  • Title

    Description

  • All manually reviewed Drosophila melanogaster entries

  • All manually reviewed Drosophila pseudoobscura pseudoobscura entries

  • before middle after

  • before middle after

  • before middle after

  • before middle after

Not in a list

golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list/input.html000066400000000000000000000072611455110214100325460ustar00rootroot00000000000000
  • Some Thing
  • Another Thing
  1. First Thing
  2. Second Thing
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 22
  1. 10
  2. 11
  3. 12
  4. 13
  1. 1
  2. 2
  3. 3
  4. 4
  1. 1
  2. 2
  3. 3
  4. 4
  1. 0
  2. 1
  3. 2
  4. 3
  1. Echo the word "foo"
echo('foo')
  1. Now echo "bar"
echo('bar')
  1. Link: example works
  2. Link: example works
  1. First Thing

    • Some Thing
    • Another Thing
  2. Second Thing
  • foo
  • bar
  • List items
  • Ending with
  • A space
  • Indent First Thing

    Second Thing

  • Third Thing

- Not List

1. Not List 1. Not List 1. Not List

  1. A paragraph with two lines.

    indented code

    A block quote.

  • Title

    Description

  • All manually reviewed Drosophila melanogaster entries
  • All manually reviewed Drosophila pseudoobscura pseudoobscura entries
  • before middle after
  • beforemiddleafter
  • beforemiddle after
  • before middleafter
  • Not in a list
  • output.asterisks.golden000066400000000000000000000026171455110214100352030ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list* Some Thing * Another Thing 1. First Thing 2. Second Thing 01. 1 02. 2 03. 3 04. 4 05. 5 06. 6 07. 7 08. 8 09. 9 10. 10 11. 11 12. 12 13. 13 14. 14 15. 15 19. 16 20. 17 21. 18 22. 19 23. 20 24. ![](http://example.com/example.png) 25. 22 10. 10 11. 11 12. 12 13. 13 1. 1 2. 2 3. 3 4. 4 1. 1 2. 2 3. 3 4. 4 0. 0 1. 1 2. 2 3. 3 3. Echo the word "foo" ``` echo('foo') ``` 4. Now echo "bar" ``` echo('bar') ``` * Link: [example](https://example.com) works * Link: [example](https://example.com) works * Link: [example](https://example.com) works * Link: [example](https://example.com) works * Link: [example](https://example.com) works 1. Link: [example](https://example.com) works 2. Link: [example](https://example.com) works 1. First Thing * Some Thing * Another Thing 2. Second Thing * foo * bar * List items * Ending with * A space * Indent First Thing Second Thing * Third Thing \- Not List 1\. Not List 1. Not List 1\. Not List 1. A paragraph with two lines. ``` indented code ``` > A block quote. * [![](http://example.com/icon.png)](http://example.com/icon) ### Title Description * All manually reviewed _Drosophila melanogaster_ entries * All manually reviewed _Drosophila pseudoobscura pseudoobscura_ entries * before _middle_ after * before _middle_ after * before _middle_ after * before _middle_ after Not in a listoutput.dash.golden000066400000000000000000000026171455110214100341120ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list- Some Thing - Another Thing 1. First Thing 2. Second Thing 01. 1 02. 2 03. 3 04. 4 05. 5 06. 6 07. 7 08. 8 09. 9 10. 10 11. 11 12. 12 13. 13 14. 14 15. 15 19. 16 20. 17 21. 18 22. 19 23. 20 24. ![](http://example.com/example.png) 25. 22 10. 10 11. 11 12. 12 13. 13 1. 1 2. 2 3. 3 4. 4 1. 1 2. 2 3. 3 4. 4 0. 0 1. 1 2. 2 3. 3 3. Echo the word "foo" ``` echo('foo') ``` 4. Now echo "bar" ``` echo('bar') ``` - Link: [example](https://example.com) works - Link: [example](https://example.com) works - Link: [example](https://example.com) works - Link: [example](https://example.com) works - Link: [example](https://example.com) works 1. Link: [example](https://example.com) works 2. Link: [example](https://example.com) works 1. First Thing - Some Thing - Another Thing 2. Second Thing - foo - bar - List items - Ending with - A space - Indent First Thing Second Thing - Third Thing \- Not List 1\. Not List 1. Not List 1\. Not List 1. A paragraph with two lines. ``` indented code ``` > A block quote. - [![](http://example.com/icon.png)](http://example.com/icon) ### Title Description - All manually reviewed _Drosophila melanogaster_ entries - All manually reviewed _Drosophila pseudoobscura pseudoobscura_ entries - before _middle_ after - before _middle_ after - before _middle_ after - before _middle_ after Not in a listoutput.plus.golden000066400000000000000000000026171455110214100341560ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list+ Some Thing + Another Thing 1. First Thing 2. Second Thing 01. 1 02. 2 03. 3 04. 4 05. 5 06. 6 07. 7 08. 8 09. 9 10. 10 11. 11 12. 12 13. 13 14. 14 15. 15 19. 16 20. 17 21. 18 22. 19 23. 20 24. ![](http://example.com/example.png) 25. 22 10. 10 11. 11 12. 12 13. 13 1. 1 2. 2 3. 3 4. 4 1. 1 2. 2 3. 3 4. 4 0. 0 1. 1 2. 2 3. 3 3. Echo the word "foo" ``` echo('foo') ``` 4. Now echo "bar" ``` echo('bar') ``` + Link: [example](https://example.com) works + Link: [example](https://example.com) works + Link: [example](https://example.com) works + Link: [example](https://example.com) works + Link: [example](https://example.com) works 1. Link: [example](https://example.com) works 2. Link: [example](https://example.com) works 1. First Thing + Some Thing + Another Thing 2. Second Thing + foo + bar + List items + Ending with + A space + Indent First Thing Second Thing + Third Thing \- Not List 1\. Not List 1. Not List 1\. Not List 1. A paragraph with two lines. ``` indented code ``` > A block quote. + [![](http://example.com/icon.png)](http://example.com/icon) ### Title Description + All manually reviewed _Drosophila melanogaster_ entries + All manually reviewed _Drosophila pseudoobscura pseudoobscura_ entries + before _middle_ after + before _middle_ after + before _middle_ after + before _middle_ after Not in a listgolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list_nested/000077500000000000000000000000001455110214100320555ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list_nested/input.html000066400000000000000000000073751455110214100341160ustar00rootroot00000000000000
    • foo
      • bar
        • baz
          • boo
    • Coffee
    • Tea
      • Black tea
      • Green tea
    • Milk

    header1

    • Bullet list
      • Nested bullet
        • Sub-nested bullet etc
    • Bullet list item 2
    • One
      • One point one
      • One point two
    1. One
      1. One point one
      2. One point two
    • 1
    • 2
      • 2.1
      • 2.2
        • 2.2.1
        • 2.2.2
        • 2.2.3
    • 3
    1. 1
    2. 2
      1. 2.1
      2. 2.2
        1. 2.2.1
        2. 2.2.2
        3. 2.2.3
    3. 3
    • First Thing

      Second Thing

      • Nested First Thing

        Nested Thing

    1. First Thing

      Second Thing

      1. Nested First Thing

        Nested Thing

      2. Date:

        20.02.2021

        26. Mai - 3. Juni

    • What...
    • about...
      different elements:
      variable
      
      The <img> tag is used to embed an image.
      
      The  tag is used to embed an image.
      

      Does it work?

    The Corinthos Center for Cancer will be partially closed for remodeling starting 4/15/21. Patients should be redirected as space permits in the following order:

    1. Metro Court West.
    2. Richie General.
      1. This place is ok.
      2. Watch out for the doctors.
        1. They bite.
        2. But not hard.
    3. Port Charles Main.

    For further information about appointment changes, contact:

    • Dorothy Hardy
      • Head of Operations
        • Interim
    • dorothy.hardy@generalhospital.org
    • 555-555-5555

    The remodel is expected to complete in June 2021. Timeframe subject to change.

    output.asterisks.golden000066400000000000000000000032621455110214100365420ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list_nested* foo * bar * baz * boo * Coffee * Tea * Black tea * Green tea * Milk # header1 * Bullet list * Nested bullet * Sub-nested bullet etc * Bullet list item 2 * One * One point one * One point two 1. One 1. One point one 2. One point two * 1 * 2 * 2.1 * 2.2 * 2.2.1 * 2.2.2 * 2.2.3 * 3 1. 1 2. 2 1. 2.1 2. 2.2 1. 2.2.1 2. 2.2.2 3. 2.2.3 4. 3 * First Thing Second Thing * Nested First Thing Nested Thing 1. First Thing Second Thing 1. Nested First Thing Nested Thing 2. Date: 20.02.2021 26\. Mai - 3. Juni * [Gesellschaft](http://example.com/gesellschaft/ "Gesellschaft") * [Panorama](http://example.com/gesellschaft/panorama/ "Panorama") * [Medien](http://example.com/gesellschaft/medien/ "Medien") * [Geschichte](http://example.com/gesellschaft/geschichte/ "Geschichte") * What... * about... ##### different elements: `variable` * * * ``` The tag is used to embed an image. The tag is used to embed an image. ``` Does it work? The Corinthos Center for Cancer will be partially closed for remodeling starting **4/15/21**. Patients should be redirected as space permits in the following order: 1. Metro Court West. 2. Richie General. 1. This place is ok. 2. Watch out for the doctors. 1. They bite. 2. But not hard. 4. Port Charles Main. For further information about appointment changes, contact: * Dorothy Hardy * _Head of Operations_ * _Interim_ * dorothy.hardy@generalhospital.org * 555-555-5555 _The remodel is_ [_expected_](http://www.google.com/) _to complete in June 2021._ **_Timeframe subject to change_** _._output.dash.golden000066400000000000000000000032621455110214100354510ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list_nested- foo - bar - baz - boo - Coffee - Tea - Black tea - Green tea - Milk # header1 - Bullet list - Nested bullet - Sub-nested bullet etc - Bullet list item 2 - One - One point one - One point two 1. One 1. One point one 2. One point two - 1 - 2 - 2.1 - 2.2 - 2.2.1 - 2.2.2 - 2.2.3 - 3 1. 1 2. 2 1. 2.1 2. 2.2 1. 2.2.1 2. 2.2.2 3. 2.2.3 4. 3 - First Thing Second Thing - Nested First Thing Nested Thing 1. First Thing Second Thing 1. Nested First Thing Nested Thing 2. Date: 20.02.2021 26\. Mai - 3. Juni - [Gesellschaft](http://example.com/gesellschaft/ "Gesellschaft") - [Panorama](http://example.com/gesellschaft/panorama/ "Panorama") - [Medien](http://example.com/gesellschaft/medien/ "Medien") - [Geschichte](http://example.com/gesellschaft/geschichte/ "Geschichte") - What... - about... ##### different elements: `variable` * * * ``` The tag is used to embed an image. The tag is used to embed an image. ``` Does it work? The Corinthos Center for Cancer will be partially closed for remodeling starting **4/15/21**. Patients should be redirected as space permits in the following order: 1. Metro Court West. 2. Richie General. 1. This place is ok. 2. Watch out for the doctors. 1. They bite. 2. But not hard. 4. Port Charles Main. For further information about appointment changes, contact: - Dorothy Hardy - _Head of Operations_ - _Interim_ - dorothy.hardy@generalhospital.org - 555-555-5555 _The remodel is_ [_expected_](http://www.google.com/) _to complete in June 2021._ **_Timeframe subject to change_** _._output.plus.golden000066400000000000000000000032621455110214100355150ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/list_nested+ foo + bar + baz + boo + Coffee + Tea + Black tea + Green tea + Milk # header1 + Bullet list + Nested bullet + Sub-nested bullet etc + Bullet list item 2 + One + One point one + One point two 1. One 1. One point one 2. One point two + 1 + 2 + 2.1 + 2.2 + 2.2.1 + 2.2.2 + 2.2.3 + 3 1. 1 2. 2 1. 2.1 2. 2.2 1. 2.2.1 2. 2.2.2 3. 2.2.3 4. 3 + First Thing Second Thing + Nested First Thing Nested Thing 1. First Thing Second Thing 1. Nested First Thing Nested Thing 2. Date: 20.02.2021 26\. Mai - 3. Juni + [Gesellschaft](http://example.com/gesellschaft/ "Gesellschaft") + [Panorama](http://example.com/gesellschaft/panorama/ "Panorama") + [Medien](http://example.com/gesellschaft/medien/ "Medien") + [Geschichte](http://example.com/gesellschaft/geschichte/ "Geschichte") + What... + about... ##### different elements: `variable` * * * ``` The tag is used to embed an image. The tag is used to embed an image. ``` Does it work? The Corinthos Center for Cancer will be partially closed for remodeling starting **4/15/21**. Patients should be redirected as space permits in the following order: 1. Metro Court West. 2. Richie General. 1. This place is ok. 2. Watch out for the doctors. 1. They bite. 2. But not hard. 4. Port Charles Main. For further information about appointment changes, contact: + Dorothy Hardy + _Head of Operations_ + _Interim_ + dorothy.hardy@generalhospital.org + 555-555-5555 _The remodel is_ [_expected_](http://www.google.com/) _to complete in June 2021._ **_Timeframe subject to change_** _._golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/p_tag/000077500000000000000000000000001455110214100306325ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/p_tag/goldmark.golden000066400000000000000000000011121455110214100336170ustar00rootroot00000000000000

    Some Content

    Text

    Some Text

    Some Content

    jmap –histo[:live]

    Sometimes a struct field, function, type, or even a whole package becomes

    redundant or unnecessary, but must be kept for compatibility with existing

    programs.

    To signal that an identifier should not be used, add a paragraph to its doc

    comment that begins with "Deprecated:" followed by some information about the

    deprecation.

    There are a few examples in the standard library.

    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/p_tag/input.html000066400000000000000000000014621455110214100326620ustar00rootroot00000000000000

    Some Content

    Text

    Some Text

    Some Content

    jmap –histo[:live]

    Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples in the standard library.

    output.default.golden000066400000000000000000000007551455110214100347370ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/p_tagSome Content Text Some Text Some Content jmap –histo\[:live\] Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library](https://golang.org/search?q=Deprecated:).golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code/000077500000000000000000000000001455110214100313205ustar00rootroot00000000000000goldmark.golden000066400000000000000000000065541455110214100342450ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code

    last_30_days

    with backtick (`)

    with backtick (``)

    here are three ``` here are four ```` here is one ` that's it

    `starting & ending with a backtick`

    Who ate the most donuts this week?

    Jeff  15
    Sam   11
    Robin  6
    
    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    

    When x = 3, that means x + 2 = 5

    The <img> tag is used to embed an image.

    The `` tag is used to embed an image.

    Two variables A B

    CSS: body { color: yellow; font-size: 16px; }

    CSS: body { color: yellow; font-size: 16px; }

    ```
    
    ~~~
    
    
    Some ~~~
    totally ~~~~~~ normal
    ~ code
    
    
    
    The <img> tag is used to embed an image.
    
    The  tag is used to embed an image.
    
    
    
    
            One
            Two
            Three
    
    
    
    
    
    
    

    We can do this like so:

    window.location.pathname.split("/");
    // ["", "blog", "javascript", "how-to-get-the-last-segment-of-a-url-in-javascript", ""]
    

    function fn() {
        x = 1;
        return x;
        // eslint-disable-next-line no-unreachable
        x = 3;
    }
    

    <p><span>Some text</span></p>
    
    

    Copy to Clipboard


    HTML:

    <p>Using CSS to change the font color is easy.</p>
    <pre>
    body {
    color: red;
    }
    </pre>
    
    

    Copy to Clipboard


    ---
    title: "Hello! This is the markdown file"
    date: 2021-09-25
    tags: ["react"]
    ---
    
    Content of the post goes here.
    
    ![Image with alt text](./image.png)
    

    <code class="language-css">p { color: red }</code>
    

    The function selectAll() highlights all the text in the input field so the user can, for example, copy or delete the text.

    The <pre> HTML element represents preformatted text.


    
    This is the content:
    _code_
    _pre_
    Cool!
    
    

    A simple equation: x = y + 2

    When the process is complete, the utility will output the text Scan complete. Found N results. You can then proceed to the next step.

    Use the command help mycommand to view documentation for the command "mycommand".

    The telnet client should display: Local Echo is on

    The operator (#) takes a bit x and a sequence a and produces a new sequence x # a with x as the head and a as the tail (very much like the built-in operation (:) for lists):

    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code/input.html000066400000000000000000000174771455110214100333650ustar00rootroot00000000000000 last_30_days
    with backtick (`)
    with backtick (``)
    here are three ``` here are four ```` here is one ` that's it
    `starting & ending with a backtick`

    Who ate the most donuts this week?

    Jeff  15
    Sam   11
    Robin  6
    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {

    When x = 3, that means x + 2 = 5

    The <img> tag is used to embed an image.

    The tag is used to embed an image.

    Two variables A B

    CSS: body { color: yellow; font-size: 16px; }

    CSS: body { color: yellow; font-size: 16px; }

    ```
    ~~~
    
    Some ~~~
    totally ~~~~~~ normal
    ~ code
    
    
    The <img> tag is used to embed an image.
    
    The  tag is used to embed an image.
    
    
        
    • One
    • Two
    • Three
    
    
        
    
    

    We can do this like so:

    window.location.pathname.split("/");
    // ["", "blog", "javascript", "how-to-get-the-last-segment-of-a-url-in-javascript", ""]

    function fn() {
        x = 1;
        return x;
        // eslint-disable-next-line no-unreachable
        x = 3;
    }

    <p><span>Some text</span></p>
    

    HTML:

    <p>Using CSS to change the font color is easy.</p>
    <pre>
    body {
    color: red;
    }
    </pre>
    

    ---
    title: "Hello! This is the markdown file"
    date: 2021-09-25
    tags: ["react"]
    ---
    
    Content of the post goes here.
    
    ![Image with alt text](./image.png)

    <code class="language-css">p { color: red }</code>

    The function selectAll() highlights all the text in the input field so the user can, for example, copy or delete the text.

    The <pre> HTML element represents preformatted text.


    
    
    This is the content:
    _code_
    
    _pre_
    Cool!

    A simple equation: x = y + 2

    When the process is complete, the utility will output the text Scan complete. Found N results. You can then proceed to the next step.

    Use the command help mycommand to view documentation for the command "mycommand".

    The telnet client should display: Local Echo is on

    The operator (#) takes a bit x and a sequence a and produces a new sequence x # a with x as the head and a as the tail (very much like the built-in operation (:) for lists):

    output.fenced_backtick.golden000066400000000000000000000047741455110214100370650ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code`last_30_days` ``with backtick (`)`` ```with backtick (``)``` `````here are three ``` here are four ```` here is one ` that's it````` `` `starting & ending with a backtick` `` Who ate the most donuts this week? ```foo+bar Jeff 15 Sam 11 Robin 6 ``` ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` When `x = 3`, that means `x + 2 = 5` The `` tag is used to embed an image. The `` tag is used to embed an image. Two variables `A` `B` CSS: ` body { color: yellow; font-size: 16px; } ` CSS: ` body { color: yellow; font-size: 16px; } ` ```` ``` ```` ``` ~~~ ``` ``` Some ~~~ totally ~~~~~~ normal ~ code ``` ``` The tag is used to embed an image. The tag is used to embed an image. ``` ``` One Two Three ``` ``` ``` We can do this like so: ```js window.location.pathname.split("/"); // ["", "blog", "javascript", "how-to-get-the-last-segment-of-a-url-in-javascript", ""] ``` * * * ```js function fn() { x = 1; return x; // eslint-disable-next-line no-unreachable x = 3; } ``` * * * ```

    Some text

    ``` Copy to Clipboard * * * #### HTML: ```

    Using CSS to change the font color is easy.

    body {
    color: red;
    }
    
    ``` Copy to Clipboard * * * ```md --- title: "Hello! This is the markdown file" date: 2021-09-25 tags: ["react"] --- Content of the post goes here. ![Image with alt text](./image.png) ``` * * * ```markup p { color: red } ``` * * * The function `selectAll()` highlights all the text in the input field so the user can, for example, copy or delete the text. The **`
    `** [HTML](http://example.com/en-US/docs/Web/HTML) element represents preformatted text.
    
    * * *
    
    ```
    
    This is the content:
    _code_
    _pre_
    Cool!
    
    ```
    
    * * *
    
    A simple equation: x = y \+ 2
    
    When the process is complete, the utility will output the text
    `Scan complete. Found N results.` You can then
    proceed to the next step.
    
    Use the command `help mycommand` to view documentation
    for the command "mycommand".
    
    The telnet client should display: `Local Echo is on`
    
    The operator `(#)` takes a bit `x` and a
    sequence `a` and produces a new sequence `x # a` with
    `x` as the head and `a` as the tail (very much like the
    built-in operation `(:)` for lists):output.fenced_tilde.golden000066400000000000000000000050041455110214100363760ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code`last_30_days`
    
    ``with backtick (`)``
    
    ```with backtick (``)```
    
    `````here are three ``` here are four ```` here is one ` that's it`````
    
    `` `starting & ending with a backtick` ``
    
    Who ate the most donuts this week?
    
    ~~~foo+bar
    Jeff  15
    Sam   11
    Robin  6
    ~~~
    
    ~~~
    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    ~~~
    
    When `x = 3`, that means `x + 2 = 5`
    
    The `` tag is used to embed an image.
    
    The `` tag is used to embed an image.
    
    Two variables `A` `B`
    
    CSS: `
    body {
        color: yellow;
        font-size: 16px;
    }
    `
    
    CSS: `
    body {
        color: yellow;
        font-size: 16px;
    }
    `
    
    ~~~
    ```
    ~~~
    
    ~~~~
    ~~~
    ~~~~
    
    ~~~~~~~
    
    Some ~~~
    totally ~~~~~~ normal
    ~ code
    
    ~~~~~~~
    
    ~~~
    
    The  tag is used to embed an image.
    
    The  tag is used to embed an image.
    
    ~~~
    
    ~~~
    
    
            One
            Two
            Three
    
    
    ~~~
    
    ~~~
    
    
    
    ~~~
    
    We can do this like so:
    
    ~~~js
    window.location.pathname.split("/");
    // ["", "blog", "javascript", "how-to-get-the-last-segment-of-a-url-in-javascript", ""]
    ~~~
    
    * * *
    
    ~~~js
    function fn() {
        x = 1;
        return x;
        // eslint-disable-next-line no-unreachable
        x = 3;
    }
    ~~~
    
    * * *
    
    ~~~
    

    Some text

    ~~~ Copy to Clipboard * * * #### HTML: ~~~

    Using CSS to change the font color is easy.

    body {
    color: red;
    }
    
    ~~~ Copy to Clipboard * * * ~~~md --- title: "Hello! This is the markdown file" date: 2021-09-25 tags: ["react"] --- Content of the post goes here. ![Image with alt text](./image.png) ~~~ * * * ~~~markup p { color: red } ~~~ * * * The function `selectAll()` highlights all the text in the input field so the user can, for example, copy or delete the text. The **`
    `** [HTML](http://example.com/en-US/docs/Web/HTML) element represents preformatted text.
    
    * * *
    
    ~~~
    
    This is the content:
    _code_
    _pre_
    Cool!
    
    ~~~
    
    * * *
    
    A simple equation: x = y \+ 2
    
    When the process is complete, the utility will output the text
    `Scan complete. Found N results.` You can then
    proceed to the next step.
    
    Use the command `help mycommand` to view documentation
    for the command "mycommand".
    
    The telnet client should display: `Local Echo is on`
    
    The operator `(#)` takes a bit `x` and a
    sequence `a` and produces a new sequence `x # a` with
    `x` as the head and `a` as the tail (very much like the
    built-in operation `(:)` for lists):output.indented.golden000066400000000000000000000047741455110214100356000ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/pre_code`last_30_days`
    
    ``with backtick (`)``
    
    ```with backtick (``)```
    
    `````here are three ``` here are four ```` here is one ` that's it`````
    
    `` `starting & ending with a backtick` ``
    
    Who ate the most donuts this week?
    
    ```foo+bar
    Jeff  15
    Sam   11
    Robin  6
    ```
    
    ```
    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    ```
    
    When `x = 3`, that means `x + 2 = 5`
    
    The `` tag is used to embed an image.
    
    The `` tag is used to embed an image.
    
    Two variables `A` `B`
    
    CSS: `
    body {
        color: yellow;
        font-size: 16px;
    }
    `
    
    CSS: `
    body {
        color: yellow;
        font-size: 16px;
    }
    `
    
    ````
    ```
    ````
    
    ```
    ~~~
    ```
    
    ```
    
    Some ~~~
    totally ~~~~~~ normal
    ~ code
    
    ```
    
    ```
    
    The  tag is used to embed an image.
    
    The  tag is used to embed an image.
    
    ```
    
    ```
    
    
            One
            Two
            Three
    
    
    ```
    
    ```
    
    
    
    ```
    
    We can do this like so:
    
    ```js
    window.location.pathname.split("/");
    // ["", "blog", "javascript", "how-to-get-the-last-segment-of-a-url-in-javascript", ""]
    ```
    
    * * *
    
    ```js
    function fn() {
        x = 1;
        return x;
        // eslint-disable-next-line no-unreachable
        x = 3;
    }
    ```
    
    * * *
    
    ```
    

    Some text

    ``` Copy to Clipboard * * * #### HTML: ```

    Using CSS to change the font color is easy.

    body {
    color: red;
    }
    
    ``` Copy to Clipboard * * * ```md --- title: "Hello! This is the markdown file" date: 2021-09-25 tags: ["react"] --- Content of the post goes here. ![Image with alt text](./image.png) ``` * * * ```markup p { color: red } ``` * * * The function `selectAll()` highlights all the text in the input field so the user can, for example, copy or delete the text. The **`
    `** [HTML](http://example.com/en-US/docs/Web/HTML) element represents preformatted text.
    
    * * *
    
    ```
    
    This is the content:
    _code_
    _pre_
    Cool!
    
    ```
    
    * * *
    
    A simple equation: x = y \+ 2
    
    When the process is complete, the utility will output the text
    `Scan complete. Found N results.` You can then
    proceed to the next step.
    
    Use the command `help mycommand` to view documentation
    for the command "mycommand".
    
    The telnet client should display: `Local Echo is on`
    
    The operator `(#)` takes a bit `x` and a
    sequence `a` and produces a new sequence `x # a` with
    `x` as the head and `a` as the tail (very much like the
    built-in operation `(:)` for lists):golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/sup_element/000077500000000000000000000000001455110214100320605ustar00rootroot00000000000000goldmark.golden000066400000000000000000000003341455110214100347730ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/sup_element

    One of the most common equations in all of physics is E=mc2.

    The ordinal number "fifth" can be abbreviated in various languages as follows:

    • English: 5th
    • French: 5ème
    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/sup_element/input.html000066400000000000000000000004731455110214100341110ustar00rootroot00000000000000

    One of the most common equations in all of physics is E=mc2.

    The ordinal number "fifth" can be abbreviated in various languages as follows:

    • English: 5th
    • French: 5ème
    output.default.golden000066400000000000000000000002541455110214100361570ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestCommonmark/sup_elementOne of the most common equations in all of physics is E=mc2. The ordinal number "fifth" can be abbreviated in various languages as follows: - English: 5th - French: 5èmegolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/000077500000000000000000000000001455110214100270565ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/checkbox/000077500000000000000000000000001455110214100306445ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/checkbox/goldmark.golden000066400000000000000000000011361455110214100336370ustar00rootroot00000000000000
    • Checked!

    • Check Me!

    • Check Me B

    • Check Me C

      • Check Nested 1
      • Check Nested 2
    • Check Me D

      • Check Nested 1
      • Check Nested 2
    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/checkbox/input.html000066400000000000000000000011101455110214100326620ustar00rootroot00000000000000
    • Checked!
    • Check Me!
    • Check Me B
    • Check Me C
      • Check Nested 1
      • Check Nested 2
    • Check Me D
      • Check Nested 1
      • Check Nested 2
    output.default.golden000066400000000000000000000002571455110214100347460ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/checkbox- [x] Checked! - [ ] Check Me! - [ ] Check Me B - [ ] Check Me C - [ ] Check Nested 1 - [ ] Check Nested 2 - [ ] Check Me D - [ ] Check Nested 1 - [ ] Check Nested 2golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/000077500000000000000000000000001455110214100323125ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/blog/000077500000000000000000000000001455110214100332355ustar00rootroot00000000000000goldmark.golden000066400000000000000000000004631455110214100361530ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/blog

    +++ type: page layout: simplelayout title: Berlin date: "2022-10-01 12:32:00 +0200" weight: 2 tags:

    • Berlin
    • Sommer url: /travel/02-berlin/ description: Lorem ipsum dolor sit amet, consectetur adipiscing elit image: files/2022/berlin.jpg +++

    {{ title }}

    input.html000066400000000000000000000004161455110214100352040ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/blog+++ type: page layout: simplelayout title: Berlin date: "2022-10-01 12:32:00 +0200" weight: 2 tags: - Berlin - Sommer url: /travel/02-berlin/ description: Lorem ipsum dolor sit amet, consectetur adipiscing elit image: files/2022/berlin.jpg +++

    {{ title }}

    output.default.golden000066400000000000000000000004061455110214100373330ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/blog+++ type: page layout: simplelayout title: Berlin date: "2022-10-01 12:32:00 +0200" weight: 2 tags: - Berlin - Sommer url: /travel/02-berlin/ description: Lorem ipsum dolor sit amet, consectetur adipiscing elit image: files/2022/berlin.jpg +++ # {{ title }}golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/jekyll/000077500000000000000000000000001455110214100336045ustar00rootroot00000000000000goldmark.golden000066400000000000000000000000641455110214100365170ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/jekyll

    +++ food: Pizza +++

    {{ page.food }}

    input.html000066400000000000000000000000561455110214100355530ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/jekyll+++ food: Pizza +++

    {{ page.food }}

    output.default.golden000066400000000000000000000000461455110214100377020ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/jekyll+++ food: Pizza +++ # {{ page.food }}golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/not/000077500000000000000000000000001455110214100331125ustar00rootroot00000000000000goldmark.golden000066400000000000000000000000761455110214100360300ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/not

    ---title---

    --- type: page tags: - Berlin ---

    input.html000066400000000000000000000000721455110214100350570ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/not

    ---title---

    --- type: page tags: - Berlin --- output.default.golden000066400000000000000000000000701455110214100372050ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/not# ---title--- \-\-\- type: page tags: \- Berlin \-\-\-golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/simple/000077500000000000000000000000001455110214100336035ustar00rootroot00000000000000goldmark.golden000066400000000000000000000002671455110214100365230ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/simple

    type: page tags:

    • Berlin

    also some content --- also_not: frontmatter ---

    Start of the HTML Document.

    Title

    input.html000066400000000000000000000003321455110214100355470ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/simple--- type: page tags: - Berlin --- some content (TODO: fix this) --- not: frontmatter ---
    also some content --- also_not: frontmatter --- Start of the HTML Document.

    Title

    output.default.golden000066400000000000000000000002051455110214100376760ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/movefrontmatter/simple--- type: page tags: - Berlin --- also some content \-\-\- also\_not: frontmatter \-\-\- Start of the **HTML** Document. # Titlegolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/strikethrough/000077500000000000000000000000001455110214100317605ustar00rootroot00000000000000goldmark.golden000066400000000000000000000002051455110214100346700ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/strikethrough

    Some Strikethrough Text

    Only blue ones left

    Some Strikethrough Text

    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/strikethrough/input.html000066400000000000000000000003061455110214100340040ustar00rootroot00000000000000

    Some Strikethrough Text

    Only blue ones left

    Some Strikethrough Text

    output.default.golden000066400000000000000000000001251455110214100360540ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/strikethroughSome ~~Strikethrough~~ Text Only ~~blue ones~~ ~~left~~ Some ~~Strikethrough~~ Textgolang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/table/000077500000000000000000000000001455110214100301455ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/table/input.html000066400000000000000000000042731455110214100322000ustar00rootroot00000000000000
    Firstname Lastname Age
    Jill Smith 50
    Eve Jackson 94
    Empty
    End

    With | Character

    Firstname With | Character Age
    Jill Smith 50
    Eve Jackson 94

    Tabelle mit thead, tfoot, and tbody

    Header content 1 Header content 2
    Footer content 1 Footer content 2
    Body content 1 Body content 2
    Unglaublich tolle Beschreibung
    Unglaublich tolle Daten

    Pegel DUISBURG-RUHRORT

    logo

    Quelle:

    A B
    A B C D
    A B C
    Strong Link Italic
    var b c

    1

    2

    3

    1

    2

    3

    output.default.golden000066400000000000000000000007061455110214100342460ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/tableFirstnameLastnameAgeJillSmith50EveJackson94EmptyEnd ### With \| Character FirstnameWith \| CharacterAgeJillSmith50 Eve Jackson 94 ### Tabelle mit thead, tfoot, and tbody Header content 1Header content 2Footer content 1Footer content 2Body content 1Body content 2Unglaublich tolle BeschreibungUnglaublich tolle Daten #### Pegel DUISBURG-RUHRORT Quelle: … ABABCDABC**Strong**[Link](http://example.com/link.html)_Italic_`var`bc 1 2 3 1 2 3golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/table/output.table.golden000066400000000000000000000015141455110214100337660ustar00rootroot00000000000000| Firstname | Lastname | Age | | --- | --- | --- | | Jill | Smith | 50 | | Eve | Jackson | 94 | | Empty | | | | End | ### With \| Character | Firstname | With \| Character | Age | | --- | --- | --- | | Jill | Smith | 50 | | Eve | Jackson | 94 | ### Tabelle mit thead, tfoot, and tbody | Header content 1 | Header content 2 | | --- | --- | | Footer content 1 | Footer content 2 | | Body content 1 | Body content 2 | | | | --- | | Unglaublich tolle Daten | Unglaublich tolle Beschreibung | | | | | | --- | --- | --- | --- | | A | B | | A | B | C | D | | A | B | C | #### Pegel DUISBURG-RUHRORT Quelle: … | | | | | --- | --- | --- | | **Strong** | [Link](http://example.com/link.html) | _Italic_ | | `var` | b | c | | | | --- | | 1
    2
    3 | | | | --- | | | | | --- | | 1
    2
    3 | |output.tablecompat.golden000066400000000000000000000010761455110214100351160ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestPlugins/tableFirstname · Lastname · Age Jill · Smith · 50 Eve · Jackson · 94 Empty End ### With \| Character Firstname · With \| Character · Age Jill · Smith · 50 Eve · Jackson · 94 ### Tabelle mit thead, tfoot, and tbody Header content 1 · Header content 2 Footer content 1 · Footer content 2 Body content 1 · Body content 2 Unglaublich tolle BeschreibungUnglaublich tolle Daten #### Pegel DUISBURG-RUHRORT Quelle: … A · B A · B · C · D A · B · C **Strong** · [Link](http://example.com/link.html) · _Italic_ `var` · b · c 1 2 3 1 2 3golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/000077500000000000000000000000001455110214100273305ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.org/000077500000000000000000000000001455110214100323075ustar00rootroot00000000000000goldmark.golden000066400000000000000000000241221455110214100352230ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.org

    Godoc: documenting Go code - The Go Blog

    The Go Programming Language

    Go

    Documents Packages The Project Help Blogsubmit search

    Next article

    Introducing Gofix

    Previous article

    Gobs of data

    Links

    Blog index

    The Go Blog

    Godoc: documenting Go code

    31 March 2011

    The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone.

    To that end, we have developed the godoc documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects.

    Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's documentation to its implementation with one click.

    Godoc is conceptually related to Python's Docstring and Java's Javadoc, but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist.

    The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the fmt package's Fprint function:

    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
    

    Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence.

    Comments on package declarations should provide general package documentation. These comments can be short, like the sort package's brief description:

    // Package sort provides primitives for sorting slices and user-defined
    // collections.
    package sort
    

    They can also be detailed like the gob package's overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, doc.go, which contains only those comments and a package clause.

    When writing package comments of any size, keep in mind that their first sentence will appear in godoc's package list.

    Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word "BUG(who)” are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the bytes package:

    // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly.
    

    Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples in the standard library.

    There are a few formatting rules that Godoc uses when converting comments to HTML:

    • Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs.

    • Pre-formatted text must be indented relative to the surrounding comment text (see gob's doc.go for an example).

    • URLs will be converted to HTML links; no special markup is necessary.

    Note that none of these rules requires you to do anything out of the ordinary.

    In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions.

    Your own code can present good documentation just by having comments as described above. Any Go packages installed inside $GOROOT/src/pkg and any GOPATH work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the -path flag or just by running "godoc ." in the source directory. See the godoc documentation for more details.

    By Andrew Gerrand

    Related articles

    Except as noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License,

    and code is licensed under a BSD license.

    Terms of Service | Privacy Policy | View the source code

    input.html000066400000000000000000000406271455110214100342660ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.org Godoc: documenting Go code - The Go Blog

    The Go Blog

    Godoc: documenting Go code

    31 March 2011

    The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone.

    To that end, we have developed the godoc documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects.

    Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's documentation to its implementation with one click.

    Godoc is conceptually related to Python's Docstring and Java's Javadoc, but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist.

    The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the fmt package's Fprint function:

    // Fprint formats using the default formats for its operands and writes to w.
    // Spaces are added between operands when neither is a string.
    // It returns the number of bytes written and any write error encountered.
    func Fprint(w io.Writer, a ...interface{}) (n int, err error) {

    Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence.

    Comments on package declarations should provide general package documentation. These comments can be short, like the sort package's brief description:

    // Package sort provides primitives for sorting slices and user-defined
    // collections.
    package sort

    They can also be detailed like the gob package's overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, doc.go, which contains only those comments and a package clause.

    When writing package comments of any size, keep in mind that their first sentence will appear in godoc's package list.

    Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word "BUG(who)” are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the bytes package:

    // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly.

    Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples in the standard library.

    There are a few formatting rules that Godoc uses when converting comments to HTML:

    • Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs.
    • Pre-formatted text must be indented relative to the surrounding comment text (see gob's doc.go for an example).
    • URLs will be converted to HTML links; no special markup is necessary.

    Note that none of these rules requires you to do anything out of the ordinary.

    In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions.

    Your own code can present good documentation just by having comments as described above. Any Go packages installed inside $GOROOT/src/pkg and any GOPATH work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the -path flag or just by running "godoc ." in the source directory. See the godoc documentation for more details.

    By Andrew Gerrand

    Related articles

    output.emphasis_asterisks.golden000066400000000000000000000213731455110214100406700ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language](http://golang.org/) [Go](http://golang.org/) ▽ [Documents](http://golang.org/doc/) [Packages](http://golang.org/pkg/) [The Project](http://golang.org/project/) [Help](http://golang.org/help/) [Blog](http://blog.golang.org/)submit search #### Next article [Introducing Gofix](http://blog.golang.org/introducing-gofix) #### Previous article [Gobs of data](http://blog.golang.org/gobs-of-data) #### Links - [golang.org](http://golang.org/) - [Install Go](http://golang.org/doc/install.html) - [A Tour of Go](http://tour.golang.org/) - [Go Documentation](http://golang.org/doc/) - [Go Mailing List](http://groups.google.com/group/golang-nuts) - [Go on Google+](http://plus.google.com/101406623878176903605) - [Go+ Community](http://plus.google.com/communities/114112804251407510571) - [Go on Twitter](http://twitter.com/golang) [Blog index](http://blog.golang.org/index) # [The Go Blog](http://blog.golang.org/) ### [Godoc: documenting Go code](http://blog.golang.org/godoc-documenting-go-code) 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc](https://golang.org/cmd/godoc/) documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation](https://golang.org/pkg/strings/#HasPrefix) to its [implementation](https://golang.org/src/pkg/strings/strings.go#L493) with one click. Godoc is conceptually related to Python's [Docstring](http://www.python.org/dev/peps/pep-0257/) and Java's [Javadoc](http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html), but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`](https://golang.org/pkg/fmt/#Fprint) function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`](https://golang.org/pkg/sort/) package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package](https://golang.org/pkg/encoding/gob/)'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go), which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list](https://golang.org/pkg/). Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package](https://golang.org/pkg/bytes/#pkg-note-BUG): ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library](https://golang.org/search?q=Deprecated:). There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go) for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation](https://golang.org/cmd/godoc/) for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push](http://blog.golang.org/h2push) - [Introducing HTTP Tracing](http://blog.golang.org/http-tracing) - [Testable Examples in Go](http://blog.golang.org/examples) - [Generating code](http://blog.golang.org/generate) - [Introducing the Go Race Detector](http://blog.golang.org/race-detector) - [Go maps in action](http://blog.golang.org/go-maps-in-action) - [go fmt your code](http://blog.golang.org/go-fmt-your-code) - [Organizing Go code](http://blog.golang.org/organizing-go-code) - [Debugging Go programs with the GNU Debugger](http://blog.golang.org/debugging-go-programs-with-gnu-debugger) - [The Go image/draw package](http://blog.golang.org/go-imagedraw-package) - [The Go image package](http://blog.golang.org/go-image-package) - [The Laws of Reflection](http://blog.golang.org/laws-of-reflection) - [Error handling and Go](http://blog.golang.org/error-handling-and-go) - ["First Class Functions in Go"](http://blog.golang.org/first-class-functions-in-go-and-new-go) - [Profiling Go Programs](http://blog.golang.org/profiling-go-programs) - [A GIF decoder: an exercise in Go interfaces](http://blog.golang.org/gif-decoder-exercise-in-go-interfaces) - [Introducing Gofix](http://blog.golang.org/introducing-gofix) - [Gobs of data](http://blog.golang.org/gobs-of-data) - [C? Go? Cgo!](http://blog.golang.org/c-go-cgo) - [JSON and Go](http://blog.golang.org/json-and-go) - [Go Slices: usage and internals](http://blog.golang.org/go-slices-usage-and-internals) - [Go Concurrency Patterns: Timing out, moving on](http://blog.golang.org/go-concurrency-patterns-timing-out-and) - [Defer, Panic, and Recover](http://blog.golang.org/defer-panic-and-recover) - [Share Memory By Communicating](http://blog.golang.org/share-memory-by-communicating) - [JSON-RPC: a tale of interfaces](http://blog.golang.org/json-rpc-tale-of-interfaces) Except as [noted](https://developers.google.com/site-policies#restrictions), the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license](http://golang.org/LICENSE). [Terms of Service](http://golang.org/doc/tos.html) \| [Privacy Policy](http://www.google.com/intl/en/policies/privacy/) \| [View the source code](https://go.googlesource.com/blog/)output.emphasis_underscores.golden000066400000000000000000000213731455110214100412140ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language](http://golang.org/) [Go](http://golang.org/) ▽ [Documents](http://golang.org/doc/) [Packages](http://golang.org/pkg/) [The Project](http://golang.org/project/) [Help](http://golang.org/help/) [Blog](http://blog.golang.org/)submit search #### Next article [Introducing Gofix](http://blog.golang.org/introducing-gofix) #### Previous article [Gobs of data](http://blog.golang.org/gobs-of-data) #### Links - [golang.org](http://golang.org/) - [Install Go](http://golang.org/doc/install.html) - [A Tour of Go](http://tour.golang.org/) - [Go Documentation](http://golang.org/doc/) - [Go Mailing List](http://groups.google.com/group/golang-nuts) - [Go on Google+](http://plus.google.com/101406623878176903605) - [Go+ Community](http://plus.google.com/communities/114112804251407510571) - [Go on Twitter](http://twitter.com/golang) [Blog index](http://blog.golang.org/index) # [The Go Blog](http://blog.golang.org/) ### [Godoc: documenting Go code](http://blog.golang.org/godoc-documenting-go-code) 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc](https://golang.org/cmd/godoc/) documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation](https://golang.org/pkg/strings/#HasPrefix) to its [implementation](https://golang.org/src/pkg/strings/strings.go#L493) with one click. Godoc is conceptually related to Python's [Docstring](http://www.python.org/dev/peps/pep-0257/) and Java's [Javadoc](http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html), but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`](https://golang.org/pkg/fmt/#Fprint) function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`](https://golang.org/pkg/sort/) package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package](https://golang.org/pkg/encoding/gob/)'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go), which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list](https://golang.org/pkg/). Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package](https://golang.org/pkg/bytes/#pkg-note-BUG): ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library](https://golang.org/search?q=Deprecated:). There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go) for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation](https://golang.org/cmd/godoc/) for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push](http://blog.golang.org/h2push) - [Introducing HTTP Tracing](http://blog.golang.org/http-tracing) - [Testable Examples in Go](http://blog.golang.org/examples) - [Generating code](http://blog.golang.org/generate) - [Introducing the Go Race Detector](http://blog.golang.org/race-detector) - [Go maps in action](http://blog.golang.org/go-maps-in-action) - [go fmt your code](http://blog.golang.org/go-fmt-your-code) - [Organizing Go code](http://blog.golang.org/organizing-go-code) - [Debugging Go programs with the GNU Debugger](http://blog.golang.org/debugging-go-programs-with-gnu-debugger) - [The Go image/draw package](http://blog.golang.org/go-imagedraw-package) - [The Go image package](http://blog.golang.org/go-image-package) - [The Laws of Reflection](http://blog.golang.org/laws-of-reflection) - [Error handling and Go](http://blog.golang.org/error-handling-and-go) - ["First Class Functions in Go"](http://blog.golang.org/first-class-functions-in-go-and-new-go) - [Profiling Go Programs](http://blog.golang.org/profiling-go-programs) - [A GIF decoder: an exercise in Go interfaces](http://blog.golang.org/gif-decoder-exercise-in-go-interfaces) - [Introducing Gofix](http://blog.golang.org/introducing-gofix) - [Gobs of data](http://blog.golang.org/gobs-of-data) - [C? Go? Cgo!](http://blog.golang.org/c-go-cgo) - [JSON and Go](http://blog.golang.org/json-and-go) - [Go Slices: usage and internals](http://blog.golang.org/go-slices-usage-and-internals) - [Go Concurrency Patterns: Timing out, moving on](http://blog.golang.org/go-concurrency-patterns-timing-out-and) - [Defer, Panic, and Recover](http://blog.golang.org/defer-panic-and-recover) - [Share Memory By Communicating](http://blog.golang.org/share-memory-by-communicating) - [JSON-RPC: a tale of interfaces](http://blog.golang.org/json-rpc-tale-of-interfaces) Except as [noted](https://developers.google.com/site-policies#restrictions), the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license](http://golang.org/LICENSE). [Terms of Service](http://golang.org/doc/tos.html) \| [Privacy Policy](http://www.google.com/intl/en/policies/privacy/) \| [View the source code](https://go.googlesource.com/blog/)output.inlined.golden000066400000000000000000000213731455110214100364110ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language](http://golang.org/) [Go](http://golang.org/) ▽ [Documents](http://golang.org/doc/) [Packages](http://golang.org/pkg/) [The Project](http://golang.org/project/) [Help](http://golang.org/help/) [Blog](http://blog.golang.org/)submit search #### Next article [Introducing Gofix](http://blog.golang.org/introducing-gofix) #### Previous article [Gobs of data](http://blog.golang.org/gobs-of-data) #### Links - [golang.org](http://golang.org/) - [Install Go](http://golang.org/doc/install.html) - [A Tour of Go](http://tour.golang.org/) - [Go Documentation](http://golang.org/doc/) - [Go Mailing List](http://groups.google.com/group/golang-nuts) - [Go on Google+](http://plus.google.com/101406623878176903605) - [Go+ Community](http://plus.google.com/communities/114112804251407510571) - [Go on Twitter](http://twitter.com/golang) [Blog index](http://blog.golang.org/index) # [The Go Blog](http://blog.golang.org/) ### [Godoc: documenting Go code](http://blog.golang.org/godoc-documenting-go-code) 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc](https://golang.org/cmd/godoc/) documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation](https://golang.org/pkg/strings/#HasPrefix) to its [implementation](https://golang.org/src/pkg/strings/strings.go#L493) with one click. Godoc is conceptually related to Python's [Docstring](http://www.python.org/dev/peps/pep-0257/) and Java's [Javadoc](http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html), but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`](https://golang.org/pkg/fmt/#Fprint) function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`](https://golang.org/pkg/sort/) package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package](https://golang.org/pkg/encoding/gob/)'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go), which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list](https://golang.org/pkg/). Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package](https://golang.org/pkg/bytes/#pkg-note-BUG): ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library](https://golang.org/search?q=Deprecated:). There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go](https://golang.org/src/pkg/encoding/gob/doc.go) for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation](https://golang.org/cmd/godoc/) for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push](http://blog.golang.org/h2push) - [Introducing HTTP Tracing](http://blog.golang.org/http-tracing) - [Testable Examples in Go](http://blog.golang.org/examples) - [Generating code](http://blog.golang.org/generate) - [Introducing the Go Race Detector](http://blog.golang.org/race-detector) - [Go maps in action](http://blog.golang.org/go-maps-in-action) - [go fmt your code](http://blog.golang.org/go-fmt-your-code) - [Organizing Go code](http://blog.golang.org/organizing-go-code) - [Debugging Go programs with the GNU Debugger](http://blog.golang.org/debugging-go-programs-with-gnu-debugger) - [The Go image/draw package](http://blog.golang.org/go-imagedraw-package) - [The Go image package](http://blog.golang.org/go-image-package) - [The Laws of Reflection](http://blog.golang.org/laws-of-reflection) - [Error handling and Go](http://blog.golang.org/error-handling-and-go) - ["First Class Functions in Go"](http://blog.golang.org/first-class-functions-in-go-and-new-go) - [Profiling Go Programs](http://blog.golang.org/profiling-go-programs) - [A GIF decoder: an exercise in Go interfaces](http://blog.golang.org/gif-decoder-exercise-in-go-interfaces) - [Introducing Gofix](http://blog.golang.org/introducing-gofix) - [Gobs of data](http://blog.golang.org/gobs-of-data) - [C? Go? Cgo!](http://blog.golang.org/c-go-cgo) - [JSON and Go](http://blog.golang.org/json-and-go) - [Go Slices: usage and internals](http://blog.golang.org/go-slices-usage-and-internals) - [Go Concurrency Patterns: Timing out, moving on](http://blog.golang.org/go-concurrency-patterns-timing-out-and) - [Defer, Panic, and Recover](http://blog.golang.org/defer-panic-and-recover) - [Share Memory By Communicating](http://blog.golang.org/share-memory-by-communicating) - [JSON-RPC: a tale of interfaces](http://blog.golang.org/json-rpc-tale-of-interfaces) Except as [noted](https://developers.google.com/site-policies#restrictions), the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license](http://golang.org/LICENSE). [Terms of Service](http://golang.org/doc/tos.html) \| [Privacy Policy](http://www.google.com/intl/en/policies/privacy/) \| [View the source code](https://go.googlesource.com/blog/)output.referenced_collapsed.golden000066400000000000000000000241371455110214100411200ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language][] [Go][] ▽ [Documents][] [Packages][] [The Project][] [Help][] [Blog][]submit search #### Next article [Introducing Gofix][] #### Previous article [Gobs of data][] #### Links - [golang.org][] - [Install Go][] - [A Tour of Go][] - [Go Documentation][] - [Go Mailing List][] - [Go on Google+][] - [Go+ Community][] - [Go on Twitter][] [Blog index][] # [The Go Blog][] ### [Godoc: documenting Go code][] 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc][] documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation][] to its [implementation][] with one click. Godoc is conceptually related to Python's [Docstring][] and Java's [Javadoc][], but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`][] function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`][] package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package][]'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go][], which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list][]. Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package][]: ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library][]. There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go][] for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation][] for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push][] - [Introducing HTTP Tracing][] - [Testable Examples in Go][] - [Generating code][] - [Introducing the Go Race Detector][] - [Go maps in action][] - [go fmt your code][] - [Organizing Go code][] - [Debugging Go programs with the GNU Debugger][] - [The Go image/draw package][] - [The Go image package][] - [The Laws of Reflection][] - [Error handling and Go][] - ["First Class Functions in Go"][] - [Profiling Go Programs][] - [A GIF decoder: an exercise in Go interfaces][] - [Introducing Gofix][] - [Gobs of data][] - [C? Go? Cgo!][] - [JSON and Go][] - [Go Slices: usage and internals][] - [Go Concurrency Patterns: Timing out, moving on][] - [Defer, Panic, and Recover][] - [Share Memory By Communicating][] - [JSON-RPC: a tale of interfaces][] Except as [noted][], the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license][]. [Terms of Service][] \| [Privacy Policy][] \| [View the source code][] [The Go Programming Language]: http://golang.org/ [Go]: http://golang.org/ [Documents]: http://golang.org/doc/ [Packages]: http://golang.org/pkg/ [The Project]: http://golang.org/project/ [Help]: http://golang.org/help/ [Blog]: http://blog.golang.org/ [Introducing Gofix]: http://blog.golang.org/introducing-gofix [Gobs of data]: http://blog.golang.org/gobs-of-data [golang.org]: http://golang.org/ [Install Go]: http://golang.org/doc/install.html [A Tour of Go]: http://tour.golang.org/ [Go Documentation]: http://golang.org/doc/ [Go Mailing List]: http://groups.google.com/group/golang-nuts [Go on Google+]: http://plus.google.com/101406623878176903605 [Go+ Community]: http://plus.google.com/communities/114112804251407510571 [Go on Twitter]: http://twitter.com/golang [Blog index]: http://blog.golang.org/index [The Go Blog]: http://blog.golang.org/ [Godoc: documenting Go code]: http://blog.golang.org/godoc-documenting-go-code [godoc]: https://golang.org/cmd/godoc/ [documentation]: https://golang.org/pkg/strings/#HasPrefix [implementation]: https://golang.org/src/pkg/strings/strings.go#L493 [Docstring]: http://www.python.org/dev/peps/pep-0257/ [Javadoc]: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html [`Fprint`]: https://golang.org/pkg/fmt/#Fprint [`sort`]: https://golang.org/pkg/sort/ [gob package]: https://golang.org/pkg/encoding/gob/ [doc.go]: https://golang.org/src/pkg/encoding/gob/doc.go [package list]: https://golang.org/pkg/ [bytes package]: https://golang.org/pkg/bytes/#pkg-note-BUG [in the standard library]: https://golang.org/search?q=Deprecated: [doc.go]: https://golang.org/src/pkg/encoding/gob/doc.go [godoc documentation]: https://golang.org/cmd/godoc/ [HTTP/2 Server Push]: http://blog.golang.org/h2push [Introducing HTTP Tracing]: http://blog.golang.org/http-tracing [Testable Examples in Go]: http://blog.golang.org/examples [Generating code]: http://blog.golang.org/generate [Introducing the Go Race Detector]: http://blog.golang.org/race-detector [Go maps in action]: http://blog.golang.org/go-maps-in-action [go fmt your code]: http://blog.golang.org/go-fmt-your-code [Organizing Go code]: http://blog.golang.org/organizing-go-code [Debugging Go programs with the GNU Debugger]: http://blog.golang.org/debugging-go-programs-with-gnu-debugger [The Go image/draw package]: http://blog.golang.org/go-imagedraw-package [The Go image package]: http://blog.golang.org/go-image-package [The Laws of Reflection]: http://blog.golang.org/laws-of-reflection [Error handling and Go]: http://blog.golang.org/error-handling-and-go ["First Class Functions in Go"]: http://blog.golang.org/first-class-functions-in-go-and-new-go [Profiling Go Programs]: http://blog.golang.org/profiling-go-programs [A GIF decoder: an exercise in Go interfaces]: http://blog.golang.org/gif-decoder-exercise-in-go-interfaces [Introducing Gofix]: http://blog.golang.org/introducing-gofix [Gobs of data]: http://blog.golang.org/gobs-of-data [C? Go? Cgo!]: http://blog.golang.org/c-go-cgo [JSON and Go]: http://blog.golang.org/json-and-go [Go Slices: usage and internals]: http://blog.golang.org/go-slices-usage-and-internals [Go Concurrency Patterns: Timing out, moving on]: http://blog.golang.org/go-concurrency-patterns-timing-out-and [Defer, Panic, and Recover]: http://blog.golang.org/defer-panic-and-recover [Share Memory By Communicating]: http://blog.golang.org/share-memory-by-communicating [JSON-RPC: a tale of interfaces]: http://blog.golang.org/json-rpc-tale-of-interfaces [noted]: https://developers.google.com/site-policies#restrictions [BSD license]: http://golang.org/LICENSE [Terms of Service]: http://golang.org/doc/tos.html [Privacy Policy]: http://www.google.com/intl/en/policies/privacy/ [View the source code]: https://go.googlesource.com/blog/output.referenced_full.golden000066400000000000000000000224541455110214100401140ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language][1] [Go][2] ▽ [Documents][4] [Packages][5] [The Project][6] [Help][7] [Blog][8]submit search #### Next article [Introducing Gofix][9] #### Previous article [Gobs of data][10] #### Links - [golang.org][11] - [Install Go][12] - [A Tour of Go][13] - [Go Documentation][14] - [Go Mailing List][15] - [Go on Google+][16] - [Go+ Community][17] - [Go on Twitter][18] [Blog index][19] # [The Go Blog][20] ### [Godoc: documenting Go code][21] 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc][22] documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation][23] to its [implementation][24] with one click. Godoc is conceptually related to Python's [Docstring][25] and Java's [Javadoc][26], but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`][27] function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`][28] package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package][29]'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go][30], which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list][31]. Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package][32]: ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library][33]. There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go][34] for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation][35] for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push][36] - [Introducing HTTP Tracing][37] - [Testable Examples in Go][38] - [Generating code][39] - [Introducing the Go Race Detector][40] - [Go maps in action][41] - [go fmt your code][42] - [Organizing Go code][43] - [Debugging Go programs with the GNU Debugger][44] - [The Go image/draw package][45] - [The Go image package][46] - [The Laws of Reflection][47] - [Error handling and Go][48] - ["First Class Functions in Go"][49] - [Profiling Go Programs][50] - [A GIF decoder: an exercise in Go interfaces][51] - [Introducing Gofix][52] - [Gobs of data][53] - [C? Go? Cgo!][54] - [JSON and Go][55] - [Go Slices: usage and internals][56] - [Go Concurrency Patterns: Timing out, moving on][57] - [Defer, Panic, and Recover][58] - [Share Memory By Communicating][59] - [JSON-RPC: a tale of interfaces][60] Except as [noted][61], the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license][62]. [Terms of Service][63] \| [Privacy Policy][64] \| [View the source code][65] [1]: http://golang.org/ [2]: http://golang.org/ [4]: http://golang.org/doc/ [5]: http://golang.org/pkg/ [6]: http://golang.org/project/ [7]: http://golang.org/help/ [8]: http://blog.golang.org/ [9]: http://blog.golang.org/introducing-gofix [10]: http://blog.golang.org/gobs-of-data [11]: http://golang.org/ [12]: http://golang.org/doc/install.html [13]: http://tour.golang.org/ [14]: http://golang.org/doc/ [15]: http://groups.google.com/group/golang-nuts [16]: http://plus.google.com/101406623878176903605 [17]: http://plus.google.com/communities/114112804251407510571 [18]: http://twitter.com/golang [19]: http://blog.golang.org/index [20]: http://blog.golang.org/ [21]: http://blog.golang.org/godoc-documenting-go-code [22]: https://golang.org/cmd/godoc/ [23]: https://golang.org/pkg/strings/#HasPrefix [24]: https://golang.org/src/pkg/strings/strings.go#L493 [25]: http://www.python.org/dev/peps/pep-0257/ [26]: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html [27]: https://golang.org/pkg/fmt/#Fprint [28]: https://golang.org/pkg/sort/ [29]: https://golang.org/pkg/encoding/gob/ [30]: https://golang.org/src/pkg/encoding/gob/doc.go [31]: https://golang.org/pkg/ [32]: https://golang.org/pkg/bytes/#pkg-note-BUG [33]: https://golang.org/search?q=Deprecated: [34]: https://golang.org/src/pkg/encoding/gob/doc.go [35]: https://golang.org/cmd/godoc/ [36]: http://blog.golang.org/h2push [37]: http://blog.golang.org/http-tracing [38]: http://blog.golang.org/examples [39]: http://blog.golang.org/generate [40]: http://blog.golang.org/race-detector [41]: http://blog.golang.org/go-maps-in-action [42]: http://blog.golang.org/go-fmt-your-code [43]: http://blog.golang.org/organizing-go-code [44]: http://blog.golang.org/debugging-go-programs-with-gnu-debugger [45]: http://blog.golang.org/go-imagedraw-package [46]: http://blog.golang.org/go-image-package [47]: http://blog.golang.org/laws-of-reflection [48]: http://blog.golang.org/error-handling-and-go [49]: http://blog.golang.org/first-class-functions-in-go-and-new-go [50]: http://blog.golang.org/profiling-go-programs [51]: http://blog.golang.org/gif-decoder-exercise-in-go-interfaces [52]: http://blog.golang.org/introducing-gofix [53]: http://blog.golang.org/gobs-of-data [54]: http://blog.golang.org/c-go-cgo [55]: http://blog.golang.org/json-and-go [56]: http://blog.golang.org/go-slices-usage-and-internals [57]: http://blog.golang.org/go-concurrency-patterns-timing-out-and [58]: http://blog.golang.org/defer-panic-and-recover [59]: http://blog.golang.org/share-memory-by-communicating [60]: http://blog.golang.org/json-rpc-tale-of-interfaces [61]: https://developers.google.com/site-policies#restrictions [62]: http://golang.org/LICENSE [63]: http://golang.org/doc/tos.html [64]: http://www.google.com/intl/en/policies/privacy/ [65]: https://go.googlesource.com/blog/output.referenced_shortcut.golden000066400000000000000000000237371455110214100410320ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/blog.golang.orgGodoc: documenting Go code - The Go Blog [The Go Programming Language] [Go] ▽ [Documents] [Packages] [The Project] [Help] [Blog]submit search #### Next article [Introducing Gofix] #### Previous article [Gobs of data] #### Links - [golang.org] - [Install Go] - [A Tour of Go] - [Go Documentation] - [Go Mailing List] - [Go on Google+] - [Go+ Community] - [Go on Twitter] [Blog index] # [The Go Blog] ### [Godoc: documenting Go code] 31 March 2011 The Go project takes documentation seriously. Documentation is a huge part of making software accessible and maintainable. Of course it must be well-written and accurate, but it also must be easy to write and to maintain. Ideally, it should be coupled to the code itself so the documentation evolves along with the code. The easier it is for programmers to produce good documentation, the better for everyone. To that end, we have developed the [godoc] documentation tool. This article describes godoc's approach to documentation, and explains how you can use our conventions and tools to write good documentation for your own projects. Godoc parses Go source code - including comments - and produces documentation as HTML or plain text. The end result is documentation tightly coupled with the code it documents. For example, through godoc's web interface you can navigate from a function's [documentation] to its [implementation] with one click. Godoc is conceptually related to Python's [Docstring] and Java's [Javadoc], but its design is simpler. The comments read by godoc are not language constructs (as with Docstring) nor must they have their own machine-readable syntax (as with Javadoc). Godoc comments are just good comments, the sort you would want to read even if godoc didn't exist. The convention is simple: to document a type, variable, constant, function, or even a package, write a regular comment directly preceding its declaration, with no intervening blank line. Godoc will then present that comment as text alongside the item it documents. For example, this is the documentation for the `fmt` package's [`Fprint`] function: ``` // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...interface{}) (n int, err error) { ``` Notice this comment is a complete sentence that begins with the name of the element it describes. This important convention allows us to generate documentation in a variety of formats, from plain text to HTML to UNIX man pages, and makes it read better when tools truncate it for brevity, such as when they extract the first line or sentence. Comments on package declarations should provide general package documentation. These comments can be short, like the [`sort`] package's brief description: ``` // Package sort provides primitives for sorting slices and user-defined // collections. package sort ``` They can also be detailed like the [gob package]'s overview. That package uses another convention for packages that need large amounts of introductory documentation: the package comment is placed in its own file, [doc.go], which contains only those comments and a package clause. When writing package comments of any size, keep in mind that their first sentence will appear in godoc's [package list]. Comments that are not adjacent to a top-level declaration are omitted from godoc's output, with one notable exception. Top-level comments that begin with the word `"BUG(who)”` are recognized as known bugs, and included in the "Bugs” section of the package documentation. The "who” part should be the user name of someone who could provide more information. For example, this is a known issue from the [bytes package]: ``` // BUG(r): The rule Title uses for word boundaries does not handle Unicode punctuation properly. ``` Sometimes a struct field, function, type, or even a whole package becomes redundant or unnecessary, but must be kept for compatibility with existing programs. To signal that an identifier should not be used, add a paragraph to its doc comment that begins with "Deprecated:" followed by some information about the deprecation. There are a few examples [in the standard library]. There are a few formatting rules that Godoc uses when converting comments to HTML: - Subsequent lines of text are considered part of the same paragraph; you must leave a blank line to separate paragraphs. - Pre-formatted text must be indented relative to the surrounding comment text (see gob's [doc.go] for an example). - URLs will be converted to HTML links; no special markup is necessary. Note that none of these rules requires you to do anything out of the ordinary. In fact, the best thing about godoc's minimal approach is how easy it is to use. As a result, a lot of Go code, including all of the standard library, already follows the conventions. Your own code can present good documentation just by having comments as described above. Any Go packages installed inside `$GOROOT/src/pkg` and any `GOPATH` work spaces will already be accessible via godoc's command-line and HTTP interfaces, and you can specify additional paths for indexing via the `-path` flag or just by running `"godoc ."` in the source directory. See the [godoc documentation] for more details. By Andrew Gerrand ## Related articles - [HTTP/2 Server Push] - [Introducing HTTP Tracing] - [Testable Examples in Go] - [Generating code] - [Introducing the Go Race Detector] - [Go maps in action] - [go fmt your code] - [Organizing Go code] - [Debugging Go programs with the GNU Debugger] - [The Go image/draw package] - [The Go image package] - [The Laws of Reflection] - [Error handling and Go] - ["First Class Functions in Go"] - [Profiling Go Programs] - [A GIF decoder: an exercise in Go interfaces] - [Introducing Gofix] - [Gobs of data] - [C? Go? Cgo!] - [JSON and Go] - [Go Slices: usage and internals] - [Go Concurrency Patterns: Timing out, moving on] - [Defer, Panic, and Recover] - [Share Memory By Communicating] - [JSON-RPC: a tale of interfaces] Except as [noted], the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license]. [Terms of Service] \| [Privacy Policy] \| [View the source code] [The Go Programming Language]: http://golang.org/ [Go]: http://golang.org/ [Documents]: http://golang.org/doc/ [Packages]: http://golang.org/pkg/ [The Project]: http://golang.org/project/ [Help]: http://golang.org/help/ [Blog]: http://blog.golang.org/ [Introducing Gofix]: http://blog.golang.org/introducing-gofix [Gobs of data]: http://blog.golang.org/gobs-of-data [golang.org]: http://golang.org/ [Install Go]: http://golang.org/doc/install.html [A Tour of Go]: http://tour.golang.org/ [Go Documentation]: http://golang.org/doc/ [Go Mailing List]: http://groups.google.com/group/golang-nuts [Go on Google+]: http://plus.google.com/101406623878176903605 [Go+ Community]: http://plus.google.com/communities/114112804251407510571 [Go on Twitter]: http://twitter.com/golang [Blog index]: http://blog.golang.org/index [The Go Blog]: http://blog.golang.org/ [Godoc: documenting Go code]: http://blog.golang.org/godoc-documenting-go-code [godoc]: https://golang.org/cmd/godoc/ [documentation]: https://golang.org/pkg/strings/#HasPrefix [implementation]: https://golang.org/src/pkg/strings/strings.go#L493 [Docstring]: http://www.python.org/dev/peps/pep-0257/ [Javadoc]: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp-135444.html [`Fprint`]: https://golang.org/pkg/fmt/#Fprint [`sort`]: https://golang.org/pkg/sort/ [gob package]: https://golang.org/pkg/encoding/gob/ [doc.go]: https://golang.org/src/pkg/encoding/gob/doc.go [package list]: https://golang.org/pkg/ [bytes package]: https://golang.org/pkg/bytes/#pkg-note-BUG [in the standard library]: https://golang.org/search?q=Deprecated: [doc.go]: https://golang.org/src/pkg/encoding/gob/doc.go [godoc documentation]: https://golang.org/cmd/godoc/ [HTTP/2 Server Push]: http://blog.golang.org/h2push [Introducing HTTP Tracing]: http://blog.golang.org/http-tracing [Testable Examples in Go]: http://blog.golang.org/examples [Generating code]: http://blog.golang.org/generate [Introducing the Go Race Detector]: http://blog.golang.org/race-detector [Go maps in action]: http://blog.golang.org/go-maps-in-action [go fmt your code]: http://blog.golang.org/go-fmt-your-code [Organizing Go code]: http://blog.golang.org/organizing-go-code [Debugging Go programs with the GNU Debugger]: http://blog.golang.org/debugging-go-programs-with-gnu-debugger [The Go image/draw package]: http://blog.golang.org/go-imagedraw-package [The Go image package]: http://blog.golang.org/go-image-package [The Laws of Reflection]: http://blog.golang.org/laws-of-reflection [Error handling and Go]: http://blog.golang.org/error-handling-and-go ["First Class Functions in Go"]: http://blog.golang.org/first-class-functions-in-go-and-new-go [Profiling Go Programs]: http://blog.golang.org/profiling-go-programs [A GIF decoder: an exercise in Go interfaces]: http://blog.golang.org/gif-decoder-exercise-in-go-interfaces [Introducing Gofix]: http://blog.golang.org/introducing-gofix [Gobs of data]: http://blog.golang.org/gobs-of-data [C? Go? Cgo!]: http://blog.golang.org/c-go-cgo [JSON and Go]: http://blog.golang.org/json-and-go [Go Slices: usage and internals]: http://blog.golang.org/go-slices-usage-and-internals [Go Concurrency Patterns: Timing out, moving on]: http://blog.golang.org/go-concurrency-patterns-timing-out-and [Defer, Panic, and Recover]: http://blog.golang.org/defer-panic-and-recover [Share Memory By Communicating]: http://blog.golang.org/share-memory-by-communicating [JSON-RPC: a tale of interfaces]: http://blog.golang.org/json-rpc-tale-of-interfaces [noted]: https://developers.google.com/site-policies#restrictions [BSD license]: http://golang.org/LICENSE [Terms of Service]: http://golang.org/doc/tos.html [Privacy Policy]: http://www.google.com/intl/en/policies/privacy/ [View the source code]: https://go.googlesource.com/blog/golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/golang.org/000077500000000000000000000000001455110214100313655ustar00rootroot00000000000000goldmark.golden000066400000000000000000000031301455110214100342750ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/golang.org

    The Go Programming Language

    ...

    The Go Programming Language

    Go

    Documents Packages The Project Help Blog Play submit search

    RunFormatShare

    Pop-out

    Try Go

    Hello, 世界
    
    

    RunShare Tour

    Hello, World!Conway's Game of LifeFibonacci ClosurePeano IntegersConcurrent piConcurrent Prime SievePeg Solitaire SolverTree Comparison

    Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.

    Download Go
    Binary distributions available for

    Linux, Mac OS X, Windows, and more.

    Featured video

    Featured articles

    Read more

    Build version go1.10.2.

    Except as noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a BSD license.

    Terms of Service | Privacy Policy

    golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/golang.org/input.html000066400000000000000000000204031455110214100334110ustar00rootroot00000000000000 The Go Programming Language
    ...
    Try Go
    Hello, 世界
    
    Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.
    Download Go Binary distributions available for
    Linux, Mac OS X, Windows, and more.
    Featured video
    Featured articles
    output.default.golden000066400000000000000000000024331455110214100354650ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/golang.orgThe Go Programming Language ... [The Go Programming Language](http://golang.org/) [Go](http://golang.org/) ▽ [Documents](http://golang.org/doc/) [Packages](http://golang.org/pkg/) [The Project](http://golang.org/project/) [Help](http://golang.org/help/) [Blog](http://golang.org/blog/) [Play](http://play.golang.org/ "Show Go Playground") submit search RunFormatShare Pop-out Try Go ``` Hello, 世界 ``` RunShare [Tour](http://tour.golang.org/ "Learn Go from your browser") Hello, World!Conway's Game of LifeFibonacci ClosurePeano IntegersConcurrent piConcurrent Prime SievePeg Solitaire SolverTree Comparison Go is an open source programming language that makes it easy to build simple, reliable, and efficient software. [Download Go\ Binary distributions available for\ \ Linux, Mac OS X, Windows, and more.](http://golang.org/dl/) Featured video Featured articles [Read more](http://blog.golang.org/) Build version go1.10.2. Except as [noted](https://developers.google.com/site-policies#restrictions), the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code is licensed under a [BSD license](http://golang.org/LICENSE). [Terms of Service](http://golang.org/doc/tos.html) \| [Privacy Policy](http://www.google.com/intl/en/policies/privacy/)golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/000077500000000000000000000000001455110214100311755ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/github_about/000077500000000000000000000000001455110214100336515ustar00rootroot00000000000000goldmark.golden000066400000000000000000000013001455110214100365560ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/github_about

    About

    ⚙️ Convert HTML to Markdown. Even works with whole websites.

    Topics

    go golang html html-to-markdown markdown

    Resources

    Readme

    License

    MIT License

    input.html000066400000000000000000000071441455110214100356250ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/github_about

    About

    ⚙️ Convert HTML to Markdown. Even works with whole websites.

    Topics

    Resources

    License

    output.default.golden000066400000000000000000000010521455110214100377450ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/github_about## About ⚙️ Convert HTML to Markdown. Even works with whole websites. ### Topics [go](http://example.com/topics/go "Topic: go") [golang](http://example.com/topics/golang "Topic: golang") [html](http://example.com/topics/html "Topic: html") [html-to-markdown](http://example.com/topics/html-to-markdown "Topic: html-to-markdown") [markdown](http://example.com/topics/markdown "Topic: markdown") ### Resources [Readme](http://example.com#readme) ### License [MIT License](http://example.com/JohannesKaufmann/html-to-markdown/blob/master/LICENSE)heading_in_link/000077500000000000000000000000001455110214100342205ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippetsgoldmark.golden000066400000000000000000000003371455110214100372150ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/heading_in_link

    #event_jul20
    Remote

    Datum

    31.07. - 02.08.20


    Uhrzeit

    Fr 15 Uhr - So 19 Uhr


    Details anzeigen →

    input.html000066400000000000000000000044631455110214100362540ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/heading_in_link output.default.golden000066400000000000000000000002321455110214100403720ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/heading_in_link[**\#event\_jul20**\ Remote\ \ Datum\ \ 31.07. - 02.08.20\ \ \ Uhrzeit\ \ Fr 15 Uhr - So 19 Uhr\ \ \ Details anzeigen →](http://example.com/event_jul20)nav_nested_list/000077500000000000000000000000001455110214100342775ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippetsgoldmark.golden000066400000000000000000000035631455110214100373000ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/nav_nested_list input.html000066400000000000000000000033001455110214100363200ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/nav_nested_list output.default.golden000066400000000000000000000026501455110214100404570ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/nav_nested_list- [Startseite](http://example.com/ "Startseite") - [Die Gruppe](http://example.com/die-gruppe/unsere-unternehmen/ "Die Gruppe") - [Unsere Unternehmen](http://example.com/die-gruppe/unsere-unternehmen/ "Unsere Unternehmen") - [Unternehmenshistorie](http://example.com/die-gruppe/unternehmenshistorie/ "Unternehmenshistorie") - [Standortportraits](http://example.com/die-gruppe/standortportraits/ "Standortportraits") - [Unsere Marken](http://example.com/die-gruppe/unsere-marken/ "Unsere Marken") - [Kontakt](http://example.com/die-gruppe/kontakt/ "Kontakt") - [Medien](http://example.com/medien/aktuelle-meldungen/ "Medien") - [Aktuelle Meldungen](http://example.com/medien/aktuelle-meldungen/ "Aktuelle Meldungen") - [Pressearchiv](http://example.com/medien/pressearchiv/ "Pressearchiv") - [Pressekontakt](http://example.com/medien/pressekontakt/ "Pressekontakt") - [Einblicke](http://example.com/medien/einblicke/ "Einblicke") - [Karriere](http://example.com/karriere/ "Karriere") - [Video-Einblicke](http://example.com/karriere/video-einblicke/ "Video-Einblicke") - [Stellenangebote](http://example.com/karriere/liste "Stellenangebote") - [Erlebnisberichte](http://example.com/karriere/erlebnisberichte/ "Erlebnisberichte") - [Traineeprogramm](http://example.com/karriere/traineeprogramm/ "Traineeprogramm") - [Termine](http://example.com/karriere/termine/ "Termine") - [News](http://example.com/karriere/news/ "News")golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/pre_code/000077500000000000000000000000001455110214100327555ustar00rootroot00000000000000goldmark.golden000066400000000000000000000341451455110214100356770ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/pre_code

    The MJML tool provides a CLI you can use to transform MJML into HTML:

    
    bash
    
    $ mjml input.mjml -o output.html
    

    Curiously, all text elements (paragraphs and headings) use the same tag, <mj-text>. You can create headings by applying cosmetic styles as inline attributes, like:

    
    html
    
    <mj-text
      align="center"
      font-size="32px"
      font-weight="bold"
      color="#FF0000"
    >
    

    func HasPrefix(s, prefix []byte) bool
    

    HTML

    <p>Using CSS to change the font color is easy.</p>
    <pre>
    body {
      color: red;
    }
    </pre>
    
    

    Copy to Clipboard

    Using CSS to change the font color is easy.

    body {
      color: red;
    }
    
    

     复制代码const Pi = 3.14159
    

    const Pi = 3.14159

     复制代码var n intf(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int
    

    mkdir hugo-contrib
    cd hugo-contrib
    git clone git@github.com:gohugoio/hugo.git
    

    Save the changes and run your first bob command:

    bob build
    

    To confirm that everything went fine, just run:

    bob --version
    
    

    Copy


    For instance, MetaMask displays your recent transactions by making an API call to etherscan:

    GET https://api.etherscan.io/api?module=account&address=0x0208376c899fdaEbA530570c008C4323803AA9E8&offset=40&order=desc&action=txlist&tag=latest&page=1 HTTP/2.0
    
    

    …displays your account balance by making an API call to Infura:

    POST https://mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb HTTP/2.0
    
    {
        "id": 2628746552039525,
        "jsonrpc": "2.0",
        "method": "eth_getBalance",
        "params": [
            "0x0208376c899fdaEbA530570c008C4323803AA9E8",
            "latest"
        ]
    }
    
    

    After it was working, I started adding as many different programming languages as I could. As you can see from this excerpt of my project’s version history, I got a little overexcited:

                                                                       languages
                                                                       ---------
    2020-06-05 df9ba38 Initial commit                                          0
    2020-06-05 5e3a4a4 Install some packages into a Docker image               0
    2020-06-05 e937c8f Simple Express server with "Hello world"                0
    2020-06-06 0961498 Embed terminal on frontend app                          0
    2020-06-06 c66cf63 Embed Monaco editor on frontend                         0
    2020-06-06 27ab1f7 Add "run" button                                        0
    2020-06-06 f417858 You can run Python code now                             1
    2020-06-07 d543081 You can run many languages now                          8
    2020-06-07 e2a3e71 All languages 17 working now                           17
    2020-06-07 473c50c ALL THE LANGUAGES                                      25
    2020-06-08 3718315 even more languages                                    33
    2020-06-08 548c1c1 repl.it superiority!!                                  38
    2020-06-08 1ae424f More languages, we need all the languages              48
    2020-06-09 c34ccf2 A lot more languages                                   77
    2020-06-09 846caf2 At this point the number of languages is absurd        79
    
    

    Disassembling the now-less-obfuscated dump reveals that one of the addresses has a label pulled out of somewhere! It’s strlen? Going down the call stack the next one is labeled vscan_fn and after that the labels end, tho I’m fairly confident it’s sscanf.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    
    ...,
    {
        "key": "WP_WCT_TINT_21_t2_v9_n2",
        "price": 45000,
        "statName": "CHAR_KIT_FM_PURCHASE20",
        "storageType": "BITFIELD",
        "bitShift": 7,
        "bitSize": 1,
        "category": ["CATEGORY_WEAPON_MOD"]
    },
    ...
    
    

    <p>This is the <code>Panel</code> constructor:</p>
    <pre><code>function Panel(element, canClose, closeHandler) {
          this.element = element;
          this.canClose = canClose;
          this.closeHandler = function () { if (closeHandler) closeHandler() };
        }</code></pre>
    

    Run code snippet Hide results

    Expand snippet

    Problem with <pre> is it modifies whitespace processing as well: all spaces are preserved, and wrapping is switched off. Unless there's a way to switch this off?

    Use the <textarea> element to share code, like so:

    <textarea class="code" contenteditable="true" spellcheck="false" aria-label='Code Sample'>
      My Sample Bookmark:
      <a href="#bookmark1" id="b1" title="View my bookmark" target="_blank" rel="noreferrer nofollow noopener" accesskey="a" tabindex="0" aria-label="Bookmark">Got to My Bookmark</a>
    </textarea>
    
    


    linux-nfs.vger.kernel.org archive mirror
     help / color / mirror / Atom feed
    
    From: Leon Romanovsky <leon@kernel.org>
    To: "J. Bruce Fields" <bfields@fieldses.org>
    Cc: Greg KH <gregkh@linuxfoundation.org>,
    	Aditya Pakki <pakki001@umn.edu>,
    	Chuck Lever <chuck.lever@oracle.com>,
    	Trond Myklebust <trond.myklebust@hammerspace.com>,
    	Anna Schumaker <anna.schumaker@netapp.com>,
    	"David S. Miller" <davem@davemloft.net>,
    	Jakub Kicinski <kuba@kernel.org>,
    	Dave Wysochanski <dwysocha@redhat.com>,
    	linux-nfs@vger.kernel.org, netdev@vger.kernel.org,
    	linux-kernel@vger.kernel.org
    Subject: Re: [PATCH] SUNRPC: Add a check for gss_release_msg
    Date: Wed, 21 Apr 2021 08:10:25 +0300	[thread overview]
    Message-ID: <YH+zwQgBBGUJdiVK@unreal> (raw)
    In-Reply-To: <20210420171008.GB4017@fieldses.org>
    
    On Tue, Apr 20, 2021 at 01:10:08PM -0400, J. Bruce Fields wrote:
    > On Tue, Apr 20, 2021 at 09:15:23AM +0200, Greg KH wrote:
    > > If you look at the code, this is impossible to have happen.
    > >
    > > Please stop submitting known-invalid patches.  Your professor is playing
    > > around with the review process in order to achieve a paper in some
    > > strange and bizarre way.
    > >
    > > This is not ok, it is wasting our time, and we will have to report this,
    > > AGAIN, to your university...
    >
    > What's the story here?
    
    Those commits are part of the following research:
    https://github.com/QiushiWu/QiushiWu.github.io/blob/main/papers/OpenSourceInsecurity.pdf
    
    They introduce kernel bugs on purpose. Yesterday, I took a look on 4
    accepted patches from Aditya and 3 of them added various severity security
    "holes".
    
    Thanks
    
    >
    > --b.
    
    

    next prev parent reply	other threads:[~2021-04-21  5:10 UTC|newest]
    
    Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
    2021-04-07  0:16 Aditya Pakki
    2021-04-07 15:34 ` J. Bruce Fields
    2021-04-08 15:01 ` Trond Myklebust
    2021-04-08 15:24   ` Olga Kornievskaia
    2021-04-08 16:02     ` Trond Myklebust
    2021-04-20  7:15 ` Greg KH
    2021-04-20 17:10   ` J. Bruce Fields
    2021-04-21  5:10     ` Leon Romanovsky [this message]
    2021-04-21  5:43       ` Greg KH
    2021-04-21  6:08         ` Leon Romanovsky
         [not found]         ` <CA+EnHHSw4X+ubOUNYP2zXNpu70G74NN1Sct2Zin6pRgq--TqhA@mail.gmail.com>
    2021-04-21  8:15           ` Greg KH
    2021-04-21 10:07         ` Sudip Mukherjee
    2021-04-21 10:21           ` Greg KH
    2021-04-21 11:58             ` Shelat, Abhi
    2021-04-21 12:08               ` Greg KH
    2021-04-21 12:19               ` Leon Romanovsky
    2021-04-21 13:11                 ` Trond Myklebust
    2021-04-21 13:20                   ` Leon Romanovsky
    2021-04-21 13:42                     ` Steven Rostedt
    2021-04-21 13:21                   ` gregkh
    2021-04-21 13:34                     ` Leon Romanovsky
    2021-04-21 13:50                       ` gregkh
    2021-04-21 14:12                         ` Leon Romanovsky
    2021-04-21 18:50                         ` Alexander Grund
    2021-04-21 13:37               ` J. Bruce Fields
    2021-04-21 13:49                 ` Leon Romanovsky
    2021-04-21 13:56                   ` J. Bruce Fields
    2021-04-22 19:39                     ` J. Bruce Fields
    2021-04-23 17:25                       ` Leon Romanovsky
    2021-04-23 18:07                         ` J. Bruce Fields
    2021-04-23 19:29                           ` Leon Romanovsky
    2021-04-23 21:48                             ` J. Bruce Fields
    2021-04-24  7:21                               ` Leon Romanovsky
    2021-04-24 18:34                               ` Al Viro
    2021-04-24 21:34                                 ` J. Bruce Fields
    2021-04-25  0:41                                   ` Theodore Ts'o
    2021-04-25  6:29                                     ` Greg KH
         [not found]                                       ` <20210426133605.GD21222@fieldses.org>
    2021-04-26 13:47                                         ` J. Bruce Fields
    2021-04-22  8:10             ` Sudip Mukherjee
    2021-04-22  8:27               ` Greg KH
    2021-04-21 12:51       ` Anna Schumaker
    2021-04-21 14:15         ` Leon Romanovsky
    2021-04-21 15:48           ` Theodore Ts'o
    2021-04-21 17:34             ` Mike Rapoport
    2021-04-22  3:57               ` Leon Romanovsky
    2021-04-21 22:52 ` Guenter Roeck
         [not found] <CAHr+ZK-ayy2vku9ovuSB4egtOxrPEKxCdVQN3nFqMK07+K5_8g@mail.gmail.com>
    2021-04-21 19:49 ` Theodore Ts'o
    2021-04-22  7:50   ` Eric Biggers
    2021-04-21 20:27 Weikeng Chen
    
    

    Reply instructions:
    
    You may reply publicly to this message via plain-text email
    using any one of the following methods:
    
    * Save the following mbox file, import it into your mail client,
      and reply-to-all from there: mbox
    
      Avoid top-posting and favor interleaved quoting:
      https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
    
    * Reply using the --to, --cc, and --in-reply-to
      switches of git-send-email(1):
    
      git send-email \
        --in-reply-to=YH+zwQgBBGUJdiVK@unreal \
        --to=leon@kernel.org \
        --cc=anna.schumaker@netapp.com \
        --cc=bfields@fieldses.org \
        --cc=chuck.lever@oracle.com \
        --cc=davem@davemloft.net \
        --cc=dwysocha@redhat.com \
        --cc=gregkh@linuxfoundation.org \
        --cc=kuba@kernel.org \
        --cc=linux-kernel@vger.kernel.org \
        --cc=linux-nfs@vger.kernel.org \
        --cc=netdev@vger.kernel.org \
        --cc=pakki001@umn.edu \
        --cc=trond.myklebust@hammerspace.com \
        --subject='Re: [PATCH] SUNRPC: Add a check for gss_release_msg' \
        /path/to/YOUR_REPLY
    
      https://kernel.org/pub/software/scm/git/docs/git-send-email.html
    
    * If your mail client supports setting the In-Reply-To header
      via mailto: links, try the mailto: link
    
    

    This is a public inbox, see mirroring instructions
    for how to clone and mirror all data and code used for this inbox;
    as well as URLs for NNTP newsgroup(s).
    

    Here's an example of an XML-RPC request:

    POST /RPC2 HTTP/1.0
    User-Agent: Frontier/5.1.2 (WinNT)
    Host: betty.userland.com
    Content-Type: text/xml
    Content-length: 181
    
    <?xml version="1.0"?>
    <methodCall>
        <methodName>examples.getStateName</methodName>
        <params>
            <param>
                <value><i4>41</i4></value>
                </param>
            </params>
        </methodCall>
    
    

    For example, running:

    octosql "SELECT email, COUNT(*) as invoice_count
             FROM invoices.csv JOIN mydb.customers ON invoices.customer_id = customers.id
             WHERE first_name <= 'D'
             GROUP BY email
             ORDER BY invoice_count DESC" --explain 1
    

    If you really want this functionality, you can implement it by using the following function:

    function sleepSync(timeout) {
      const sab = new SharedArrayBuffer(1024);
      const int32 = new Int32Array(sab);
      Atomics.wait(int32, 0, 0, timeout);
    }
    

    It doesn’t get much simpler than this. You’ll notice we only define a main() function followed by a println to stdout.

    fn main() {
        println!("Hello, world!");
    }
    
    

    # Ruby knows what you
    # mean, even if you
    # want to do math on
    # an entire Array
    cities  = %w[ London
                  Oslo
                  Paris
                  Amsterdam
                  Berlin ]
    visited = %w[Berlin Oslo]
    
    puts "I still need " +
         "to visit the " +
         "following cities:",
         cities - visited
    

    The equivalent portion is just these two lines:

      var btn = new Gtk.Button.with_label("Hello World");
      btn.click.connect(win.close);
    

    To preserve linebreaks inside a div using CSS:

    div.code {
      white-space: pre;
    }
    
    

    @font-face {
    	src: url(http://lea.verou.me/logo.otf);
    	font-family: 'LeaVerou';
    }
    

    This raw text
    is not highlighted
    but it still has
    line numbers
    

    Live JSX Editor JSX?

    class HelloMessage extends React.Component {
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    }
    
    root.render(<HelloMessage name="Taylor" />);
    
    

    Result

    Hello Taylor

    input.html000066400000000000000000001176061455110214100347360ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/pre_code

    The MJML tool provides a CLI you can use to transform MJML into HTML:

    bash

    Curiously, all text elements (paragraphs and headings) use the same tag, <mj-text>. You can create headings by applying cosmetic styles as inline attributes, like:

    html

    func HasPrefix(s, prefix []byte) bool

    HTML

    <p>Using CSS to change the font color is easy.</p>
    <pre>
    body {
      color: red;
    }
    </pre>
    

    Using CSS to change the font color is easy.

    body {
      color: red;
    }
    

    1. const Pi = 3.14159
    const Pi = 3.14159
    1. var n int
    2. f(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int

    mkdir hugo-contrib
    cd hugo-contrib
    git clone git@github.com:gohugoio/hugo.git

    Save the changes and run your first bob command:

    bob build

    To confirm that everything went fine, just run:

    bob --version

    For instance, MetaMask displays your recent transactions by making an API call to etherscan:

    GET https://api.etherscan.io/api?module=account&address=0x0208376c899fdaEbA530570c008C4323803AA9E8&offset=40&order=desc&action=txlist&tag=latest&page=1 HTTP/2.0                                                          
    

    …displays your account balance by making an API call to Infura:

    POST https://mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb HTTP/2.0
    
    {
        "id": 2628746552039525,
        "jsonrpc": "2.0",
        "method": "eth_getBalance",
        "params": [
            "0x0208376c899fdaEbA530570c008C4323803AA9E8",
            "latest"
        ]
    }
    

    After it was working, I started adding as many different programming languages as I could. As you can see from this excerpt of my project’s version history, I got a little overexcited:

                                                                       languages
                                                                       ---------
    2020-06-05 df9ba38 Initial commit                                          0
    2020-06-05 5e3a4a4 Install some packages into a Docker image               0
    2020-06-05 e937c8f Simple Express server with "Hello world"                0
    2020-06-06 0961498 Embed terminal on frontend app                          0
    2020-06-06 c66cf63 Embed Monaco editor on frontend                         0
    2020-06-06 27ab1f7 Add "run" button                                        0
    2020-06-06 f417858 You can run Python code now                             1
    2020-06-07 d543081 You can run many languages now                          8
    2020-06-07 e2a3e71 All languages 17 working now                           17
    2020-06-07 473c50c ALL THE LANGUAGES                                      25
    2020-06-08 3718315 even more languages                                    33
    2020-06-08 548c1c1 repl.it superiority!!                                  38
    2020-06-08 1ae424f More languages, we need all the languages              48
    2020-06-09 c34ccf2 A lot more languages                                   77
    2020-06-09 846caf2 At this point the number of languages is absurd        79
    

    Disassembling the now-less-obfuscated dump reveals that one of the addresses has a label pulled out of somewhere! It’s strlen? Going down the call stack the next one is labeled vscan_fn and after that the labels end, tho I’m fairly confident it’s sscanf.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ...,
    {
    "key": "WP_WCT_TINT_21_t2_v9_n2",
    "price": 45000,
    "statName": "CHAR_KIT_FM_PURCHASE20",
    "storageType": "BITFIELD",
    "bitShift": 7,
    "bitSize": 1,
    "category": ["CATEGORY_WEAPON_MOD"]
    },
    ...

    <p>This is the <code>Panel</code> constructor:</p>
    <pre><code>function Panel(element, canClose, closeHandler) {
          this.element = element;
          this.canClose = canClose;
          this.closeHandler = function () { if (closeHandler) closeHandler() };
        }</code></pre>
    Problem with <pre> is it modifies whitespace processing as well: all spaces are preserved, and wrapping is switched off. Unless there's a way to switch this off?

    Use the <textarea> element to share code, like so:

    <textarea class="code" contenteditable="true" spellcheck="false" aria-label='Code Sample'>
      My Sample Bookmark:
      <a href="#bookmark1" id="b1" title="View my bookmark" target="_blank" rel="noreferrer nofollow noopener" accesskey="a" tabindex="0" aria-label="Bookmark">Got to My Bookmark</a>
    </textarea>
    




    linux-nfs.vger.kernel.org archive mirror
     help / color / mirror / Atom feed
    From: Leon Romanovsky <leon@kernel.org>
    To: "J. Bruce Fields" <bfields@fieldses.org>
    Cc: Greg KH <gregkh@linuxfoundation.org>,
    	Aditya Pakki <pakki001@umn.edu>,
    	Chuck Lever <chuck.lever@oracle.com>,
    	Trond Myklebust <trond.myklebust@hammerspace.com>,
    	Anna Schumaker <anna.schumaker@netapp.com>,
    	"David S. Miller" <davem@davemloft.net>,
    	Jakub Kicinski <kuba@kernel.org>,
    	Dave Wysochanski <dwysocha@redhat.com>,
    	linux-nfs@vger.kernel.org, netdev@vger.kernel.org,
    	linux-kernel@vger.kernel.org
    Subject: Re: [PATCH] SUNRPC: Add a check for gss_release_msg
    Date: Wed, 21 Apr 2021 08:10:25 +0300	[thread overview]
    Message-ID: <YH+zwQgBBGUJdiVK@unreal> (raw)
    In-Reply-To: <20210420171008.GB4017@fieldses.org>
    
    On Tue, Apr 20, 2021 at 01:10:08PM -0400, J. Bruce Fields wrote:
    > On Tue, Apr 20, 2021 at 09:15:23AM +0200, Greg KH wrote:
    > > If you look at the code, this is impossible to have happen.
    > > 
    > > Please stop submitting known-invalid patches.  Your professor is playing
    > > around with the review process in order to achieve a paper in some
    > > strange and bizarre way.
    > > 
    > > This is not ok, it is wasting our time, and we will have to report this,
    > > AGAIN, to your university...
    > 
    > What's the story here?
    
    Those commits are part of the following research:
    https://github.com/QiushiWu/QiushiWu.github.io/blob/main/papers/OpenSourceInsecurity.pdf
    
    They introduce kernel bugs on purpose. Yesterday, I took a look on 4
    accepted patches from Aditya and 3 of them added various severity security
    "holes".
    
    Thanks
    
    > 
    > --b.
    

      reply	other threads:[~2021-04-21  5:10 UTC|newest]
    
    Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
    2021-04-07  0:16 Aditya Pakki
    2021-04-07 15:34 ` J. Bruce Fields
    2021-04-08 15:01 ` Trond Myklebust
    2021-04-08 15:24   ` Olga Kornievskaia
    2021-04-08 16:02     ` Trond Myklebust
    2021-04-20  7:15 ` Greg KH
    2021-04-20 17:10   ` J. Bruce Fields
    2021-04-21  5:10     ` Leon Romanovsky [this message]
    2021-04-21  5:43       ` Greg KH
    2021-04-21  6:08         ` Leon Romanovsky
         [not found]         ` <CA+EnHHSw4X+ubOUNYP2zXNpu70G74NN1Sct2Zin6pRgq--TqhA@mail.gmail.com>
    2021-04-21  8:15           ` Greg KH
    2021-04-21 10:07         ` Sudip Mukherjee
    2021-04-21 10:21           ` Greg KH
    2021-04-21 11:58             ` Shelat, Abhi
    2021-04-21 12:08               ` Greg KH
    2021-04-21 12:19               ` Leon Romanovsky
    2021-04-21 13:11                 ` Trond Myklebust
    2021-04-21 13:20                   ` Leon Romanovsky
    2021-04-21 13:42                     ` Steven Rostedt
    2021-04-21 13:21                   ` gregkh
    2021-04-21 13:34                     ` Leon Romanovsky
    2021-04-21 13:50                       ` gregkh
    2021-04-21 14:12                         ` Leon Romanovsky
    2021-04-21 18:50                         ` Alexander Grund
    2021-04-21 13:37               ` J. Bruce Fields
    2021-04-21 13:49                 ` Leon Romanovsky
    2021-04-21 13:56                   ` J. Bruce Fields
    2021-04-22 19:39                     ` J. Bruce Fields
    2021-04-23 17:25                       ` Leon Romanovsky
    2021-04-23 18:07                         ` J. Bruce Fields
    2021-04-23 19:29                           ` Leon Romanovsky
    2021-04-23 21:48                             ` J. Bruce Fields
    2021-04-24  7:21                               ` Leon Romanovsky
    2021-04-24 18:34                               ` Al Viro
    2021-04-24 21:34                                 ` J. Bruce Fields
    2021-04-25  0:41                                   ` Theodore Ts'o
    2021-04-25  6:29                                     ` Greg KH
         [not found]                                       ` <20210426133605.GD21222@fieldses.org>
    2021-04-26 13:47                                         ` J. Bruce Fields
    2021-04-22  8:10             ` Sudip Mukherjee
    2021-04-22  8:27               ` Greg KH
    2021-04-21 12:51       ` Anna Schumaker
    2021-04-21 14:15         ` Leon Romanovsky
    2021-04-21 15:48           ` Theodore Ts'o
    2021-04-21 17:34             ` Mike Rapoport
    2021-04-22  3:57               ` Leon Romanovsky
    2021-04-21 22:52 ` Guenter Roeck
         [not found] <CAHr+ZK-ayy2vku9ovuSB4egtOxrPEKxCdVQN3nFqMK07+K5_8g@mail.gmail.com>
    2021-04-21 19:49 ` Theodore Ts'o
    2021-04-22  7:50   ` Eric Biggers
    2021-04-21 20:27 Weikeng Chen
    

    Reply instructions:
    
    You may reply publicly to this message via plain-text email
    using any one of the following methods:
    
    * Save the following mbox file, import it into your mail client,
      and reply-to-all from there: mbox
    
      Avoid top-posting and favor interleaved quoting:
      https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
    
    * Reply using the --to, --cc, and --in-reply-to
      switches of git-send-email(1):
    
      git send-email \
        --in-reply-to=YH+zwQgBBGUJdiVK@unreal \
        --to=leon@kernel.org \
        --cc=anna.schumaker@netapp.com \
        --cc=bfields@fieldses.org \
        --cc=chuck.lever@oracle.com \
        --cc=davem@davemloft.net \
        --cc=dwysocha@redhat.com \
        --cc=gregkh@linuxfoundation.org \
        --cc=kuba@kernel.org \
        --cc=linux-kernel@vger.kernel.org \
        --cc=linux-nfs@vger.kernel.org \
        --cc=netdev@vger.kernel.org \
        --cc=pakki001@umn.edu \
        --cc=trond.myklebust@hammerspace.com \
        --subject='Re: [PATCH] SUNRPC: Add a check for gss_release_msg' \
        /path/to/YOUR_REPLY
    
      https://kernel.org/pub/software/scm/git/docs/git-send-email.html
    
    * If your mail client supports setting the In-Reply-To header
      via mailto: links, try the mailto: link
    

    This is a public inbox, see mirroring instructions
    for how to clone and mirror all data and code used for this inbox;
    as well as URLs for NNTP newsgroup(s).

    Here's an example of an XML-RPC request:

    POST /RPC2 HTTP/1.0
    User-Agent: Frontier/5.1.2 (WinNT)
    Host: betty.userland.com
    Content-Type: text/xml
    Content-length: 181
    
    <?xml version="1.0"?>
    <methodCall>
        <methodName>examples.getStateName</methodName>
        <params>
            <param>
                <value><i4>41</i4></value>
                </param>
            </params>
        </methodCall>
    

    For example, running:

    octosql "SELECT email, COUNT(*) as invoice_count
             FROM invoices.csv JOIN mydb.customers ON invoices.customer_id = customers.id
             WHERE first_name <= 'D'
             GROUP BY email
             ORDER BY invoice_count DESC" --explain 1

    If you really want this functionality, you can implement it by using the following function:

    function sleepSync(timeout) {
      const sab = new SharedArrayBuffer(1024);
      const int32 = new Int32Array(sab);
      Atomics.wait(int32, 0, 0, timeout);
    }

    It doesn’t get much simpler than this. You’ll notice we only define a main() function followed by a println to stdout.

    fn main() {
        println!("Hello, world!");
    }
    

    # Ruby knows what you
    # mean, even if you
    # want to do math on
    # an entire Array
    cities  = %w[ London
                  Oslo
                  Paris
                  Amsterdam
                  Berlin ]
    visited = %w[Berlin Oslo]
    
    puts "I still need " +
         "to visit the " +
         "following cities:",
         cities - visited

    The equivalent portion is just these two lines:

      var btn = new Gtk.Button.with_label("Hello World");
      btn.click.connect(win.close);

    To preserve linebreaks inside a div using CSS:

    div.code {
      white-space: pre;
    }
    

    @font-face {
    	src: url(http://lea.verou.me/logo.otf);
    	font-family: 'LeaVerou';
    }

    This raw text
    is not highlighted
    but it still has
    line numbers

    Live JSX Editor
    class HelloMessage extends React.Component {
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    }
    
    root.render(<HelloMessage name="Taylor" />);
    
    Result
    Hello Taylor
    output.default.golden000066400000000000000000000305631455110214100370620ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/pre_codeThe MJML tool provides a CLI you can use to transform MJML into HTML: ``` bash $ mjml input.mjml -o output.html ``` Curiously, all text elements (paragraphs and headings) use the same tag, ``. You can create headings by applying cosmetic styles as inline attributes, like: ``` html ``` * * * ``` func HasPrefix(s, prefix []byte) bool ``` * * * #### HTML ```

    Using CSS to change the font color is easy.

    body {
      color: red;
    }
    
    ``` Copy to Clipboard Using CSS to change the font color is easy. ``` body { color: red; } ``` * * * ```lang-go 复制代码const Pi = 3.14159 ``` `const Pi = 3.14159` ```lang-go 复制代码var n intf(n + 5) // 无类型的数字型常量 “5” 它的类型在这里变成了 int ``` * * * ``` mkdir hugo-contrib cd hugo-contrib git clone git@github.com:gohugoio/hugo.git ``` Save the changes and run your first bob command: ``` bob build ``` * * * To confirm that everything went fine, just run: ```codeBlockLines_39YC bob --version ``` Copy * * * For instance, MetaMask displays your recent transactions by making an API call to etherscan: ``` GET https://api.etherscan.io/api?module=account&address=0x0208376c899fdaEbA530570c008C4323803AA9E8&offset=40&order=desc&action=txlist&tag=latest&page=1 HTTP/2.0 ``` …displays your account balance by making an API call to Infura: ``` POST https://mainnet.infura.io/v3/d039103314584a379e33c21fbe89b6cb HTTP/2.0 { "id": 2628746552039525, "jsonrpc": "2.0", "method": "eth_getBalance", "params": [ "0x0208376c899fdaEbA530570c008C4323803AA9E8", "latest" ] } ``` * * * After it was working, I started adding as many different programming languages as I could. As you can see from this excerpt of my project’s version history, I got a little overexcited: ``` languages --------- 2020-06-05 df9ba38 Initial commit 0 2020-06-05 5e3a4a4 Install some packages into a Docker image 0 2020-06-05 e937c8f Simple Express server with "Hello world" 0 2020-06-06 0961498 Embed terminal on frontend app 0 2020-06-06 c66cf63 Embed Monaco editor on frontend 0 2020-06-06 27ab1f7 Add "run" button 0 2020-06-06 f417858 You can run Python code now 1 2020-06-07 d543081 You can run many languages now 8 2020-06-07 e2a3e71 All languages 17 working now 17 2020-06-07 473c50c ALL THE LANGUAGES 25 2020-06-08 3718315 even more languages 33 2020-06-08 548c1c1 repl.it superiority!! 38 2020-06-08 1ae424f More languages, we need all the languages 48 2020-06-09 c34ccf2 A lot more languages 77 2020-06-09 846caf2 At this point the number of languages is absurd 79 ``` * * * Disassembling the now-less-obfuscated dump reveals that one of the addresses has a label pulled out of somewhere! It’s `strlen`? Going down the call stack the next one is labeled `vscan_fn` and after that the labels end, tho I’m fairly confident it’s [`sscanf`](https://github.com/chakra-core/ChakraCore/blob/master/pal/src/safecrt/sscanf.c#L47). ``` 1 2 3 4 5 6 7 8 9 10 11 ``` ``` ..., { "key": "WP_WCT_TINT_21_t2_v9_n2", "price": 45000, "statName": "CHAR_KIT_FM_PURCHASE20", "storageType": "BITFIELD", "bitShift": 7, "bitSize": 1, "category": ["CATEGORY_WEAPON_MOD"] }, ... ``` * * * ```hljs xml

    This is the Panel constructor:

    function Panel(element, canClose, closeHandler) {
          this.element = element;
          this.canClose = canClose;
          this.closeHandler = function () { if (closeHandler) closeHandler() };
        }
    ``` Run code snippet Hide results Expand snippet Problem with `
    ` is it modifies whitespace processing
    as well: all spaces are preserved, and wrapping is switched off. Unless
    there's a way to switch this off?
    
    **Use the `
    
    ```
    
    * * *
    
    * * *
    
    ```
    linux-nfs.vger.kernel.org archive mirror
     help / color / mirror / Atom feed
    ```
    
    ```
    From: Leon Romanovsky 
    To: "J. Bruce Fields" 
    Cc: Greg KH ,
    	Aditya Pakki ,
    	Chuck Lever ,
    	Trond Myklebust ,
    	Anna Schumaker ,
    	"David S. Miller" ,
    	Jakub Kicinski ,
    	Dave Wysochanski ,
    	linux-nfs@vger.kernel.org, netdev@vger.kernel.org,
    	linux-kernel@vger.kernel.org
    Subject: Re: [PATCH] SUNRPC: Add a check for gss_release_msg
    Date: Wed, 21 Apr 2021 08:10:25 +0300	[thread overview]
    Message-ID:  (raw)
    In-Reply-To: <20210420171008.GB4017@fieldses.org>
    
    On Tue, Apr 20, 2021 at 01:10:08PM -0400, J. Bruce Fields wrote:
    > On Tue, Apr 20, 2021 at 09:15:23AM +0200, Greg KH wrote:
    > > If you look at the code, this is impossible to have happen.
    > >
    > > Please stop submitting known-invalid patches.  Your professor is playing
    > > around with the review process in order to achieve a paper in some
    > > strange and bizarre way.
    > >
    > > This is not ok, it is wasting our time, and we will have to report this,
    > > AGAIN, to your university...
    >
    > What's the story here?
    
    Those commits are part of the following research:
    https://github.com/QiushiWu/QiushiWu.github.io/blob/main/papers/OpenSourceInsecurity.pdf
    
    They introduce kernel bugs on purpose. Yesterday, I took a look on 4
    accepted patches from Aditya and 3 of them added various severity security
    "holes".
    
    Thanks
    
    >
    > --b.
    
    ```
    
    * * *
    
    ```
    next prev parent reply	other threads:[~2021-04-21  5:10 UTC|newest]
    
    Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
    2021-04-07  0:16 Aditya Pakki
    2021-04-07 15:34 ` J. Bruce Fields
    2021-04-08 15:01 ` Trond Myklebust
    2021-04-08 15:24   ` Olga Kornievskaia
    2021-04-08 16:02     ` Trond Myklebust
    2021-04-20  7:15 ` Greg KH
    2021-04-20 17:10   ` J. Bruce Fields
    2021-04-21  5:10     ` Leon Romanovsky [this message]
    2021-04-21  5:43       ` Greg KH
    2021-04-21  6:08         ` Leon Romanovsky
         [not found]         ` 
    2021-04-21  8:15           ` Greg KH
    2021-04-21 10:07         ` Sudip Mukherjee
    2021-04-21 10:21           ` Greg KH
    2021-04-21 11:58             ` Shelat, Abhi
    2021-04-21 12:08               ` Greg KH
    2021-04-21 12:19               ` Leon Romanovsky
    2021-04-21 13:11                 ` Trond Myklebust
    2021-04-21 13:20                   ` Leon Romanovsky
    2021-04-21 13:42                     ` Steven Rostedt
    2021-04-21 13:21                   ` gregkh
    2021-04-21 13:34                     ` Leon Romanovsky
    2021-04-21 13:50                       ` gregkh
    2021-04-21 14:12                         ` Leon Romanovsky
    2021-04-21 18:50                         ` Alexander Grund
    2021-04-21 13:37               ` J. Bruce Fields
    2021-04-21 13:49                 ` Leon Romanovsky
    2021-04-21 13:56                   ` J. Bruce Fields
    2021-04-22 19:39                     ` J. Bruce Fields
    2021-04-23 17:25                       ` Leon Romanovsky
    2021-04-23 18:07                         ` J. Bruce Fields
    2021-04-23 19:29                           ` Leon Romanovsky
    2021-04-23 21:48                             ` J. Bruce Fields
    2021-04-24  7:21                               ` Leon Romanovsky
    2021-04-24 18:34                               ` Al Viro
    2021-04-24 21:34                                 ` J. Bruce Fields
    2021-04-25  0:41                                   ` Theodore Ts'o
    2021-04-25  6:29                                     ` Greg KH
         [not found]                                       ` <20210426133605.GD21222@fieldses.org>
    2021-04-26 13:47                                         ` J. Bruce Fields
    2021-04-22  8:10             ` Sudip Mukherjee
    2021-04-22  8:27               ` Greg KH
    2021-04-21 12:51       ` Anna Schumaker
    2021-04-21 14:15         ` Leon Romanovsky
    2021-04-21 15:48           ` Theodore Ts'o
    2021-04-21 17:34             ` Mike Rapoport
    2021-04-22  3:57               ` Leon Romanovsky
    2021-04-21 22:52 ` Guenter Roeck
         [not found] 
    2021-04-21 19:49 ` Theodore Ts'o
    2021-04-22  7:50   ` Eric Biggers
    2021-04-21 20:27 Weikeng Chen
    
    ```
    
    * * *
    
    ```
    Reply instructions:
    
    You may reply publicly to this message via plain-text email
    using any one of the following methods:
    
    * Save the following mbox file, import it into your mail client,
      and reply-to-all from there: mbox
    
      Avoid top-posting and favor interleaved quoting:
      https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
    
    * Reply using the --to, --cc, and --in-reply-to
      switches of git-send-email(1):
    
      git send-email \
        --in-reply-to=YH+zwQgBBGUJdiVK@unreal \
        --to=leon@kernel.org \
        --cc=anna.schumaker@netapp.com \
        --cc=bfields@fieldses.org \
        --cc=chuck.lever@oracle.com \
        --cc=davem@davemloft.net \
        --cc=dwysocha@redhat.com \
        --cc=gregkh@linuxfoundation.org \
        --cc=kuba@kernel.org \
        --cc=linux-kernel@vger.kernel.org \
        --cc=linux-nfs@vger.kernel.org \
        --cc=netdev@vger.kernel.org \
        --cc=pakki001@umn.edu \
        --cc=trond.myklebust@hammerspace.com \
        --subject='Re: [PATCH] SUNRPC: Add a check for gss_release_msg' \
        /path/to/YOUR_REPLY
    
      https://kernel.org/pub/software/scm/git/docs/git-send-email.html
    
    * If your mail client supports setting the In-Reply-To header
      via mailto: links, try the mailto: link
    
    ```
    
    * * *
    
    ```
    This is a public inbox, see mirroring instructions
    for how to clone and mirror all data and code used for this inbox;
    as well as URLs for NNTP newsgroup(s).
    ```
    
    * * *
    
    Here's an example of an XML-RPC request:
    
    ```xml
    POST /RPC2 HTTP/1.0
    User-Agent: Frontier/5.1.2 (WinNT)
    Host: betty.userland.com
    Content-Type: text/xml
    Content-length: 181
    
    
    
        examples.getStateName
        
            
                41
                
            
        
    
    ```
    
    * * *
    
    For example, running:
    
    ```
    octosql "SELECT email, COUNT(*) as invoice_count
             FROM invoices.csv JOIN mydb.customers ON invoices.customer_id = customers.id
             WHERE first_name <= 'D'
             GROUP BY email
             ORDER BY invoice_count DESC" --explain 1
    ```
    
    * * *
    
    If you really want this functionality, you can implement it by using the
    following function:
    
    ```
    function sleepSync(timeout) {
      const sab = new SharedArrayBuffer(1024);
      const int32 = new Int32Array(sab);
      Atomics.wait(int32, 0, 0, timeout);
    }
    ```
    
    * * *
    
    It doesn’t get much simpler than this. You’ll notice we only define a main()
    function followed by a println to stdout.
    
    ``` rust
    fn main() {
        println!("Hello, world!");
    }
    
    ```
    
    * * *
    
    ```ruby
    # Ruby knows what you
    # mean, even if you
    # want to do math on
    # an entire Array
    cities  = %w[ London
                  Oslo
                  Paris
                  Amsterdam
                  Berlin ]
    visited = %w[Berlin Oslo]
    
    puts "I still need " +
         "to visit the " +
         "following cities:",
         cities - visited
    ```
    
    * * *
    
    The equivalent portion is just these two lines:
    
    ```
      var btn = new Gtk.Button.with_label("Hello World");
      btn.click.connect(win.close);
    ```
    
    * * *
    
    To preserve linebreaks inside a `div` using CSS:
    
    ```css hljs
    div.code {
      white-space: pre;
    }
    
    ```
    
    * * *
    
    ```css
    @font-face {
    	src: url(http://lea.verou.me/logo.otf);
    	font-family: 'LeaVerou';
    }
    ```
    
    * * *
    
    ```none
    This raw text
    is not highlighted
    but it still has
    line numbers
    ```
    
    * * *
    
    Live JSX Editor JSX?
    
    ```
    class HelloMessage extends React.Component {
      render() {
        return 
    Hello {this.props.name}
    ; } } root.render(); ``` Result Hello Taylorprice_em_in_a_p/000077500000000000000000000000001455110214100342065ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippetsgoldmark.golden000066400000000000000000000000461455110214100372000ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/price_em_in_a_p

    首付 19,8万 月供

    input.html000066400000000000000000000000671455110214100362360ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/price_em_in_a_p

    首付19,8 月供

    output.default.golden000066400000000000000000000000271455110214100403620ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/price_em_in_a_p首付 _19,8万_ 月供square_brackets/000077500000000000000000000000001455110214100342745ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippetsgoldmark.golden000066400000000000000000000003741455110214100372720ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/square_brackets

    first [literal] brackets

    then [one] way to escape

    then [another] one

    jmap –histo[:live]

    [this should be escaped](http://test)

    before: [this should be escaped](http://test) after

    image ![alt](/img.png)

    input.html000066400000000000000000000004241455110214100363210ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/square_brackets

    first [literal] brackets

    then [one] way to escape

    then [another] one

    jmap –histo[:live]

    [this should be escaped](http://test)

    before: [this should be escaped](http://test) after

    image ![alt](/img.png)

    output.default.golden000066400000000000000000000003361455110214100404530ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/square_bracketsfirst \[literal\] brackets then \[one\] way to escape then \[another\] one jmap –histo\[:live\] \[this should be escaped\](http://test) before: \[this should be escaped\](http://test) after image !\[alt\](/img.png)text_with_whitespace/000077500000000000000000000000001455110214100353515ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippetsgoldmark.golden000066400000000000000000000013011455110214100403360ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/text_with_whitespace

    Aktuelles

    Event Title

    25 Mai

    Event Title

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ... More


    Aktuelles

    Title

    Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper. More

    input.html000066400000000000000000000020261455110214100373760ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/text_with_whitespace

    Aktuelles

    25 Mai

    Event Title

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ... More

    Aktuelles

    Title

    Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper. More
    output.default.golden000066400000000000000000000011041455110214100415220ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/text_with_whitespace# Aktuelles [Event Title](http://example.com/img.jpg "Event Title") 25 Mai ![](http://example.com/img.jpg) ### [Event Title](http://example.com/event) Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ... [More](http://example.com/event1) * * * # Aktuelles ### [Title](http://example.com/some_url) Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper. [More](http://example.com/other_url)golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/turndown_demo/000077500000000000000000000000001455110214100340615ustar00rootroot00000000000000goldmark.golden000066400000000000000000000015031455110214100367730ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/turndown_demo

    Turndown Demo

    This demonstrates turndown – an HTML to Markdown converter in JavaScript.

    Usage

    var turndownService = new TurndownService()
    console.log(
      turndownService.turndown('<h1>Hello world</h1>')
    )
    

    It aims to be CommonMark compliant, and includes options to style the output. These options include:

    • headingStyle (setext or atx)
    • horizontalRule (*, -, or _)
    • bullet (*, -, or +)
    • codeBlockStyle (indented or fenced)
    • fence
    • emDelimiter (_ or *)
    • strongDelimiter (** or __)
    • linkStyle (inlined or referenced)
    • linkReferenceStyle (full, collapsed, or shortcut)
    input.html000066400000000000000000000016321455110214100360310ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/turndown_demo

    Turndown Demo

    This demonstrates turndown – an HTML to Markdown converter in JavaScript.

    Usage

    var turndownService = new TurndownService()
    console.log(
      turndownService.turndown('<h1>Hello world</h1>')
    )

    It aims to be CommonMark compliant, and includes options to style the output. These options include:

    • headingStyle (setext or atx)
    • horizontalRule (*, -, or _)
    • bullet (*, -, or +)
    • codeBlockStyle (indented or fenced)
    • fence
    • emDelimiter (_ or *)
    • strongDelimiter (** or __)
    • linkStyle (inlined or referenced)
    • linkReferenceStyle (full, collapsed, or shortcut)
    output.default.golden000066400000000000000000000012501455110214100401550ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/turndown_demo# Turndown Demo This demonstrates [turndown](https://github.com/domchristie/turndown) – an HTML to Markdown converter in JavaScript. ## Usage ```js var turndownService = new TurndownService() console.log( turndownService.turndown('

    Hello world

    ') ) ``` * * * It aims to be [CommonMark](http://commonmark.org/) compliant, and includes options to style the output. These options include: - headingStyle (setext or atx) - horizontalRule (\*, -, or \_) - bullet (\*, -, or +) - codeBlockStyle (indented or fenced) - fence - emDelimiter (\_ or \*) - strongDelimiter (\*\* or \_\_) - linkStyle (inlined or referenced) - linkReferenceStyle (full, collapsed, or shortcut)golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/tweet/000077500000000000000000000000001455110214100323255ustar00rootroot00000000000000goldmark.golden000066400000000000000000000005511455110214100352410ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/tweet

    Max Mustermann @max

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.


    June 12th 2020

    17 Retweets93 Likes

    input.html000066400000000000000000000012121455110214100342670ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/tweet output.default.golden000066400000000000000000000004451455110214100364260ustar00rootroot00000000000000golang-github-johanneskaufmann-html-to-markdown-1.5.0/testdata/TestRealWorld/snippets/tweet[![](http://example.com/image/profile.jpg)Max Mustermann @max\ \ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. \ ![](http://example.com/img.jpg)\ \ June 12th 2020\ \ 17 Retweets93 Likes](http://example.com/status/1)golang-github-johanneskaufmann-html-to-markdown-1.5.0/utils.go000066400000000000000000000303701455110214100244560ustar00rootroot00000000000000package md import ( "bytes" "fmt" "regexp" "strconv" "strings" "unicode" "unicode/utf8" "github.com/PuerkitoBio/goquery" "golang.org/x/net/html" ) /* WARNING: The functions from this file can be used externally but there is no garanty that they will stay exported. */ // CollectText returns the text of the node and all its children func CollectText(n *html.Node) string { text := &bytes.Buffer{} collectText(n, text) return text.String() } func collectText(n *html.Node, buf *bytes.Buffer) { if n.Type == html.TextNode { buf.WriteString(n.Data) } for c := n.FirstChild; c != nil; c = c.NextSibling { collectText(c, buf) } } func getName(node *html.Node) string { selec := &goquery.Selection{Nodes: []*html.Node{node}} return goquery.NodeName(selec) } // What elements automatically trim their content? // Don't add another space if the other element is going to add a // space already. func isTrimmedElement(name string) bool { nodes := []string{ "a", "strong", "b", "i", "em", "del", "s", "strike", "code", } for _, node := range nodes { if name == node { return true } } return false } func getPrevNodeText(node *html.Node) (string, bool) { if node == nil { return "", false } for ; node != nil; node = node.PrevSibling { text := CollectText(node) name := getName(node) if name == "br" { return "\n", true } // if the content is empty, try our luck with the next node if strings.TrimSpace(text) == "" { continue } if isTrimmedElement(name) { text = strings.TrimSpace(text) } return text, true } return "", false } func getNextNodeText(node *html.Node) (string, bool) { if node == nil { return "", false } for ; node != nil; node = node.NextSibling { text := CollectText(node) name := getName(node) if name == "br" { return "\n", true } // if the content is empty, try our luck with the next node if strings.TrimSpace(text) == "" { continue } // if you have "a a a", three elements that are trimmed, then only add // a space to one side, since the other's are also adding a space. if isTrimmedElement(name) { text = " " } return text, true } return "", false } // AddSpaceIfNessesary adds spaces to the text based on the neighbors. // That makes sure that there is always a space to the side, to recognize the delimiter. func AddSpaceIfNessesary(selec *goquery.Selection, markdown string) string { if len(selec.Nodes) == 0 { return markdown } rootNode := selec.Nodes[0] prev, hasPrev := getPrevNodeText(rootNode.PrevSibling) if hasPrev { lastChar, size := utf8.DecodeLastRuneInString(prev) if size > 0 && !unicode.IsSpace(lastChar) { markdown = " " + markdown } } next, hasNext := getNextNodeText(rootNode.NextSibling) if hasNext { firstChar, size := utf8.DecodeRuneInString(next) if size > 0 && !unicode.IsSpace(firstChar) && !unicode.IsPunct(firstChar) { markdown = markdown + " " } } return markdown } func isLineCodeDelimiter(chars []rune) bool { if len(chars) < 3 { return false } // TODO: If it starts with 4 (instead of 3) fence characters, we should only end it // if we see the same amount of ending fence characters. return chars[0] == '`' && chars[1] == '`' && chars[2] == '`' } // TrimpLeadingSpaces removes spaces from the beginning of a line // but makes sure that list items and code blocks are not affected. func TrimpLeadingSpaces(text string) string { var insideCodeBlock bool lines := strings.Split(text, "\n") for index := range lines { chars := []rune(lines[index]) if isLineCodeDelimiter(chars) { if !insideCodeBlock { // start the code block insideCodeBlock = true } else { // end the code block insideCodeBlock = false } } if insideCodeBlock { // We are inside a code block and don't want to // disturb that formatting (e.g. python indentation) continue } var spaces int for i := 0; i < len(chars); i++ { if unicode.IsSpace(chars[i]) { if chars[i] == ' ' { spaces = spaces + 4 } else { spaces++ } continue } // this seems to be a list item if chars[i] == '-' { break } // this seems to be a code block if spaces >= 4 { break } // remove the space characters from the string chars = chars[i:] break } lines[index] = string(chars) } return strings.Join(lines, "\n") } // TrimTrailingSpaces removes unnecessary spaces from the end of lines. func TrimTrailingSpaces(text string) string { parts := strings.Split(text, "\n") for i := range parts { parts[i] = strings.TrimRightFunc(parts[i], func(r rune) bool { return unicode.IsSpace(r) }) } return strings.Join(parts, "\n") } // The same as `multipleNewLinesRegex`, but applies to escaped new lines inside a link `\n\` var multipleNewLinesInLinkRegex = regexp.MustCompile(`(\n\\){1,}`) // `([\n\r\s]\\)` // EscapeMultiLine deals with multiline content inside a link func EscapeMultiLine(content string) string { content = strings.TrimSpace(content) content = strings.Replace(content, "\n", `\`+"\n", -1) content = multipleNewLinesInLinkRegex.ReplaceAllString(content, "\n\\") return content } func calculateCodeFenceOccurrences(fenceChar rune, content string) int { var occurrences []int var charsTogether int for _, char := range content { // we encountered a fence character, now count how many // are directly afterwards if char == fenceChar { charsTogether++ } else if charsTogether != 0 { occurrences = append(occurrences, charsTogether) charsTogether = 0 } } // if the last element in the content was a fenceChar if charsTogether != 0 { occurrences = append(occurrences, charsTogether) } return findMax(occurrences) } // CalculateCodeFence can be passed the content of a code block and it returns // how many fence characters (` or ~) should be used. // // This is useful if the html content includes the same fence characters // for example ``` // -> https://stackoverflow.com/a/49268657 func CalculateCodeFence(fenceChar rune, content string) string { repeat := calculateCodeFenceOccurrences(fenceChar, content) // the outer fence block always has to have // at least one character more than any content inside repeat++ // you have to have at least three fence characters // to be recognized as a code block if repeat < 3 { repeat = 3 } return strings.Repeat(string(fenceChar), repeat) } func findMax(a []int) (max int) { for i, value := range a { if i == 0 { max = a[i] } if value > max { max = value } } return max } func getCodeWithoutTags(startNode *html.Node) []byte { var buf bytes.Buffer var f func(*html.Node) f = func(n *html.Node) { if n.Type == html.ElementNode && (n.Data == "style" || n.Data == "script" || n.Data == "textarea") { return } if n.Type == html.ElementNode && (n.Data == "br" || n.Data == "div") { buf.WriteString("\n") } if n.Type == html.TextNode { buf.WriteString(n.Data) return } for c := n.FirstChild; c != nil; c = c.NextSibling { f(c) } } f(startNode) return buf.Bytes() } // getCodeContent gets the content of pre/code and unescapes the encoded characters. // Returns "" if there is an error. func getCodeContent(selec *goquery.Selection) string { if len(selec.Nodes) == 0 { return "" } code := getCodeWithoutTags(selec.Nodes[0]) return string(code) } // delimiterForEveryLine puts the delimiter not just at the start and end of the string // but if the text is divided on multiple lines, puts the delimiters on every line with content. // // Otherwise the bold/italic delimiters won't be recognized if it contains new line characters. func delimiterForEveryLine(text string, delimiter string) string { lines := strings.Split(text, "\n") for i, line := range lines { line = strings.TrimSpace(line) if line == "" { // Skip empty lines continue } lines[i] = delimiter + line + delimiter } return strings.Join(lines, "\n") } // isWrapperListItem returns wether the list item has own // content or is just a wrapper for another list. // e.g. "
    • ..." func isWrapperListItem(s *goquery.Selection) bool { directText := s.Contents().Not("ul").Not("ol").Text() noOwnText := strings.TrimSpace(directText) == "" childIsList := s.ChildrenFiltered("ul").Length() > 0 || s.ChildrenFiltered("ol").Length() > 0 return noOwnText && childIsList } // getListStart returns the integer from which the counting // for for the list items should start from. // -> https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol#start func getListStart(parent *goquery.Selection) int { val := parent.AttrOr("start", "") if val == "" { return 1 } num, err := strconv.Atoi(val) if err != nil { return 1 } if num < 0 { return 1 } return num } // getListPrefix returns the appropriate prefix for the list item. // For example "- ", "* ", "1. ", "01. ", ... func getListPrefix(opt *Options, s *goquery.Selection) string { if isWrapperListItem(s) { return "" } parent := s.Parent() if parent.Is("ul") { return opt.BulletListMarker + " " } else if parent.Is("ol") { start := getListStart(parent) currentIndex := start + s.Index() lastIndex := parent.Children().Last().Index() + 1 maxLength := len(strconv.Itoa(lastIndex)) // pad the numbers so that all prefix numbers in the list take up the same space // `%02d.` -> "01. " format := `%0` + strconv.Itoa(maxLength) + `d. ` return fmt.Sprintf(format, currentIndex) } // If the HTML is malformed and the list element isn't in a ul or ol, return no prefix return "" } // countListParents counts how much space is reserved for the prefixes at all the parent lists. // This is useful to calculate the correct level of indentation for nested lists. func countListParents(opt *Options, selec *goquery.Selection) (int, int) { var values []int for n := selec.Parent(); n != nil; n = n.Parent() { if n.Is("li") { continue } if !n.Is("ul") && !n.Is("ol") { break } prefix := n.Children().First().AttrOr(attrListPrefix, "") values = append(values, len(prefix)) } // how many spaces are reserved for the prefixes of my siblings var prefixCount int // how many spaces are reserved in total for all of the other // list parents up the tree var previousPrefixCounts int for i, val := range values { if i == 0 { prefixCount = val continue } previousPrefixCounts += val } return prefixCount, previousPrefixCounts } // IndentMultiLineListItem makes sure that multiline list items // are properly indented. func IndentMultiLineListItem(opt *Options, text string, spaces int) string { parts := strings.Split(text, "\n") for i := range parts { // dont touch the first line since its indented through the prefix if i == 0 { continue } if isListItem(opt, parts[i]) { return strings.Join(parts, "\n") } indent := strings.Repeat(" ", spaces) parts[i] = indent + parts[i] } return strings.Join(parts, "\n") } // isListItem checks wether the line is a markdown list item func isListItem(opt *Options, line string) bool { b := []rune(line) bulletMarker := []rune(opt.BulletListMarker)[0] var hasNumber bool var hasMarker bool var hasSpace bool for i := 0; i < len(b); i++ { // A marker followed by a space qualifies as a list item if hasMarker && hasSpace { if b[i] == bulletMarker { // But if another BulletListMarker is found, it // might be a HorizontalRule return false } if !unicode.IsSpace(b[i]) { // Now we have some text return true } } if hasMarker { if unicode.IsSpace(b[i]) { hasSpace = true continue } // A marker like "1." that is not immediately followed by a space // is probably a false positive return false } if b[i] == bulletMarker { hasMarker = true continue } if hasNumber && b[i] == '.' { hasMarker = true continue } if unicode.IsDigit(b[i]) { hasNumber = true continue } if unicode.IsSpace(b[i]) { continue } // If we encouter any other character // before finding an indicator, its // not a list item return false } return false } // IndexWithText is similar to goquery's Index function but // returns the index of the current element while // NOT counting the empty elements beforehand. func IndexWithText(s *goquery.Selection) int { return s.PrevAll().FilterFunction(func(i int, s *goquery.Selection) bool { return strings.TrimSpace(s.Text()) != "" }).Length() } golang-github-johanneskaufmann-html-to-markdown-1.5.0/utils_test.go000066400000000000000000000235511455110214100255200ustar00rootroot00000000000000package md import ( "strings" "testing" "github.com/PuerkitoBio/goquery" "golang.org/x/net/html" ) func getNodeFromString(t *testing.T, rawHTML string) *html.Node { docNode, err := html.Parse(strings.NewReader(rawHTML)) if err != nil { t.Error(err) return nil } // -> #document -> body -> actuall content return docNode.FirstChild.LastChild.FirstChild } func TestAddSpaceIfNessesary(t *testing.T) { var tests = []struct { Name string Prev string Next string Markdown string Expect string }{ { Name: "dont count comment", Prev: ` `, Next: ` `, Markdown: `_Comment Content_`, Expect: `_Comment Content_`, }, { Name: "bold with break", Prev: `
      `, Next: `
      `, Markdown: `**Bold**`, Expect: `**Bold**`, }, { Name: "italic with no space", Prev: ``, Next: `and no space afterward.`, // #text Markdown: `_Content_`, Expect: `_Content_ `, }, { Name: "bold with no space", Prev: `Some`, Next: `Text`, Markdown: `**Bold**`, Expect: ` **Bold** `, }, { Name: "bold with no space in span", Prev: `Some`, Next: `Text`, Markdown: `**Bold**`, Expect: ` **Bold** `, }, { Name: "italic with no space", Prev: ``, Next: `and no space afterward.`, Markdown: `_Content_`, Expect: `_Content_ `, }, { Name: "github example without new lines", Prev: `go`, Next: `html`, Markdown: `[golang](http://example.com/topics/golang "Topic: golang")`, Expect: ` [golang](http://example.com/topics/golang "Topic: golang")`, }, { Name: "github example", Prev: ` go `, Next: ` html `, Markdown: `[golang](http://example.com/topics/golang "Topic: golang")`, Expect: ` [golang](http://example.com/topics/golang "Topic: golang")`, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // build a selection for goquery with siblings selec := &goquery.Selection{ Nodes: []*html.Node{ { Data: "a", PrevSibling: getNodeFromString(t, test.Prev), NextSibling: getNodeFromString(t, test.Next), }, }, } output := AddSpaceIfNessesary(selec, test.Markdown) if output != test.Expect { t.Errorf("expected '%s' but got '%s'", test.Expect, output) } }) } } func TestTrimpLeadingSpaces(t *testing.T) { var tests = []struct { Name string Text string Expect string }{ { Name: "trim normal text", Text: ` This is a normal paragraph this as well just with some spaces before `, Expect: ` This is a normal paragraph this as well just with some spaces before `, }, { Name: "dont trim nested lists", Text: ` - Home - About - People - History - 2019 - 2020 `, Expect: ` - Home - About - People - History - 2019 - 2020 `, }, { Name: "dont trim list with multiple paragraphs", Text: ` 1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing. `, Expect: ` 1. This is a list item with two paragraphs. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 2. Suspendisse id sem consectetuer libero luctus adipiscing. `, }, { Name: "dont trim code blocks", Text: ` This is a normal paragraph: This is a code block. `, Expect: ` This is a normal paragraph: This is a code block. `, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { output := TrimpLeadingSpaces(test.Text) if output != test.Expect { t.Errorf("expected '%s' but got '%s'", test.Expect, output) } }) } } func TestTrimTrailingSpaces(t *testing.T) { var tests = []struct { Name string Text string Expect string }{ { Name: "trim after normal text", Text: ` 1\. xxx 2\. xxxx `, Expect: ` 1\. xxx 2\. xxxx `, }, { Name: "dont trim inside normal text", Text: "When `x = 3`, that means `x + 2 = 5`", Expect: "When `x = 3`, that means `x + 2 = 5`", }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { output := TrimTrailingSpaces(test.Text) if output != test.Expect { t.Errorf("expected '%s' but got '%s'", test.Expect, output) } }) } } func TestEscapeMultiLine(t *testing.T) { var tests = []struct { Name string Text string Expect string }{ { Name: "escape new lines", Text: `line1 line2 line3 line4`, Expect: `line1\ line2\ \ line3\ \ line4`, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { output := EscapeMultiLine(test.Text) if output != test.Expect { t.Errorf("expected '%s' but got '%s'", test.Expect, output) } }) } } func TestCalculateCodeFence(t *testing.T) { var tests = []struct { Name string FenceChar rune Text string Expect string }{ { Name: "no occurrences with backtick", FenceChar: '`', Text: `normal ~~~ code block`, Expect: "```", }, { Name: "no occurrences with tilde", FenceChar: '~', Text: "normal ``` code block", Expect: "~~~", }, { Name: "one exact occurrence", FenceChar: '`', Text: "```", Expect: "````", }, { Name: "one occurrences with backtick", FenceChar: '`', Text: "normal ``` code block", Expect: "````", }, { Name: "one bigger occurrences with backtick", FenceChar: '`', Text: "normal ````` code block", Expect: "``````", }, { Name: "multiple occurrences with backtick", FenceChar: '`', Text: "normal ``` code `````` block", Expect: "```````", }, { Name: "multiple occurrences with tilde", FenceChar: '~', Text: "normal ~~~ code ~~~~~~~~~~~~ block", Expect: "~~~~~~~~~~~~~", }, { Name: "multiple occurrences on different lines with tilde", FenceChar: '~', Text: ` normal ~~~ code ~~~~~~~~~~~~ block `, Expect: "~~~~~~~~~~~~~", }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { output := CalculateCodeFence(test.FenceChar, test.Text) if output != test.Expect { t.Errorf("expected '%s' (x%d) but got '%s' (x%d)", test.Expect, strings.Count(test.Expect, string(test.FenceChar)), output, strings.Count(output, string(test.FenceChar))) } }) } } func TestIsListItem(t *testing.T) { var tests = []struct { name string opt *Options line string expected bool }{ { name: "nothing", opt: &Options{ BulletListMarker: "-", }, line: "", expected: false, }, { name: "just spaces", opt: &Options{ BulletListMarker: "-", }, line: " ", expected: false, }, { name: "just text", opt: &Options{ BulletListMarker: "-", }, line: " text", expected: false, }, { name: "just numbers", opt: &Options{ BulletListMarker: "-", }, line: " 123", expected: false, }, { name: "unordered: with -", opt: &Options{ BulletListMarker: "-", }, line: " - item", expected: true, }, { name: "unordered: with *", opt: &Options{ BulletListMarker: "*", }, line: " * item", expected: true, }, { name: "unordered: with * false positive", opt: &Options{ BulletListMarker: "*", }, line: " - item", expected: false, }, { name: "unordered: with multiple spaces", opt: &Options{ BulletListMarker: "-", }, line: " - item", expected: true, }, { name: "unordered: without space", opt: &Options{ BulletListMarker: "-", }, line: " -item", expected: false, }, { name: "ordered: without space", opt: &Options{ BulletListMarker: "-", }, line: " 1.item", expected: false, }, { name: "ordered: with space", opt: &Options{ BulletListMarker: "-", }, line: " 1. item", expected: true, }, { name: "ordered: without dot", opt: &Options{ BulletListMarker: "-", }, line: " 1 item", expected: false, }, { name: "ordered: with dot before", opt: &Options{ BulletListMarker: "-", }, line: " .1 item", expected: false, }, { name: "ordered: with big number", opt: &Options{ BulletListMarker: "-", }, line: " 1001. item", expected: true, }, { name: "ordered: with date", opt: &Options{ BulletListMarker: "-", }, line: " 01.01 January", expected: false, }, { name: "with divider", opt: &Options{ BulletListMarker: "*", }, line: "***", expected: false, }, { name: "with divider and spaces", opt: &Options{ BulletListMarker: "*", }, line: "* * *", expected: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := isListItem(test.opt, test.line) if result != test.expected { t.Errorf("expected '%+v' but got '%+v' for '%s'", test.expected, result, test.line) } }) } }