opml package
This commit is contained in:
parent
0d49377879
commit
1e65da9aa4
4 changed files with 148 additions and 58 deletions
83
src/opml/builder.go
Normal file
83
src/opml/builder.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package opml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type OPMLBuilder struct {
|
||||
rootfolder *OPMLFolder
|
||||
folders []*OPMLFolder
|
||||
}
|
||||
|
||||
type OPMLFolder struct {
|
||||
title string
|
||||
feeds []*OPMLFeed
|
||||
}
|
||||
|
||||
type OPMLFeed struct {
|
||||
title, description, feedUrl, siteUrl string
|
||||
}
|
||||
|
||||
func NewBuilder() *OPMLBuilder {
|
||||
return &OPMLBuilder{
|
||||
rootfolder: &OPMLFolder{feeds: make([]*OPMLFeed, 0)},
|
||||
folders: make([]*OPMLFolder, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *OPMLBuilder) AddFeed(title, description, feedUrl, siteUrl string) {
|
||||
b.rootfolder.AddFeed(title, description, feedUrl, siteUrl)
|
||||
}
|
||||
|
||||
func (b *OPMLBuilder) AddFolder(title string) *OPMLFolder {
|
||||
folder := &OPMLFolder{title: title}
|
||||
b.folders = append(b.folders, folder)
|
||||
return folder
|
||||
}
|
||||
|
||||
func (f *OPMLFolder) AddFeed(title, description, feedUrl, siteUrl string) {
|
||||
f.feeds = append(f.feeds, &OPMLFeed{title, description, feedUrl, siteUrl})
|
||||
}
|
||||
|
||||
func (b *OPMLBuilder) String() string {
|
||||
builder := strings.Builder{}
|
||||
|
||||
line := func(s string, args ...string) {
|
||||
if len(args) > 0 {
|
||||
escapedargs := make([]interface{}, len(args))
|
||||
for idx, arg := range args {
|
||||
escapedargs[idx] = html.EscapeString(arg)
|
||||
}
|
||||
s = fmt.Sprintf(s, escapedargs...)
|
||||
}
|
||||
builder.WriteString(s)
|
||||
builder.WriteRune('\n')
|
||||
}
|
||||
feedline := func(feed *OPMLFeed, indent int) {
|
||||
line(
|
||||
strings.Repeat(" ", indent) + `<outline type="rss" text="%s" description="%s" xmlUrl="%s" htmlUrl="%s"/>`,
|
||||
feed.title, feed.description,
|
||||
feed.feedUrl, feed.siteUrl,
|
||||
)
|
||||
}
|
||||
line(`<?xml version="1.0" encoding="UTF-8"?>`)
|
||||
line(`<opml version="1.1">`)
|
||||
line(`<head><title>Subscriptions</title></head>`)
|
||||
line(`<body>`)
|
||||
for _, folder := range b.folders {
|
||||
line(` <outline text="%s">`, folder.title)
|
||||
for _, feed := range folder.feeds {
|
||||
feedline(feed, 4)
|
||||
}
|
||||
line(` </outline>`)
|
||||
}
|
||||
for _, feed := range b.rootfolder.feeds {
|
||||
feedline(feed, 2)
|
||||
}
|
||||
line(`</body>`)
|
||||
line(`</opml>`)
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
30
src/opml/builder_test.go
Normal file
30
src/opml/builder_test.go
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package opml
|
||||
|
||||
import "testing"
|
||||
|
||||
var sample = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<opml version="1.1">
|
||||
<head><title>Subscriptions</title></head>
|
||||
<body>
|
||||
<outline text="sub">
|
||||
<outline type="rss" text="subtitle1" description="sub1" xmlUrl="https://foo.com/feed.xml" htmlUrl="https://foo.com/"/>
|
||||
<outline type="rss" text="&>" description="<>" xmlUrl="https://bar.com/feed.xml" htmlUrl="https://bar.com/"/>
|
||||
</outline>
|
||||
<outline type="rss" text="title1" description="desc1" xmlUrl="https://example.com/feed.xml" htmlUrl="https://example.com/"/>
|
||||
</body>
|
||||
</opml>
|
||||
`
|
||||
|
||||
func TestOPMLBuilder(t *testing.T) {
|
||||
builder := NewBuilder()
|
||||
builder.AddFeed("title1", "desc1", "https://example.com/feed.xml", "https://example.com/")
|
||||
|
||||
folder := builder.AddFolder("sub")
|
||||
folder.AddFeed("subtitle1", "sub1", "https://foo.com/feed.xml", "https://foo.com/")
|
||||
folder.AddFeed("&>", "<>", "https://bar.com/feed.xml", "https://bar.com/")
|
||||
|
||||
output := builder.String()
|
||||
if output != sample {
|
||||
t.Errorf("\n=== expected:\n%s\n=== got:\n%s\n===", sample, output)
|
||||
}
|
||||
}
|
||||
42
src/opml/parser.go
Normal file
42
src/opml/parser.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package opml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
)
|
||||
|
||||
type OPML struct {
|
||||
XMLName xml.Name `xml:"opml"`
|
||||
Version string `xml:"version,attr"`
|
||||
Outlines []Outline `xml:"body>outline"`
|
||||
}
|
||||
|
||||
type Outline struct {
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Title string `xml:"text,attr"`
|
||||
FeedURL string `xml:"xmlUrl,attr,omitempty"`
|
||||
SiteURL string `xml:"htmlUrl,attr,omitempty"`
|
||||
Description string `xml:"description,attr,omitempty"`
|
||||
Outlines []Outline `xml:"outline,omitempty"`
|
||||
}
|
||||
|
||||
func (o Outline) AllFeeds() []Outline {
|
||||
result := make([]Outline, 0)
|
||||
for _, sub := range o.Outlines {
|
||||
if sub.Type == "rss" {
|
||||
result = append(result, sub)
|
||||
} else {
|
||||
result = append(result, sub.AllFeeds()...)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Parse(r io.Reader) (*OPML, error) {
|
||||
feeds := new(OPML)
|
||||
decoder := xml.NewDecoder(r)
|
||||
decoder.Entity = xml.HTMLEntity
|
||||
decoder.Strict = false
|
||||
err := decoder.Decode(&feeds)
|
||||
return feeds, err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue