Browse Source

webdav: Refactor XML normalisation for use with multistatusWriter tests.

This CL aims to provide a reusable XML normalisation mechanism across the
golden XML tests.

Change-Id: I39c957f1af4ce4672add38ea6a38c6a7c5f4f25c
Reviewed-on: https://go-review.googlesource.com/11032
Reviewed-by: Nigel Tao <nigeltao@golang.org>
Robert Stepanek 10 years ago
parent
commit
9339372135
1 changed files with 94 additions and 88 deletions
  1. 94 88
      webdav/xml_test.go

+ 94 - 88
webdav/xml_test.go

@@ -456,20 +456,20 @@ func TestMultistatusWriter(t *testing.T) {
 		respdesc: "There has been an access violation error.",
 		wantXML: `` +
 			`<?xml version="1.0" encoding="UTF-8"?>` +
-			`<multistatus xmlns="DAV:">` +
+			`<multistatus xmlns="DAV:" xmlns:B="http://ns.example.com/boxschema/">` +
 			`  <response>` +
 			`    <href>http://example.com/foo</href>` +
 			`    <propstat>` +
 			`      <prop>` +
-			`        <bigbox xmlns="http://ns.example.com/boxschema/"><BoxType xmlns="http://ns.example.com/boxschema/">Box type A</BoxType></bigbox>` +
-			`        <author xmlns="http://ns.example.com/boxschema/"><Name xmlns="http://ns.example.com/boxschema/">J.J. Johnson</Name></author>` +
+			`        <B:bigbox><B:BoxType>Box type A</B:BoxType></B:bigbox>` +
+			`        <B:author><B:Name>J.J. Johnson</B:Name></B:author>` +
 			`      </prop>` +
 			`      <status>HTTP/1.1 200 OK</status>` +
 			`    </propstat>` +
 			`    <propstat>` +
 			`      <prop>` +
-			`        <DingALing xmlns="http://ns.example.com/boxschema/"></DingALing>` +
-			`        <Random xmlns="http://ns.example.com/boxschema/"></Random>` +
+			`        <B:DingALing/>` +
+			`        <B:Random/>` +
 			`      </prop>` +
 			`      <status>HTTP/1.1 403 Forbidden</status>` +
 			`      <responsedescription>The user does not have access to the DingALing property.</responsedescription>` +
@@ -564,6 +564,7 @@ func TestMultistatusWriter(t *testing.T) {
 		wantCode: http.StatusOK,
 	}}
 
+	n := xmlNormalizer{omitWhitespace: true}
 loop:
 	for _, tc := range testCases {
 		rec := httptest.NewRecorder()
@@ -593,44 +594,14 @@ loop:
 				tc.desc, rec.Code, tc.wantCode)
 			continue
 		}
-
-		// normalize returns the normalized XML content of s. In contrast to
-		// the WebDAV specification, it ignores whitespace within property
-		// values of mixed XML content.
-		normalize := func(s string) string {
-			d := xml.NewDecoder(strings.NewReader(s))
-			var b bytes.Buffer
-			e := xml.NewEncoder(&b)
-			for {
-				tok, err := d.Token()
-				if err != nil {
-					if err == io.EOF {
-						break
-					}
-					t.Fatalf("%s: Token %v", tc.desc, err)
-				}
-				switch val := tok.(type) {
-				case xml.Comment, xml.Directive, xml.ProcInst:
-					continue
-				case xml.CharData:
-					if len(bytes.TrimSpace(val)) == 0 {
-						continue
-					}
-				}
-				if err := e.EncodeToken(tok); err != nil {
-					t.Fatalf("%s: EncodeToken: %v", tc.desc, err)
-				}
-			}
-			if err := e.Flush(); err != nil {
-				t.Fatalf("%s: Flush: %v", tc.desc, err)
-			}
-			return b.String()
+		gotXML := rec.Body.String()
+		eq, err := n.equalXML(strings.NewReader(gotXML), strings.NewReader(tc.wantXML))
+		if err != nil {
+			t.Errorf("%s: equalXML: %v", tc.desc, err)
+			continue
 		}
-
-		gotXML := normalize(rec.Body.String())
-		wantXML := normalize(tc.wantXML)
-		if gotXML != wantXML {
-			t.Errorf("%s: XML body\ngot  %q\nwant %q", tc.desc, gotXML, wantXML)
+		if !eq {
+			t.Errorf("%s: XML body\ngot  %s\nwant %s", tc.desc, gotXML, tc.wantXML)
 		}
 	}
 }
@@ -828,66 +799,101 @@ func TestUnmarshalXMLValue(t *testing.T) {
 			`  </notes>`,
 	}}
 
-	// Normalize namespace declarations, prefixes and attribute order.
-	normalize := func(s string) (string, error) {
-		d := xml.NewDecoder(strings.NewReader(s))
-		var b bytes.Buffer
-		e := xml.NewEncoder(&b)
-		for {
-			t, err := d.Token()
-			if err != nil {
-				if t == nil && err == io.EOF {
-					break
-				}
-				return "", err
-			}
-			t = xml.CopyToken(t)
-			if start, ok := t.(xml.StartElement); ok {
-				attr := start.Attr[:0]
-				for _, a := range start.Attr {
-					if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" {
-						continue
-					}
-					attr = append(attr, a)
-				}
-				sort.Sort(byName(attr))
-				start.Attr = attr
-				t = start
-			}
-			err = e.EncodeToken(t)
-			if err != nil {
-				return "", err
-			}
-		}
-		err := e.Flush()
-		if err != nil {
-			return "", err
-		}
-		return b.String(), nil
-	}
-
+	var n xmlNormalizer
 	for _, tc := range testCases {
 		d := xml.NewDecoder(strings.NewReader(tc.input))
 		var v xmlValue
-		err := d.Decode(&v)
-		if err != nil {
+		if err := d.Decode(&v); err != nil {
 			t.Errorf("%s: got error %v, want nil", tc.desc, err)
 			continue
 		}
-		got, err := normalize(string(v))
+		eq, err := n.equalXML(bytes.NewReader(v), strings.NewReader(tc.wantVal))
 		if err != nil {
-			t.Errorf("%s: normalize: %v", tc.desc, err)
+			t.Errorf("%s: equalXML: %v", tc.desc, err)
 			continue
 		}
-		want, err := normalize(tc.wantVal)
+		if !eq {
+			t.Errorf("%s:\ngot  %s\nwant %s", tc.desc, string(v), tc.wantVal)
+		}
+	}
+}
+
+// xmlNormalizer normalizes XML.
+type xmlNormalizer struct {
+	// omitWhitespace instructs to ignore whitespace between element tags.
+	omitWhitespace bool
+	// omitComments instructs to ignore XML comments.
+	omitComments bool
+}
+
+// normalize writes the normalized XML content of r to w. It applies the
+// following rules
+//
+//     * Rename namespace prefixes according to an internal heuristic.
+//     * Remove unnecessary namespace declarations.
+//     * Sort attributes in XML start elements in lexical order of their
+//       fully qualified name.
+//     * Remove XML directives and processing instructions.
+//     * Remove CDATA between XML tags that only contains whitespace, if
+//       instructed to do so.
+//     * Remove comments, if instructed to do so.
+//
+func (n *xmlNormalizer) normalize(w io.Writer, r io.Reader) error {
+	d := xml.NewDecoder(r)
+	e := xml.NewEncoder(w)
+	for {
+		t, err := d.Token()
 		if err != nil {
-			t.Errorf("%s: normalize: %v", tc.desc, err)
+			if t == nil && err == io.EOF {
+				break
+			}
+			return err
+		}
+		switch val := t.(type) {
+		case xml.Directive, xml.ProcInst:
 			continue
+		case xml.Comment:
+			if n.omitComments {
+				continue
+			}
+		case xml.CharData:
+			if n.omitWhitespace && len(bytes.TrimSpace(val)) == 0 {
+				continue
+			}
+		case xml.StartElement:
+			start, _ := xml.CopyToken(val).(xml.StartElement)
+			attr := start.Attr[:0]
+			for _, a := range start.Attr {
+				if a.Name.Space == "xmlns" || a.Name.Local == "xmlns" {
+					continue
+				}
+				attr = append(attr, a)
+			}
+			sort.Sort(byName(attr))
+			start.Attr = attr
+			t = start
 		}
-		if got != want {
-			t.Errorf("%s:\ngot  %s\nwant %s", tc.desc, got, want)
+		err = e.EncodeToken(t)
+		if err != nil {
+			return err
 		}
 	}
+	return e.Flush()
+}
+
+// equalXML tests for equality of the normalized XML contents of a and b.
+func (n *xmlNormalizer) equalXML(a, b io.Reader) (bool, error) {
+	var buf bytes.Buffer
+	if err := n.normalize(&buf, a); err != nil {
+		return false, err
+	}
+	normA := buf.String()
+	buf.Reset()
+	if err := n.normalize(&buf, b); err != nil {
+		return false, err
+	}
+	normB := buf.String()
+	return normA == normB, nil
 }
 
 type byName []xml.Attr