func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) (css.TokenType, []byte) { if tt == css.NumberToken || tt == css.PercentageToken || tt == css.DimensionToken { if tt == css.NumberToken && (prop == css.Z_Index || prop == css.Counter_Increment || prop == css.Counter_Reset || prop == css.Orphans || prop == css.Widows) { return tt, data // integers } n := len(data) if tt == css.PercentageToken { n-- } else if tt == css.DimensionToken { n = parse.Number(data) } dim := data[n:] data = minify.Number(data[:n], c.o.Decimals) if len(data) != 1 || data[0] != '0' { if tt == css.PercentageToken { data = append(data, '%') } else if tt == css.DimensionToken { parse.ToLower(dim) data = append(data, dim...) } } } else if tt == css.IdentToken { parse.ToLower(data) if hex, ok := ShortenColorName[css.ToHash(data)]; ok { tt = css.HashToken data = hex } } else if tt == css.HashToken { parse.ToLower(data) if ident, ok := ShortenColorHex[string(data)]; ok { tt = css.IdentToken data = ident } else if len(data) == 7 && data[1] == data[2] && data[3] == data[4] && data[5] == data[6] { tt = css.HashToken data[2] = data[3] data[3] = data[5] data = data[:4] } } else if tt == css.StringToken { // remove any \\\r\n \\\r \\\n for i := 1; i < len(data)-2; i++ { if data[i] == '\\' && (data[i+1] == '\n' || data[i+1] == '\r') { // encountered first replacee, now start to move bytes to the front j := i + 2 if data[i+1] == '\r' && len(data) > i+2 && data[i+2] == '\n' { j++ } for ; j < len(data); j++ { if data[j] == '\\' && len(data) > j+1 && (data[j+1] == '\n' || data[j+1] == '\r') { if data[j+1] == '\r' && len(data) > j+2 && data[j+2] == '\n' { j++ } j++ } else { data[i] = data[j] i++ } } data = data[:i] break } } } else if tt == css.URLToken { parse.ToLower(data[:3]) if len(data) > 10 { uri := data[4 : len(data)-1] delim := byte('"') if uri[0] == '\'' || uri[0] == '"' { delim = uri[0] uri = uri[1 : len(uri)-1] } uri = minify.DataURI(c.m, uri) if css.IsURLUnquoted(uri) { data = append(append([]byte("url("), uri...), ')') } else { data = append(append(append([]byte("url("), delim), uri...), delim, ')') } } } return tt, data }
func (c *cssMinifier) minifyFunction(values []css.Token) (int, error) { n := 1 simple := true for i, value := range values[1:] { if value.TokenType == css.RightParenthesisToken { n++ break } if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) { simple = false } n++ } values = values[:n] if simple && (n-1)%2 == 0 { fun := css.ToHash(values[0].Data[:len(values[0].Data)-1]) nArgs := (n - 1) / 2 if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 { d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken if d-1.0 > -minify.Epsilon { if fun == css.Rgba { values[0].Data = []byte("rgb(") fun = css.Rgb } else { values[0].Data = []byte("hsl(") fun = css.Hsl } values = values[:len(values)-2] values[len(values)-1].Data = []byte(")") nArgs = 3 } else if d < minify.Epsilon { values[0].Data = []byte("transparent") values = values[:1] fun = 0 nArgs = 0 } } if fun == css.Rgb && nArgs == 3 { var err [3]error rgb := [3]byte{} for j := 0; j < 3; j++ { val := values[j*2+1] if val.TokenType == css.NumberToken { var d int64 d, err[j] = strconv.ParseInt(string(val.Data), 10, 32) if d < 0 { d = 0 } else if d > 255 { d = 255 } rgb[j] = byte(d) } else if val.TokenType == css.PercentageToken { var d float64 d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32) if d < 0.0 { d = 0.0 } else if d > 100.0 { d = 100.0 } rgb[j] = byte((d / 100.0 * 255.0) + 0.5) } } if err[0] == nil && err[1] == nil && err[2] == nil { val := make([]byte, 7) val[0] = '#' hex.Encode(val[1:], rgb[:]) parse.ToLower(val) if s, ok := ShortenColorHex[string(val)]; ok { if _, err := c.w.Write(s); err != nil { return 0, err } } else { if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { val[2] = val[3] val[3] = val[5] val = val[:4] } if _, err := c.w.Write(val); err != nil { return 0, err } } return n, nil } } else if fun == css.Hsl && nArgs == 3 { if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken { h, err1 := strconv.ParseFloat(string(values[1].Data), 32) s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32) l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32) if err1 == nil && err2 == nil && err3 == nil { r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0) rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)} val := make([]byte, 7) val[0] = '#' hex.Encode(val[1:], rgb[:]) parse.ToLower(val) if s, ok := ShortenColorHex[string(val)]; ok { if _, err := c.w.Write(s); err != nil { return 0, err } } else { if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { val[2] = val[3] val[3] = val[5] val = val[:4] } if _, err := c.w.Write(val); err != nil { return 0, err } } return n, nil } } } } for _, value := range values { if _, err := c.w.Write(value.Data); err != nil { return 0, err } } return n, nil }
// Minify minifies SVG data, it reads from r and writes to w. func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error { var tag svg.Hash defaultStyleType := cssMimeBytes defaultStyleParams := map[string]string(nil) defaultInlineStyleParams := map[string]string{"inline": "1"} p := NewPathData(o) minifyBuffer := buffer.NewWriter(make([]byte, 0, 64)) attrByteBuffer := make([]byte, 0, 64) gStack := make([]bool, 0) l := xml.NewLexer(r) tb := NewTokenBuffer(l) for { t := *tb.Shift() SWITCH: switch t.TokenType { case xml.ErrorToken: if l.Err() == io.EOF { return nil } return l.Err() case xml.DOCTYPEToken: if len(t.Text) > 0 && t.Text[len(t.Text)-1] == ']' { if _, err := w.Write(t.Data); err != nil { return err } } case xml.TextToken: t.Data = parse.ReplaceMultipleWhitespace(parse.TrimWhitespace(t.Data)) if tag == svg.Style && len(t.Data) > 0 { if err := m.MinifyMimetype(defaultStyleType, w, buffer.NewReader(t.Data), defaultStyleParams); err != nil { if err != minify.ErrNotExist { return err } else if _, err := w.Write(t.Data); err != nil { return err } } } else if _, err := w.Write(t.Data); err != nil { return err } case xml.CDATAToken: if tag == svg.Style { minifyBuffer.Reset() if err := m.MinifyMimetype(defaultStyleType, minifyBuffer, buffer.NewReader(t.Text), defaultStyleParams); err == nil { t.Data = append(t.Data[:9], minifyBuffer.Bytes()...) t.Text = t.Data[9:] t.Data = append(t.Data, cdataEndBytes...) } else if err != minify.ErrNotExist { return err } } var useText bool if t.Text, useText = xml.EscapeCDATAVal(&attrByteBuffer, t.Text); useText { t.Text = parse.ReplaceMultipleWhitespace(parse.TrimWhitespace(t.Text)) if _, err := w.Write(t.Text); err != nil { return err } } else if _, err := w.Write(t.Data); err != nil { return err } case xml.StartTagPIToken: for { if t := *tb.Shift(); t.TokenType == xml.StartTagClosePIToken || t.TokenType == xml.ErrorToken { break } } case xml.StartTagToken: tag = t.Hash if containerTagMap[tag] { // skip empty containers i := 0 for { next := tb.Peek(i) i++ if next.TokenType == xml.EndTagToken && next.Hash == tag || next.TokenType == xml.StartTagCloseVoidToken || next.TokenType == xml.ErrorToken { for j := 0; j < i; j++ { tb.Shift() } break SWITCH } else if next.TokenType != xml.AttributeToken && next.TokenType != xml.StartTagCloseToken { break } } if tag == svg.G { if tb.Peek(0).TokenType == xml.StartTagCloseToken { gStack = append(gStack, false) tb.Shift() break } gStack = append(gStack, true) } } else if tag == svg.Metadata { skipTag(tb, tag) break } else if tag == svg.Line { o.shortenLine(tb, &t, p) } else if tag == svg.Rect && !o.shortenRect(tb, &t, p) { skipTag(tb, tag) break } else if tag == svg.Polygon || tag == svg.Polyline { o.shortenPoly(tb, &t, p) } if _, err := w.Write(t.Data); err != nil { return err } case xml.AttributeToken: if len(t.AttrVal) == 0 || t.Text == nil { // data is nil when attribute has been removed continue } attr := t.Hash val := t.AttrVal if n, m := parse.Dimension(val); n+m == len(val) { // TODO: inefficient, temporary measure val, _ = o.shortenDimension(val) } if attr == svg.Xml_Space && parse.Equal(val, []byte("preserve")) || tag == svg.Svg && (attr == svg.Version && parse.Equal(val, []byte("1.1")) || attr == svg.X && parse.Equal(val, []byte("0")) || attr == svg.Y && parse.Equal(val, []byte("0")) || attr == svg.Width && parse.Equal(val, []byte("100%")) || attr == svg.Height && parse.Equal(val, []byte("100%")) || attr == svg.PreserveAspectRatio && parse.Equal(val, []byte("xMidYMid meet")) || attr == svg.BaseProfile && parse.Equal(val, []byte("none")) || attr == svg.ContentScriptType && parse.Equal(val, []byte("application/ecmascript")) || attr == svg.ContentStyleType && parse.Equal(val, []byte("text/css"))) || tag == svg.Style && attr == svg.Type && parse.Equal(val, []byte("text/css")) { continue } if _, err := w.Write(spaceBytes); err != nil { return err } if _, err := w.Write(t.Text); err != nil { return err } if _, err := w.Write(isBytes); err != nil { return err } if tag == svg.Svg && attr == svg.ContentStyleType { val = minify.ContentType(val) defaultStyleType = val } else if attr == svg.Style { minifyBuffer.Reset() if err := m.MinifyMimetype(defaultStyleType, minifyBuffer, buffer.NewReader(val), defaultInlineStyleParams); err == nil { val = minifyBuffer.Bytes() } else if err != minify.ErrNotExist { return err } } else if attr == svg.D { val = p.ShortenPathData(val) } else if attr == svg.ViewBox { j := 0 newVal := val[:0] for i := 0; i < 4; i++ { if i != 0 { if j >= len(val) || val[j] != ' ' && val[j] != ',' { newVal = append(newVal, val[j:]...) break } newVal = append(newVal, ' ') j++ } if dim, n := o.shortenDimension(val[j:]); n > 0 { newVal = append(newVal, dim...) j += n } else { newVal = append(newVal, val[j:]...) break } } val = newVal } else if colorAttrMap[attr] && len(val) > 0 { parse.ToLower(val) if val[0] == '#' { if name, ok := minifyCSS.ShortenColorHex[string(val)]; ok { val = name } else if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { val[2] = val[3] val[3] = val[5] val = val[:4] } } else if hex, ok := minifyCSS.ShortenColorName[css.ToHash(val)]; ok { val = hex } else if len(val) > 5 && parse.Equal(val[:4], []byte("rgb(")) && val[len(val)-1] == ')' { // TODO: handle rgb(x, y, z) and hsl(x, y, z) } } // prefer single or double quotes depending on what occurs more often in value val = xml.EscapeAttrVal(&attrByteBuffer, val) if _, err := w.Write(val); err != nil { return err } case xml.StartTagCloseToken: next := tb.Peek(0) skipExtra := false if next.TokenType == xml.TextToken && parse.IsAllWhitespace(next.Data) { next = tb.Peek(1) skipExtra = true } if next.TokenType == xml.EndTagToken { // collapse empty tags to single void tag tb.Shift() if skipExtra { tb.Shift() } if _, err := w.Write(voidBytes); err != nil { return err } } else { if _, err := w.Write(t.Data); err != nil { return err } } case xml.StartTagCloseVoidToken: tag = 0 if _, err := w.Write(t.Data); err != nil { return err } case xml.EndTagToken: tag = 0 if t.Hash == svg.G && len(gStack) > 0 { if !gStack[len(gStack)-1] { gStack = gStack[:len(gStack)-1] break } gStack = gStack[:len(gStack)-1] } if len(t.Data) > 3+len(t.Text) { t.Data[2+len(t.Text)] = '>' t.Data = t.Data[:3+len(t.Text)] } if _, err := w.Write(t.Data); err != nil { return err } } } }
func (c *cssMinifier) minifyDeclaration(property []byte, values []css.Token) error { if len(values) == 0 { return nil } prop := css.ToHash(property) inProgid := false for i, value := range values { if inProgid { if value.TokenType == css.FunctionToken { inProgid = false } continue } else if value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Progid { inProgid = true continue } value.TokenType, value.Data = c.shortenToken(prop, value.TokenType, value.Data) if prop == css.Font || prop == css.Font_Family || prop == css.Font_Weight { if value.TokenType == css.IdentToken && (prop == css.Font || prop == css.Font_Weight) { val := css.ToHash(value.Data) if val == css.Normal && prop == css.Font_Weight { // normal could also be specified for font-variant, not just font-weight value.TokenType = css.NumberToken value.Data = []byte("400") } else if val == css.Bold { value.TokenType = css.NumberToken value.Data = []byte("700") } } else if value.TokenType == css.StringToken && (prop == css.Font || prop == css.Font_Family) && len(value.Data) > 2 { unquote := true parse.ToLower(value.Data) s := value.Data[1 : len(value.Data)-1] if len(s) > 0 { for _, split := range bytes.Split(s, spaceBytes) { val := css.ToHash(split) // if len is zero, it contains two consecutive spaces if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default || len(split) == 0 || !css.IsIdent(split) { unquote = false break } } } if unquote { value.Data = s } } } else if prop == css.Outline || prop == css.Border || prop == css.Border_Bottom || prop == css.Border_Left || prop == css.Border_Right || prop == css.Border_Top { if css.ToHash(value.Data) == css.None { value.TokenType = css.NumberToken value.Data = zeroBytes } } values[i].TokenType, values[i].Data = value.TokenType, value.Data } important := false if len(values) > 2 && values[len(values)-2].TokenType == css.DelimToken && values[len(values)-2].Data[0] == '!' && css.ToHash(values[len(values)-1].Data) == css.Important { values = values[:len(values)-2] important = true } if len(values) == 1 { if prop == css.Background && css.ToHash(values[0].Data) == css.None { values[0].Data = backgroundNoneBytes } else if bytes.Equal(property, msfilterBytes) { alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=") if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) { values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...) } } } else { if prop == css.Margin || prop == css.Padding || prop == css.Border_Width { if (values[0].TokenType == css.NumberToken || values[0].TokenType == css.DimensionToken || values[0].TokenType == css.PercentageToken) && (len(values)+1)%2 == 0 { valid := true for i := 1; i < len(values); i += 2 { if values[i].TokenType != css.WhitespaceToken || values[i+1].TokenType != css.NumberToken && values[i+1].TokenType != css.DimensionToken && values[i+1].TokenType != css.PercentageToken { valid = false break } } if valid { n := (len(values) + 1) / 2 if n == 2 { if bytes.Equal(values[0].Data, values[2].Data) { values = values[:1] } } else if n == 3 { if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) { values = values[:1] } else if bytes.Equal(values[0].Data, values[4].Data) { values = values[:3] } } else if n == 4 { if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[0].Data, values[6].Data) { values = values[:1] } else if bytes.Equal(values[0].Data, values[4].Data) && bytes.Equal(values[2].Data, values[6].Data) { values = values[:3] } else if bytes.Equal(values[2].Data, values[6].Data) { values = values[:5] } } } } } else if prop == css.Filter && len(values) == 11 { if bytes.Equal(values[0].Data, []byte("progid")) && values[1].TokenType == css.ColonToken && bytes.Equal(values[2].Data, []byte("DXImageTransform")) && values[3].Data[0] == '.' && bytes.Equal(values[4].Data, []byte("Microsoft")) && values[5].Data[0] == '.' && bytes.Equal(values[6].Data, []byte("Alpha(")) && bytes.Equal(parse.ToLower(values[7].Data), []byte("opacity")) && values[8].Data[0] == '=' && values[10].Data[0] == ')' { values = values[6:] values[0].Data = []byte("alpha(") } } } for i := 0; i < len(values); i++ { if values[i].TokenType == css.FunctionToken { n, err := c.minifyFunction(values[i:]) if err != nil { return err } i += n - 1 } else if _, err := c.w.Write(values[i].Data); err != nil { return err } } if important { if _, err := c.w.Write([]byte("!important")); err != nil { return err } } return nil }
// Minify minifies SVG data, it reads from r and writes to w. func Minify(m minify.Minifier, _ string, w io.Writer, r io.Reader) error { var tag svg.Hash attrMinifyBuffer := buffer.NewWriter(make([]byte, 0, 64)) attrByteBuffer := make([]byte, 0, 64) l := xml.NewLexer(r) tb := xml.NewTokenBuffer(l) for { t := *tb.Shift() if t.TokenType == xml.CDATAToken { var useCDATA bool if t.Data, useCDATA = xml.EscapeCDATAVal(&attrByteBuffer, t.Data); !useCDATA { t.TokenType = xml.TextToken } } SWITCH: switch t.TokenType { case xml.ErrorToken: if l.Err() == io.EOF { return nil } return l.Err() case xml.TextToken: t.Data = parse.ReplaceMultiple(parse.Trim(t.Data, parse.IsWhitespace), parse.IsWhitespace, ' ') if tag == svg.Style && len(t.Data) > 0 { if err := m.Minify("text/css", w, buffer.NewReader(t.Data)); err != nil { if err == minify.ErrNotExist { // no minifier, write the original if _, err := w.Write(t.Data); err != nil { return err } } else { return err } } } else if _, err := w.Write(t.Data); err != nil { return err } case xml.CDATAToken: if _, err := w.Write(cdataStartBytes); err != nil { return err } t.Data = parse.ReplaceMultiple(parse.Trim(t.Data, parse.IsWhitespace), parse.IsWhitespace, ' ') if tag == svg.Style && len(t.Data) > 0 { if err := m.Minify("text/css", w, buffer.NewReader(t.Data)); err != nil { if err == minify.ErrNotExist { // no minifier, write the original if _, err := w.Write(t.Data); err != nil { return err } } else { return err } } } else if _, err := w.Write(t.Data); err != nil { return err } if _, err := w.Write(cdataEndBytes); err != nil { return err } case xml.StartTagPIToken: for { if t := *tb.Shift(); t.TokenType == xml.StartTagClosePIToken || t.TokenType == xml.ErrorToken { break } } case xml.StartTagToken: tag = svg.ToHash(t.Data) if containerTagMap[tag] { // skip empty containers i := 0 for { next := tb.Peek(i) i++ if next.TokenType == xml.EndTagToken && svg.ToHash(next.Data) == tag || next.TokenType == xml.StartTagCloseVoidToken || next.TokenType == xml.ErrorToken { for j := 0; j < i; j++ { tb.Shift() } break SWITCH } else if next.TokenType != xml.AttributeToken && next.TokenType != xml.StartTagCloseToken { break } } } else if tag == svg.Metadata { for { if t := *tb.Shift(); t.TokenType == xml.EndTagToken && svg.ToHash(t.Data) == tag || t.TokenType == xml.StartTagCloseVoidToken || t.TokenType == xml.ErrorToken { break } } break } else if tag == svg.Line || tag == svg.Rect { // TODO: shape2path also for polygon and polyline // x1, y1, x2, y2 float64 := 0, 0, 0, 0 // valid := true // i := 0 // for { // next := tb.Peek(i) // i++ // if next.TokenType != xml.AttributeToken { // break // } // v *int // attr := svg.ToHash(next.Data) // if tag == svg.Line { // if attr == svg.X1 { // v = &x1 // } else if attr == svg.Y1 { // v = &y1 // } else if attr == svg.X2 { // v = &x2 // } else if attr == svg.Y2 { // v = &Y2 // } else { // continue // } // } else if attr == svg.X { // rect // v = &x1 // } else if attr == svg.Y { // v = &y1 // } else if attr == svg.Width { // v = &x2 // } else if attr == svg.Height { // v = &Y2 // } else if attr == svg.Rx || attr == svg.Ry { // valid = false // break // } else { // continue // } // } // if valid { // t.Data = pathBytes // } } if _, err := w.Write(ltBytes); err != nil { return err } if _, err := w.Write(t.Data); err != nil { return err } case xml.AttributeToken: if len(t.AttrVal) < 2 { continue } attr := svg.ToHash(t.Data) val := parse.ReplaceMultiple(parse.Trim(t.AttrVal[1:len(t.AttrVal)-1], parse.IsWhitespace), parse.IsWhitespace, ' ') if tag == svg.Svg && attr == svg.Version { continue } if _, err := w.Write(spaceBytes); err != nil { return err } if _, err := w.Write(t.Data); err != nil { return err } if _, err := w.Write(isBytes); err != nil { return err } if attr == svg.Style { attrMinifyBuffer.Reset() if m.Minify("text/css;inline=1", attrMinifyBuffer, buffer.NewReader(val)) == nil { val = attrMinifyBuffer.Bytes() } } else if attr == svg.D { val = shortenPathData(val) } else if attr == svg.ViewBox { j := 0 newVal := val[:0] for i := 0; i < 4; i++ { if i != 0 { if j >= len(val) || val[j] != ' ' && val[j] != ',' { newVal = append(newVal, val[j:]...) break } newVal = append(newVal, ' ') j++ } if dim, n := shortenDimension(val[j:]); n > 0 { newVal = append(newVal, dim...) j += n } else { newVal = append(newVal, val[j:]...) break } } val = newVal } else if colorAttrMap[attr] && len(val) > 0 { parse.ToLower(val) if val[0] == '#' { if name, ok := minifyCSS.ShortenColorHex[string(val)]; ok { val = name } else if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] { val[2] = val[3] val[3] = val[5] val = val[:4] } } else if hex, ok := minifyCSS.ShortenColorName[css.ToHash(val)]; ok { val = hex } else if len(val) > 5 && parse.Equal(val[:4], []byte("rgb(")) && val[len(val)-1] == ')' { // TODO: handle rgb(x, y, z) and hsl(x, y, z) } } else if dim, n := shortenDimension(val); n == len(val) { val = dim } // prefer single or double quotes depending on what occurs more often in value val = xml.EscapeAttrVal(&attrByteBuffer, val) if _, err := w.Write(val); err != nil { return err } case xml.StartTagCloseToken: next := tb.Peek(0) skipExtra := false if next.TokenType == xml.TextToken && parse.IsAllWhitespace(next.Data) { next = tb.Peek(1) skipExtra = true } if next.TokenType == xml.EndTagToken { // collapse empty tags to single void tag tb.Shift() if skipExtra { tb.Shift() } if _, err := w.Write(voidBytes); err != nil { return err } } else { if _, err := w.Write(gtBytes); err != nil { return err } } case xml.StartTagCloseVoidToken: if _, err := w.Write(voidBytes); err != nil { return err } case xml.EndTagToken: if _, err := w.Write(endBytes); err != nil { return err } if _, err := w.Write(t.Data); err != nil { return err } if _, err := w.Write(gtBytes); err != nil { return err } } } }