Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sparse/keyed array support #76

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestIllegalLaxDecode(t *testing.T) {

func TestIllegalDecode(t *testing.T) {
i := int64(0)
m := make(map[string]string)
b := false
plists := []struct {
pl string
Expand All @@ -94,6 +95,8 @@ func TestIllegalDecode(t *testing.T) {
{"<dict><key>a</key><integer>0</integer></dict>", &b},
{"<array><true/><true/><true/></array>", &[1]int{1}},
{"<data>SGVsbG8=</data>", &[3]byte{}},
{"<array><string>a</string><key>123</key></array>", &m},
{"<array><key>123</key><string>a</string><string>b</string></array>", &m},
}

for _, plist := range plists {
Expand All @@ -107,6 +110,22 @@ func TestIllegalDecode(t *testing.T) {
}
}

func TestSparseArrayDecode(t *testing.T) {
buf := bytes.NewReader([]byte("<array><key>100</key><string>a</string><key>200</key><string>b</string></array>"))
decoder := NewDecoder(buf)
var val map[string]string
err := decoder.Decode(&val)
if err != nil {
t.Error(err)
}
if want, got := "a", val["100"]; want != got {
t.Errorf("Expected %q at key 100, got %q", want, got)
}
if want, got := "b", val["200"]; want != got {
t.Errorf("Expected %q at key 200, got %q", want, got)
}
}

func TestDecode(t *testing.T) {
for _, test := range tests {
subtest(t, test.Name, func(t *testing.T) {
Expand Down
49 changes: 47 additions & 2 deletions xml_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"runtime"
"strconv"
"strings"
"time"
)
Expand Down Expand Up @@ -181,7 +182,15 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue {
return dict.maybeUID(false)
case "array":
p.ntags++
values := make([]cfValue, 0, 10)
var key *int
// Maintain a list of keys: either seen explicitly, or implicitly from
// ordering in an array without keys.
keys := make([]int, 0, 32)
// Two flags to make note of what kind of array we have encountered so
// far. Mixed type is currently not allowed.
sawExplicitKey := false
sawImplicitValue := false
values := make([]cfValue, 0, 32)
for {
token, err := p.xmlDecoder.Token()
if err != nil {
Expand All @@ -193,9 +202,45 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue {
}

if el, ok := token.(xml.StartElement); ok {
values = append(values, p.parseXMLElement(el))
if el.Name.Local == "key" {
sawExplicitKey = true
if sawImplicitValue {
panic(errors.New("mixed type array"))
}
if key != nil {
panic(errors.New("double key in array"))
}
var k int
p.xmlDecoder.DecodeElement(&k, &el)
key = &k
} else {
if key != nil {
keys = append(keys, *key)
key = nil
} else {
sawImplicitValue = true
if sawExplicitKey {
panic(errors.New("mixed type array"))
}
keys = append(keys, len(values))
}
values = append(values, p.parseXMLElement(el))
}
}
}
// If the array keys are non-continuous, return a dictionary.
for i := 0; i < len(keys); i++ {
if keys[i] != i {
// ... but first convert the keys into strings.
keys2 := make([]string, len(keys))
for j, k := range keys {
keys2[j] = strconv.Itoa(k)
}
dict := &cfDictionary{keys: keys2, values: values}
return dict.maybeUID(false)
}
}
// If the array is indeed continuous, return it as an array.
return &cfArray{values}
}
err := fmt.Errorf("encountered unknown element %s", element.Name.Local)
Expand Down