Add <head> tag to OPML export
This commit is contained in:
parent
47b47cc32c
commit
0f6f4c8c60
4 changed files with 79 additions and 66 deletions
|
@ -8,21 +8,49 @@ import (
|
|||
"encoding/xml"
|
||||
)
|
||||
|
||||
type opml struct {
|
||||
XMLName xml.Name `xml:"opml"`
|
||||
Version string `xml:"version,attr"`
|
||||
Outlines []outline `xml:"body>outline"`
|
||||
// Specs: http://opml.org/spec2.opml
|
||||
type opmlDocument struct {
|
||||
XMLName xml.Name `xml:"opml"`
|
||||
Version string `xml:"version,attr"`
|
||||
Header opmlHeader `xml:"head"`
|
||||
Outlines []opmlOutline `xml:"body>outline"`
|
||||
}
|
||||
|
||||
type outline struct {
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
Text string `xml:"text,attr"`
|
||||
FeedURL string `xml:"xmlUrl,attr,omitempty"`
|
||||
SiteURL string `xml:"htmlUrl,attr,omitempty"`
|
||||
Outlines []outline `xml:"outline,omitempty"`
|
||||
func NewOPMLDocument() *opmlDocument {
|
||||
return &opmlDocument{}
|
||||
}
|
||||
|
||||
func (o *outline) GetTitle() string {
|
||||
func (o *opmlDocument) GetSubscriptionList() SubcriptionList {
|
||||
var subscriptions SubcriptionList
|
||||
for _, outline := range o.Outlines {
|
||||
if len(outline.Outlines) > 0 {
|
||||
for _, element := range outline.Outlines {
|
||||
// outline.Text is only available in OPML v2.
|
||||
subscriptions = element.Append(subscriptions, outline.Text)
|
||||
}
|
||||
} else {
|
||||
subscriptions = outline.Append(subscriptions, "")
|
||||
}
|
||||
}
|
||||
|
||||
return subscriptions
|
||||
}
|
||||
|
||||
type opmlHeader struct {
|
||||
Title string `xml:"title,omitempty"`
|
||||
DateCreated string `xml:"dateCreated,omitempty"`
|
||||
OwnerName string `xml:"ownerName,omitempty"`
|
||||
}
|
||||
|
||||
type opmlOutline struct {
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
Text string `xml:"text,attr"`
|
||||
FeedURL string `xml:"xmlUrl,attr,omitempty"`
|
||||
SiteURL string `xml:"htmlUrl,attr,omitempty"`
|
||||
Outlines []opmlOutline `xml:"outline,omitempty"`
|
||||
}
|
||||
|
||||
func (o *opmlOutline) GetTitle() string {
|
||||
if o.Title != "" {
|
||||
return o.Title
|
||||
}
|
||||
|
@ -42,7 +70,7 @@ func (o *outline) GetTitle() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (o *outline) GetSiteURL() string {
|
||||
func (o *opmlOutline) GetSiteURL() string {
|
||||
if o.SiteURL != "" {
|
||||
return o.SiteURL
|
||||
}
|
||||
|
@ -50,7 +78,7 @@ func (o *outline) GetSiteURL() string {
|
|||
return o.FeedURL
|
||||
}
|
||||
|
||||
func (o *outline) Append(subscriptions SubcriptionList, category string) SubcriptionList {
|
||||
func (o *opmlOutline) Append(subscriptions SubcriptionList, category string) SubcriptionList {
|
||||
if o.FeedURL != "" {
|
||||
subscriptions = append(subscriptions, &Subcription{
|
||||
Title: o.GetTitle(),
|
||||
|
@ -62,19 +90,3 @@ func (o *outline) Append(subscriptions SubcriptionList, category string) Subcrip
|
|||
|
||||
return subscriptions
|
||||
}
|
||||
|
||||
func (o *opml) Transform() SubcriptionList {
|
||||
var subscriptions SubcriptionList
|
||||
for _, outline := range o.Outlines {
|
||||
if len(outline.Outlines) > 0 {
|
||||
for _, element := range outline.Outlines {
|
||||
// outline.Text is only available in OPML v2.
|
||||
subscriptions = element.Append(subscriptions, outline.Text)
|
||||
}
|
||||
} else {
|
||||
subscriptions = outline.Append(subscriptions, "")
|
||||
}
|
||||
}
|
||||
|
||||
return subscriptions
|
||||
}
|
||||
|
|
|
@ -14,16 +14,16 @@ import (
|
|||
|
||||
// Parse reads an OPML file and returns a SubcriptionList.
|
||||
func Parse(data io.Reader) (SubcriptionList, *errors.LocalizedError) {
|
||||
feeds := new(opml)
|
||||
opmlDocument := NewOPMLDocument()
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.Entity = xml.HTMLEntity
|
||||
decoder.Strict = false
|
||||
decoder.CharsetReader = encoding.CharsetReader
|
||||
|
||||
err := decoder.Decode(feeds)
|
||||
err := decoder.Decode(opmlDocument)
|
||||
if err != nil {
|
||||
return nil, errors.NewLocalizedError("Unable to parse OPML file: %q", err)
|
||||
}
|
||||
|
||||
return feeds.Transform(), nil
|
||||
return opmlDocument.GetSubscriptionList(), nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/xml"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"miniflux.app/logger"
|
||||
)
|
||||
|
@ -19,10 +20,10 @@ func Serialize(subscriptions SubcriptionList) string {
|
|||
writer := bufio.NewWriter(&b)
|
||||
writer.WriteString(xml.Header)
|
||||
|
||||
feeds := normalizeFeeds(subscriptions)
|
||||
opmlDocument := convertSubscriptionsToOPML(subscriptions)
|
||||
encoder := xml.NewEncoder(writer)
|
||||
encoder.Indent(" ", " ")
|
||||
if err := encoder.Encode(feeds); err != nil {
|
||||
encoder.Indent("", " ")
|
||||
if err := encoder.Encode(opmlDocument); err != nil {
|
||||
logger.Error("[OPML:Serialize] %v", err)
|
||||
return ""
|
||||
}
|
||||
|
@ -30,6 +31,36 @@ func Serialize(subscriptions SubcriptionList) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func convertSubscriptionsToOPML(subscriptions SubcriptionList) *opmlDocument {
|
||||
opmlDocument := NewOPMLDocument()
|
||||
opmlDocument.Version = "2.0"
|
||||
opmlDocument.Header.Title = "Miniflux"
|
||||
opmlDocument.Header.DateCreated = time.Now().Format("Mon, 02 Jan 2006 15:04:05 MST")
|
||||
|
||||
groupedSubs := groupSubscriptionsByFeed(subscriptions)
|
||||
var categories []string
|
||||
for k := range groupedSubs {
|
||||
categories = append(categories, k)
|
||||
}
|
||||
sort.Strings(categories)
|
||||
|
||||
for _, categoryName := range categories {
|
||||
category := opmlOutline{Text: categoryName}
|
||||
for _, subscription := range groupedSubs[categoryName] {
|
||||
category.Outlines = append(category.Outlines, opmlOutline{
|
||||
Title: subscription.Title,
|
||||
Text: subscription.Title,
|
||||
FeedURL: subscription.FeedURL,
|
||||
SiteURL: subscription.SiteURL,
|
||||
})
|
||||
}
|
||||
|
||||
opmlDocument.Outlines = append(opmlDocument.Outlines, category)
|
||||
}
|
||||
|
||||
return opmlDocument
|
||||
}
|
||||
|
||||
func groupSubscriptionsByFeed(subscriptions SubcriptionList) map[string]SubcriptionList {
|
||||
groups := make(map[string]SubcriptionList)
|
||||
|
||||
|
@ -39,31 +70,3 @@ func groupSubscriptionsByFeed(subscriptions SubcriptionList) map[string]Subcript
|
|||
|
||||
return groups
|
||||
}
|
||||
|
||||
func normalizeFeeds(subscriptions SubcriptionList) *opml {
|
||||
feeds := new(opml)
|
||||
feeds.Version = "2.0"
|
||||
|
||||
groupedSubs := groupSubscriptionsByFeed(subscriptions)
|
||||
var categories []string
|
||||
for k := range groupedSubs {
|
||||
categories = append(categories, k)
|
||||
}
|
||||
sort.Strings(categories)
|
||||
|
||||
for _, categoryName := range categories {
|
||||
category := outline{Text: categoryName}
|
||||
for _, subscription := range groupedSubs[categoryName] {
|
||||
category.Outlines = append(category.Outlines, outline{
|
||||
Title: subscription.Title,
|
||||
Text: subscription.Title,
|
||||
FeedURL: subscription.FeedURL,
|
||||
SiteURL: subscription.SiteURL,
|
||||
})
|
||||
}
|
||||
|
||||
feeds.Outlines = append(feeds.Outlines, category)
|
||||
}
|
||||
|
||||
return feeds
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ package opml // import "miniflux.app/reader/opml"
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -17,7 +16,6 @@ func TestSerialize(t *testing.T) {
|
|||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: "Category 2"})
|
||||
|
||||
output := Serialize(subscriptions)
|
||||
fmt.Println(output)
|
||||
feeds, err := Parse(bytes.NewBufferString(output))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
@ -56,7 +54,7 @@ func TestNormalizedCategoriesOrder(t *testing.T) {
|
|||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: orderTests[1].naturalOrderName})
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: orderTests[2].naturalOrderName})
|
||||
|
||||
feeds := normalizeFeeds(subscriptions)
|
||||
feeds := convertSubscriptionsToOPML(subscriptions)
|
||||
|
||||
for i, o := range orderTests {
|
||||
if feeds.Outlines[i].Text != o.correctOrderName {
|
||||
|
|
Loading…
Reference in a new issue