gitlab.science.ru.nl/irma/gomobile.git@v0.0.0-20200320223732-da812b634d1f/cmd/gomobile/binary_xml.go (about)

     1  // Copyright 2015 The Go Authors.  All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"encoding/xml"
     9  	"fmt"
    10  	"io"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"unicode/utf16"
    15  )
    16  
    17  // binaryXML converts XML into Android's undocumented binary XML format.
    18  //
    19  // The best source of information on this format seems to be the source code
    20  // in AOSP frameworks-base. Android "resource" types seem to describe the
    21  // encoded bytes, in particular:
    22  //
    23  //	ResChunk_header
    24  //	ResStringPool_header
    25  //	ResXMLTree_node
    26  //
    27  // These are defined in:
    28  //
    29  //	https://android.googlesource.com/platform/frameworks/base/+/master/include/androidfw/ResourceTypes.h
    30  //
    31  // The rough format of the file is a resource chunk containing a sequence of
    32  // chunks. Each chunk is made up of a header and a body. The header begins with
    33  // the contents of the ResChunk_header struct, which includes the size of both
    34  // the header and the body.
    35  //
    36  // Both the header and body are 4-byte aligned.
    37  //
    38  // Values are encoded as little-endian.
    39  //
    40  // The android source code for encoding is done in the aapt tool. Its source
    41  // code lives in AOSP:
    42  //
    43  //	https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt
    44  //
    45  // A sample layout:
    46  //
    47  //	File Header (ResChunk_header, type XML)
    48  //	Chunk: String Pool (type STRING_POOL)
    49  //	Sequence of strings, each with the format:
    50  //		uint16 length
    51  //		uint16 extended_length -- only if top bit set on length
    52  //		UTF-16LE string
    53  //		two zero bytes
    54  //	Resource Map
    55  //		The [i]th 4-byte entry in the resource map corresponds with
    56  //		the [i]th string from the string pool. The 4-bytes are a
    57  //		Resource ID constant defined:
    58  //			http://developer.android.com/reference/android/R.attr.html
    59  //		This appears to be a way to map strings onto enum values.
    60  //	Chunk: Namespace Start (ResXMLTree_node; ResXMLTree_namespaceExt)
    61  //	Chunk: Element Start
    62  //		ResXMLTree_node
    63  //		ResXMLTree_attrExt
    64  //		ResXMLTree_attribute (repeated attributeCount times)
    65  //	Chunk: Element End
    66  //		(ResXMLTree_node; ResXMLTree_endElementExt)
    67  //	...
    68  //	Chunk: Namespace End
    69  func binaryXML(r io.Reader) ([]byte, error) {
    70  	lr := &lineReader{r: r}
    71  	d := xml.NewDecoder(lr)
    72  
    73  	pool := new(binStringPool)
    74  	depth := 0
    75  	elements := []chunk{}
    76  	namespaceEnds := make(map[int][]binEndNamespace)
    77  
    78  	for {
    79  		line := lr.line(d.InputOffset())
    80  		tok, err := d.Token()
    81  		if err != nil {
    82  			if err == io.EOF {
    83  				break
    84  			}
    85  			return nil, err
    86  		}
    87  		switch tok := tok.(type) {
    88  		case xml.StartElement:
    89  			// uses-sdk is synthesized by the writer, disallow declaration in manifest.
    90  			if tok.Name.Local == "uses-sdk" {
    91  				return nil, fmt.Errorf("unsupported manifest tag <uses-sdk .../>")
    92  			} else if tok.Name.Local == "application" {
    93  				// synthesize <uses-sdk/> before handling <application> token
    94  				attr := xml.Attr{
    95  					Name: xml.Name{
    96  						Space: "http://schemas.android.com/apk/res/android",
    97  						Local: "minSdkVersion",
    98  					},
    99  					Value: "15",
   100  				}
   101  				ba, err := pool.getAttr(attr)
   102  				if err != nil {
   103  					return nil, fmt.Errorf("failed to synthesize attr minSdkVersion=\"15\"")
   104  				}
   105  				elements = append(elements,
   106  					&binStartElement{
   107  						line: line - 1, // current testing strategy is not friendly to synthesized tags, -1 for would-be location
   108  						ns:   pool.getNS(""),
   109  						name: pool.get("uses-sdk"),
   110  						attr: []*binAttr{ba},
   111  					},
   112  					&binEndElement{
   113  						line: line - 1,
   114  						ns:   pool.getNS(""),
   115  						name: pool.get("uses-sdk"),
   116  					})
   117  			}
   118  			// Intercept namespace definitions.
   119  			var attr []*binAttr
   120  			for _, a := range tok.Attr {
   121  				if a.Name.Space == "xmlns" {
   122  					elements = append(elements, binStartNamespace{
   123  						line:   line,
   124  						prefix: pool.get(a.Name.Local),
   125  						url:    pool.get(a.Value),
   126  					})
   127  					namespaceEnds[depth] = append([]binEndNamespace{{
   128  						line:   line,
   129  						prefix: pool.get(a.Name.Local),
   130  						url:    pool.get(a.Value),
   131  					}}, namespaceEnds[depth]...)
   132  					continue
   133  				}
   134  				ba, err := pool.getAttr(a)
   135  				if err != nil {
   136  					return nil, fmt.Errorf("%d: %s: %v", line, a.Name.Local, err)
   137  				}
   138  				attr = append(attr, ba)
   139  			}
   140  
   141  			depth++
   142  			elements = append(elements, &binStartElement{
   143  				line: line,
   144  				ns:   pool.getNS(tok.Name.Space),
   145  				name: pool.get(tok.Name.Local),
   146  				attr: attr,
   147  			})
   148  		case xml.EndElement:
   149  			elements = append(elements, &binEndElement{
   150  				line: line,
   151  				ns:   pool.getNS(tok.Name.Space),
   152  				name: pool.get(tok.Name.Local),
   153  			})
   154  			depth--
   155  			if nsEnds := namespaceEnds[depth]; len(nsEnds) > 0 {
   156  				delete(namespaceEnds, depth)
   157  				for _, nsEnd := range nsEnds {
   158  					elements = append(elements, nsEnd)
   159  				}
   160  			}
   161  		case xml.CharData:
   162  			// The aapt tool appears to "compact" leading and
   163  			// trailing whitepsace. See XMLNode::removeWhitespace in
   164  			// https://android.googlesource.com/platform/frameworks/base.git/+/master/tools/aapt/XMLNode.cpp
   165  			if len(tok) == 0 {
   166  				continue
   167  			}
   168  			start, end := 0, len(tok)
   169  			for start < len(tok) && isSpace(tok[start]) {
   170  				start++
   171  			}
   172  			for end > start && isSpace(tok[end-1]) {
   173  				end--
   174  			}
   175  			if start == end {
   176  				continue // all whitespace, skip it
   177  			}
   178  
   179  			// Preserve one character of whitespace.
   180  			if start > 0 {
   181  				start--
   182  			}
   183  			if end < len(tok) {
   184  				end++
   185  			}
   186  
   187  			elements = append(elements, &binCharData{
   188  				line: line,
   189  				data: pool.get(string(tok[start:end])),
   190  			})
   191  		case xml.Comment:
   192  			// Ignored by Anroid Binary XML format.
   193  		case xml.ProcInst:
   194  			// Ignored by Anroid Binary XML format?
   195  		case xml.Directive:
   196  			// Ignored by Anroid Binary XML format.
   197  		default:
   198  			return nil, fmt.Errorf("apk: unexpected token: %v (%T)", tok, tok)
   199  		}
   200  	}
   201  
   202  	sortPool(pool)
   203  	for _, e := range elements {
   204  		if e, ok := e.(*binStartElement); ok {
   205  			sortAttr(e, pool)
   206  		}
   207  	}
   208  
   209  	resMap := &binResMap{pool}
   210  
   211  	size := 8 + pool.size() + resMap.size()
   212  	for _, e := range elements {
   213  		size += e.size()
   214  	}
   215  
   216  	b := make([]byte, 0, size)
   217  	b = appendHeader(b, headerXML, size)
   218  	b = pool.append(b)
   219  	b = resMap.append(b)
   220  	for _, e := range elements {
   221  		b = e.append(b)
   222  	}
   223  
   224  	return b, nil
   225  }
   226  
   227  func isSpace(b byte) bool {
   228  	switch b {
   229  	case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0:
   230  		return true
   231  	}
   232  	return false
   233  }
   234  
   235  type headerType uint16
   236  
   237  const (
   238  	headerXML            headerType = 0x0003
   239  	headerStringPool                = 0x0001
   240  	headerResourceMap               = 0x0180
   241  	headerStartNamespace            = 0x0100
   242  	headerEndNamespace              = 0x0101
   243  	headerStartElement              = 0x0102
   244  	headerEndElement                = 0x0103
   245  	headerCharData                  = 0x0104
   246  )
   247  
   248  func appendU16(b []byte, v uint16) []byte {
   249  	return append(b, byte(v), byte(v>>8))
   250  }
   251  
   252  func appendU32(b []byte, v uint32) []byte {
   253  	return append(b, byte(v), byte(v>>8), byte(v>>16), byte(v>>24))
   254  }
   255  
   256  func appendHeader(b []byte, typ headerType, size int) []byte {
   257  	b = appendU16(b, uint16(typ))
   258  	b = appendU16(b, 8)
   259  	b = appendU16(b, uint16(size))
   260  	b = appendU16(b, 0)
   261  	return b
   262  }
   263  
   264  // Attributes of the form android:key are mapped to resource IDs, which are
   265  // embedded into the Binary XML format.
   266  //
   267  // http://developer.android.com/reference/android/R.attr.html
   268  var resourceCodes = map[string]uint32{
   269  	"versionCode":       0x0101021b,
   270  	"versionName":       0x0101021c,
   271  	"minSdkVersion":     0x0101020c,
   272  	"windowFullscreen":  0x0101020d,
   273  	"theme":             0x01010000,
   274  	"label":             0x01010001,
   275  	"hasCode":           0x0101000c,
   276  	"debuggable":        0x0101000f,
   277  	"name":              0x01010003,
   278  	"screenOrientation": 0x0101001e,
   279  	"configChanges":     0x0101001f,
   280  	"value":             0x01010024,
   281  }
   282  
   283  // http://developer.android.com/reference/android/R.attr.html#configChanges
   284  var configChanges = map[string]uint32{
   285  	"mcc":                0x0001,
   286  	"mnc":                0x0002,
   287  	"locale":             0x0004,
   288  	"touchscreen":        0x0008,
   289  	"keyboard":           0x0010,
   290  	"keyboardHidden":     0x0020,
   291  	"navigation":         0x0040,
   292  	"orientation":        0x0080,
   293  	"screenLayout":       0x0100,
   294  	"uiMode":             0x0200,
   295  	"screenSize":         0x0400,
   296  	"smallestScreenSize": 0x0800,
   297  	"layoutDirection":    0x2000,
   298  	"fontScale":          0x40000000,
   299  }
   300  
   301  // http://developer.android.com/reference/android/R.attr.html#screenOrientation
   302  var screenOrientation = map[string]int{
   303  	"unspecified":      -1,
   304  	"landscape":        0,
   305  	"portrait":         1,
   306  	"user":             2,
   307  	"behind":           3,
   308  	"sensor":           4,
   309  	"nosensor":         5,
   310  	"sensorLandscape":  6,
   311  	"sensorPortrait":   7,
   312  	"reverseLandscape": 8,
   313  	"reversePortrait":  9,
   314  	"fullSensor":       10,
   315  	"userLandscape":    11,
   316  	"userPortrait":     12,
   317  	"fullUser":         13,
   318  	"locked":           14,
   319  }
   320  
   321  // reference is an alias used to write out correct type in bin.
   322  type reference uint32
   323  
   324  // http://developer.android.com/reference/android/R.style.html
   325  var theme = map[string]reference{
   326  	"Theme":                                   0x01030005,
   327  	"Theme_NoTitleBar":                        0x01030006,
   328  	"Theme_NoTitleBar_Fullscreen":             0x01030007,
   329  	"Theme_Black":                             0x01030008,
   330  	"Theme_Black_NoTitleBar":                  0x01030009,
   331  	"Theme_Black_NoTitleBar_Fullscreen":       0x0103000a,
   332  	"Theme_Light":                             0x0103000c,
   333  	"Theme_Light_NoTitleBar":                  0x0103000d,
   334  	"Theme_Light_NoTitleBar_Fullscreen":       0x0103000e,
   335  	"Theme_Translucent":                       0x0103000f,
   336  	"Theme_Translucent_NoTitleBar":            0x01030010,
   337  	"Theme_Translucent_NoTitleBar_Fullscreen": 0x01030011,
   338  }
   339  
   340  type lineReader struct {
   341  	off   int64
   342  	lines []int64
   343  	r     io.Reader
   344  }
   345  
   346  func (r *lineReader) Read(p []byte) (n int, err error) {
   347  	n, err = r.r.Read(p)
   348  	for i := 0; i < n; i++ {
   349  		if p[i] == '\n' {
   350  			r.lines = append(r.lines, r.off+int64(i))
   351  		}
   352  	}
   353  	r.off += int64(n)
   354  	return n, err
   355  }
   356  
   357  func (r *lineReader) line(pos int64) int {
   358  	return sort.Search(len(r.lines), func(i int) bool {
   359  		return pos < r.lines[i]
   360  	}) + 1
   361  }
   362  
   363  type bstring struct {
   364  	ind uint32
   365  	str string
   366  	enc []byte // 2-byte length, utf16le, 2-byte zero
   367  }
   368  
   369  type chunk interface {
   370  	size() int
   371  	append([]byte) []byte
   372  }
   373  
   374  type binResMap struct {
   375  	pool *binStringPool
   376  }
   377  
   378  func (p *binResMap) append(b []byte) []byte {
   379  	b = appendHeader(b, headerResourceMap, p.size())
   380  	for _, bstr := range p.pool.s {
   381  		c, ok := resourceCodes[bstr.str]
   382  		if !ok {
   383  			break
   384  		}
   385  		b = appendU32(b, c)
   386  	}
   387  	return b
   388  }
   389  
   390  func (p *binResMap) size() int {
   391  	count := 0
   392  	for _, bstr := range p.pool.s {
   393  		if _, ok := resourceCodes[bstr.str]; !ok {
   394  			break
   395  		}
   396  		count++
   397  	}
   398  	return 8 + 4*count
   399  }
   400  
   401  type binStringPool struct {
   402  	s []*bstring
   403  	m map[string]*bstring
   404  }
   405  
   406  func (p *binStringPool) get(str string) *bstring {
   407  	if p.m == nil {
   408  		p.m = make(map[string]*bstring)
   409  	}
   410  	res := p.m[str]
   411  	if res != nil {
   412  		return res
   413  	}
   414  	res = &bstring{
   415  		ind: uint32(len(p.s)),
   416  		str: str,
   417  	}
   418  	p.s = append(p.s, res)
   419  	p.m[str] = res
   420  
   421  	if len(str)>>16 > 0 {
   422  		panic(fmt.Sprintf("string lengths over 1<<15 not yet supported, got len %d for string that starts %q", len(str), str[:100]))
   423  	}
   424  	strUTF16 := utf16.Encode([]rune(str))
   425  	res.enc = appendU16(nil, uint16(len(strUTF16)))
   426  	for _, w := range strUTF16 {
   427  		res.enc = appendU16(res.enc, w)
   428  	}
   429  	res.enc = appendU16(res.enc, 0)
   430  	return res
   431  }
   432  
   433  func (p *binStringPool) getNS(ns string) *bstring {
   434  	if ns == "" {
   435  		// Register empty string for inclusion in output (like aapt),
   436  		// but do not reference it from namespace elements.
   437  		p.get("")
   438  		return nil
   439  	}
   440  	return p.get(ns)
   441  }
   442  
   443  func (p *binStringPool) getAttr(attr xml.Attr) (*binAttr, error) {
   444  	a := &binAttr{
   445  		ns:   p.getNS(attr.Name.Space),
   446  		name: p.get(attr.Name.Local),
   447  	}
   448  	if attr.Name.Space != "http://schemas.android.com/apk/res/android" {
   449  		a.data = p.get(attr.Value)
   450  		return a, nil
   451  	}
   452  
   453  	// Some android attributes have interesting values.
   454  	switch attr.Name.Local {
   455  	case "versionCode", "minSdkVersion":
   456  		v, err := strconv.Atoi(attr.Value)
   457  		if err != nil {
   458  			return nil, err
   459  		}
   460  		a.data = int(v)
   461  	case "hasCode", "debuggable":
   462  		v, err := strconv.ParseBool(attr.Value)
   463  		if err != nil {
   464  			return nil, err
   465  		}
   466  		a.data = v
   467  	case "configChanges":
   468  		v := uint32(0)
   469  		for _, c := range strings.Split(attr.Value, "|") {
   470  			v |= configChanges[c]
   471  		}
   472  		a.data = v
   473  	case "screenOrientation":
   474  		v := 0
   475  		for _, c := range strings.Split(attr.Value, "|") {
   476  			v |= screenOrientation[c]
   477  		}
   478  		a.data = v
   479  	case "theme":
   480  		v := attr.Value
   481  		// strip prefix if present as only platform themes are supported
   482  		if idx := strings.Index(attr.Value, "/"); idx != -1 {
   483  			v = v[idx+1:]
   484  		}
   485  		v = strings.Replace(v, ".", "_", -1)
   486  		a.data = theme[v]
   487  	default:
   488  		a.data = p.get(attr.Value)
   489  	}
   490  	return a, nil
   491  }
   492  
   493  const stringPoolPreamble = 0 +
   494  	8 + // chunk header
   495  	4 + // string count
   496  	4 + // style count
   497  	4 + // flags
   498  	4 + // strings start
   499  	4 + // styles start
   500  	0
   501  
   502  func (p *binStringPool) unpaddedSize() int {
   503  	strLens := 0
   504  	for _, s := range p.s {
   505  		strLens += len(s.enc)
   506  	}
   507  	return stringPoolPreamble + 4*len(p.s) + strLens
   508  }
   509  
   510  func (p *binStringPool) size() int {
   511  	size := p.unpaddedSize()
   512  	size += size % 0x04
   513  	return size
   514  }
   515  
   516  // overloaded for testing.
   517  var (
   518  	sortPool = func(p *binStringPool) {
   519  		sort.Sort(p)
   520  
   521  		// Move resourceCodes to the front.
   522  		s := make([]*bstring, 0)
   523  		m := make(map[string]*bstring)
   524  		for str := range resourceCodes {
   525  			bstr := p.m[str]
   526  			if bstr == nil {
   527  				continue
   528  			}
   529  			bstr.ind = uint32(len(s))
   530  			s = append(s, bstr)
   531  			m[str] = bstr
   532  			delete(p.m, str)
   533  		}
   534  		for _, bstr := range p.m {
   535  			bstr.ind = uint32(len(s))
   536  			s = append(s, bstr)
   537  		}
   538  		p.s = s
   539  		p.m = m
   540  	}
   541  	sortAttr = func(e *binStartElement, p *binStringPool) {}
   542  )
   543  
   544  func (b *binStringPool) Len() int           { return len(b.s) }
   545  func (b *binStringPool) Less(i, j int) bool { return b.s[i].str < b.s[j].str }
   546  func (b *binStringPool) Swap(i, j int) {
   547  	b.s[i], b.s[j] = b.s[j], b.s[i]
   548  	b.s[i].ind, b.s[j].ind = b.s[j].ind, b.s[i].ind
   549  }
   550  
   551  func (p *binStringPool) append(b []byte) []byte {
   552  	stringsStart := uint32(stringPoolPreamble + 4*len(p.s))
   553  	b = appendU16(b, uint16(headerStringPool))
   554  	b = appendU16(b, 0x1c) // chunk header size
   555  	b = appendU16(b, uint16(p.size()))
   556  	b = appendU16(b, 0)
   557  	b = appendU32(b, uint32(len(p.s)))
   558  	b = appendU32(b, 0) // style count
   559  	b = appendU32(b, 0) // flags
   560  	b = appendU32(b, stringsStart)
   561  	b = appendU32(b, 0) // styles start
   562  
   563  	off := 0
   564  	for _, bstr := range p.s {
   565  		b = appendU32(b, uint32(off))
   566  		off += len(bstr.enc)
   567  	}
   568  	for _, bstr := range p.s {
   569  		b = append(b, bstr.enc...)
   570  	}
   571  
   572  	for i := p.unpaddedSize() % 0x04; i > 0; i-- {
   573  		b = append(b, 0)
   574  	}
   575  	return b
   576  }
   577  
   578  type binStartElement struct {
   579  	line int
   580  	ns   *bstring
   581  	name *bstring
   582  	attr []*binAttr
   583  }
   584  
   585  func (e *binStartElement) size() int {
   586  	return 8 + // chunk header
   587  		4 + // line number
   588  		4 + // comment
   589  		4 + // ns
   590  		4 + // name
   591  		2 + 2 + 2 + // attribute start, size, count
   592  		2 + 2 + 2 + // id/class/style index
   593  		len(e.attr)*(4+4+4+4+4)
   594  }
   595  
   596  func (e *binStartElement) append(b []byte) []byte {
   597  	b = appendU16(b, uint16(headerStartElement))
   598  	b = appendU16(b, 0x10) // chunk header size
   599  	b = appendU16(b, uint16(e.size()))
   600  	b = appendU16(b, 0)
   601  	b = appendU32(b, uint32(e.line))
   602  	b = appendU32(b, 0xffffffff) // comment
   603  	if e.ns == nil {
   604  		b = appendU32(b, 0xffffffff)
   605  	} else {
   606  		b = appendU32(b, e.ns.ind)
   607  	}
   608  	b = appendU32(b, e.name.ind)
   609  	b = appendU16(b, 0x14) // attribute start
   610  	b = appendU16(b, 0x14) // attribute size
   611  	b = appendU16(b, uint16(len(e.attr)))
   612  	b = appendU16(b, 0) // ID index (none)
   613  	b = appendU16(b, 0) // class index (none)
   614  	b = appendU16(b, 0) // style index (none)
   615  	for _, a := range e.attr {
   616  		b = a.append(b)
   617  	}
   618  	return b
   619  }
   620  
   621  type binAttr struct {
   622  	ns   *bstring
   623  	name *bstring
   624  	data interface{} // either int (INT_DEC) or *bstring (STRING)
   625  }
   626  
   627  func (a *binAttr) append(b []byte) []byte {
   628  	if a.ns != nil {
   629  		b = appendU32(b, a.ns.ind)
   630  	} else {
   631  		b = appendU32(b, 0xffffffff)
   632  	}
   633  	b = appendU32(b, a.name.ind)
   634  	switch v := a.data.(type) {
   635  	case int:
   636  		b = appendU32(b, 0xffffffff) // raw value
   637  		b = appendU16(b, 8)          // size
   638  		b = append(b, 0)             // unused padding
   639  		b = append(b, 0x10)          // INT_DEC
   640  		b = appendU32(b, uint32(v))
   641  	case bool:
   642  		b = appendU32(b, 0xffffffff) // raw value
   643  		b = appendU16(b, 8)          // size
   644  		b = append(b, 0)             // unused padding
   645  		b = append(b, 0x12)          // INT_BOOLEAN
   646  		if v {
   647  			b = appendU32(b, 0xffffffff)
   648  		} else {
   649  			b = appendU32(b, 0)
   650  		}
   651  	case uint32:
   652  		b = appendU32(b, 0xffffffff) // raw value
   653  		b = appendU16(b, 8)          // size
   654  		b = append(b, 0)             // unused padding
   655  		b = append(b, 0x11)          // INT_HEX
   656  		b = appendU32(b, uint32(v))
   657  	case reference:
   658  		b = appendU32(b, 0xffffffff) // raw value
   659  		b = appendU16(b, 8)          // size
   660  		b = append(b, 0)             // unused padding
   661  		b = append(b, 0x01)          // REFERENCE
   662  		b = appendU32(b, uint32(v))
   663  	case *bstring:
   664  		b = appendU32(b, v.ind) // raw value
   665  		b = appendU16(b, 8)     // size
   666  		b = append(b, 0)        // unused padding
   667  		b = append(b, 0x03)     // STRING
   668  		b = appendU32(b, v.ind)
   669  	default:
   670  		panic(fmt.Sprintf("unexpected attr type: %T (%v)", v, v))
   671  	}
   672  	return b
   673  }
   674  
   675  type binEndElement struct {
   676  	line int
   677  	ns   *bstring
   678  	name *bstring
   679  	attr []*binAttr
   680  }
   681  
   682  func (*binEndElement) size() int {
   683  	return 8 + // chunk header
   684  		4 + // line number
   685  		4 + // comment
   686  		4 + // ns
   687  		4 // name
   688  }
   689  
   690  func (e *binEndElement) append(b []byte) []byte {
   691  	b = appendU16(b, uint16(headerEndElement))
   692  	b = appendU16(b, 0x10) // chunk header size
   693  	b = appendU16(b, uint16(e.size()))
   694  	b = appendU16(b, 0)
   695  	b = appendU32(b, uint32(e.line))
   696  	b = appendU32(b, 0xffffffff) // comment
   697  	if e.ns == nil {
   698  		b = appendU32(b, 0xffffffff)
   699  	} else {
   700  		b = appendU32(b, e.ns.ind)
   701  	}
   702  	b = appendU32(b, e.name.ind)
   703  	return b
   704  }
   705  
   706  type binStartNamespace struct {
   707  	line   int
   708  	prefix *bstring
   709  	url    *bstring
   710  }
   711  
   712  func (binStartNamespace) size() int {
   713  	return 8 + // chunk header
   714  		4 + // line number
   715  		4 + // comment
   716  		4 + // prefix
   717  		4 // url
   718  }
   719  
   720  func (e binStartNamespace) append(b []byte) []byte {
   721  	b = appendU16(b, uint16(headerStartNamespace))
   722  	b = appendU16(b, 0x10) // chunk header size
   723  	b = appendU16(b, uint16(e.size()))
   724  	b = appendU16(b, 0)
   725  	b = appendU32(b, uint32(e.line))
   726  	b = appendU32(b, 0xffffffff) // comment
   727  	b = appendU32(b, e.prefix.ind)
   728  	b = appendU32(b, e.url.ind)
   729  	return b
   730  }
   731  
   732  type binEndNamespace struct {
   733  	line   int
   734  	prefix *bstring
   735  	url    *bstring
   736  }
   737  
   738  func (binEndNamespace) size() int {
   739  	return 8 + // chunk header
   740  		4 + // line number
   741  		4 + // comment
   742  		4 + // prefix
   743  		4 // url
   744  }
   745  
   746  func (e binEndNamespace) append(b []byte) []byte {
   747  	b = appendU16(b, uint16(headerEndNamespace))
   748  	b = appendU16(b, 0x10) // chunk header size
   749  	b = appendU16(b, uint16(e.size()))
   750  	b = appendU16(b, 0)
   751  	b = appendU32(b, uint32(e.line))
   752  	b = appendU32(b, 0xffffffff) // comment
   753  	b = appendU32(b, e.prefix.ind)
   754  	b = appendU32(b, e.url.ind)
   755  	return b
   756  }
   757  
   758  type binCharData struct {
   759  	line int
   760  	data *bstring
   761  }
   762  
   763  func (*binCharData) size() int {
   764  	return 8 + // chunk header
   765  		4 + // line number
   766  		4 + // comment
   767  		4 + // data
   768  		8 // junk
   769  }
   770  
   771  func (e *binCharData) append(b []byte) []byte {
   772  	b = appendU16(b, uint16(headerCharData))
   773  	b = appendU16(b, 0x10) // chunk header size
   774  	b = appendU16(b, 0x1c) // size
   775  	b = appendU16(b, 0)
   776  	b = appendU32(b, uint32(e.line))
   777  	b = appendU32(b, 0xffffffff) // comment
   778  	b = appendU32(b, e.data.ind)
   779  	b = appendU16(b, 0x08)
   780  	b = appendU16(b, 0)
   781  	b = appendU16(b, 0)
   782  	b = appendU16(b, 0)
   783  	return b
   784  }