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 6944190..9aa0635 100644
Binary files a/internal/binres/testdata/bootstrap.bin and b/internal/binres/testdata/bootstrap.bin differ
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">
+
+
+