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
}
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.
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)
type BeforeHook func(selec *goquery.Selection)
type Afterhook func(markdown string) string
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"
)
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) {
			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")
		markdown = TrimTrailingSpaces(markdown)
		return markdown
	})
	if enableCommonmark {
		conv.AddRules(commonmark...)
		conv.remove["script"] = struct{}{}
		conv.remove["style"] = struct{}{}
		conv.remove["textarea"] = struct{}{}
	}
	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"
	}
	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  
		}
		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
	}
}
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
}