123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686 |
- package gofeed
- import (
- "fmt"
- "strings"
- "time"
- "github.com/mmcdole/gofeed/atom"
- "github.com/mmcdole/gofeed/extensions"
- "github.com/mmcdole/gofeed/internal/shared"
- "github.com/mmcdole/gofeed/rss"
- )
- // Translator converts a particular feed (atom.Feed or rss.Feed)
- // into the generic Feed struct
- type Translator interface {
- Translate(feed interface{}) (*Feed, error)
- }
- // DefaultRSSTranslator converts an rss.Feed struct
- // into the generic Feed struct.
- //
- // This default implementation defines a set of
- // mapping rules between rss.Feed -> Feed
- // for each of the fields in Feed.
- type DefaultRSSTranslator struct{}
- // Translate converts an RSS feed into the universal
- // feed type.
- func (t *DefaultRSSTranslator) Translate(feed interface{}) (*Feed, error) {
- rss, found := feed.(*rss.Feed)
- if !found {
- return nil, fmt.Errorf("Feed did not match expected type of *rss.Feed")
- }
- result := &Feed{}
- result.Title = t.translateFeedTitle(rss)
- result.Description = t.translateFeedDescription(rss)
- result.Link = t.translateFeedLink(rss)
- result.FeedLink = t.translateFeedFeedLink(rss)
- result.Updated = t.translateFeedUpdated(rss)
- result.UpdatedParsed = t.translateFeedUpdatedParsed(rss)
- result.Published = t.translateFeedPublished(rss)
- result.PublishedParsed = t.translateFeedPublishedParsed(rss)
- result.Author = t.translateFeedAuthor(rss)
- result.Language = t.translateFeedLanguage(rss)
- result.Image = t.translateFeedImage(rss)
- result.Copyright = t.translateFeedCopyright(rss)
- result.Generator = t.translateFeedGenerator(rss)
- result.Categories = t.translateFeedCategories(rss)
- result.Items = t.translateFeedItems(rss)
- result.ITunesExt = rss.ITunesExt
- result.DublinCoreExt = rss.DublinCoreExt
- result.Extensions = rss.Extensions
- result.FeedVersion = rss.Version
- result.FeedType = "rss"
- return result, nil
- }
- func (t *DefaultRSSTranslator) translateFeedItem(rssItem *rss.Item) (item *Item) {
- item = &Item{}
- item.Title = t.translateItemTitle(rssItem)
- item.Description = t.translateItemDescription(rssItem)
- item.Content = t.translateItemContent(rssItem)
- item.Link = t.translateItemLink(rssItem)
- item.Published = t.translateItemPublished(rssItem)
- item.PublishedParsed = t.translateItemPublishedParsed(rssItem)
- item.Author = t.translateItemAuthor(rssItem)
- item.GUID = t.translateItemGUID(rssItem)
- item.Image = t.translateItemImage(rssItem)
- item.Categories = t.translateItemCategories(rssItem)
- item.Enclosures = t.translateItemEnclosures(rssItem)
- item.DublinCoreExt = rssItem.DublinCoreExt
- item.ITunesExt = rssItem.ITunesExt
- item.Extensions = rssItem.Extensions
- return
- }
- func (t *DefaultRSSTranslator) translateFeedTitle(rss *rss.Feed) (title string) {
- if rss.Title != "" {
- title = rss.Title
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Title != nil {
- title = t.firstEntry(rss.DublinCoreExt.Title)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedDescription(rss *rss.Feed) (desc string) {
- return rss.Description
- }
- func (t *DefaultRSSTranslator) translateFeedLink(rss *rss.Feed) (link string) {
- if rss.Link != "" {
- link = rss.Link
- } else if rss.ITunesExt != nil && rss.ITunesExt.Subtitle != "" {
- link = rss.ITunesExt.Subtitle
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedFeedLink(rss *rss.Feed) (link string) {
- atomExtensions := t.extensionsForKeys([]string{"atom", "atom10", "atom03"}, rss.Extensions)
- for _, ex := range atomExtensions {
- if links, ok := ex["link"]; ok {
- for _, l := range links {
- if l.Attrs["Rel"] == "self" {
- link = l.Value
- }
- }
- }
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedUpdated(rss *rss.Feed) (updated string) {
- if rss.LastBuildDate != "" {
- updated = rss.LastBuildDate
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Date != nil {
- updated = t.firstEntry(rss.DublinCoreExt.Date)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedUpdatedParsed(rss *rss.Feed) (updated *time.Time) {
- if rss.LastBuildDateParsed != nil {
- updated = rss.LastBuildDateParsed
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Date != nil {
- dateText := t.firstEntry(rss.DublinCoreExt.Date)
- date, err := shared.ParseDate(dateText)
- if err == nil {
- updated = &date
- }
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedPublished(rss *rss.Feed) (published string) {
- return rss.PubDate
- }
- func (t *DefaultRSSTranslator) translateFeedPublishedParsed(rss *rss.Feed) (published *time.Time) {
- return rss.PubDateParsed
- }
- func (t *DefaultRSSTranslator) translateFeedAuthor(rss *rss.Feed) (author *Person) {
- if rss.ManagingEditor != "" {
- name, address := shared.ParseNameAddress(rss.ManagingEditor)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rss.WebMaster != "" {
- name, address := shared.ParseNameAddress(rss.WebMaster)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Author != nil {
- dcAuthor := t.firstEntry(rss.DublinCoreExt.Author)
- name, address := shared.ParseNameAddress(dcAuthor)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Creator != nil {
- dcCreator := t.firstEntry(rss.DublinCoreExt.Creator)
- name, address := shared.ParseNameAddress(dcCreator)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rss.ITunesExt != nil && rss.ITunesExt.Author != "" {
- name, address := shared.ParseNameAddress(rss.ITunesExt.Author)
- author = &Person{}
- author.Name = name
- author.Email = address
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedLanguage(rss *rss.Feed) (language string) {
- if rss.Language != "" {
- language = rss.Language
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Language != nil {
- language = t.firstEntry(rss.DublinCoreExt.Language)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedImage(rss *rss.Feed) (image *Image) {
- if rss.Image != nil {
- image = &Image{}
- image.Title = rss.Image.Title
- image.URL = rss.Image.URL
- } else if rss.ITunesExt != nil && rss.ITunesExt.Image != "" {
- image = &Image{}
- image.URL = rss.ITunesExt.Image
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedCopyright(rss *rss.Feed) (rights string) {
- if rss.Copyright != "" {
- rights = rss.Copyright
- } else if rss.DublinCoreExt != nil && rss.DublinCoreExt.Rights != nil {
- rights = t.firstEntry(rss.DublinCoreExt.Rights)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedGenerator(rss *rss.Feed) (generator string) {
- return rss.Generator
- }
- func (t *DefaultRSSTranslator) translateFeedCategories(rss *rss.Feed) (categories []string) {
- cats := []string{}
- if rss.Categories != nil {
- for _, c := range rss.Categories {
- cats = append(cats, c.Value)
- }
- }
- if rss.ITunesExt != nil && rss.ITunesExt.Keywords != "" {
- keywords := strings.Split(rss.ITunesExt.Keywords, ",")
- for _, k := range keywords {
- cats = append(cats, k)
- }
- }
- if rss.ITunesExt != nil && rss.ITunesExt.Categories != nil {
- for _, c := range rss.ITunesExt.Categories {
- cats = append(cats, c.Text)
- if c.Subcategory != nil {
- cats = append(cats, c.Subcategory.Text)
- }
- }
- }
- if rss.DublinCoreExt != nil && rss.DublinCoreExt.Subject != nil {
- for _, c := range rss.DublinCoreExt.Subject {
- cats = append(cats, c)
- }
- }
- if len(cats) > 0 {
- categories = cats
- }
- return
- }
- func (t *DefaultRSSTranslator) translateFeedItems(rss *rss.Feed) (items []*Item) {
- items = []*Item{}
- for _, i := range rss.Items {
- items = append(items, t.translateFeedItem(i))
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemTitle(rssItem *rss.Item) (title string) {
- if rssItem.Title != "" {
- title = rssItem.Title
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Title != nil {
- title = t.firstEntry(rssItem.DublinCoreExt.Title)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemDescription(rssItem *rss.Item) (desc string) {
- if rssItem.Description != "" {
- desc = rssItem.Description
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Description != nil {
- desc = t.firstEntry(rssItem.DublinCoreExt.Description)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemContent(rssItem *rss.Item) (content string) {
- return rssItem.Content
- }
- func (t *DefaultRSSTranslator) translateItemLink(rssItem *rss.Item) (link string) {
- return rssItem.Link
- }
- func (t *DefaultRSSTranslator) translateItemUpdated(rssItem *rss.Item) (updated string) {
- if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Date != nil {
- updated = t.firstEntry(rssItem.DublinCoreExt.Date)
- }
- return updated
- }
- func (t *DefaultRSSTranslator) translateItemUpdatedParsed(rssItem *rss.Item) (updated *time.Time) {
- if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Date != nil {
- updatedText := t.firstEntry(rssItem.DublinCoreExt.Date)
- updatedDate, err := shared.ParseDate(updatedText)
- if err == nil {
- updated = &updatedDate
- }
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemPublished(rssItem *rss.Item) (pubDate string) {
- if rssItem.PubDate != "" {
- return rssItem.PubDate
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Date != nil {
- return t.firstEntry(rssItem.DublinCoreExt.Date)
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemPublishedParsed(rssItem *rss.Item) (pubDate *time.Time) {
- if rssItem.PubDateParsed != nil {
- return rssItem.PubDateParsed
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Date != nil {
- pubDateText := t.firstEntry(rssItem.DublinCoreExt.Date)
- pubDateParsed, err := shared.ParseDate(pubDateText)
- if err == nil {
- pubDate = &pubDateParsed
- }
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemAuthor(rssItem *rss.Item) (author *Person) {
- if rssItem.Author != "" {
- name, address := shared.ParseNameAddress(rssItem.Author)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Author != nil {
- dcAuthor := t.firstEntry(rssItem.DublinCoreExt.Author)
- name, address := shared.ParseNameAddress(dcAuthor)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Creator != nil {
- dcCreator := t.firstEntry(rssItem.DublinCoreExt.Creator)
- name, address := shared.ParseNameAddress(dcCreator)
- author = &Person{}
- author.Name = name
- author.Email = address
- } else if rssItem.ITunesExt != nil && rssItem.ITunesExt.Author != "" {
- name, address := shared.ParseNameAddress(rssItem.ITunesExt.Author)
- author = &Person{}
- author.Name = name
- author.Email = address
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemGUID(rssItem *rss.Item) (guid string) {
- if rssItem.GUID != nil {
- guid = rssItem.GUID.Value
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemImage(rssItem *rss.Item) (image *Image) {
- if rssItem.ITunesExt != nil && rssItem.ITunesExt.Image != "" {
- image = &Image{}
- image.URL = rssItem.ITunesExt.Image
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemCategories(rssItem *rss.Item) (categories []string) {
- cats := []string{}
- if rssItem.Categories != nil {
- for _, c := range rssItem.Categories {
- cats = append(cats, c.Value)
- }
- }
- if rssItem.ITunesExt != nil && rssItem.ITunesExt.Keywords != "" {
- keywords := strings.Split(rssItem.ITunesExt.Keywords, ",")
- for _, k := range keywords {
- cats = append(cats, k)
- }
- }
- if rssItem.DublinCoreExt != nil && rssItem.DublinCoreExt.Subject != nil {
- for _, c := range rssItem.DublinCoreExt.Subject {
- cats = append(cats, c)
- }
- }
- if len(cats) > 0 {
- categories = cats
- }
- return
- }
- func (t *DefaultRSSTranslator) translateItemEnclosures(rssItem *rss.Item) (enclosures []*Enclosure) {
- if rssItem.Enclosure != nil {
- e := &Enclosure{}
- e.URL = rssItem.Enclosure.URL
- e.Type = rssItem.Enclosure.Type
- e.Length = rssItem.Enclosure.Length
- enclosures = []*Enclosure{e}
- }
- return
- }
- func (t *DefaultRSSTranslator) extensionsForKeys(keys []string, extensions ext.Extensions) (matches []map[string][]ext.Extension) {
- matches = []map[string][]ext.Extension{}
- if extensions == nil {
- return
- }
- for _, key := range keys {
- if match, ok := extensions[key]; ok {
- matches = append(matches, match)
- }
- }
- return
- }
- func (t *DefaultRSSTranslator) firstEntry(entries []string) (value string) {
- if entries == nil {
- return
- }
- if len(entries) == 0 {
- return
- }
- return entries[0]
- }
- // DefaultAtomTranslator converts an atom.Feed struct
- // into the generic Feed struct.
- //
- // This default implementation defines a set of
- // mapping rules between atom.Feed -> Feed
- // for each of the fields in Feed.
- type DefaultAtomTranslator struct{}
- // Translate converts an Atom feed into the universal
- // feed type.
- func (t *DefaultAtomTranslator) Translate(feed interface{}) (*Feed, error) {
- atom, found := feed.(*atom.Feed)
- if !found {
- return nil, fmt.Errorf("Feed did not match expected type of *atom.Feed")
- }
- result := &Feed{}
- result.Title = t.translateFeedTitle(atom)
- result.Description = t.translateFeedDescription(atom)
- result.Link = t.translateFeedLink(atom)
- result.FeedLink = t.translateFeedFeedLink(atom)
- result.Updated = t.translateFeedUpdated(atom)
- result.UpdatedParsed = t.translateFeedUpdatedParsed(atom)
- result.Author = t.translateFeedAuthor(atom)
- result.Language = t.translateFeedLanguage(atom)
- result.Image = t.translateFeedImage(atom)
- result.Copyright = t.translateFeedCopyright(atom)
- result.Categories = t.translateFeedCategories(atom)
- result.Generator = t.translateFeedGenerator(atom)
- result.Items = t.translateFeedItems(atom)
- result.Extensions = atom.Extensions
- result.FeedVersion = atom.Version
- result.FeedType = "atom"
- return result, nil
- }
- func (t *DefaultAtomTranslator) translateFeedItem(entry *atom.Entry) (item *Item) {
- item = &Item{}
- item.Title = t.translateItemTitle(entry)
- item.Description = t.translateItemDescription(entry)
- item.Content = t.translateItemContent(entry)
- item.Link = t.translateItemLink(entry)
- item.Updated = t.translateItemUpdated(entry)
- item.UpdatedParsed = t.translateItemUpdatedParsed(entry)
- item.Published = t.translateItemPublished(entry)
- item.PublishedParsed = t.translateItemPublishedParsed(entry)
- item.Author = t.translateItemAuthor(entry)
- item.GUID = t.translateItemGUID(entry)
- item.Image = t.translateItemImage(entry)
- item.Categories = t.translateItemCategories(entry)
- item.Enclosures = t.translateItemEnclosures(entry)
- item.Extensions = entry.Extensions
- return
- }
- func (t *DefaultAtomTranslator) translateFeedTitle(atom *atom.Feed) (title string) {
- return atom.Title
- }
- func (t *DefaultAtomTranslator) translateFeedDescription(atom *atom.Feed) (desc string) {
- return atom.Subtitle
- }
- func (t *DefaultAtomTranslator) translateFeedLink(atom *atom.Feed) (link string) {
- l := t.firstLinkWithType("alternate", atom.Links)
- if l != nil {
- link = l.Href
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedFeedLink(atom *atom.Feed) (link string) {
- feedLink := t.firstLinkWithType("self", atom.Links)
- if feedLink != nil {
- link = feedLink.Href
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedUpdated(atom *atom.Feed) (updated string) {
- return atom.Updated
- }
- func (t *DefaultAtomTranslator) translateFeedUpdatedParsed(atom *atom.Feed) (updated *time.Time) {
- return atom.UpdatedParsed
- }
- func (t *DefaultAtomTranslator) translateFeedAuthor(atom *atom.Feed) (author *Person) {
- a := t.firstPerson(atom.Authors)
- if a != nil {
- feedAuthor := Person{}
- feedAuthor.Name = a.Name
- feedAuthor.Email = a.Email
- author = &feedAuthor
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedLanguage(atom *atom.Feed) (language string) {
- return atom.Language
- }
- func (t *DefaultAtomTranslator) translateFeedImage(atom *atom.Feed) (image *Image) {
- if atom.Logo != "" {
- feedImage := Image{}
- feedImage.URL = atom.Logo
- image = &feedImage
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedCopyright(atom *atom.Feed) (rights string) {
- return atom.Rights
- }
- func (t *DefaultAtomTranslator) translateFeedGenerator(atom *atom.Feed) (generator string) {
- if atom.Generator != nil {
- if atom.Generator.Value != "" {
- generator += atom.Generator.Value
- }
- if atom.Generator.Version != "" {
- generator += " v" + atom.Generator.Version
- }
- if atom.Generator.URI != "" {
- generator += " " + atom.Generator.URI
- }
- generator = strings.TrimSpace(generator)
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedCategories(atom *atom.Feed) (categories []string) {
- if atom.Categories != nil {
- categories = []string{}
- for _, c := range atom.Categories {
- categories = append(categories, c.Term)
- }
- }
- return
- }
- func (t *DefaultAtomTranslator) translateFeedItems(atom *atom.Feed) (items []*Item) {
- items = []*Item{}
- for _, entry := range atom.Entries {
- items = append(items, t.translateFeedItem(entry))
- }
- return
- }
- func (t *DefaultAtomTranslator) translateItemTitle(entry *atom.Entry) (title string) {
- return entry.Title
- }
- func (t *DefaultAtomTranslator) translateItemDescription(entry *atom.Entry) (desc string) {
- return entry.Summary
- }
- func (t *DefaultAtomTranslator) translateItemContent(entry *atom.Entry) (content string) {
- if entry.Content != nil {
- content = entry.Content.Value
- }
- return
- }
- func (t *DefaultAtomTranslator) translateItemLink(entry *atom.Entry) (link string) {
- l := t.firstLinkWithType("alternate", entry.Links)
- if l != nil {
- link = l.Href
- }
- return
- }
- func (t *DefaultAtomTranslator) translateItemUpdated(entry *atom.Entry) (updated string) {
- return entry.Updated
- }
- func (t *DefaultAtomTranslator) translateItemUpdatedParsed(entry *atom.Entry) (updated *time.Time) {
- return entry.UpdatedParsed
- }
- func (t *DefaultAtomTranslator) translateItemPublished(entry *atom.Entry) (updated string) {
- return entry.Published
- }
- func (t *DefaultAtomTranslator) translateItemPublishedParsed(entry *atom.Entry) (updated *time.Time) {
- return entry.PublishedParsed
- }
- func (t *DefaultAtomTranslator) translateItemAuthor(entry *atom.Entry) (author *Person) {
- a := t.firstPerson(entry.Authors)
- if a != nil {
- author = &Person{}
- author.Name = a.Name
- author.Email = a.Email
- }
- return
- }
- func (t *DefaultAtomTranslator) translateItemGUID(entry *atom.Entry) (guid string) {
- return entry.ID
- }
- func (t *DefaultAtomTranslator) translateItemImage(entry *atom.Entry) (image *Image) {
- return nil
- }
- func (t *DefaultAtomTranslator) translateItemCategories(entry *atom.Entry) (categories []string) {
- if entry.Categories != nil {
- categories = []string{}
- for _, c := range entry.Categories {
- categories = append(categories, c.Term)
- }
- }
- return
- }
- func (t *DefaultAtomTranslator) translateItemEnclosures(entry *atom.Entry) (enclosures []*Enclosure) {
- if entry.Links != nil {
- enclosures = []*Enclosure{}
- for _, e := range entry.Links {
- if e.Rel == "enclosure" {
- enclosure := &Enclosure{}
- enclosure.URL = e.Href
- enclosure.Length = e.Length
- enclosure.Type = e.Type
- enclosures = append(enclosures, enclosure)
- }
- }
- if len(enclosures) == 0 {
- enclosures = nil
- }
- }
- return
- }
- func (t *DefaultAtomTranslator) firstLinkWithType(linkType string, links []*atom.Link) *atom.Link {
- if links == nil {
- return nil
- }
- for _, link := range links {
- if link.Rel == linkType {
- return link
- }
- }
- return nil
- }
- func (t *DefaultAtomTranslator) firstPerson(persons []*atom.Person) (person *atom.Person) {
- if persons == nil || len(persons) == 0 {
- return
- }
- person = persons[0]
- return
- }
|