Compare commits

..

No commits in common. "f4dfd6d0606fdac70d51935db3175d790225c112" and "574e2331a9eb4fe329af70425e2743e30ee6113e" have entirely different histories.

24 changed files with 226 additions and 404 deletions

View File

@ -1,24 +1,23 @@
# atom # atom
An extensible Atom feed generator library that aims to be very close to RFC4287. An extensible Atom feed generator library that aims to be very close to
It diligently checks for compliance with the standard and provides functions for RFC4287. It diligently checks for compliance with the standard and provides
easy creation and extension of elements. functions for easy creation and extension of elements.
## Installation ## Installation
To install the latest version of the module, use the following command: To install the latest version of the module, use the following command:
```sh ```
go get git.streifling.com/jason/atom@latest go get git.streifling.com/jason/atom@latest
``` ```
## Usage ## Usage
This library provides convenient functions to safely create and extend elements This library provides easy to use functions to create and extend elements of an
and attributes of an Atom feed. This is because it can be hard to know all Atom feed. The intended way of using it entails using these functions.
pitfalls of RFC4287. The intended way of using atom is with these functions.
```go ```
package main package main
import ( import (
@ -61,10 +60,10 @@ func main() {
} }
``` ```
It is also possible to use this library in a way similar to what other libraries It is also possible to use this library in a way similar to what other
would provide. This is, of course, making it easier to make mistakes. libraries would provide. This, of course, making it easier to make mistakes.
```go ```
package main package main
import ( import (
@ -115,9 +114,9 @@ func main() {
} }
``` ```
The output of both ways of using it is an RFC4287 compliant Atom feed: The output of both ways of using it being an RFC4287 compliant Atom feed:
```xml ```
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
<author> <author>

31
atom.go
View File

@ -10,11 +10,18 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
type (
EmailAddress string
LanguageTag string
MediaType string
IRI string
)
// isValidIRI checks whether an IRI is valid or not. It returns a bool. // isValidIRI checks whether an IRI is valid or not. It returns a bool.
// https://www.w3.org/2011/04/XMLSchema/TypeLibrary-IRI-RFC3987.xsd // https://www.w3.org/2011/04/XMLSchema/TypeLibrary-IRI-RFC3987.xsd
func isValidIRI(iri string) bool { 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])|[!$&'()*+,;=:@])|/|\?))*))?)` 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(iri) return regexp.MustCompile(pattern).MatchString(string(iri))
} }
// isCorrectlyEscaped checks whether a string is correctly escaped as per // isCorrectlyEscaped checks whether a string is correctly escaped as per
@ -33,8 +40,8 @@ func isCorrectlyEscaped(text string) bool {
// isCompositeMediaType checks whether a string is a composite media type. It // isCompositeMediaType checks whether a string is a composite media type. It
// returns a bool. // returns a bool.
func isCompositeMediaType(m string) bool { func isCompositeMediaType(mediaType string) bool {
mediaType, _, err := mime.ParseMediaType(m) mediaType, _, err := mime.ParseMediaType(mediaType)
if err != nil { if err != nil {
return false return false
} }
@ -44,8 +51,8 @@ func isCompositeMediaType(m string) bool {
// isXMLMediaType checks whether a string is an xml media type. It returns a // isXMLMediaType checks whether a string is an xml media type. It returns a
// bool. // bool.
func isXMLMediaType(m string) bool { func isXMLMediaType(mediaType string) bool {
mediaType, _, err := mime.ParseMediaType(m) mediaType, _, err := mime.ParseMediaType(mediaType)
if err != nil { if err != nil {
return false return false
} }
@ -55,8 +62,8 @@ func isXMLMediaType(m string) bool {
// isValidMediaType checks whether a string is a valid media type. It returns a // isValidMediaType checks whether a string is a valid media type. It returns a
// bool. // bool.
func isValidMediaType(m string) bool { func isValidMediaType(mediaType string) bool {
mediaType, _, err := mime.ParseMediaType(m) mediaType, _, err := mime.ParseMediaType(mediaType)
if err != nil { if err != nil {
return false return false
} }
@ -70,8 +77,8 @@ func isValidMediaType(m string) bool {
} }
// isValidLanguageTag checks whether a LanguageTag is valid. It returns a bool. // isValidLanguageTag checks whether a LanguageTag is valid. It returns a bool.
func isValidLanguageTag(languageTag string) bool { func isValidLanguageTag(tag LanguageTag) bool {
_, err := language.Parse(languageTag) _, err := language.Parse(string(tag))
return err == nil return err == nil
} }
@ -82,6 +89,6 @@ func isValidAttribute(attribute string) bool {
} }
// NewURN generates an new valid IRI based on a UUID. It returns an IRI. // NewURN generates an new valid IRI based on a UUID. It returns an IRI.
func NewURN() string { func NewURN() IRI {
return fmt.Sprint("urn:uuid:", uuid.New()) return IRI(fmt.Sprint("urn:uuid:", uuid.New()))
} }

View File

@ -11,64 +11,35 @@ type Category struct {
XMLName xml.Name `xml:"category"` XMLName xml.Name `xml:"category"`
*CommonAttributes *CommonAttributes
Term string `xml:"term,attr"` Term string `xml:"term,attr"`
Scheme string `xml:"scheme,attr,omitempty"` // IRI Scheme IRI `xml:"scheme,attr,omitempty"`
Label string `xml:"label,attr,omitempty"` Label string `xml:"label,attr,omitempty"`
} }
// NewCategory creates a new Category. It returns a *Category and an error. // NewCategory creates a new Category. It returns a *Category.
func NewCategory(term string) (*Category, error) { func NewCategory(term string) *Category {
if term == "" { return &Category{Term: term}
return nil, errors.New("error creating new category: term string empty")
}
return &Category{Term: term}, nil
} }
// SetTerm sets the Term attribute of the Category. It returns an error. // SetLabel sets the label of the Category.
func (c *Category) SetTerm(t string) error { func (c *Category) SetLabel(label string) {
if t == "" {
return errors.New("error setting term of category: t string empty")
}
c.Term = t
return nil
}
// SetScheme sets the Scheme attribute of the Category. It returns an error.
func (c *Category) SetScheme(s string) error {
if !isValidIRI(s) {
return fmt.Errorf("scheme %v not correctly formatted", s)
}
c.Scheme = s
return nil
}
// SetLabel sets the Label attribute of the Category. It returns an error.
func (c *Category) SetLabel(label string) error {
if label == "" {
return errors.New("error setting label of category: label string empty")
}
c.Label = html.UnescapeString(label) c.Label = html.UnescapeString(label)
return nil
} }
// Check checks the Category for incompatibilities with RFC4287. It returns an // Check checks the Category for incompatibilities with RFC4287. It returns an
// error. // error.
func (c *Category) Check() error { func (c *Category) Check() error {
if c.Term == "" { if c.Term == "" {
return fmt.Errorf("term attribute of category %v empty", c) return errors.New("term attribute of category empty")
} }
if c.Scheme != "" { if c.Scheme != "" {
if !isValidIRI(c.Scheme) { if !isValidIRI(c.Scheme) {
return fmt.Errorf("scheme attribute of category %v not correctly formatted", c) return fmt.Errorf("scheme attribute %v of category not correctly formatted", c.Scheme)
} }
} }
if !isCorrectlyEscaped(c.Label) { if !isCorrectlyEscaped(c.Label) {
return fmt.Errorf("label attribute of category %v not correctly escaped", c) return fmt.Errorf("label attribute %v of category not correctly escaped", c.Label)
} }
return nil return nil

View File

@ -2,12 +2,11 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
) )
type CommonAttributes struct { type CommonAttributes struct {
Base string `xml:"base,attr,omitempty"` // IRI Base IRI `xml:"base,attr,omitempty"`
Lang string `xml:"lang,attr,omitempty"` // LanguageTag Lang LanguageTag `xml:"lang,attr,omitempty"`
UndefinedAttributes []*xml.Attr `xml:",attr,omitempty"` UndefinedAttributes []*xml.Attr `xml:",attr,omitempty"`
} }
@ -17,21 +16,12 @@ func NewCommonAttributes() *CommonAttributes {
return new(CommonAttributes) return new(CommonAttributes)
} }
// AddAttribute adds the Attribute to the CommonAttributes. It returns an error. // AddExtensionAttribute adds the ExtensionAttribute to the CommonAttributes.
func (c *CommonAttributes) AddAttribute(name, value string) error { func (c *CommonAttributes) AddExtensionAttribute(name, value string) {
if name == "" {
return errors.New("error adding attribute: name string empty")
}
if value == "" {
return errors.New("error adding attribute: value string empty")
}
if c.UndefinedAttributes == nil { if c.UndefinedAttributes == nil {
c.UndefinedAttributes = make([]*xml.Attr, 1) c.UndefinedAttributes = make([]*xml.Attr, 1)
c.UndefinedAttributes[0] = &xml.Attr{Name: xml.Name{Local: name}, Value: value} c.UndefinedAttributes[0] = &xml.Attr{Name: xml.Name{Local: name}, Value: value}
} else { } else {
c.UndefinedAttributes = append(c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value}) c.UndefinedAttributes = append(c.UndefinedAttributes, &xml.Attr{Name: xml.Name{Local: name}, Value: value})
} }
return nil
} }

View File

@ -1,6 +1,8 @@
package atom package atom
import "fmt" import (
"fmt"
)
const ( const (
InlineText = iota InlineText = iota
@ -28,6 +30,6 @@ func NewContent(contentType int, mediaType string, content any) (Content, error)
case 3: case 3:
return newOutOfLineContent(mediaType, content) return newOutOfLineContent(mediaType, content)
default: default:
return nil, fmt.Errorf("error creating new content: %v is not a valid text type", contentType) return nil, fmt.Errorf("%v is not a valid text type", contentType)
} }
} }

View File

@ -13,7 +13,7 @@ type Date struct {
// DateTime formats a time.Time to string formated as defined by RFC3339. It // DateTime formats a time.Time to string formated as defined by RFC3339. It
// returns a string. // returns a string.
func DateTime(t time.Time) string { func DateTime(t time.Time) string {
return t.Format(time.RFC3339) return string(t.Format(time.RFC3339))
} }
// NewDate creates a new Date. It returns a *Date. // NewDate creates a new Date. It returns a *Date.

View File

@ -40,16 +40,16 @@ func (e *Entry) checkAuthors(authorInFeed bool) error {
if e.Authors == nil { if e.Authors == nil {
if !authorInFeed { if !authorInFeed {
if e.Source == nil { if e.Source == nil {
return fmt.Errorf("no authors set in entry %v", e) return errors.New("no authors set in entry")
} }
if e.Source.Authors == nil { if e.Source.Authors == nil {
return fmt.Errorf("no authors set in entry %v", e) return errors.New("no authors set in entry")
} }
} }
} else { } else {
for i, a := range e.Authors { for i, a := range e.Authors {
if err := a.Check(); err != nil { if err := a.Check(); err != nil {
return fmt.Errorf("author element %v of entry %v: %v", i, e, err) return fmt.Errorf("author element %v of entry: %v", i, err)
} }
} }
} }
@ -76,12 +76,8 @@ func NewEntry(title string) (*Entry, error) {
}, nil }, nil
} }
// AddAuthor adds the Person as an author to the Entry. It returns an error. // AddAuthor adds the Person as an author to the Entry.
func (e *Entry) AddAuthor(p *Person) error { func (e *Entry) AddAuthor(p *Person) {
if p == nil {
return errors.New("error adding author element to entry: *Person is nil")
}
if e.Authors == nil { if e.Authors == nil {
e.Authors = make([]*Person, 1) e.Authors = make([]*Person, 1)
e.Authors[0] = p e.Authors[0] = p
@ -90,15 +86,10 @@ func (e *Entry) AddAuthor(p *Person) error {
} }
e.Updated.DateTime = DateTime(time.Now()) e.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddCategory adds the Category to the Entry. It returns an error. // AddCategory adds the Category to the Entry.
func (e *Entry) AddCategory(c *Category) error { func (e *Entry) AddCategory(c *Category) {
if c == nil {
return errors.New("error adding category element to entry: *Category is nil")
}
if e.Categories == nil { if e.Categories == nil {
e.Categories = make([]*Category, 1) e.Categories = make([]*Category, 1)
e.Categories[0] = c e.Categories[0] = c
@ -107,16 +98,10 @@ func (e *Entry) AddCategory(c *Category) error {
} }
e.Updated.DateTime = DateTime(time.Now()) e.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddContributor adds the Person as a contributor to the Entry. It returns an // AddContributor adds the Person as a contributor to the Entry.
// error. func (e *Entry) AddContributor(c *Person) {
func (e *Entry) AddContributor(c *Person) error {
if c == nil {
return errors.New("error adding contributor element to entry: *Person is nil")
}
if e.Contributors == nil { if e.Contributors == nil {
e.Contributors = make([]*Person, 1) e.Contributors = make([]*Person, 1)
e.Contributors[0] = c e.Contributors[0] = c
@ -125,15 +110,10 @@ func (e *Entry) AddContributor(c *Person) error {
} }
e.Updated.DateTime = DateTime(time.Now()) e.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddLink adds the Link to the Entry. It returns an error. // AddLink adds the Link to the Entry.
func (e *Entry) AddLink(l *Link) error { func (e *Entry) AddLink(l *Link) {
if l == nil {
return errors.New("error adding link element to entry: *Link is nil")
}
if e.Links == nil { if e.Links == nil {
e.Links = make([]*Link, 1) e.Links = make([]*Link, 1)
e.Links[0] = l e.Links[0] = l
@ -142,15 +122,10 @@ func (e *Entry) AddLink(l *Link) error {
} }
e.Updated.DateTime = DateTime(time.Now()) e.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddExtension adds the ExtensionElement to the Entry. It returns an error. // AddExtension adds the ExtensionElement to the Entry.
func (e *Entry) AddExtension(x *ExtensionElement) error { func (e *Entry) AddExtension(x *ExtensionElement) {
if x == nil {
return errors.New("error adding extension element to entry: *ExtensionElement is nil")
}
if e.Extensions == nil { if e.Extensions == nil {
e.Extensions = make([]*ExtensionElement, 1) e.Extensions = make([]*ExtensionElement, 1)
e.Extensions[0] = x e.Extensions[0] = x
@ -159,79 +134,78 @@ func (e *Entry) AddExtension(x *ExtensionElement) error {
} }
e.Updated.DateTime = DateTime(time.Now()) e.Updated.DateTime = DateTime(time.Now())
return nil
} }
// Check checks the Entry for incompatibilities with RFC4287. It returns an // Check checks the Entry for incompatibilities with RFC4287. It returns an
// error. // error.
func (e *Entry) Check() error { func (e *Entry) Check() error {
if e.ID == nil { if e.ID == nil {
return fmt.Errorf("no id element of entry %v", e) return errors.New("no id element of entry")
} else { } else {
if err := e.ID.Check(); err != nil { if err := e.ID.Check(); err != nil {
return fmt.Errorf("id element of entry %v: %v", e, err) return fmt.Errorf("id element of entry: %v", err)
} }
} }
if err := e.checkAuthors(true); err != nil { if err := e.checkAuthors(true); err != nil {
return fmt.Errorf("entry %v: %v", e, err) return fmt.Errorf("entry %v: %v", e.ID.URI, err)
} }
for i, c := range e.Categories { for i, c := range e.Categories {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("category element %v of entry %v: %v", i, e, err) return fmt.Errorf("category element %v of entry %v: %v", i, e.ID.URI, err)
} }
} }
if e.Content != nil { if e.Content != nil {
if err := e.Content.Check(); err != nil { if err := e.Content.Check(); err != nil {
return fmt.Errorf("content element of entry %v: %v", e, err) return fmt.Errorf("content element of entry %v: %v", e.ID.URI, err)
} }
} else { } else {
// atom:entry elements that contain no child atom:content element MUST // atom:entry elements that contain no child atom:content element MUST
// contain at least one atom:link element with a rel attribute value of // contain at least one atom:link element with a rel attribute value of
// "alternate". // "alternate".
if !alternateRelExists(e.Links) { if !alternateRelExists(e.Links) {
return fmt.Errorf("no content element of entry %v and no link element with rel \"alternate\"", e) return errors.New("no content element of entry %v and no link element with rel \"alternate\"")
} }
} }
for i, c := range e.Contributors { for i, c := range e.Contributors {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of entry %v: %v", i, e, err) return fmt.Errorf("contributor element %v of entry %v: %v", i, e.ID.URI, err)
} }
} }
for i, l := range e.Links { for i, l := range e.Links {
if err := l.Check(); err != nil { if err := l.Check(); err != nil {
return fmt.Errorf("link element %v of entry %v: %v", i, e, err) return fmt.Errorf("link element %v of entry %v: %v", i, e.ID.URI, err)
} }
} }
if hasAlternateDuplicateLinks(e.Links) { if hasAlternateDuplicateLinks(e.Links) {
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in entry %v", e) return fmt.Errorf("links with with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in entry %v", e.ID.URI)
} }
if e.Published != nil { if e.Published != nil {
if err := e.Published.Check(); err != nil { if err := e.Published.Check(); err != nil {
return fmt.Errorf("published element of entry %v: %v", e, err) return fmt.Errorf("published element of entry %v: %v", e.ID.URI, err)
} }
} }
if e.Rights != nil { if e.Rights != nil {
if err := e.Rights.Check(); err != nil { if err := e.Rights.Check(); err != nil {
return fmt.Errorf("rights element of entry %v: %v", e, err) return fmt.Errorf("rights element of entry %v: %v", e.ID.URI, err)
} }
} }
if e.Source != nil { if e.Source != nil {
if err := e.Source.Check(); err != nil { if err := e.Source.Check(); err != nil {
return fmt.Errorf("source element of entry %v: %v", e, err) return fmt.Errorf("source element of entry %v: %v", e.ID.URI, err)
} }
} }
if e.Summary != nil { if e.Summary != nil {
if err := e.Summary.Check(); err != nil { if err := e.Summary.Check(); err != nil {
return fmt.Errorf("summary element of entry %v: %v", e, err) return fmt.Errorf("summary element of entry %v: %v", e.ID.URI, err)
} }
} else { } else {
// atom:entry elements MUST contain an atom:summary element in either // atom:entry elements MUST contain an atom:summary element in either
@ -239,7 +213,7 @@ func (e *Entry) Check() error {
// the atom:entry contains an atom:content that has a "src" attribute // the atom:entry contains an atom:content that has a "src" attribute
// (and is thus empty). // (and is thus empty).
if e.Content.hasSRC() { if e.Content.hasSRC() {
return fmt.Errorf("no summary element of entry %v but content of type out of line content", e) return fmt.Errorf("no summary element of entry %v but content of type out of line content", e.ID.URI)
} }
// the atom:entry contains content that is encoded in Base64; i.e., the // the atom:entry contains content that is encoded in Base64; i.e., the
// "type" attribute of atom:content is a MIME media type [MIMEREG], but // "type" attribute of atom:content is a MIME media type [MIMEREG], but
@ -247,29 +221,29 @@ func (e *Entry) Check() error {
// does not end with "/xml" or "+xml". // does not end with "/xml" or "+xml".
mediaType := e.Content.getType() mediaType := e.Content.getType()
if isValidMediaType(mediaType) && !isXMLMediaType(mediaType) && !strings.HasPrefix(mediaType, "text/") { if isValidMediaType(mediaType) && !isXMLMediaType(mediaType) && !strings.HasPrefix(mediaType, "text/") {
return fmt.Errorf("no summary element of entry %v but media type not xml", e) return fmt.Errorf("no summary element of entry %v but media type not xml", e.ID.URI)
} }
} }
if e.Title == nil { if e.Title == nil {
return fmt.Errorf("no title element of entry %v", e) return fmt.Errorf("no title element of entry %v", e.ID.URI)
} else { } else {
if err := e.Title.Check(); err != nil { if err := e.Title.Check(); err != nil {
return fmt.Errorf("title element of entry %v: %v", e, err) return fmt.Errorf("title element of entry %v: %v", e.ID.URI, err)
} }
} }
if e.Updated == nil { if e.Updated == nil {
return fmt.Errorf("no updated element of entry %v", e) return fmt.Errorf("no updated element of entry %v", e.ID.URI)
} else { } else {
if err := e.Updated.Check(); err != nil { if err := e.Updated.Check(); err != nil {
return fmt.Errorf("updated element of entry %v: %v", e, err) return fmt.Errorf("updated element of entry %v: %v", e.ID.URI, err)
} }
} }
for i, x := range e.Extensions { for i, x := range e.Extensions {
if err := x.Check(); err != nil { if err := x.Check(); err != nil {
return fmt.Errorf("extension element %v of entry %v: %v", i, e, err) return fmt.Errorf("extension element %v of entry %v: %v", i, e.ID.URI, err)
} }
} }

View File

@ -11,27 +11,16 @@ type ExtensionElement struct {
} }
// NewExtensionElement creates a new ExtensionElement. It returns a // NewExtensionElement creates a new ExtensionElement. It returns a
// *ExtensionElement and an error. // *ExtensionElement.
func NewExtensionElement(name string, value any) (*ExtensionElement, error) { func NewExtensionElement(name string, value any) *ExtensionElement {
if name == "" { return &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value}
return nil, errors.New("error adding extension attribute: name string empty")
}
if value == "" {
return nil, errors.New("error adding extension attribute: value string empty")
}
return &ExtensionElement{XMLName: xml.Name{Local: name}, Value: value}, nil
} }
// Check checks the ExtensionElement for incompatibilities with RFC4287. It // Check checks the ExtensionElement for incompatibilities with RFC4287. It
// returns an error. // returns an error.
func (e *ExtensionElement) Check() error { func (e *ExtensionElement) Check() error {
if e.XMLName.Local == "" {
return errors.New("xml name of extension empty")
}
if e.Value == nil { if e.Value == nil {
return errors.New("value element of extension empty") return errors.New("value element of extension element empty")
} }
return nil return nil

94
feed.go
View File

@ -45,12 +45,8 @@ func NewFeed(title string) (*Feed, error) {
}, nil }, nil
} }
// AddAuthor adds the Person as an author to the Feed. It returns an error. // AddAuthor adds the Person as an author to the Feed.
func (f *Feed) AddAuthor(p *Person) error { func (f *Feed) AddAuthor(p *Person) {
if p == nil {
return errors.New("error adding author element to feed: *Person is nil")
}
if f.Authors == nil { if f.Authors == nil {
f.Authors = make([]*Person, 1) f.Authors = make([]*Person, 1)
f.Authors[0] = p f.Authors[0] = p
@ -59,15 +55,10 @@ func (f *Feed) AddAuthor(p *Person) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddCategory adds the Category to the Feed. It returns an error. // AddCategory adds the Category to the Feed.
func (f *Feed) AddCategory(c *Category) error { func (f *Feed) AddCategory(c *Category) {
if c == nil {
return errors.New("error adding category element to feed: *Category is nil")
}
if f.Categories == nil { if f.Categories == nil {
f.Categories = make([]*Category, 1) f.Categories = make([]*Category, 1)
f.Categories[0] = c f.Categories[0] = c
@ -76,16 +67,10 @@ func (f *Feed) AddCategory(c *Category) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddContributor adds the Person as a contributor to the Feed. It returns an // AddContributor adds the Person as a contributor to the Feed.
// error. func (f *Feed) AddContributor(c *Person) {
func (f *Feed) AddContributor(c *Person) error {
if c == nil {
return errors.New("error adding contributor element to feed: *Person is nil")
}
if f.Contributors == nil { if f.Contributors == nil {
f.Contributors = make([]*Person, 1) f.Contributors = make([]*Person, 1)
f.Contributors[0] = c f.Contributors[0] = c
@ -94,16 +79,10 @@ func (f *Feed) AddContributor(c *Person) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddLink adds the Link to the Feed. It returns an error. There should be one // AddLink adds the Link to the Feed. There should be one Link with Rel "self".
// Link with Rel "self". func (f *Feed) AddLink(l *Link) {
func (f *Feed) AddLink(l *Link) error {
if l == nil {
return errors.New("error adding link element to feed: *Link is nil")
}
if f.Links == nil { if f.Links == nil {
f.Links = make([]*Link, 1) f.Links = make([]*Link, 1)
f.Links[0] = l f.Links[0] = l
@ -112,15 +91,10 @@ func (f *Feed) AddLink(l *Link) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddExtension adds the Extension to the Feed. It returns an error. // AddExtension adds the Extension to the Feed.
func (f *Feed) AddExtension(e *ExtensionElement) error { func (f *Feed) AddExtension(e *ExtensionElement) {
if e == nil {
return errors.New("error adding extension element to feed: *ExtensionElement is nil")
}
if f.Extensions == nil { if f.Extensions == nil {
f.Extensions = make([]*ExtensionElement, 1) f.Extensions = make([]*ExtensionElement, 1)
f.Extensions[0] = e f.Extensions[0] = e
@ -129,15 +103,10 @@ func (f *Feed) AddExtension(e *ExtensionElement) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// AddEntry adds the Entry to the Feed. It returns an error. // AddEntry adds the Entry to the Feed.
func (f *Feed) AddEntry(e *Entry) error { func (f *Feed) AddEntry(e *Entry) {
if e == nil {
return errors.New("error adding entry element to feed: *Entry is nil")
}
if f.Entries == nil { if f.Entries == nil {
f.Entries = make([]*Entry, 1) f.Entries = make([]*Entry, 1)
f.Entries[0] = e f.Entries[0] = e
@ -146,17 +115,16 @@ func (f *Feed) AddEntry(e *Entry) error {
} }
f.Updated.DateTime = DateTime(time.Now()) f.Updated.DateTime = DateTime(time.Now())
return nil
} }
// Check checks the Feed for incompatibilities with RFC4287. It returns an // Check checks the Feed for incompatibilities with RFC4287. It returns an
// error. // error.
func (f *Feed) Check() error { func (f *Feed) Check() error {
if f.ID == nil { if f.ID == nil {
return fmt.Errorf("no id element of feed %v", f) return errors.New("no id element of feed")
} else { } else {
if err := f.ID.Check(); err != nil { if err := f.ID.Check(); err != nil {
return fmt.Errorf("id element of feed %v: %v", f, err) return fmt.Errorf("id element of feed: %v", err)
} }
} }
@ -166,93 +134,93 @@ func (f *Feed) Check() error {
if f.Authors == nil { if f.Authors == nil {
for _, e := range f.Entries { for _, e := range f.Entries {
if err := e.checkAuthors(false); err != nil { if err := e.checkAuthors(false); err != nil {
return fmt.Errorf("no authors set in feed %v: %v", f, err) return fmt.Errorf("no authors set in feed %v: %v", f.ID.URI, err)
} }
} }
} else { } else {
for i, a := range f.Authors { for i, a := range f.Authors {
if err := a.Check(); err != nil { if err := a.Check(); err != nil {
return fmt.Errorf("author element %v of feed %v: %v", i, f, err) return fmt.Errorf("author element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }
} }
for i, c := range f.Categories { for i, c := range f.Categories {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("category element %v of feed %v: %v", i, f, err) return fmt.Errorf("category element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }
for i, c := range f.Contributors { for i, c := range f.Contributors {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of feed %v: %v", i, f, err) return fmt.Errorf("contributor element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }
if f.Generator != nil { if f.Generator != nil {
if err := f.Generator.Check(); err != nil { if err := f.Generator.Check(); err != nil {
return fmt.Errorf("generator element of feed %v: %v", f, err) return fmt.Errorf("generator element of feed %v: %v", f.ID.URI, err)
} }
} }
if f.Icon != nil { if f.Icon != nil {
if err := f.Icon.Check(); err != nil { if err := f.Icon.Check(); err != nil {
return fmt.Errorf("icon element of feed %v: %v", f, err) return fmt.Errorf("icon element of feed %v: %v", f.ID.URI, err)
} }
} }
for i, l := range f.Links { for i, l := range f.Links {
if err := l.Check(); err != nil { if err := l.Check(); err != nil {
return fmt.Errorf("link element %v of feed %v: %v", i, f, err) return fmt.Errorf("link element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }
if hasAlternateDuplicateLinks(f.Links) { if hasAlternateDuplicateLinks(f.Links) {
return fmt.Errorf("links with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f) return fmt.Errorf("links with with a rel attribute value of \"alternate\" and duplicate type and hreflang attribute values found in feed %v", f.ID.URI)
} }
if f.Logo != nil { if f.Logo != nil {
if err := f.Logo.Check(); err != nil { if err := f.Logo.Check(); err != nil {
return fmt.Errorf("logo element of feed %v: %v", f, err) return fmt.Errorf("logo element of feed %v: %v", f.ID.URI, err)
} }
} }
if f.Rights != nil { if f.Rights != nil {
if err := f.Rights.Check(); err != nil { if err := f.Rights.Check(); err != nil {
return fmt.Errorf("rights element of feed %v: %v", f, err) return fmt.Errorf("rights element of feed %v: %v", f.ID.URI, err)
} }
} }
if f.Subtitle != nil { if f.Subtitle != nil {
if err := f.Subtitle.Check(); err != nil { if err := f.Subtitle.Check(); err != nil {
return fmt.Errorf("subtitle element of feed %v: %v", f, err) return fmt.Errorf("subtitle element of feed %v: %v", f.ID.URI, err)
} }
} }
if f.Title == nil { if f.Title == nil {
return fmt.Errorf("no title element of feed %v", f) return fmt.Errorf("no title element of feed %v", f.ID.URI)
} else { } else {
if err := f.Title.Check(); err != nil { if err := f.Title.Check(); err != nil {
return fmt.Errorf("title element of feed %v: %v", f, err) return fmt.Errorf("title element of feed %v: %v", f.ID.URI, err)
} }
} }
if f.Updated == nil { if f.Updated == nil {
return fmt.Errorf("no updated element of feed %v", f) return fmt.Errorf("no updated element of feed %v", f.ID)
} else { } else {
if err := f.Updated.Check(); err != nil { if err := f.Updated.Check(); err != nil {
return fmt.Errorf("updated element of feed %v: %v", f, err) return fmt.Errorf("updated element of feed %v: %v", f.ID.URI, err)
} }
} }
for i, x := range f.Extensions { for i, x := range f.Extensions {
if err := x.Check(); err != nil { if err := x.Check(); err != nil {
return fmt.Errorf("extension element %v of feed %v: %v", i, f, err) return fmt.Errorf("extension element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }
for i, n := range f.Entries { for i, n := range f.Entries {
if err := n.Check(); err != nil { if err := n.Check(); err != nil {
return fmt.Errorf("entry element %v of feed %v: %v", i, f, err) return fmt.Errorf("entry element %v of feed %v: %v", i, f.ID.URI, err)
} }
} }

View File

@ -10,27 +10,14 @@ import (
type Generator struct { type Generator struct {
XMLName xml.Name `xml:"generator"` XMLName xml.Name `xml:"generator"`
*CommonAttributes *CommonAttributes
URI string `xml:"uri,attr,omitempty"` // IRI URI IRI `xml:"uri,attr,omitempty"`
Version string `xml:"version,attr,omitempty"` Version string `xml:"version,attr,omitempty"`
Text string `xml:",chardata"` Text string `xml:",chardata"`
} }
// NewGenerator creates a new Generator. It returns a *Generator and an error. // NewGenerator creates a new Generator. It returns a *Generator.
func NewGenerator(text string) (*Generator, error) { func NewGenerator(text string) *Generator {
if text == "" { return &Generator{Text: html.UnescapeString(text)}
return nil, errors.New("error creating new generator: text string empty")
}
return &Generator{Text: html.UnescapeString(text)}, nil
}
// SetURI sets the URI attribute of the Generator. It returns an error.
func (g *Generator) SetURI(uri string) error {
if !isValidIRI(uri) {
return fmt.Errorf("uri %v not correctly formatted", g)
}
return nil
} }
// Check checks the Generator for incompatibilities with RFC4287. It returns an // Check checks the Generator for incompatibilities with RFC4287. It returns an
@ -38,16 +25,16 @@ func (g *Generator) SetURI(uri string) error {
func (g *Generator) Check() error { func (g *Generator) Check() error {
if g.URI != "" { if g.URI != "" {
if !isValidIRI(g.URI) { if !isValidIRI(g.URI) {
return fmt.Errorf("uri attribute of generator %v not correctly formatted", g) return fmt.Errorf("uri attribute %v of generator not correctly formatted", g.URI)
} }
} }
if g.Text == "" { if g.Text == "" {
return fmt.Errorf("text element of generator %v empty", g) return errors.New("text element of generator empty")
} }
if !isCorrectlyEscaped(g.Text) { if !isCorrectlyEscaped(g.Text) {
return fmt.Errorf("text element of generator %v not correctly escaped", g) return fmt.Errorf("text element %v of generator not correctly escaped", g.Text)
} }
return nil return nil

View File

@ -9,11 +9,11 @@ import (
type Icon struct { type Icon struct {
XMLName xml.Name `xml:"icon"` XMLName xml.Name `xml:"icon"`
*CommonAttributes *CommonAttributes
URI string `xml:",chardata"` // IRI URI IRI `xml:",chardata"`
} }
// NewIcon creates a new Icon. It returns a *Icon and an error. // NewIcon creates a new Icon. It returns a *Icon and an error.
func NewIcon(uri string) (*Icon, error) { func NewIcon(uri IRI) (*Icon, error) {
if !isValidIRI(uri) { if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri) return nil, fmt.Errorf("uri %v not correctly formatted", uri)
} }
@ -28,7 +28,7 @@ func (i *Icon) Check() error {
return errors.New("uri element of icon empty") return errors.New("uri element of icon empty")
} else { } else {
if !isValidIRI(i.URI) { if !isValidIRI(i.URI) {
return fmt.Errorf("uri attribute of icon %v not correctly formatted", i) return fmt.Errorf("uri attribute %v of icon not correctly formatted", i.URI)
} }
} }

8
id.go
View File

@ -9,16 +9,16 @@ import (
type ID struct { type ID struct {
XMLName xml.Name `xml:"id"` XMLName xml.Name `xml:"id"`
*CommonAttributes *CommonAttributes
URI string `xml:",chardata"` // IRI URI IRI `xml:",chardata"`
} }
// NewID creates a new ID. It returns a *ID and an error. // NewID creates a new ID. It returns a *ID and an error.
func NewID(uri string) (*ID, error) { func NewID(uri IRI) (*ID, error) {
if !isValidIRI(uri) { if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri) return nil, fmt.Errorf("uri %v not correctly formatted", uri)
} }
return &ID{URI: uri}, nil return &ID{URI: IRI(uri)}, nil
} }
// Check checks the ID for incompatibilities with RFC4287. It returns an error. // Check checks the ID for incompatibilities with RFC4287. It returns an error.
@ -27,7 +27,7 @@ func (i *ID) Check() error {
return errors.New("uri element of id empty") return errors.New("uri element of id empty")
} else { } else {
if !isValidIRI(i.URI) { if !isValidIRI(i.URI) {
return fmt.Errorf("uri element of id %v not correctly formatted", i) return fmt.Errorf("uri element %v of id not correctly formatted", i.URI)
} }
} }

View File

@ -2,6 +2,7 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"mime" "mime"
) )
@ -10,18 +11,17 @@ type InlineOtherContent struct {
XMLName xml.Name `xml:"content"` XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
AnyElement any `xml:",chardata"` AnyElement any `xml:",chardata"`
Type string `xml:"type,attr,omitempty"` // MediaType Type MediaType `xml:"type,attr,omitempty"`
} }
// newInlineOtherContent creates a new InlineOtherContent. It returns a // newInlineOtherContent creates a new InlineOtherContent. It returns a
// *InlineOtherContent and an error. // *InlineOtherContent and an error.
func newInlineOtherContent(mediaType string, content any) (*InlineOtherContent, error) { func newInlineOtherContent(mediaType string, content any) (*InlineOtherContent, error) {
if !isValidMediaType(mediaType) { if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil {
return nil, fmt.Errorf("error creating new inline other content: media type %v invalid", mediaType) return nil, fmt.Errorf("media type %v incompatible with inline other content", mediaType)
} }
mediaType, _, _ = mime.ParseMediaType(mediaType)
return &InlineOtherContent{Type: mediaType, AnyElement: content}, nil return &InlineOtherContent{Type: MediaType(mediaType), AnyElement: content}, nil
} }
// isContent checks whether the InlineOtherContent is a Content. It returns a // isContent checks whether the InlineOtherContent is a Content. It returns a
@ -33,19 +33,19 @@ func (i *InlineOtherContent) isContent() bool { return true }
func (i *InlineOtherContent) hasSRC() bool { return false } func (i *InlineOtherContent) hasSRC() bool { return false }
// getType returns the Type of the InlineOtherContent as a string. // getType returns the Type of the InlineOtherContent as a string.
func (i *InlineOtherContent) getType() string { return i.Type } func (i *InlineOtherContent) getType() string { return string(i.Type) }
// Check checks the InlineOtherContent for incompatibilities with RFC4287. It // Check checks the InlineOtherContent for incompatibilities with RFC4287. It
// returns an error. // returns an error.
func (i *InlineOtherContent) Check() error { func (i *InlineOtherContent) Check() error {
mediaType := i.getType() mediaType := i.getType()
if !isValidMediaType(mediaType) { if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil {
return fmt.Errorf("type attribute of inline other content %v invalid media type", i) return fmt.Errorf("type attribute %v incompatible with inline other content", mediaType)
} }
if isCompositeMediaType(mediaType) { if isCompositeMediaType(mediaType) {
return fmt.Errorf("type attribute of inline other content %v must not be a composite type", i) return errors.New("type attribute of inline other content must not be a composite type")
} }
return nil return nil

View File

@ -2,6 +2,7 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
) )
@ -42,7 +43,7 @@ func (i *InlineTextContent) getType() string { return i.Type }
// returns an error. // returns an error.
func (i *InlineTextContent) Check() error { func (i *InlineTextContent) Check() error {
if i.Type != "" && i.Type != "text" && i.Type != "html" { if i.Type != "" && i.Type != "text" && i.Type != "html" {
return fmt.Errorf("type attribute of inline text content %v must be text or html if not omitted", i) return errors.New("type attribute of inline text content must be text or html if not omitted")
} }
return nil return nil

View File

@ -2,6 +2,7 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
) )
@ -42,11 +43,11 @@ func (i *InlineXHTMLContent) getType() string { return i.Type }
// returns an error. // returns an error.
func (i *InlineXHTMLContent) Check() error { func (i *InlineXHTMLContent) Check() error {
if i.Type != "xhtml" { if i.Type != "xhtml" {
return fmt.Errorf("type attribute of inline xhtml content %v must be xhtml", i) return errors.New("type attribute of inline xhtml content must be xhtml")
} }
if err := i.XHTMLDiv.Check(); err != nil { if err := i.XHTMLDiv.Check(); err != nil {
return fmt.Errorf("xhtml div element of inline xhtml content %v: %v", i, err) return fmt.Errorf("xhtml div element %v of inline xhtml content %v: %v", i.XHTMLDiv, i, err)
} }
return nil return nil

49
link.go
View File

@ -2,6 +2,7 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"strings" "strings"
) )
@ -10,66 +11,44 @@ type Link struct {
XMLName xml.Name `xml:"link"` XMLName xml.Name `xml:"link"`
*CommonAttributes *CommonAttributes
Title string `xml:"title,attr,omitempty"` Title string `xml:"title,attr,omitempty"`
Href string `xml:"href,attr"` // IRI Href IRI `xml:"href,attr"`
Rel string `xml:"rel,attr,omitempty"` Rel string `xml:"rel,attr,omitempty"`
Type string `xml:"type,attr,omitempty"` // MediaType Type MediaType `xml:"type,attr,omitempty"`
HrefLang string `xml:"hreflang,attr,omitempty"` // LanguageTag HrefLang LanguageTag `xml:"hreflang,attr,omitempty"`
Length uint `xml:"length,attr,omitempty"` Length uint `xml:"length,attr,omitempty"`
} }
// NewLink creates a new Link. It returns a *Link and an error. // NewLink creates a new Link. It returns a *Link.
func NewLink(href string) (*Link, error) { func NewLink(href string) *Link {
if !isValidIRI(href) { return &Link{Href: IRI(href)}
return nil, fmt.Errorf("href %v not correctly formatted", href)
}
return &Link{Href: href}, nil
}
// SetType sets the Type attribute of the Link. It returns an error.
func (l *Link) SetType(t string) error {
if !isValidMediaType(t) {
return fmt.Errorf("type %v invalid media type", l)
}
return nil
}
// SetHrefLang sets the HrefLang attribute of the Link. It returns an error.
func (l *Link) SetHrefLang(h string) error {
if !isValidLanguageTag(h) {
return fmt.Errorf("hreflang %v invalid language tag", l)
}
return nil
} }
// Check checks the Link for incompatibilities with RFC4287. It returns an // Check checks the Link for incompatibilities with RFC4287. It returns an
// error. // error.
func (l *Link) Check() error { func (l *Link) Check() error {
if l.Href == "" { if l.Href == "" {
return fmt.Errorf("href attribute of link %v empty", l) return errors.New("href attribute of link empty")
} else { } else {
if !isValidIRI(l.Href) { if !isValidIRI(l.Href) {
return fmt.Errorf("href attribute of link %v not correctly formatted", l) return fmt.Errorf("href attribute %v of link not correctly formatted", l.Href)
} }
} }
if l.Rel != "" { if l.Rel != "" {
if strings.Contains(l.Rel, ":") && !isValidIRI(l.Rel) { if strings.Contains(l.Rel, ":") && !isValidIRI(IRI(l.Rel)) {
return fmt.Errorf("rel attribute of link %v not correctly formatted", l) return fmt.Errorf("rel attribute %v of link %v not correctly formatted", l.Rel, l.Href)
} }
} }
if l.Type != "" { if l.Type != "" {
if !isValidMediaType(l.Type) { if !isValidMediaType(string(l.Type)) {
return fmt.Errorf("type attribute of link %v invalid media type", l) return fmt.Errorf("type attribute %v of link %v invalid media type", l.Type, l.Href)
} }
} }
if l.HrefLang != "" { if l.HrefLang != "" {
if !isValidLanguageTag(l.HrefLang) { if !isValidLanguageTag(l.HrefLang) {
return fmt.Errorf("hreflang attribute of link %v invalid language tag", l) return fmt.Errorf("hreflang attribute %v of link %v invalid language tag", l.Type, l.HrefLang)
} }
} }

11
logo.go
View File

@ -2,17 +2,18 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
) )
type Logo struct { type Logo struct {
XMLName xml.Name `xml:"logo"` XMLName xml.Name `xml:"logo"`
*CommonAttributes *CommonAttributes
URI string `xml:",chardata"` // IRI URI IRI `xml:",chardata"`
} }
// NewLogo creates a new Logo. It returns a *Logo and an error. // NewLogo creates a new Logo. It returns a *Logo.
func NewLogo(uri string) (*Logo, error) { func NewLogo(uri IRI) (*Logo, error) {
if !isValidIRI(uri) { if !isValidIRI(uri) {
return nil, fmt.Errorf("uri %v not correctly formatted", uri) return nil, fmt.Errorf("uri %v not correctly formatted", uri)
} }
@ -24,10 +25,10 @@ func NewLogo(uri string) (*Logo, error) {
// error. // error.
func (l *Logo) Check() error { func (l *Logo) Check() error {
if l.URI == "" { if l.URI == "" {
return fmt.Errorf("uri element of logo %v empty", l) return errors.New("uri element of logo empty")
} else { } else {
if !isValidIRI(l.URI) { if !isValidIRI(l.URI) {
return fmt.Errorf("uri element of logo %v not correctly formatted", l) return fmt.Errorf("uri element %v of logo not correctly formatted", l.URI)
} }
} }

View File

@ -2,6 +2,7 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"mime" "mime"
) )
@ -9,28 +10,27 @@ import (
type OutOfLineContent struct { type OutOfLineContent struct {
XMLName xml.Name `xml:"content"` XMLName xml.Name `xml:"content"`
*CommonAttributes *CommonAttributes
Type string `xml:"type,attr,omitempty"` // MediaType Type MediaType `xml:"type,attr,omitempty"`
SRC string `xml:"src,attr"` // IRI SRC IRI `xml:"src,attr"`
} }
// newOutOfLineContent creates a new OutOfLineContent. It returns a // newOutOfLineContent creates a new OutOfLineContent. It returns a
// *OutOfLineContent and an error. // *OutOfLineContent and an error.
func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, error) { func newOutOfLineContent(mediaType string, content any) (*OutOfLineContent, error) {
if !isValidMediaType(mediaType) { if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil {
return nil, fmt.Errorf("error creating new out of line content: media type %v invalid", mediaType) return nil, fmt.Errorf("media type %v incompatible with out of line content", mediaType)
} }
mediaType, _, _ = mime.ParseMediaType(mediaType)
iri, ok := content.(string) iri, ok := content.(IRI)
if !ok { if !ok {
return nil, fmt.Errorf("content type %T incompatible with out of line content", content) return nil, fmt.Errorf("content type %T incompatible with out of line content", content)
} }
if !isValidIRI(iri) { if !isValidIRI(iri) {
return nil, fmt.Errorf("content %v not a valid uri", iri) return nil, errors.New("content not a valid uri")
} }
return &OutOfLineContent{Type: mediaType, SRC: iri}, nil return &OutOfLineContent{Type: MediaType(mediaType), SRC: iri}, nil
} }
// isContent checks whether the OutOfLineContent is a Content. It returns a // isContent checks whether the OutOfLineContent is a Content. It returns a
@ -42,23 +42,23 @@ func (o *OutOfLineContent) isContent() bool { return true }
func (o *OutOfLineContent) hasSRC() bool { return true } func (o *OutOfLineContent) hasSRC() bool { return true }
// getType returns the Type of the OutOfLineContent as a string. // getType returns the Type of the OutOfLineContent as a string.
func (o *OutOfLineContent) getType() string { return o.Type } func (o *OutOfLineContent) getType() string { return string(o.Type) }
// Check checks the OutOfLineContent for incompatibilities with RFC4287. It // Check checks the OutOfLineContent for incompatibilities with RFC4287. It
// returns an error. // returns an error.
func (o *OutOfLineContent) Check() error { func (o *OutOfLineContent) Check() error {
mediaType := o.getType() mediaType := o.getType()
if !isValidMediaType(mediaType) { if mediaType, _, err := mime.ParseMediaType(mediaType); err != nil {
return fmt.Errorf("type attribute of out of line content %v invalid media type", o) return fmt.Errorf("type attribute %v incompatible with out of line content", mediaType)
} }
if isCompositeMediaType(mediaType) { if isCompositeMediaType(mediaType) {
return fmt.Errorf("type attribute of out of line content %v must not be a composite type", o) return errors.New("type attribute of out of line content must not be a composite type")
} }
if o.SRC == "" { if o.SRC == "" {
return fmt.Errorf("src attribute of out of line content %v empty", o) return errors.New("src attribute of out of line content empty")
} }
return nil return nil

View File

@ -9,68 +9,49 @@ import (
type Person struct { type Person struct {
*CommonAttributes *CommonAttributes
Name string `xml:"name"` Name string `xml:"name"`
URI string `xml:"uri,omitempty"` // IRI URI IRI `xml:"uri,omitempty"`
Email string `xml:"email,omitempty"` // EmailAddress Email EmailAddress `xml:"email,omitempty"`
Extensions []*ExtensionElement `xml:",any,omitempty"` Extensions []*ExtensionElement `xml:",any,omitempty"`
} }
// NewPerson creates a new Person. It returns a *Person and an error. // NewPerson creates a new Person. It returns a *Person.
func NewPerson(name string) (*Person, error) { func NewPerson(name string) *Person {
if name == "" { return &Person{Name: name}
return nil, errors.New("error creating new person: name string empty")
}
return &Person{Name: name}, nil
} }
// SetURI sets the URI element of the Person. It returns an error. // AddExtension adds the Extension to the Person.
func (l *Link) SetURI(uri string) error { func (p *Person) AddExtension(e *ExtensionElement) {
if !isValidIRI(uri) {
return fmt.Errorf("uri %v not correctly formatted", uri)
}
return nil
}
// AddExtension adds the Extension to the Person. It returns an error.
func (p *Person) AddExtension(e *ExtensionElement) error {
if e == nil {
return errors.New("error adding extension element to person: *ExtensionElement is nil")
}
if p.Extensions == nil { if p.Extensions == nil {
p.Extensions = make([]*ExtensionElement, 1) p.Extensions = make([]*ExtensionElement, 1)
p.Extensions[0] = e p.Extensions[0] = e
} else { } else {
p.Extensions = append(p.Extensions, e) p.Extensions = append(p.Extensions, e)
} }
return nil
} }
// Check checks the Person for incompatibilities with RFC4287. It returns an // Check checks the Person for incompatibilities with RFC4287. It returns an
// error. // error.
func (p *Person) Check() error { func (p *Person) Check() error {
if p.Name == "" { if p.Name == "" {
return fmt.Errorf("name element of person %v empty", p) return errors.New("name element of person element empty")
} }
if p.URI != "" { if p.URI != "" {
if !isValidIRI(p.URI) { if !isValidIRI(p.URI) {
return fmt.Errorf("uri element of person %v not correctly formatted", p) return fmt.Errorf("uri element of person %v not correctly formatted", p.Name)
} }
} }
if p.Email != "" { if p.Email != "" {
if _, err := mail.ParseAddress(p.Email); err != nil { if _, err := mail.ParseAddress(string(p.Email)); err != nil {
return fmt.Errorf("email element of person %v not correctly formatted", p) return fmt.Errorf("email element of person %v not correctly formatted", p.Name)
} }
} }
if p.Extensions != nil { if p.Extensions != nil {
for i, e := range p.Extensions { for i, e := range p.Extensions {
if err := e.Check(); err != nil { if err := e.Check(); err != nil {
return fmt.Errorf("extension element %v of person %v: %v", i, p, err) return fmt.Errorf("extension element %v of person %v: %v", i, p.Name, err)
} }
} }
} }

View File

@ -1,9 +1,6 @@
package atom package atom
import ( import "errors"
"errors"
"fmt"
)
type PlainText struct { type PlainText struct {
*CommonAttributes *CommonAttributes
@ -14,28 +11,19 @@ type PlainText struct {
// isText checks whether the PlainText is a Text. It returns a bool. // isText checks whether the PlainText is a Text. It returns a bool.
func (p *PlainText) isText() bool { return true } func (p *PlainText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText and an error.
func newPlainText(textType, content string) (*PlainText, error) {
if content == "" {
return nil, errors.New("error creating new plain text: content string empty")
}
return &PlainText{Type: textType, Text: content}, nil
}
// Check checks the PlainText for incompatibilities with RFC4287. It returns an // Check checks the PlainText for incompatibilities with RFC4287. It returns an
// error. // error.
func (p *PlainText) Check() error { func (p *PlainText) Check() error {
if p.Type != "" && p.Type != "text" && p.Type != "html" { if p.Type != "" && p.Type != "text" && p.Type != "html" {
return fmt.Errorf("type attribute of plain text %v must be text or html if not omitted", p) return errors.New("type attribute of plain text must be text or html if not omitted")
} }
if p.Type == "html" && !isCorrectlyEscaped(p.Text) { if p.Type == "html" && !isCorrectlyEscaped(p.Text) {
return fmt.Errorf("text element of plain text %v not correctly escaped", p) return errors.New("text element of plain text not correctly escaped")
} }
if p.Text == "" { if p.Text == "" {
return fmt.Errorf("text element of plain text %v empty", p) return errors.New("text element of plain text empty")
} }
return nil return nil

View File

@ -23,89 +23,84 @@ type Source struct {
Extensions []*ExtensionElement `xml:",any,omitempty"` Extensions []*ExtensionElement `xml:",any,omitempty"`
} }
// NewSource creates a new Source. It returns a *Source.
func NewSource() *Source {
return new(Source)
}
// Check checks the Source for incompatibilities with RFC4287. It returns an // Check checks the Source for incompatibilities with RFC4287. It returns an
// error. // error.
func (s *Source) Check() error { func (s *Source) Check() error {
for i, a := range s.Authors { for i, a := range s.Authors {
if err := a.Check(); err != nil { if err := a.Check(); err != nil {
return fmt.Errorf("author element %v of source %v: %v", i, s, err) return fmt.Errorf("author element %v of source %v: %v", i, s.ID.URI, err)
} }
} }
for i, c := range s.Categories { for i, c := range s.Categories {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("category element %v of source %v: %v", i, s, err) return fmt.Errorf("category element %v of source %v: %v", i, s.ID.URI, err)
} }
} }
for i, c := range s.Contributors { for i, c := range s.Contributors {
if err := c.Check(); err != nil { if err := c.Check(); err != nil {
return fmt.Errorf("contributor element %v of source %v: %v", i, s, err) return fmt.Errorf("contributor element %v of source %v: %v", i, s.ID.URI, err)
} }
} }
if s.Generator != nil { if s.Generator != nil {
if err := s.Generator.Check(); err != nil { if err := s.Generator.Check(); err != nil {
return fmt.Errorf("generator element of source %v: %v", s, err) return fmt.Errorf("generator element of source %v: %v", s.ID.URI, err)
} }
} }
if s.Icon != nil { if s.Icon != nil {
if err := s.Icon.Check(); err != nil { if err := s.Icon.Check(); err != nil {
return fmt.Errorf("icon element of source %v: %v", s, err) return fmt.Errorf("icon element of source %v: %v", s.ID.URI, err)
} }
} }
if s.ID != nil { if s.ID != nil {
if err := s.ID.Check(); err != nil { if err := s.ID.Check(); err != nil {
return fmt.Errorf("id element of source %v: %v", s, err) return fmt.Errorf("id element of source %v: %v", s.ID.URI, err)
} }
} }
for i, l := range s.Links { for i, l := range s.Links {
if err := l.Check(); err != nil { if err := l.Check(); err != nil {
return fmt.Errorf("link element %v of source %v: %v", i, s, err) return fmt.Errorf("link element %v of source %v: %v", i, s.ID.URI, err)
} }
} }
if s.Logo != nil { if s.Logo != nil {
if err := s.Logo.Check(); err != nil { if err := s.Logo.Check(); err != nil {
return fmt.Errorf("logo element of source %v: %v", s, err) return fmt.Errorf("logo element of source %v: %v", s.ID.URI, err)
} }
} }
if s.Rights != nil { if s.Rights != nil {
if err := s.Rights.Check(); err != nil { if err := s.Rights.Check(); err != nil {
return fmt.Errorf("rights element of source %v: %v", s, err) return fmt.Errorf("rights element of source %v: %v", s.ID.URI, err)
} }
} }
if s.Subtitle != nil { if s.Subtitle != nil {
if err := s.Subtitle.Check(); err != nil { if err := s.Subtitle.Check(); err != nil {
return fmt.Errorf("subtitle element of source %v: %v", s, err) return fmt.Errorf("subtitle element of source %v: %v", s.ID.URI, err)
} }
} }
if s.Title != nil { if s.Title != nil {
if err := s.Title.Check(); err != nil { if err := s.Title.Check(); err != nil {
return fmt.Errorf("title element of source %v: %v", s, err) return fmt.Errorf("title element of source %v: %v", s.ID.URI, err)
} }
} }
if s.Updated != nil { if s.Updated != nil {
if err := s.Updated.Check(); err != nil { if err := s.Updated.Check(); err != nil {
return fmt.Errorf("updated element of source %v: %v", s, err) return fmt.Errorf("updated element of source %v: %v", s.ID.URI, err)
} }
} }
for i, e := range s.Extensions { for i, e := range s.Extensions {
if err := e.Check(); err != nil { if err := e.Check(); err != nil {
return fmt.Errorf("extension element %v of source %v: %v", i, s, err) return fmt.Errorf("extension element %v of source %v: %v", i, s.ID.URI, err)
} }
} }

12
text.go
View File

@ -14,11 +14,17 @@ type Text interface {
func NewText(textType, content string) (Text, error) { func NewText(textType, content string) (Text, error) {
switch textType { switch textType {
case "text", "": case "text", "":
return newPlainText(textType, content) return &PlainText{Type: textType, Text: content}, nil
case "html": case "html":
return newPlainText(textType, html.UnescapeString(content)) return &PlainText{Type: textType, Text: html.UnescapeString(content)}, nil
case "xhtml": case "xhtml":
return newXHTMLText(textType, content) return &XHTMLText{
Type: textType,
XHTMLDiv: &XHTMLDiv{
XMLNS: "http://www.w3.org/1999/xhtml",
Content: content,
},
}, nil
default: default:
return nil, fmt.Errorf("%v is not a valid text type", textType) return nil, fmt.Errorf("%v is not a valid text type", textType)
} }

View File

@ -3,7 +3,6 @@ package atom
import ( import (
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt"
) )
type XHTMLDiv struct { type XHTMLDiv struct {
@ -12,23 +11,19 @@ type XHTMLDiv struct {
Content string `xml:",innerxml"` Content string `xml:",innerxml"`
} }
// NewXHTMLDiv creates a new XHTMLDiv. It returns a *XHTMLDiv and an error. // NewXHTMLDiv creates a new XHTMLDiv. It returns a *XHTMLDiv.
func NewXHTMLDiv(content string) (*XHTMLDiv, error) { func NewXHTMLDiv(content string) *XHTMLDiv {
if content == "" {
return nil, errors.New("error creating new xhtml div: content string empty")
}
return &XHTMLDiv{ return &XHTMLDiv{
XMLNS: "http://www.w3.org/1999/xhtml", XMLNS: "http://www.w3.org/1999/xhtml",
Content: content, Content: content,
}, nil }
} }
// Check checks the XHTMLDiv for incompatibilities with RFC4287. It returns an // Check checks the XHTMLDiv for incompatibilities with RFC4287. It returns an
// error. // error.
func (x *XHTMLDiv) Check() error { func (x *XHTMLDiv) Check() error {
if x.XMLNS != "http://www.w3.org/1999/xhtml" { if x.XMLNS != "http://www.w3.org/1999/xhtml" {
return fmt.Errorf("xmlns attribute of xhtml text %v must be http://www.w3.org/1999/xhtml", x) return errors.New("xmlns attribute of xhtml text must be http://www.w3.org/1999/xhtml")
} }
return nil return nil

View File

@ -1,6 +1,7 @@
package atom package atom
import ( import (
"errors"
"fmt" "fmt"
) )
@ -13,24 +14,11 @@ type XHTMLText struct {
// isText checks whether the XHTMLText is a Text. It returns a bool. // isText checks whether the XHTMLText is a Text. It returns a bool.
func (x *XHTMLText) isText() bool { return true } func (x *XHTMLText) isText() bool { return true }
// newPlainText creates a new PlainText. It returns a *PlainText and an error.
func newXHTMLText(textType, content string) (*XHTMLText, error) {
xhtmlDiv, err := NewXHTMLDiv(content)
if err != nil {
return nil, fmt.Errorf("error creating new xhtml text: %v", err)
}
return &XHTMLText{
Type: textType,
XHTMLDiv: xhtmlDiv,
}, nil
}
// Check checks the XHTMLText for incompatibilities with RFC4287. It returns an // Check checks the XHTMLText for incompatibilities with RFC4287. It returns an
// error. // error.
func (x *XHTMLText) Check() error { func (x *XHTMLText) Check() error {
if x.Type != "xhtml" { if x.Type != "xhtml" {
return fmt.Errorf("type attribute of xhtml text %v must be xhtml", x) return errors.New("type attribute of xhtml text must be xhtml")
} }
if err := x.XHTMLDiv.Check(); err != nil { if err := x.XHTMLDiv.Check(); err != nil {