Compare commits
No commits in common. "f11dee630d3563ce18110d54d5e14bc095e96076" and "b76e529ca3ce3005d94244380961e649fb10f682" have entirely different histories.
f11dee630d
...
b76e529ca3
47
atom.go
47
atom.go
@ -2,41 +2,30 @@ package atomfeed
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
EmailAddress string
|
||||
LanguageTag string
|
||||
MediaType string
|
||||
// URI string
|
||||
IRI string
|
||||
URI string
|
||||
)
|
||||
|
||||
// func isValidURL(uri URI) bool {
|
||||
// _, err := url.ParseRequestURI(string(uri))
|
||||
// return err == nil
|
||||
// }
|
||||
//
|
||||
// func isValidURN(uri URI) bool {
|
||||
// pattern := `\A(?i:urn:(?!urn:)(?<nid>[a-z0-9][a-z0-9-]{1,31}):(?<nss>(?:[-a-z0-9()+,.:=@;$_!*'&~\/]|%[0-9a-f]{2})+)(?:\?\+(?<rcomponent>.*?))?(?:\?=(?<qcomponent>.*?))?(?:#(?<fcomponent>.*?))?)\z`
|
||||
// return regexp.MustCompile(pattern).MatchString(string(uri))
|
||||
// }
|
||||
//
|
||||
// // isValidURI checks whether an URI is valid or not.
|
||||
// func isValidURI(uri URI) bool {
|
||||
// return isValidURL(uri) || isValidURN(uri)
|
||||
// }
|
||||
func isValidURL(uri URI) bool {
|
||||
_, err := url.ParseRequestURI(string(uri))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// isValidIRI checks whether an IRI is valid or not.
|
||||
// The used pattern stems from
|
||||
// https://www.w3.org/2011/04/XMLSchema/TypeLibrary-IRI-RFC3987.xsd
|
||||
func isValidIRI(iri IRI) bool {
|
||||
pattern := `((([A-Za-z])[A-Za-z0-9+\-\.]*):((//(((([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------!$&'()*+,;=:]|(%[0-9A-Fa-f][0-9A-Fa-f]))*@))?((\[((((([0-9A-Fa-f]{0,4}:)){6}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|(::(([0-9A-Fa-f]{0,4}:)){5}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|(([0-9A-Fa-f]{0,4})?::(([0-9A-Fa-f]{0,4}:)){4}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:))?[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:)){3}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,2}[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:)){2}(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,3}[0-9A-Fa-f]{0,4}))?::[0-9A-Fa-f]{0,4}:(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,4}[0-9A-Fa-f]{0,4}))?::(([0-9A-Fa-f]{0,4}:[0-9A-Fa-f]{0,4})|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))))|((((([0-9A-Fa-f]{0,4}:)){0,5}[0-9A-Fa-f]{0,4}))?::[0-9A-Fa-f]{0,4})|((((([0-9A-Fa-f]{0,4}:)){0,6}[0-9A-Fa-f]{0,4}))?::))|(v[0-9A-Fa-f]+\.[A-Za-z0-9\-\._~!$&'()*+,;=:]+))\])|(([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5]))\.([0-9]|([1-9][0-9])|(1([0-9]){2})|(2[0-4][0-9])|(25[0-5])))|(([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=]))*)((:[0-9]*))?)((/(([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*)|(/(((([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))+((/(([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*))?)|((([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))+((/(([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@]))*))*)|)((\?(([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@])|[---/?])*))?((#((([A-Za-z0-9\-\._~ -豈-﷏ﷰ-𐀀-𠀀-𰀀------------]|(%[0-9A-Fa-f][0-9A-Fa-f])|[!$&'()*+,;=:@])|/|\?))*))?)`
|
||||
return regexp.MustCompile(pattern).MatchString(string(iri))
|
||||
func isValidURN(uri URI) bool {
|
||||
pattern := `\A(?i:urn:(?!urn:)(?<nid>[a-z0-9][a-z0-9-]{1,31}):(?<nss>(?:[-a-z0-9()+,.:=@;$_!*'&~\/]|%[0-9a-f]{2})+)(?:\?\+(?<rcomponent>.*?))?(?:\?=(?<qcomponent>.*?))?(?:#(?<fcomponent>.*?))?)\z`
|
||||
return regexp.MustCompile(pattern).MatchString(string(uri))
|
||||
}
|
||||
|
||||
func isValidURI(uri URI) bool {
|
||||
return isValidURL(uri) || isValidURN(uri)
|
||||
}
|
||||
|
||||
func isCorrectlyEscaped(text string) bool {
|
||||
@ -68,13 +57,3 @@ func isXMLMediaType(mediaType string) bool {
|
||||
|
||||
return strings.HasSuffix(mediaType, "/xml") || strings.HasSuffix(mediaType, "+xml")
|
||||
}
|
||||
|
||||
func isValidMediaType(mediaType string) bool {
|
||||
_, _, err := mime.ParseMediaType(mediaType)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isValidLanguageTag(tag LanguageTag) bool {
|
||||
_, err := language.Parse(string(tag))
|
||||
return err == nil
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ type Category struct {
|
||||
*CommonAttributes
|
||||
Content Content `xml:"content"` // undefinedContent in RFC4287
|
||||
Term string `xml:"term,attr"`
|
||||
Scheme IRI `xml:"scheme,attr,omitempty"`
|
||||
Scheme URI `xml:"scheme,attr,omitempty"`
|
||||
Label string `xml:"label,attr,omitempty"`
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func (c *Category) Check() error {
|
||||
}
|
||||
|
||||
if c.Scheme != "" {
|
||||
if !isValidIRI(c.Scheme) {
|
||||
if !isValidURI(c.Scheme) {
|
||||
return fmt.Errorf("scheme attribute %v of category not correctly formatted", c.Scheme)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package atomfeed
|
||||
import "fmt"
|
||||
|
||||
type CommonAttributes struct {
|
||||
Base IRI `xml:"base,attr,omitempty"`
|
||||
Base URI `xml:"base,attr,omitempty"`
|
||||
Lang LanguageTag `xml:"lang,attr,omitempty"`
|
||||
UndefinedAttributes []*ExtensionAttribute `xml:",any"`
|
||||
}
|
||||
|
14
entry.go
14
entry.go
@ -1,10 +1,10 @@
|
||||
package atomfeed
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// It is advisable that each atom:entry element contain a non-empty atom:title
|
||||
@ -58,16 +58,8 @@ func alternateRelExists(l []*Link) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// AddExtension adds the extension to the entry
|
||||
func (e *Entry) AddExtension(x *ExtensionElement) {
|
||||
if e.Extensions == nil {
|
||||
e.Extensions = make([]*ExtensionElement, 1)
|
||||
e.Extensions[0] = x
|
||||
} else {
|
||||
e.Extensions = append(e.Extensions, x)
|
||||
}
|
||||
|
||||
e.Updated.DateTime = DateTime(time.Now())
|
||||
func (e *Entry) AddExtension(name string, value any) {
|
||||
e.Extensions = append(e.Extensions, &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value})
|
||||
}
|
||||
|
||||
func (e *Entry) Check() error {
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
type Generator struct {
|
||||
*CommonAttributes
|
||||
URI IRI `xml:"uri,attr,omitempty"`
|
||||
URI URI `xml:"uri,attr,omitempty"`
|
||||
Version string `xml:"version,attr,omitempty"`
|
||||
Text string `xml:"text"`
|
||||
}
|
||||
@ -19,7 +19,7 @@ func NewGenerator(text string) *Generator {
|
||||
|
||||
func (g *Generator) Check() error {
|
||||
if g.URI != "" {
|
||||
if !isValidIRI(g.URI) {
|
||||
if !isValidURI(g.URI) {
|
||||
return fmt.Errorf("uri attribute %v of generator not correctly formatted", g.URI)
|
||||
}
|
||||
}
|
||||
|
5
go.mod
5
go.mod
@ -2,7 +2,4 @@ module streifling.com/jason/atom-feed
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
golang.org/x/text v0.19.0
|
||||
)
|
||||
require github.com/google/uuid v1.6.0
|
||||
|
2
go.sum
2
go.sum
@ -1,4 +1,2 @@
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
|
6
icon.go
6
icon.go
@ -7,18 +7,18 @@ import (
|
||||
|
||||
type Icon struct {
|
||||
*CommonAttributes
|
||||
URI IRI `xml:"uri"`
|
||||
URI URI `xml:"uri"`
|
||||
}
|
||||
|
||||
func NewIcon(uri string) *Icon {
|
||||
return &Icon{URI: IRI(uri)}
|
||||
return &Icon{URI: URI(uri)}
|
||||
}
|
||||
|
||||
func (i *Icon) Check() error {
|
||||
if i.URI == "" {
|
||||
return errors.New("uri element of icon empty")
|
||||
} else {
|
||||
if !isValidIRI(i.URI) {
|
||||
if !isValidURI(i.URI) {
|
||||
return fmt.Errorf("uri attribute %v of icon not correctly formatted", i.URI)
|
||||
}
|
||||
}
|
||||
|
6
id.go
6
id.go
@ -9,18 +9,18 @@ import (
|
||||
|
||||
type ID struct {
|
||||
*CommonAttributes
|
||||
URI IRI `xml:"uri"`
|
||||
URI URI `xml:"uri"`
|
||||
}
|
||||
|
||||
func NewID() *ID {
|
||||
return &ID{URI: IRI(fmt.Sprint("urn:uuid:", uuid.New()))}
|
||||
return &ID{URI: URI(fmt.Sprint("urn:uuid:", uuid.New()))}
|
||||
}
|
||||
|
||||
func (i *ID) Check() error {
|
||||
if i.URI == "" {
|
||||
return errors.New("uri element of id empty")
|
||||
} else {
|
||||
if !isValidIRI(i.URI) {
|
||||
if !isValidURI(i.URI) {
|
||||
return fmt.Errorf("uri element %v of id not correctly formatted", i.URI)
|
||||
}
|
||||
}
|
||||
|
27
link.go
27
link.go
@ -3,14 +3,13 @@ package atomfeed
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
*CommonAttributes
|
||||
Title string `xml:"title,attr,omitempty"`
|
||||
Title Text `xml:"title,attr,omitempty"`
|
||||
Content Content `xml:"content"` // undefinedContent in RFC4287
|
||||
Href IRI `xml:"href,attr"`
|
||||
Href URI `xml:"href,attr"`
|
||||
Rel string `xml:"rel,attr,omitempty"`
|
||||
Type MediaType `xml:"type,attr,omitempty"`
|
||||
HrefLang LanguageTag `xml:"hreflang,attr,omitempty"`
|
||||
@ -23,30 +22,20 @@ func NewLink(href string) (*Link, error) {
|
||||
return nil, fmt.Errorf("error creating content element: %v", err)
|
||||
}
|
||||
|
||||
return &Link{Href: IRI(href), Content: content}, nil
|
||||
return &Link{Href: URI(href), Content: content}, nil
|
||||
}
|
||||
|
||||
func (l *Link) Check() error {
|
||||
if l.Href == "" {
|
||||
return errors.New("href attribute of link empty")
|
||||
} else {
|
||||
if !isValidIRI(l.Href) {
|
||||
return fmt.Errorf("href attribute %v of link not correctly formatted", l.Href)
|
||||
}
|
||||
|
||||
if l.Title != nil {
|
||||
if err := l.Title.Check(); err != nil {
|
||||
return fmt.Errorf("title attribute of link %v: %v", l.Href, err)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(l.Rel, ":") || !isValidIRI(IRI(l.Rel)) {
|
||||
return fmt.Errorf("rel attribute %v of link %v not correctly formatted", l.Rel, l.Href)
|
||||
}
|
||||
|
||||
if !isValidMediaType(string(l.Type)) {
|
||||
return fmt.Errorf("type attribute %v of link %v invalid media type", l.Type, l.Href)
|
||||
}
|
||||
|
||||
if !isValidLanguageTag(l.HrefLang) {
|
||||
return fmt.Errorf("hreflang attribute %v of link %v invalid language tag", l.Type, l.HrefLang)
|
||||
}
|
||||
|
||||
if l.Content == nil {
|
||||
return fmt.Errorf("no content element of link %v", l.Href)
|
||||
} else {
|
||||
|
2
logo.go
2
logo.go
@ -4,7 +4,7 @@ import "errors"
|
||||
|
||||
type Logo struct {
|
||||
*CommonAttributes
|
||||
URI IRI `xml:"uri"`
|
||||
URI URI `xml:"uri"`
|
||||
}
|
||||
|
||||
func (l *Logo) Check() error {
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
type OutOfLineContent struct {
|
||||
*CommonAttributes
|
||||
Type MediaType `xml:"type,attr,omitempty"`
|
||||
SRC IRI `xml:"src,attr"`
|
||||
SRC URI `xml:"src,attr"`
|
||||
}
|
||||
|
||||
func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, error) {
|
||||
@ -22,11 +22,11 @@ func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, erro
|
||||
return nil, fmt.Errorf("content type %T incompatible with out of line content", content)
|
||||
}
|
||||
|
||||
if !isValidIRI(content.(IRI)) {
|
||||
if !isValidURI(content.(URI)) {
|
||||
return nil, errors.New("content not a valid uri")
|
||||
}
|
||||
|
||||
return &OutOfLineContent{Type: MediaType(mediaType), SRC: content.(IRI)}, nil
|
||||
return &OutOfLineContent{Type: MediaType(mediaType), SRC: content.(URI)}, nil
|
||||
}
|
||||
|
||||
func (o *OutOfLineContent) isContent() bool { return true }
|
||||
|
15
person.go
15
person.go
@ -1,6 +1,7 @@
|
||||
package atomfeed
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
@ -9,7 +10,7 @@ import (
|
||||
type Person struct {
|
||||
*CommonAttributes
|
||||
Name string `xml:"name"`
|
||||
URI IRI `xml:"uri,omitempty"`
|
||||
URI URI `xml:"uri,omitempty"`
|
||||
Email EmailAddress `xml:"email,omitempty"`
|
||||
Extensions []*ExtensionElement `xml:",any,omitempty"`
|
||||
}
|
||||
@ -18,14 +19,8 @@ func NewPerson(name string) *Person {
|
||||
return &Person{Name: name}
|
||||
}
|
||||
|
||||
// AddExtension adds the extension to the person
|
||||
func (p *Person) AddExtension(e *ExtensionElement) {
|
||||
if p.Extensions == nil {
|
||||
p.Extensions = make([]*ExtensionElement, 1)
|
||||
p.Extensions[0] = e
|
||||
} else {
|
||||
p.Extensions = append(p.Extensions, e)
|
||||
}
|
||||
func (p *Person) AddExtension(name string, value any) {
|
||||
p.Extensions = append(p.Extensions, &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value})
|
||||
}
|
||||
|
||||
func (p *Person) Check() error {
|
||||
@ -34,7 +29,7 @@ func (p *Person) Check() error {
|
||||
}
|
||||
|
||||
if p.URI != "" {
|
||||
if !isValidIRI(p.URI) {
|
||||
if !isValidURI(p.URI) {
|
||||
return fmt.Errorf("uri element of person %v not correctly formatted", p.Name)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ type PlainText struct {
|
||||
Text string `xml:"text"`
|
||||
}
|
||||
|
||||
func (p *PlainText) isText() bool { return true }
|
||||
func (p *PlainText) IsText() bool { return true }
|
||||
|
||||
func (p *PlainText) Check() error {
|
||||
if p.Type != "" && p.Type != "text" && p.Type != "html" {
|
||||
|
2
text.go
2
text.go
@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
type Text interface {
|
||||
isText() bool
|
||||
Check() error
|
||||
IsText() bool
|
||||
}
|
||||
|
||||
func NewText(textType, content string) (Text, error) {
|
||||
|
@ -17,7 +17,7 @@ type XHTMLText struct {
|
||||
XHTMLDiv XHTMLDiv
|
||||
}
|
||||
|
||||
func (x *XHTMLText) isText() bool { return true }
|
||||
func (x *XHTMLText) IsText() bool { return true }
|
||||
|
||||
func (x *XHTMLText) Check() error {
|
||||
if x.Type != "xhtml" {
|
||||
|
Loading…
x
Reference in New Issue
Block a user