Compare commits

..

No commits in common. "f11dee630d3563ce18110d54d5e14bc095e96076" and "b76e529ca3ce3005d94244380961e649fb10f682" have entirely different histories.

16 changed files with 48 additions and 98 deletions

47
atom.go
View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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"`
}

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
View File

@ -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)
}
}

23
link.go
View File

@ -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,28 +22,18 @@ 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 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 l.Title != nil {
if err := l.Title.Check(); err != nil {
return fmt.Errorf("title attribute of link %v: %v", l.Href, err)
}
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 {

View File

@ -4,7 +4,7 @@ import "errors"
type Logo struct {
*CommonAttributes
URI IRI `xml:"uri"`
URI URI `xml:"uri"`
}
func (l *Logo) Check() error {

View File

@ -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 }

View File

@ -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)
}
}

View File

@ -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" {

View File

@ -6,8 +6,8 @@ import (
)
type Text interface {
isText() bool
Check() error
IsText() bool
}
func NewText(textType, content string) (Text, error) {

View File

@ -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" {