From 002f07f8bed58bc1e59b8a319522ca93a613a26b Mon Sep 17 00:00:00 2001 From: Daniel Skinner Date: Thu, 28 Jan 2016 12:46:43 -0600 Subject: [PATCH] internal/binres: sort element attributes Change-Id: I3ff6b98aa1c729600f154272b8250f673ae2778f Reviewed-on: https://go-review.googlesource.com/19040 Reviewed-by: David Crawshaw --- internal/binres/binres.go | 239 +++++++++++++++---------- internal/binres/testdata/bootstrap.bin | Bin 2204 -> 2520 bytes internal/binres/testdata/bootstrap.xml | 6 + 3 files changed, 146 insertions(+), 99 deletions(-) diff --git a/internal/binres/binres.go b/internal/binres/binres.go index f09236e..682c378 100644 --- a/internal/binres/binres.go +++ b/internal/binres/binres.go @@ -176,27 +176,12 @@ const ( toolsSchema = "http://schemas.android.com/tools" ) -type xattr struct { - xml.Attr - bool -} - -// TODO remove once table can be sorted correctly -type xnode struct { - line uint32 - name xml.Name - attrs []xattr - cdata []string -} - func UnmarshalXML(r io.Reader) (*XML, error) { tbl, err := OpenTable() if err != nil { return nil, err } - var nodes []xnode - lr := &lineReader{r: r} dec := xml.NewDecoder(lr) bx := new(XML) @@ -217,8 +202,6 @@ func UnmarshalXML(r io.Reader) (*XML, error) { switch tkn := tkn.(type) { case xml.StartElement: - nodes = append(nodes, xnode{line: uint32(line), name: tkn.Name}) - el := &Element{ NodeHeader: NodeHeader{ LineNumber: uint32(line), @@ -283,13 +266,9 @@ func UnmarshalXML(r io.Reader) (*XML, error) { } el.attrs = append(el.attrs, nattr) - att := xattr{attr, false} if attr.Name.Space == "" { // TODO it's unclear how to query these switch attr.Name.Local { - case "package", "platformBuildVersionName": - nattr.RawValue = pool.ref(attr.Value) - nattr.TypedValue.Type = DataString case "platformBuildVersionCode": nattr.TypedValue.Type = DataIntDec i, err := strconv.Atoi(attr.Value) @@ -297,8 +276,9 @@ func UnmarshalXML(r io.Reader) (*XML, error) { return nil, err } nattr.TypedValue.Value = uint32(i) - default: - return nil, fmt.Errorf("unknown attribute lacking namespace %q", attr.Name.Local) + default: // "package", "platformBuildVersionName", and any invalid + nattr.RawValue = pool.ref(attr.Value) + nattr.TypedValue.Type = DataString } } else { // get type spec and value data type @@ -324,7 +304,6 @@ func UnmarshalXML(r io.Reader) (*XML, error) { switch t { case DataString, DataAttribute, DataType(0x3e): // TODO identify 0x3e, in bootstrap.xml this is the native lib name - att.bool = true nattr.RawValue = pool.ref(attr.Value) nattr.TypedValue.Type = DataString case DataIntBool, DataType(0x08): @@ -399,11 +378,24 @@ func UnmarshalXML(r io.Reader) (*XML, error) { } } } - nodes[len(nodes)-1].attrs = append(nodes[len(nodes)-1].attrs, att) } case xml.CharData: if s := poolTrim(string(tkn)); s != "" { - nodes[len(nodes)-1].cdata = append(nodes[len(nodes)-1].cdata, s) + cdt := &CharData{ + NodeHeader: NodeHeader{ + LineNumber: uint32(line), + Comment: NoEntry, + }, + RawData: pool.ref(s), + } + el := bx.stack[len(bx.stack)-1] + if el.head == nil { + el.head = cdt + } else if el.tail == nil { + el.tail = cdt + } else { + return nil, fmt.Errorf("element head and tail already contain chardata") + } } case xml.EndElement: n := len(bx.stack) @@ -420,22 +412,6 @@ func UnmarshalXML(r io.Reader) (*XML, error) { } } - bvc := xml.Attr{ - Name: xml.Name{ - Space: "", - Local: "platformBuildVersionCode", - }, - Value: "15", - } - bvn := xml.Attr{ - Name: xml.Name{ - Space: "", - Local: "platformBuildVersionName", - }, - Value: "4.0.3", - } - nodes[0].attrs = append(nodes[0].attrs, xattr{bvc, true}, xattr{bvn, true}) - // pools appear to be sorted as follows: // * attribute names prefixed with android: // * "android", [schema-url], [empty-string] @@ -445,12 +421,19 @@ func UnmarshalXML(r io.Reader) (*XML, error) { // * attribute value if data type of name is DataString, DataAttribute, or 0x3e (an unknown) bx.Pool = new(Pool) - for _, node := range nodes { - for _, attr := range node.attrs { - if attr.Name.Space == androidSchema { - bx.Pool.strings = append(bx.Pool.strings, attr.Name.Local) + var arecurse func(*Element) + arecurse = func(el *Element) { + for _, attr := range el.attrs { + if attr.NS.Resolve(pool) == androidSchema { + bx.Pool.strings = append(bx.Pool.strings, attr.Name.Resolve(pool)) } } + for _, child := range el.Children { + arecurse(child) + } + } + for _, el := range bx.Children { + arecurse(el) } // TODO encoding/xml does not enforce namespace prefix and manifest encoding in aapt @@ -462,21 +445,37 @@ func UnmarshalXML(r io.Reader) (*XML, error) { // not present in manifest. bx.Pool.strings = append(bx.Pool.strings, "") - for _, node := range nodes { - for _, attr := range node.attrs { - if attr.Name.Space == "" { - bx.Pool.strings = append(bx.Pool.strings, attr.Name.Local) + var brecurse func(*Element) + brecurse = func(el *Element) { + for _, attr := range el.attrs { + if attr.NS.Resolve(pool) == "" { + bx.Pool.strings = append(bx.Pool.strings, attr.Name.Resolve(pool)) } } - bx.Pool.strings = append(bx.Pool.strings, node.name.Local) - for _, attr := range node.attrs { - if attr.bool { - bx.Pool.strings = append(bx.Pool.strings, attr.Value) + + bx.Pool.strings = append(bx.Pool.strings, el.Name.Resolve(pool)) + + for _, attr := range el.attrs { + if attr.RawValue != NoEntry { + bx.Pool.strings = append(bx.Pool.strings, attr.RawValue.Resolve(pool)) + } else if attr.NS.Resolve(pool) == "" { + bx.Pool.strings = append(bx.Pool.strings, fmt.Sprintf("%+v", attr.TypedValue.Value)) } } - for _, x := range node.cdata { - bx.Pool.strings = append(bx.Pool.strings, x) + + if el.head != nil { + bx.Pool.strings = append(bx.Pool.strings, el.head.RawData.Resolve(pool)) } + if el.tail != nil { + bx.Pool.strings = append(bx.Pool.strings, el.tail.RawData.Resolve(pool)) + } + + for _, child := range el.Children { + brecurse(child) + } + } + for _, el := range bx.Children { + brecurse(el) } // do not eliminate duplicates until the entire slice has been composed. @@ -522,52 +521,22 @@ func UnmarshalXML(r io.Reader) (*XML, error) { resolve(el) } + var asort func(*Element) + asort = func(el *Element) { + sort.Sort(byType(el.attrs)) + // sort.Sort(byName(el.attrs)) + sort.Sort(byNamespace(el.attrs)) + for _, child := range el.Children { + asort(child) + } + } + for _, el := range bx.Children { + asort(el) + } + return bx, nil } -// asSet returns a set from a slice of strings. -func asSet(xs []string) []string { - m := make(map[string]bool) - fo := xs[:0] - for _, x := range xs { - if !m[x] { - m[x] = true - fo = append(fo, x) - } - } - return fo -} - -// poolTrim trims all but immediately surrounding space. -// \n\t\tfoobar\n\t\t becomes \tfoobar\n -func poolTrim(s string) string { - var start, end int - for i, r := range s { - if !unicode.IsSpace(r) { - if i != 0 { - start = i - 1 // preserve preceding space - } - break - } - } - - for i := len(s) - 1; i >= 0; i-- { - r := rune(s[i]) - if !unicode.IsSpace(r) { - if i != len(s)-1 { - end = i + 2 - } - break - } - } - - if start == 0 && end == 0 { - return "" // every char was a space - } - - return s[start:end] -} - func (bx *XML) UnmarshalBinary(bin []byte) error { buf := bin if err := (&bx.chunkHeader).UnmarshalBinary(bin); err != nil { @@ -751,6 +720,78 @@ func iterElementsRecurse(el *Element, ch chan *Element) { } } +// asSet returns a set from a slice of strings. +func asSet(xs []string) []string { + m := make(map[string]bool) + fo := xs[:0] + for _, x := range xs { + if !m[x] { + m[x] = true + fo = append(fo, x) + } + } + return fo +} + +// poolTrim trims all but immediately surrounding space. +// \n\t\tfoobar\n\t\t becomes \tfoobar\n +func poolTrim(s string) string { + var start, end int + for i, r := range s { + if !unicode.IsSpace(r) { + if i != 0 { + start = i - 1 // preserve preceding space + } + break + } + } + + for i := len(s) - 1; i >= 0; i-- { + r := rune(s[i]) + if !unicode.IsSpace(r) { + if i != len(s)-1 { + end = i + 2 + } + break + } + } + + if start == 0 && end == 0 { + return "" // every char was a space + } + + return s[start:end] +} + +// byNamespace sorts attributes based on string pool position of namespace. +// Given that "android" always preceeds "" in the pool, this results in the +// correct ordering of attributes. +type byNamespace []*Attribute + +func (a byNamespace) Len() int { return len(a) } +func (a byNamespace) Less(i, j int) bool { + return a[i].NS < a[j].NS +} +func (a byNamespace) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// byType sorts attributes by the uint8 value of the type. +type byType []*Attribute + +func (a byType) Len() int { return len(a) } +func (a byType) Less(i, j int) bool { + return a[i].TypedValue.Type < a[j].TypedValue.Type +} +func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// byName sorts attributes that have matching types based on string pool position of name. +type byName []*Attribute + +func (a byName) Len() int { return len(a) } +func (a byName) Less(i, j int) bool { + return a[i].TypedValue.Type == a[j].TypedValue.Type && a[i].Name < a[j].Name +} +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + type lineReader struct { off int64 lines []int64 diff --git a/internal/binres/testdata/bootstrap.bin b/internal/binres/testdata/bootstrap.bin index 6944190a0bc1fbb76096303cd780f8e82ec2e68f..9aa06359833188a0a113a570c49407cd2fe3bb3f 100644 GIT binary patch delta 1362 zcmbOuctcp0nSq1h1}6gpBZCY>1uFxC76SqtnJ6n>AIHGJki)>h(8j>PFoS`CVFLpL z!#)NEh9?XR3`~p+3<8V{3`&d)3{{K_40VhQ3=+P9F#KR-VBle5U@&1~V6b6g zV2EO3V8~-)V3@$fz;KF*f#C`h1H(Ng28JI@3=DkC3=9&?3=BHV3=ApE3=Cz=3=BQY z3=BJ%8N?Y*F*7i{U}j(tVqsuVVqsv2Vqsv&W0|-q#E^v{k0FsEmm!sbfq|7Fhar(6 zi6Is2SZ;67$WA6kMj0kXMjj?cMrH;^Mn(ok#s(0dfsv7a@>dpRRvrch2HwfCtm5@T3=9k+ z3?dAy(D>qo(*OVe|Ifj|AOMO;kQgHa0|Ore0|N&G12c#Q$@4QXFbG4%Kr~290Bjyu zod8H4Bqqqfz#s}$2cnr61sFi?jT+1hk=0s>KSC9 z27+ji*Fg$E8aN>O89-v}3=9k~F%S(B0|g<74N?c9L0FQ3fk75(9*72sNuh~>9M{3X zzyK2e|34d~+!Uk&V(0(=icnjbKvqJ-1Z0{L)HDzcGL3ukUv}|&be$lNs6gEV zGZDlGr7x&~3@nTS-~a}>6{JQLVlr47q#u+nK(>J7Kv)gxBJ@~S2gMX9F{*=-0W{jt zHNp&3hI$@kpau>DH6adR2GOu&gsu-J4)WCh|H2^S{{IJYU=pg6bveWxKyHS}{r?Y2 YWiVNgQ(@wul*kBmHcT9xD;XFV02CqyssI20 delta 1029 zcmca1JV#KLnSq004hI7RBZCaX85RZx6$S)YGf`H&K7oOOp@@Njp^JfmVHyJi!x9Dt zhEog-3@;cM7``zuFmNz3FeETCFr+XtFqAPeFsx!^V7SM~!0?HYfkBFifkBChfx&@^ zfgyy6fgz8HfuV(ofnf#{1H%O-28KsW3=BV*7#LKT85qo%85lyC85ruA85sJQ85oW+ zGcep@W|(+7n46U$har(6i6NCCXR<$|E;|cD9z!BS?&fmF!;F(xF&k`t!n}Y{)`Wq9 zL7It?QHF_;k%x(qk&%Isk%xhik)MH)k$Lh`R%KRh1_lP6$!A%`>jfDY7(^IE7+9g6 z<$==w|NsBb!N4G(1yacX3Ka$hUXUjl7??pcNS=>@fk6l=2BJY?{0s~X!Vob55E~>W z0JejHfrEj8S%iUsfr(Lo!GeK-fscVfiUFhmghj!ML>L&M4&a$w%cjl?5(Oy-g+2e| zL^k1iW|%w!0|SUH4zY|GM1vg9!oUDFlLO*529OxYb09lFVjvo1Vh7khh+2@C1elK+ z5bOxE7}*(^8F?5O?7)^vK`jN*APd+S7#Jj>VjvnM#(~f*0P++_jB_$Chd86;WJY%3 zdL|@~fb`2i^@C`TS}q0#23e>Wh-PLKU;sH1WIo8jAS?|HO;B0|xdkK!!g5g0K&@b4 zVH98hnF~`R&%jU*iVmU|;|M*i^mQ diff --git a/internal/binres/testdata/bootstrap.xml b/internal/binres/testdata/bootstrap.xml index e50dbe6..680e76b 100644 --- a/internal/binres/testdata/bootstrap.xml +++ b/internal/binres/testdata/bootstrap.xml @@ -12,10 +12,16 @@ license that can be found in the LICENSE file. xmlns:tools="http://schemas.android.com/tools"> + + +