Improve XML parsingUnverified
e82f9b0 parent: ff5d998 modified
internal/feed/parser.go +35 -16 | @@ -1,6 +1,7 @@ | ||
| 1 | 1 | package feed |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | + "bytes" | |
| 4 | 5 | "encoding/json" |
| 5 | 6 | "encoding/xml" |
| 6 | 7 | "fmt" |
| @@ -182,8 +183,8 @@ func parseJSONFeed(data []byte, feedURL string) (*ParseResult, error) { | ||
| 182 | 183 | return result, nil |
| 183 | 184 | } |
| 184 | 185 | |
| 185 | -func makeXMLDecoder(data []byte) *xml.Decoder { | |
| 186 | - decoder := xml.NewDecoder(strings.NewReader(string(data))) | |
| 186 | +func makeXMLDecoder(r io.Reader) *xml.Decoder { | |
| 187 | + decoder := xml.NewDecoder(r) | |
| 187 | 188 | decoder.Strict = false |
| 188 | 189 | decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { |
| 189 | 190 | return htmlcharset.NewReader(input, "text/xml; charset="+charset) |
| @@ -191,26 +192,44 @@ func makeXMLDecoder(data []byte) *xml.Decoder { | ||
| 191 | 192 | return decoder |
| 192 | 193 | } |
| 193 | 194 | |
| 194 | -func parseXMLFeed(data []byte, feedURL string) (*ParseResult, error) { | |
| 195 | - var rss rssFeed | |
| 196 | - if err := makeXMLDecoder(data).Decode(&rss); err == nil { | |
| 197 | - if rss.XMLName.Local == "rss" { | |
| 198 | - return convertRSS(&rss, feedURL), nil | |
| 195 | +func detectXMLRoot(data []byte) string { | |
| 196 | + decoder := makeXMLDecoder(bytes.NewReader(data)) | |
| 197 | + for { | |
| 198 | + tok, err := decoder.Token() | |
| 199 | + if err != nil { | |
| 200 | + return "" | |
| 201 | + } | |
| 202 | + if se, ok := tok.(xml.StartElement); ok { | |
| 203 | + return se.Name.Local | |
| 199 | 204 | } |
| 200 | 205 | } |
| 206 | +} | |
| 201 | 207 | |
| 202 | - var atom atomFeed | |
| 203 | - if err := makeXMLDecoder(data).Decode(&atom); err == nil { | |
| 204 | - if atom.XMLName.Local == "feed" { | |
| 205 | - return convertAtom(&atom, feedURL), nil | |
| 206 | - } | |
| 208 | +func parseXMLFeed(data []byte, feedURL string) (*ParseResult, error) { | |
| 209 | + root := detectXMLRoot(data) | |
| 210 | + if root == "" { | |
| 211 | + return nil, fmt.Errorf("unable to detect feed format") | |
| 207 | 212 | } |
| 208 | 213 | |
| 209 | - var rdf rdfFeed | |
| 210 | - if err := makeXMLDecoder(data).Decode(&rdf); err == nil { | |
| 211 | - if rdf.XMLName.Local == "RDF" { | |
| 212 | - return convertRDF(&rdf, feedURL), nil | |
| 214 | + switch root { | |
| 215 | + case "rss": | |
| 216 | + var rss rssFeed | |
| 217 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&rss); err != nil { | |
| 218 | + return nil, fmt.Errorf("parsing RSS feed: %w", err) | |
| 219 | + } | |
| 220 | + return convertRSS(&rss, feedURL), nil | |
| 221 | + case "feed": | |
| 222 | + var atom atomFeed | |
| 223 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&atom); err != nil { | |
| 224 | + return nil, fmt.Errorf("parsing Atom feed: %w", err) | |
| 225 | + } | |
| 226 | + return convertAtom(&atom, feedURL), nil | |
| 227 | + case "RDF": | |
| 228 | + var rdf rdfFeed | |
| 229 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&rdf); err != nil { | |
| 230 | + return nil, fmt.Errorf("parsing RDF feed: %w", err) | |
| 213 | 231 | } |
| 232 | + return convertRDF(&rdf, feedURL), nil | |
| 214 | 233 | } |
| 215 | 234 | |
| 216 | 235 | return nil, fmt.Errorf("unable to detect feed format") |
| @@ -1,6 +1,7 @@ | |||
| 1 | package feed | 1 | package feed |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | + "bytes" | ||
| 4 | "encoding/json" | 5 | "encoding/json" |
| 5 | "encoding/xml" | 6 | "encoding/xml" |
| 6 | "fmt" | 7 | "fmt" |
| @@ -182,8 +183,8 @@ func parseJSONFeed(data []byte, feedURL string) (*ParseResult, error) { | |||
| 182 | return result, nil | 183 | return result, nil |
| 183 | } | 184 | } |
| 184 | 185 | ||
| 185 | -func makeXMLDecoder(data []byte) *xml.Decoder { | 186 | +func makeXMLDecoder(r io.Reader) *xml.Decoder { |
| 186 | - decoder := xml.NewDecoder(strings.NewReader(string(data))) | 187 | + decoder := xml.NewDecoder(r) |
| 187 | decoder.Strict = false | 188 | decoder.Strict = false |
| 188 | decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { | 189 | decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { |
| 189 | return htmlcharset.NewReader(input, "text/xml; charset="+charset) | 190 | return htmlcharset.NewReader(input, "text/xml; charset="+charset) |
| @@ -191,26 +192,44 @@ func makeXMLDecoder(data []byte) *xml.Decoder { | |||
| 191 | return decoder | 192 | return decoder |
| 192 | } | 193 | } |
| 193 | 194 | ||
| 194 | -func parseXMLFeed(data []byte, feedURL string) (*ParseResult, error) { | 195 | +func detectXMLRoot(data []byte) string { |
| 195 | - var rss rssFeed | 196 | + decoder := makeXMLDecoder(bytes.NewReader(data)) |
| 196 | - if err := makeXMLDecoder(data).Decode(&rss); err == nil { | 197 | + for { |
| 197 | - if rss.XMLName.Local == "rss" { | 198 | + tok, err := decoder.Token() |
| 198 | - return convertRSS(&rss, feedURL), nil | 199 | + if err != nil { |
| 200 | + return "" | ||
| 201 | + } | ||
| 202 | + if se, ok := tok.(xml.StartElement); ok { | ||
| 203 | + return se.Name.Local | ||
| 199 | } | 204 | } |
| 200 | } | 205 | } |
| 206 | +} | ||
| 201 | 207 | ||
| 202 | - var atom atomFeed | 208 | +func parseXMLFeed(data []byte, feedURL string) (*ParseResult, error) { |
| 203 | - if err := makeXMLDecoder(data).Decode(&atom); err == nil { | 209 | + root := detectXMLRoot(data) |
| 204 | - if atom.XMLName.Local == "feed" { | 210 | + if root == "" { |
| 205 | - return convertAtom(&atom, feedURL), nil | 211 | + return nil, fmt.Errorf("unable to detect feed format") |
| 206 | - } | ||
| 207 | } | 212 | } |
| 208 | 213 | ||
| 209 | - var rdf rdfFeed | 214 | + switch root { |
| 210 | - if err := makeXMLDecoder(data).Decode(&rdf); err == nil { | 215 | + case "rss": |
| 211 | - if rdf.XMLName.Local == "RDF" { | 216 | + var rss rssFeed |
| 212 | - return convertRDF(&rdf, feedURL), nil | 217 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&rss); err != nil { |
| 218 | + return nil, fmt.Errorf("parsing RSS feed: %w", err) | ||
| 219 | + } | ||
| 220 | + return convertRSS(&rss, feedURL), nil | ||
| 221 | + case "feed": | ||
| 222 | + var atom atomFeed | ||
| 223 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&atom); err != nil { | ||
| 224 | + return nil, fmt.Errorf("parsing Atom feed: %w", err) | ||
| 225 | + } | ||
| 226 | + return convertAtom(&atom, feedURL), nil | ||
| 227 | + case "RDF": | ||
| 228 | + var rdf rdfFeed | ||
| 229 | + if err := makeXMLDecoder(bytes.NewReader(data)).Decode(&rdf); err != nil { | ||
| 230 | + return nil, fmt.Errorf("parsing RDF feed: %w", err) | ||
| 213 | } | 231 | } |
| 232 | + return convertRDF(&rdf, feedURL), nil | ||
| 214 | } | 233 | } |
| 215 | 234 | ||
| 216 | return nil, fmt.Errorf("unable to detect feed format") | 235 | return nil, fmt.Errorf("unable to detect feed format") |