1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219 |
- package html
- import (
- "bytes"
- "errors"
- "io"
- "strconv"
- "strings"
- "golang.org/x/net/html/atom"
- )
- type TokenType uint32
- const (
-
- ErrorToken TokenType = iota
-
- TextToken
-
- StartTagToken
-
- EndTagToken
-
- SelfClosingTagToken
-
- CommentToken
-
- DoctypeToken
- )
- var ErrBufferExceeded = errors.New("max buffer exceeded")
- func (t TokenType) String() string {
- switch t {
- case ErrorToken:
- return "Error"
- case TextToken:
- return "Text"
- case StartTagToken:
- return "StartTag"
- case EndTagToken:
- return "EndTag"
- case SelfClosingTagToken:
- return "SelfClosingTag"
- case CommentToken:
- return "Comment"
- case DoctypeToken:
- return "Doctype"
- }
- return "Invalid(" + strconv.Itoa(int(t)) + ")"
- }
- type Attribute struct {
- Namespace, Key, Val string
- }
- type Token struct {
- Type TokenType
- DataAtom atom.Atom
- Data string
- Attr []Attribute
- }
- func (t Token) tagString() string {
- if len(t.Attr) == 0 {
- return t.Data
- }
- buf := bytes.NewBufferString(t.Data)
- for _, a := range t.Attr {
- buf.WriteByte(' ')
- buf.WriteString(a.Key)
- buf.WriteString(`="`)
- escape(buf, a.Val)
- buf.WriteByte('"')
- }
- return buf.String()
- }
- func (t Token) String() string {
- switch t.Type {
- case ErrorToken:
- return ""
- case TextToken:
- return EscapeString(t.Data)
- case StartTagToken:
- return "<" + t.tagString() + ">"
- case EndTagToken:
- return "</" + t.tagString() + ">"
- case SelfClosingTagToken:
- return "<" + t.tagString() + "/>"
- case CommentToken:
- return "<!--" + t.Data + "-->"
- case DoctypeToken:
- return "<!DOCTYPE " + t.Data + ">"
- }
- return "Invalid(" + strconv.Itoa(int(t.Type)) + ")"
- }
- type span struct {
- start, end int
- }
- type Tokenizer struct {
-
- r io.Reader
-
- tt TokenType
-
-
-
-
-
-
-
- err error
-
-
-
-
- readErr error
-
-
- raw span
- buf []byte
-
- maxBuf int
-
-
- data span
-
-
-
- pendingAttr [2]span
- attr [][2]span
- nAttrReturned int
-
-
-
-
- rawTag string
-
- textIsRaw bool
-
-
- convertNUL bool
-
- allowCDATA bool
- }
- func (z *Tokenizer) AllowCDATA(allowCDATA bool) {
- z.allowCDATA = allowCDATA
- }
- func (z *Tokenizer) NextIsNotRawText() {
- z.rawTag = ""
- }
- func (z *Tokenizer) Err() error {
- if z.tt != ErrorToken {
- return nil
- }
- return z.err
- }
- func (z *Tokenizer) readByte() byte {
- if z.raw.end >= len(z.buf) {
-
-
- if z.readErr != nil {
- z.err = z.readErr
- return 0
- }
-
-
-
- c := cap(z.buf)
- d := z.raw.end - z.raw.start
- var buf1 []byte
- if 2*d > c {
- buf1 = make([]byte, d, 2*c)
- } else {
- buf1 = z.buf[:d]
- }
- copy(buf1, z.buf[z.raw.start:z.raw.end])
- if x := z.raw.start; x != 0 {
-
- z.data.start -= x
- z.data.end -= x
- z.pendingAttr[0].start -= x
- z.pendingAttr[0].end -= x
- z.pendingAttr[1].start -= x
- z.pendingAttr[1].end -= x
- for i := range z.attr {
- z.attr[i][0].start -= x
- z.attr[i][0].end -= x
- z.attr[i][1].start -= x
- z.attr[i][1].end -= x
- }
- }
- z.raw.start, z.raw.end, z.buf = 0, d, buf1[:d]
-
-
- var n int
- n, z.readErr = readAtLeastOneByte(z.r, buf1[d:cap(buf1)])
- if n == 0 {
- z.err = z.readErr
- return 0
- }
- z.buf = buf1[:d+n]
- }
- x := z.buf[z.raw.end]
- z.raw.end++
- if z.maxBuf > 0 && z.raw.end-z.raw.start >= z.maxBuf {
- z.err = ErrBufferExceeded
- return 0
- }
- return x
- }
- func (z *Tokenizer) Buffered() []byte {
- return z.buf[z.raw.end:]
- }
- func readAtLeastOneByte(r io.Reader, b []byte) (int, error) {
- for i := 0; i < 100; i++ {
- n, err := r.Read(b)
- if n != 0 || err != nil {
- return n, err
- }
- }
- return 0, io.ErrNoProgress
- }
- func (z *Tokenizer) skipWhiteSpace() {
- if z.err != nil {
- return
- }
- for {
- c := z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f':
-
- default:
- z.raw.end--
- return
- }
- }
- }
- func (z *Tokenizer) readRawOrRCDATA() {
- if z.rawTag == "script" {
- z.readScript()
- z.textIsRaw = true
- z.rawTag = ""
- return
- }
- loop:
- for {
- c := z.readByte()
- if z.err != nil {
- break loop
- }
- if c != '<' {
- continue loop
- }
- c = z.readByte()
- if z.err != nil {
- break loop
- }
- if c != '/' {
- continue loop
- }
- if z.readRawEndTag() || z.err != nil {
- break loop
- }
- }
- z.data.end = z.raw.end
-
- z.textIsRaw = z.rawTag != "textarea" && z.rawTag != "title"
- z.rawTag = ""
- }
- func (z *Tokenizer) readRawEndTag() bool {
- for i := 0; i < len(z.rawTag); i++ {
- c := z.readByte()
- if z.err != nil {
- return false
- }
- if c != z.rawTag[i] && c != z.rawTag[i]-('a'-'A') {
- z.raw.end--
- return false
- }
- }
- c := z.readByte()
- if z.err != nil {
- return false
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f', '/', '>':
-
- z.raw.end -= 3 + len(z.rawTag)
- return true
- }
- z.raw.end--
- return false
- }
- func (z *Tokenizer) readScript() {
- defer func() {
- z.data.end = z.raw.end
- }()
- var c byte
- scriptData:
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c == '<' {
- goto scriptDataLessThanSign
- }
- goto scriptData
- scriptDataLessThanSign:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '/':
- goto scriptDataEndTagOpen
- case '!':
- goto scriptDataEscapeStart
- }
- z.raw.end--
- goto scriptData
- scriptDataEndTagOpen:
- if z.readRawEndTag() || z.err != nil {
- return
- }
- goto scriptData
- scriptDataEscapeStart:
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c == '-' {
- goto scriptDataEscapeStartDash
- }
- z.raw.end--
- goto scriptData
- scriptDataEscapeStartDash:
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c == '-' {
- goto scriptDataEscapedDashDash
- }
- z.raw.end--
- goto scriptData
- scriptDataEscaped:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataEscapedDash
- case '<':
- goto scriptDataEscapedLessThanSign
- }
- goto scriptDataEscaped
- scriptDataEscapedDash:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataEscapedDashDash
- case '<':
- goto scriptDataEscapedLessThanSign
- }
- goto scriptDataEscaped
- scriptDataEscapedDashDash:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataEscapedDashDash
- case '<':
- goto scriptDataEscapedLessThanSign
- case '>':
- goto scriptData
- }
- goto scriptDataEscaped
- scriptDataEscapedLessThanSign:
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c == '/' {
- goto scriptDataEscapedEndTagOpen
- }
- if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
- goto scriptDataDoubleEscapeStart
- }
- z.raw.end--
- goto scriptData
- scriptDataEscapedEndTagOpen:
- if z.readRawEndTag() || z.err != nil {
- return
- }
- goto scriptDataEscaped
- scriptDataDoubleEscapeStart:
- z.raw.end--
- for i := 0; i < len("script"); i++ {
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c != "script"[i] && c != "SCRIPT"[i] {
- z.raw.end--
- goto scriptDataEscaped
- }
- }
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f', '/', '>':
- goto scriptDataDoubleEscaped
- }
- z.raw.end--
- goto scriptDataEscaped
- scriptDataDoubleEscaped:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataDoubleEscapedDash
- case '<':
- goto scriptDataDoubleEscapedLessThanSign
- }
- goto scriptDataDoubleEscaped
- scriptDataDoubleEscapedDash:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataDoubleEscapedDashDash
- case '<':
- goto scriptDataDoubleEscapedLessThanSign
- }
- goto scriptDataDoubleEscaped
- scriptDataDoubleEscapedDashDash:
- c = z.readByte()
- if z.err != nil {
- return
- }
- switch c {
- case '-':
- goto scriptDataDoubleEscapedDashDash
- case '<':
- goto scriptDataDoubleEscapedLessThanSign
- case '>':
- goto scriptData
- }
- goto scriptDataDoubleEscaped
- scriptDataDoubleEscapedLessThanSign:
- c = z.readByte()
- if z.err != nil {
- return
- }
- if c == '/' {
- goto scriptDataDoubleEscapeEnd
- }
- z.raw.end--
- goto scriptDataDoubleEscaped
- scriptDataDoubleEscapeEnd:
- if z.readRawEndTag() {
- z.raw.end += len("</script>")
- goto scriptDataEscaped
- }
- if z.err != nil {
- return
- }
- goto scriptDataDoubleEscaped
- }
- func (z *Tokenizer) readComment() {
- z.data.start = z.raw.end
- defer func() {
- if z.data.end < z.data.start {
-
- z.data.end = z.data.start
- }
- }()
- for dashCount := 2; ; {
- c := z.readByte()
- if z.err != nil {
-
- if dashCount > 2 {
- dashCount = 2
- }
- z.data.end = z.raw.end - dashCount
- return
- }
- switch c {
- case '-':
- dashCount++
- continue
- case '>':
- if dashCount >= 2 {
- z.data.end = z.raw.end - len("-->")
- return
- }
- case '!':
- if dashCount >= 2 {
- c = z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return
- }
- if c == '>' {
- z.data.end = z.raw.end - len("--!>")
- return
- }
- }
- }
- dashCount = 0
- }
- }
- func (z *Tokenizer) readUntilCloseAngle() {
- z.data.start = z.raw.end
- for {
- c := z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return
- }
- if c == '>' {
- z.data.end = z.raw.end - len(">")
- return
- }
- }
- }
- func (z *Tokenizer) readMarkupDeclaration() TokenType {
- z.data.start = z.raw.end
- var c [2]byte
- for i := 0; i < 2; i++ {
- c[i] = z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return CommentToken
- }
- }
- if c[0] == '-' && c[1] == '-' {
- z.readComment()
- return CommentToken
- }
- z.raw.end -= 2
- if z.readDoctype() {
- return DoctypeToken
- }
- if z.allowCDATA && z.readCDATA() {
- z.convertNUL = true
- return TextToken
- }
-
- z.readUntilCloseAngle()
- return CommentToken
- }
- func (z *Tokenizer) readDoctype() bool {
- const s = "DOCTYPE"
- for i := 0; i < len(s); i++ {
- c := z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return false
- }
- if c != s[i] && c != s[i]+('a'-'A') {
-
- z.raw.end = z.data.start
- return false
- }
- }
- if z.skipWhiteSpace(); z.err != nil {
- z.data.start = z.raw.end
- z.data.end = z.raw.end
- return true
- }
- z.readUntilCloseAngle()
- return true
- }
- func (z *Tokenizer) readCDATA() bool {
- const s = "[CDATA["
- for i := 0; i < len(s); i++ {
- c := z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return false
- }
- if c != s[i] {
-
- z.raw.end = z.data.start
- return false
- }
- }
- z.data.start = z.raw.end
- brackets := 0
- for {
- c := z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return true
- }
- switch c {
- case ']':
- brackets++
- case '>':
- if brackets >= 2 {
- z.data.end = z.raw.end - len("]]>")
- return true
- }
- brackets = 0
- default:
- brackets = 0
- }
- }
- }
- func (z *Tokenizer) startTagIn(ss ...string) bool {
- loop:
- for _, s := range ss {
- if z.data.end-z.data.start != len(s) {
- continue loop
- }
- for i := 0; i < len(s); i++ {
- c := z.buf[z.data.start+i]
- if 'A' <= c && c <= 'Z' {
- c += 'a' - 'A'
- }
- if c != s[i] {
- continue loop
- }
- }
- return true
- }
- return false
- }
- func (z *Tokenizer) readStartTag() TokenType {
- z.readTag(true)
- if z.err != nil {
- return ErrorToken
- }
-
- c, raw := z.buf[z.data.start], false
- if 'A' <= c && c <= 'Z' {
- c += 'a' - 'A'
- }
- switch c {
- case 'i':
- raw = z.startTagIn("iframe")
- case 'n':
- raw = z.startTagIn("noembed", "noframes", "noscript")
- case 'p':
- raw = z.startTagIn("plaintext")
- case 's':
- raw = z.startTagIn("script", "style")
- case 't':
- raw = z.startTagIn("textarea", "title")
- case 'x':
- raw = z.startTagIn("xmp")
- }
- if raw {
- z.rawTag = strings.ToLower(string(z.buf[z.data.start:z.data.end]))
- }
-
- if z.err == nil && z.buf[z.raw.end-2] == '/' {
- return SelfClosingTagToken
- }
- return StartTagToken
- }
- func (z *Tokenizer) readTag(saveAttr bool) {
- z.attr = z.attr[:0]
- z.nAttrReturned = 0
-
- z.readTagName()
- if z.skipWhiteSpace(); z.err != nil {
- return
- }
- for {
- c := z.readByte()
- if z.err != nil || c == '>' {
- break
- }
- z.raw.end--
- z.readTagAttrKey()
- z.readTagAttrVal()
-
- if saveAttr && z.pendingAttr[0].start != z.pendingAttr[0].end {
- z.attr = append(z.attr, z.pendingAttr)
- }
- if z.skipWhiteSpace(); z.err != nil {
- break
- }
- }
- }
- func (z *Tokenizer) readTagName() {
- z.data.start = z.raw.end - 1
- for {
- c := z.readByte()
- if z.err != nil {
- z.data.end = z.raw.end
- return
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f':
- z.data.end = z.raw.end - 1
- return
- case '/', '>':
- z.raw.end--
- z.data.end = z.raw.end
- return
- }
- }
- }
- func (z *Tokenizer) readTagAttrKey() {
- z.pendingAttr[0].start = z.raw.end
- for {
- c := z.readByte()
- if z.err != nil {
- z.pendingAttr[0].end = z.raw.end
- return
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f', '/':
- z.pendingAttr[0].end = z.raw.end - 1
- return
- case '=', '>':
- z.raw.end--
- z.pendingAttr[0].end = z.raw.end
- return
- }
- }
- }
- func (z *Tokenizer) readTagAttrVal() {
- z.pendingAttr[1].start = z.raw.end
- z.pendingAttr[1].end = z.raw.end
- if z.skipWhiteSpace(); z.err != nil {
- return
- }
- c := z.readByte()
- if z.err != nil {
- return
- }
- if c != '=' {
- z.raw.end--
- return
- }
- if z.skipWhiteSpace(); z.err != nil {
- return
- }
- quote := z.readByte()
- if z.err != nil {
- return
- }
- switch quote {
- case '>':
- z.raw.end--
- return
- case '\'', '"':
- z.pendingAttr[1].start = z.raw.end
- for {
- c := z.readByte()
- if z.err != nil {
- z.pendingAttr[1].end = z.raw.end
- return
- }
- if c == quote {
- z.pendingAttr[1].end = z.raw.end - 1
- return
- }
- }
- default:
- z.pendingAttr[1].start = z.raw.end - 1
- for {
- c := z.readByte()
- if z.err != nil {
- z.pendingAttr[1].end = z.raw.end
- return
- }
- switch c {
- case ' ', '\n', '\r', '\t', '\f':
- z.pendingAttr[1].end = z.raw.end - 1
- return
- case '>':
- z.raw.end--
- z.pendingAttr[1].end = z.raw.end
- return
- }
- }
- }
- }
- func (z *Tokenizer) Next() TokenType {
- z.raw.start = z.raw.end
- z.data.start = z.raw.end
- z.data.end = z.raw.end
- if z.err != nil {
- z.tt = ErrorToken
- return z.tt
- }
- if z.rawTag != "" {
- if z.rawTag == "plaintext" {
-
- for z.err == nil {
- z.readByte()
- }
- z.data.end = z.raw.end
- z.textIsRaw = true
- } else {
- z.readRawOrRCDATA()
- }
- if z.data.end > z.data.start {
- z.tt = TextToken
- z.convertNUL = true
- return z.tt
- }
- }
- z.textIsRaw = false
- z.convertNUL = false
- loop:
- for {
- c := z.readByte()
- if z.err != nil {
- break loop
- }
- if c != '<' {
- continue loop
- }
-
-
- c = z.readByte()
- if z.err != nil {
- break loop
- }
- var tokenType TokenType
- switch {
- case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
- tokenType = StartTagToken
- case c == '/':
- tokenType = EndTagToken
- case c == '!' || c == '?':
-
-
- tokenType = CommentToken
- default:
-
- z.raw.end--
- continue
- }
-
-
-
- if x := z.raw.end - len("<a"); z.raw.start < x {
- z.raw.end = x
- z.data.end = x
- z.tt = TextToken
- return z.tt
- }
- switch tokenType {
- case StartTagToken:
- z.tt = z.readStartTag()
- return z.tt
- case EndTagToken:
- c = z.readByte()
- if z.err != nil {
- break loop
- }
- if c == '>' {
-
-
-
- z.tt = CommentToken
- return z.tt
- }
- if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' {
- z.readTag(false)
- if z.err != nil {
- z.tt = ErrorToken
- } else {
- z.tt = EndTagToken
- }
- return z.tt
- }
- z.raw.end--
- z.readUntilCloseAngle()
- z.tt = CommentToken
- return z.tt
- case CommentToken:
- if c == '!' {
- z.tt = z.readMarkupDeclaration()
- return z.tt
- }
- z.raw.end--
- z.readUntilCloseAngle()
- z.tt = CommentToken
- return z.tt
- }
- }
- if z.raw.start < z.raw.end {
- z.data.end = z.raw.end
- z.tt = TextToken
- return z.tt
- }
- z.tt = ErrorToken
- return z.tt
- }
- func (z *Tokenizer) Raw() []byte {
- return z.buf[z.raw.start:z.raw.end]
- }
- func convertNewlines(s []byte) []byte {
- for i, c := range s {
- if c != '\r' {
- continue
- }
- src := i + 1
- if src >= len(s) || s[src] != '\n' {
- s[i] = '\n'
- continue
- }
- dst := i
- for src < len(s) {
- if s[src] == '\r' {
- if src+1 < len(s) && s[src+1] == '\n' {
- src++
- }
- s[dst] = '\n'
- } else {
- s[dst] = s[src]
- }
- src++
- dst++
- }
- return s[:dst]
- }
- return s
- }
- var (
- nul = []byte("\x00")
- replacement = []byte("\ufffd")
- )
- func (z *Tokenizer) Text() []byte {
- switch z.tt {
- case TextToken, CommentToken, DoctypeToken:
- s := z.buf[z.data.start:z.data.end]
- z.data.start = z.raw.end
- z.data.end = z.raw.end
- s = convertNewlines(s)
- if (z.convertNUL || z.tt == CommentToken) && bytes.Contains(s, nul) {
- s = bytes.Replace(s, nul, replacement, -1)
- }
- if !z.textIsRaw {
- s = unescape(s, false)
- }
- return s
- }
- return nil
- }
- func (z *Tokenizer) TagName() (name []byte, hasAttr bool) {
- if z.data.start < z.data.end {
- switch z.tt {
- case StartTagToken, EndTagToken, SelfClosingTagToken:
- s := z.buf[z.data.start:z.data.end]
- z.data.start = z.raw.end
- z.data.end = z.raw.end
- return lower(s), z.nAttrReturned < len(z.attr)
- }
- }
- return nil, false
- }
- func (z *Tokenizer) TagAttr() (key, val []byte, moreAttr bool) {
- if z.nAttrReturned < len(z.attr) {
- switch z.tt {
- case StartTagToken, SelfClosingTagToken:
- x := z.attr[z.nAttrReturned]
- z.nAttrReturned++
- key = z.buf[x[0].start:x[0].end]
- val = z.buf[x[1].start:x[1].end]
- return lower(key), unescape(convertNewlines(val), true), z.nAttrReturned < len(z.attr)
- }
- }
- return nil, nil, false
- }
- func (z *Tokenizer) Token() Token {
- t := Token{Type: z.tt}
- switch z.tt {
- case TextToken, CommentToken, DoctypeToken:
- t.Data = string(z.Text())
- case StartTagToken, SelfClosingTagToken, EndTagToken:
- name, moreAttr := z.TagName()
- for moreAttr {
- var key, val []byte
- key, val, moreAttr = z.TagAttr()
- t.Attr = append(t.Attr, Attribute{"", atom.String(key), string(val)})
- }
- if a := atom.Lookup(name); a != 0 {
- t.DataAtom, t.Data = a, a.String()
- } else {
- t.DataAtom, t.Data = 0, string(name)
- }
- }
- return t
- }
- func (z *Tokenizer) SetMaxBuf(n int) {
- z.maxBuf = n
- }
- func NewTokenizer(r io.Reader) *Tokenizer {
- return NewTokenizerFragment(r, "")
- }
- func NewTokenizerFragment(r io.Reader, contextTag string) *Tokenizer {
- z := &Tokenizer{
- r: r,
- buf: make([]byte, 0, 4096),
- }
- if contextTag != "" {
- switch s := strings.ToLower(contextTag); s {
- case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "title", "textarea", "xmp":
- z.rawTag = s
- }
- }
- return z
- }
|