commit 9e0ba934362a7537b7f206c419f40c138508d96b from: Matthew Streatfield via: GitHub date: Sun Apr 13 09:21:20 2025 UTC Merge pull request #3 from ollytom/main add opml package commit - ccfca0aabb4388eed90e53cb4bad4798a81e6458 commit + 9e0ba934362a7537b7f206c419f40c138508d96b blob - /dev/null blob + 42ceb2ef3c9ea7e29c9faebd1d1949424320ed04 (mode 644) --- /dev/null +++ opml/opml.go @@ -0,0 +1,43 @@ +// Package opml implements decoding of OPML documents described at [opml.org]. +// Only a subset of the format is implemented; +// enough to support the use of OPML as an interchange format by +// feed reader applications for web feed collections. +// +// [opml.org]: https://opml.org +package opml + +import ( + "encoding/xml" + "io" +) + +type Document struct { + XMLName struct{} `xml:"opml"` + Version string `xml:"version,attr"` + Body []Outline `xml:"body>outline"` + + // head holds the document's metadata. + // TODO(otl): should we even support this? + // NetNewWire generates an empty tree. + head struct{} +} + +type Outline struct { + XMLName struct{} `xml:"outline"` + Text string `xml:"text,attr"` + Title string `xml:"title,attr"` + Description string `xml:"description,attr,omitempty"` + Type string `xml:"type,attr"` + Version string `xml:"version,attr,omitempty"` + HTML string `xml:"htmlUrl,attr"` + XML string `xml:"xmlUrl,attr"` + Children []Outline `xml:"outline,omitempty"` +} + +func Decode(r io.Reader) (*Document, error) { + var doc Document + if err := xml.NewDecoder(r).Decode(&doc); err != nil { + return nil, err + } + return &doc, nil +} blob - /dev/null blob + 09d1e40cc29f67221327a8158acec8ccbfaa1379 (mode 644) --- /dev/null +++ opml/opml_test.go @@ -0,0 +1,42 @@ +package opml + +import ( + "os" + "testing" +) + +func TestDecode(t *testing.T) { + f, err := os.Open("otl.opml") + if err != nil { + t.Fatal(err) + } + doc, err := Decode(f) + if err != nil { + t.Fatal(err) + } + + // The tricky thing is decoding child elements recursively + // just using struct tags. So checking the number of elements + // is expected gives us some confidence we're reading + // the document correctly. + var counts = map[string]int{ + "aggregators": 6, + "apple": 6, + "corp": 4, + "email": 4, + "Friends": 5, + "lang": 4, + "misc": 92, + "repos": 6, + } + for _, node := range doc.Body { + want, ok := counts[node.Title] + if !ok { + t.Errorf("unknown node title %s", node.Title) + continue + } + if len(node.Children) != want { + t.Errorf("%d child elements in %s, want %d", len(node.Children), node.Title, want) + } + } +} blob - /dev/null blob + 1cc9855d4414da78d4b81e667f79ddc9db717ea6 (mode 644) --- /dev/null +++ opml/otl.opml @@ -0,0 +1,152 @@ + + + + + Subscriptions-iCloud.opml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file