changeset 2118:8a6ed058af22

WFS downloader: Use a custom XML streaming parser to detect error reports.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Tue, 05 Feb 2019 11:39:06 +0100
parents 28fb26536b8f
children d17a8dd1dfd3
files client/yarn.lock pkg/wfs/download.go
diffstat 2 files changed, 88 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/client/yarn.lock	Tue Feb 05 09:38:01 2019 +0100
+++ b/client/yarn.lock	Tue Feb 05 11:39:06 2019 +0100
@@ -4647,7 +4647,7 @@
     loader-utils "^1.0.2"
     schema-utils "^1.0.0"
 
-"file-saver@github:eligrey/FileSaver.js#1.3.8":
+file-saver@eligrey/FileSaver.js#1.3.8:
   version "1.3.8"
   resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e"
 
--- a/pkg/wfs/download.go	Tue Feb 05 09:38:01 2019 +0100
+++ b/pkg/wfs/download.go	Tue Feb 05 11:39:06 2019 +0100
@@ -24,6 +24,7 @@
 	"net/http"
 	"net/url"
 	"strconv"
+	"strings"
 
 	"gemma.intevation.de/gemma/pkg/config"
 	"golang.org/x/net/html/charset"
@@ -274,45 +275,109 @@
 }
 
 type ExceptionReport struct {
-	XMLName   xml.Name `xml:"http://www.opengis.net/ows/1.1 ExceptionReport"`
-	Exception struct {
-		ExceptionCode *string `xml:"exceptionCode,attr"`
-		ExceptionText struct {
-			Text *string `xml:",cdata"`
-		} `xml:"http://www.opengis.net/ows/1.1 ExceptionText"`
-	} `xml:"http://www.opengis.net/ows/1.1 Exception"`
+	Code string
+	Text string
 }
 
 func (er *ExceptionReport) Error() string {
-	nilString := func(s *string) string {
-		if s == nil {
-			return ""
-		}
-		return *s
-	}
 	return fmt.Sprintf(
 		"WFS GetFeature error: ExceptionCode: '%s' / ExceptionText: %s",
-		nilString(er.Exception.ExceptionCode),
-		nilString(er.Exception.ExceptionText.Text))
+		er.Code, er.Text)
 }
 
 func scanExceptionReport(r io.Reader) *ExceptionReport {
 
+	const ows = "http://www.opengis.net/ows/1.1"
+
 	decoder := xml.NewDecoder(r)
 	decoder.CharsetReader = charset.NewReaderLabel
 
-	var er ExceptionReport
+	var (
+		isError bool
+		code    string
+		depth   int
+		text    strings.Builder
+	)
+
+	type tokenFunc func(xml.Token) tokenFunc
+
+	var exceptionReportFn, exceptionFn, exceptionTextFn, collectTextFn tokenFunc
 
-	if decoder.Decode(&er) != nil {
-		// Fine because of other kind of document
-		return nil
+	exceptionReportFn = func(t xml.Token) tokenFunc {
+		e, ok := t.(xml.StartElement)
+		if !ok {
+			return exceptionReportFn
+		}
+		if e.Name.Local != "ExceptionReport" && e.Name.Space != ows {
+			return nil
+		}
+		isError = true
+		return exceptionFn
+	}
+
+	exceptionFn = func(t xml.Token) tokenFunc {
+		e, ok := t.(xml.StartElement)
+		if !ok {
+			return exceptionFn
+		}
+		if e.Name.Local == "Exception" && e.Name.Space == ows {
+			for i := range e.Attr {
+				at := &e.Attr[i]
+				if at.Name.Local == "code" || at.Name.Local == "exceptionCode" {
+					code = at.Value
+					break
+				}
+			}
+			return exceptionTextFn
+		}
+		return exceptionFn
 	}
 
-	if er.Exception.ExceptionText.Text == nil && er.Exception.ExceptionCode == nil {
-		return nil
+	exceptionTextFn = func(t xml.Token) tokenFunc {
+		e, ok := t.(xml.StartElement)
+		if ok && e.Name.Local == "ExceptionText" && e.Name.Space == ows {
+			return collectTextFn
+		}
+		return exceptionTextFn
 	}
 
-	return &er
+	collectTextFn = func(t xml.Token) tokenFunc {
+		switch e := t.(type) {
+		case xml.StartElement:
+			depth++
+		case xml.CharData:
+			if depth == 0 {
+				text.Write(e)
+			}
+		case xml.EndElement:
+			if depth == 0 {
+				return nil
+			}
+			depth--
+		}
+		return collectTextFn
+	}
+
+tokens:
+	for fn := exceptionReportFn; fn != nil; {
+		tok, err := decoder.Token()
+		switch {
+		case tok == nil && err == io.EOF:
+			break tokens
+		case err != nil:
+			return nil
+		}
+		fn = fn(tok)
+	}
+
+	if isError {
+		return &ExceptionReport{
+			Code: code,
+			Text: text.String(),
+		}
+	}
+
+	return nil
 }
 
 // DownloadURLs does the actual GetFeature requests downloads