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

WIP: add newlines and indents to self closing tags #24

Closed
wants to merge 1 commit into from
Closed
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
3 changes: 3 additions & 0 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ func (e *Encoder) Encode(v interface{}) error {
}

enc := newXMLEncoder(e.w)

// this indent value is for xmlEncoder to track and write booleans. It's a hack
enc.indent = e.indent
enc.Indent("", e.indent)
return enc.generateDocument(pval)
}
Expand Down
80 changes: 66 additions & 14 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ var indentRefOmit = `<?xml version="1.0" encoding="UTF-8"?>
</plist>
`

var boolRef = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>Absent</key><false/><key>False</key><false/><key>True</key><true/></dict></plist>
`

var boolIndentRef = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Absent</key>
<false/>
<key>False</key>
<false/>
<key>True</key>
<true/>
</dict>
</plist>
`

type testStruct struct {
UnusedString string `plist:"unused-string"`
}
Expand Down Expand Up @@ -300,28 +319,61 @@ func TestMarshaler(t *testing.T) {
}

func TestSelfClosing(t *testing.T) {
t.Parallel()
tests := []struct {
description string
marshalFunc func(interface{}) ([]byte, error)
want string
}{
{
description: "without indent",
marshalFunc: func(in interface{}) ([]byte, error) {
return Marshal(in)
},
want: boolRef,
},
{
description: "indent",
marshalFunc: func(in interface{}) ([]byte, error) {
return MarshalIndent(in, " ")
},
want: boolIndentRef,
},
}

type AA struct {
AA bool
Multiple []bool
Strings []string
}
type Foo struct {
A bool
B bool
AA AA
}

selfClosing := struct {
True bool
False bool
Absent bool
Foo Foo
}{
True: true,
False: false,
Foo: Foo{AA: AA{Strings: []string{"A", "b"}, Multiple: []bool{false, true, true}}},
}

want := []byte(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict><key>Absent</key><false/><key>False</key><false/><key>True</key><true/></dict></plist>
`)

have, err := Marshal(selfClosing)
if err != nil {
t.Fatal(err)
for _, tt := range tests {
tt := tt
t.Run(tt.description, func(t *testing.T) {
t.Parallel()
got, err := tt.marshalFunc(selfClosing)
if err != nil {
t.Fatal(err)
}

if string(got) != tt.want {
t.Errorf("got\n%s\n, want\n%s\n", got, tt.want)
}
})
}

if !bytes.Equal(have, want) {
t.Errorf("expected \n%s got \n%s\n", have, want)
}

}
41 changes: 33 additions & 8 deletions xml_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ const xmlDOCTYPE = `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http:/

type xmlEncoder struct {
writer io.Writer
indent string
*xml.Encoder
depth int
writeNewline bool
}

func newXMLEncoder(w io.Writer) *xmlEncoder {
return &xmlEncoder{w, xml.NewEncoder(w)}
return &xmlEncoder{writer: w, Encoder: xml.NewEncoder(w)}
}

func (e *xmlEncoder) generateDocument(pval *plistValue) error {
Expand Down Expand Up @@ -60,6 +63,7 @@ func (e *xmlEncoder) generateDocument(pval *plistValue) error {
}

func (e *xmlEncoder) writePlistValue(pval *plistValue) error {
e.depth++
switch pval.kind {
case String:
return e.writeStringValue(pval)
Expand Down Expand Up @@ -139,6 +143,11 @@ func (e *xmlEncoder) writeElement(name string, pval *plistValue, valFunc func(*p
return err
}

// drop the indent level after array or dictionary tag is closed.
if name == "dict" || name == "array" {
e.depth--
}

// flush
return e.Flush()
}
Expand All @@ -152,6 +161,16 @@ func (e *xmlEncoder) writeArrayValue(pval *plistValue) error {
return err
}
}
// if the values of the array are self closing booleans, we have to write a newline + indent
// and then decrement depth by 2. 1 for the bool and one for the array key itself.
if e.writeNewline {
e.writer.Write([]byte("\n"))
for i := 0; i < e.depth; i++ {
e.writer.Write([]byte(e.indent))
}
e.depth -= 2
e.writeNewline = false
}
return nil
}
return e.writeElement("array", pval, tokenFunc)
Expand Down Expand Up @@ -211,14 +230,20 @@ func (e *xmlEncoder) writeStringValue(pval *plistValue) error {
}

func (e *xmlEncoder) writeBoolValue(pval *plistValue) error {
// EncodeElement results in <true></true> instead of <true/>
// use writer to write self closing tags
b := pval.value.(bool)
_, err := e.writer.Write([]byte(fmt.Sprintf("<%t/>", b)))
if err != nil {
return err
e.writeNewline = true
boolVal := pval.value.(bool)
var err error
if e.indent != "" {
e.writer.Write([]byte("\n"))
for i := 0; i < e.depth; i++ {
e.writer.Write([]byte(e.indent))
}
e.depth--
_, err = e.writer.Write([]byte(fmt.Sprintf("<%t/>", boolVal)))
} else {
_, err = e.writer.Write([]byte(fmt.Sprintf("<%t/>", boolVal)))
}
return nil
return err
}

func (e *xmlEncoder) writeIntegerValue(pval *plistValue) error {
Expand Down