///|
fn atom_attribute_value(
element : @xml.NamespaceElement,
expected : String,
) -> String? {
for attribute in element.attributes {
if attribute.name.local_name == expected &&
attribute.name.namespace_uri is None {
return Some(attribute.value)
}
}
None
}
///|
// Preserve the declared value for consumers, but flag an invalid Atom Date
// Construct at the element that supplied it. No local timezone is inferred.
fn atom_date_value(
raw : String,
field : String,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> String? {
match normalize_date(Atom1, Some(raw)) {
Ok(_) => ()
Err(_) => diagnostics.push({ kind: InvalidAtomDate(field), line, column })
}
non_blank_text(raw)
}
///|
// Atom ids are stable absolute IRIs. Keep malformed declarations in the
// public model, but never turn a relative id into a source-dependent URL.
fn atom_id_value(
raw : String,
owner : String,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> String? {
match non_blank_text(raw) {
Some(id) => {
if uri_absolute(id) is None {
diagnostics.push({ kind: InvalidAtomId(owner), line, column, })
}
Some(id)
}
None => None
}
}
///|
fn atom_alternate_link(element : @xml.NamespaceElement) -> String? {
let rel = atom_attribute_value(element, "rel")
let href = atom_iri_attribute(element, "href")
match rel {
None
| Some("alternate")
| Some("http://www.iana.org/assignments/relation/alternate") =>
match href {
Some(value) => Some(value)
_ => None
}
_ => None
}
}
///|
// RFC 4287 treats a registered short relation as equivalent to this IRI.
// Preserve the declared spelling in AtomLink; compare semantics only here.
fn atom_relation_is(relation : String, name : String) -> Bool {
relation == name ||
relation == "http://www.iana.org/assignments/relation/" + name
}
///|
/// RFC 4287 section 4.2.7.2 permits one nonempty IRI path segment with no
/// colon, or an absolute IRI. A longer relative reference is not a relation.
fn atom_relation_syntax(value : String) -> Bool {
if value.is_empty() {
return false
}
match uri_parts(value) {
None => false
Some(parts) =>
if parts.scheme is Some(_) {
uri_valid_absolute(parts)
} else {
parts.authority is None &&
!parts.path.is_empty() &&
!parts.path.contains("/") &&
parts.query is None &&
parts.fragment is None
}
}
}
///|
/// A link may use an empty or relative IRI reference without a document URL.
/// Reject malformed component text before source-aware resolution is asked for.
fn atom_href_syntax(value : String) -> Bool {
match uri_parts(value) {
None => false
Some(parts) =>
if parts.scheme is Some(_) {
uri_valid_absolute(parts)
} else {
true
}
}
}
///|
fn atom_element_relation_is(
element : @xml.NamespaceElement,
name : String,
) -> Bool {
match atom_attribute_value(element, "rel") {
Some(relation) => atom_relation_is(relation, name)
None => name == "alternate"
}
}
///|
// RFC 3066 ABNF: 1..8 ASCII letters in the primary subtag, followed by
// zero or more 1..8 ASCII alphanumeric subtags. Registry membership is a
// separate concern; keep the original spelling in AtomLink.
fn atom_language_tag_syntax(value : String) -> Bool {
let chars = date_chars(value)
if chars.is_empty() {
return false
}
let mut primary = true
let mut segment_length = 0
for character in chars {
if character == '-' {
if segment_length == 0 {
return false
}
segment_length = 0
primary = false
} else {
let letter = (character >= 'a' && character <= 'z') ||
(character >= 'A' && character <= 'Z')
let digit = character >= '0' && character <= '9'
if (!letter && (primary || !digit)) || segment_length == 8 {
return false
}
segment_length = segment_length + 1
}
}
segment_length > 0
}
///|
fn atom_same_hreflang(left : String?, right : String?) -> Bool {
match (left, right) {
(None, None) => true
(Some(a), Some(b)) => a.to_lower() == b.to_lower()
_ => false
}
}
///|
// MIME type/subtype names are case-insensitive. Retain the declared spelling
// publicly, and compare parameter tails exactly to avoid treating differently
// cased, possibly case-sensitive parameter values as the same declaration.
fn atom_same_link_type(left : String?, right : String?) -> Bool {
match (left, right) {
(None, None) => true
(Some(a), Some(b)) => {
let (a_head, a_parameters) = match a.split_once(";") {
Some((head, parameters)) =>
(head.to_owned(), Some(parameters.to_owned()))
None => (a, None)
}
let (b_head, b_parameters) = match b.split_once(";") {
Some((head, parameters)) =>
(head.to_owned(), Some(parameters.to_owned()))
None => (b, None)
}
match (atom_mime_essence(a), atom_mime_essence(b)) {
(Some(_), Some(_)) =>
a_head.trim().to_lower() == b_head.trim().to_lower() &&
a_parameters == b_parameters
_ => a == b
}
}
_ => false
}
}
///|
fn atom_link(
element : @xml.NamespaceElement,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> AtomLink? {
let relation = atom_attribute_value(element, "rel").unwrap_or("alternate")
if !atom_relation_syntax(relation) {
diagnostics.push({ kind: InvalidAtomLink("invalid rel"), line, column, })
}
let href = match atom_iri_attribute(element, "href") {
Some(href) => href
None => {
if !atom_relation_is(relation, "enclosure") {
diagnostics.push({
kind: InvalidAtomLink("missing href"),
line,
column,
})
}
return None
}
}
if !atom_href_syntax(href) {
diagnostics.push({
kind: if atom_relation_is(relation, "enclosure") {
InvalidAttachment("invalid Atom enclosure href")
} else {
InvalidAtomLink("invalid href")
},
line,
column,
})
}
let length = match atom_attribute_value(element, "length") {
Some(raw) =>
match attachment_size(raw) {
Some(value) => Some(value)
None => {
if !atom_relation_is(relation, "enclosure") {
diagnostics.push({
kind: InvalidAtomLink("invalid length"),
line,
column,
})
}
None
}
}
None => None
}
match atom_attribute_value(element, "type") {
Some(raw) if atom_mime_essence(raw) is None => {
// Enclosures report this through xml_attachment, avoiding two
// diagnostics for the same declaration.
if !atom_relation_is(relation, "enclosure") {
diagnostics.push({
kind: InvalidAtomLink("invalid type"),
line,
column,
})
}
}
_ => ()
}
match atom_attribute_value(element, "hreflang") {
Some(raw) if !atom_language_tag_syntax(raw) =>
diagnostics.push({
kind: InvalidAtomLink("invalid hreflang"),
line,
column,
})
_ => ()
}
Some({
relation,
href,
media_type: atom_attribute_value(element, "type"),
hreflang: atom_attribute_value(element, "hreflang"),
title: atom_attribute_value(element, "title"),
length,
})
}
///|
fn atom_append_link(
links : Array[AtomLink],
element : @xml.NamespaceElement,
owner : String,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> Unit {
match atom_link(element, line, column, diagnostics) {
Some(link) => {
if atom_relation_is(link.relation, "alternate") &&
links.any(fn(previous) {
atom_relation_is(previous.relation, "alternate") &&
atom_same_link_type(previous.media_type, link.media_type) &&
atom_same_hreflang(previous.hreflang, link.hreflang)
}) {
diagnostics.push({
kind: DuplicateAtomField(owner + " alternate link"),
line,
column,
})
}
links.push(link)
}
None => ()
}
}
///|
fn atom_text_kind(element : @xml.NamespaceElement) -> TextKind? {
match atom_attribute_value(element, "type") {
None | Some("text") => Some(Text)
Some("html") => Some(Html)
Some("xhtml") => Some(Xhtml)
_ => None
}
}
///|
fn atom_non_blank_attribute(
element : @xml.NamespaceElement,
expected : String,
) -> String? {
match atom_attribute_value(element, expected) {
Some(value) => non_blank_text(value)
None => None
}
}
///|
// The empty string is an IRI reference (ipath-empty), unlike missing href.
fn atom_iri_attribute(
element : @xml.NamespaceElement,
expected : String,
) -> String? {
match atom_attribute_value(element, expected) {
Some(value) if value.is_empty() || !value.trim().is_empty() => Some(value)
_ => None
}
}
///|
fn atom_generator_uri(
element : @xml.NamespaceElement,
owner : String,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> String? {
let raw = atom_attribute_value(element, "uri")
match raw {
Some(value) if !atom_href_syntax(value) =>
diagnostics.push({
kind: InvalidAtomGeneratorUri(owner),
line,
column,
})
_ => ()
}
raw
}
///|
fn atom_category(
element : @xml.NamespaceElement,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> Category? {
match atom_attribute_value(element, "term") {
Some(term) => {
let scheme = atom_attribute_value(element, "scheme")
match scheme {
Some(value) if uri_absolute(value) is None =>
diagnostics.push({
kind: InvalidCategory(
"atom category scheme must be an absolute IRI",
),
line,
column,
})
_ => ()
}
Some({
source: Atom1,
term,
domain: None,
scheme,
label: atom_attribute_value(element, "label"),
})
}
None => None
}
}
///|
fn copy_authors(authors : Array[Author]) -> Array[Author] {
let copied : Array[Author] = []
for author in authors {
copied.push(author)
}
copied
}
///|
fn with_authors(entry : Entry, authors : Array[Author]) -> Entry {
{
identifier: entry.identifier,
title: entry.title,
title_kind: entry.title_kind,
title_xhtml: entry.title_xhtml,
link: entry.link,
external_url: entry.external_url,
language: entry.language,
description: entry.description,
description_kind: entry.description_kind,
description_xhtml: entry.description_xhtml,
content: entry.content,
content_text: entry.content_text,
content_html: entry.content_html,
content_kind: entry.content_kind,
content_xhtml: entry.content_xhtml,
content_source: entry.content_source,
content_media_type: entry.content_media_type,
atom_body: entry.atom_body,
atom_source: entry.atom_source,
links: entry.links,
comments_url: entry.comments_url,
source_url: entry.source_url,
source_title: entry.source_title,
guid_is_permalink: entry.guid_is_permalink,
rights: entry.rights,
rights_kind: entry.rights_kind,
rights_xhtml: entry.rights_xhtml,
authors,
contributors: entry.contributors,
categories: entry.categories,
attachments: entry.attachments,
images: entry.images,
published_at: entry.published_at,
updated_at: entry.updated_at,
}
}
///|
/// RFC 4287 ยง4.2.10: an entry without its own rights inherits the containing
/// feed's declaration, even when that declaration follows the entry in XML.
fn with_inherited_atom_rights(
entry : Entry,
rights : String,
kind : TextKind,
fragment : XhtmlFragment?,
) -> Entry {
{
identifier: entry.identifier,
title: entry.title,
title_kind: entry.title_kind,
title_xhtml: entry.title_xhtml,
link: entry.link,
external_url: entry.external_url,
language: entry.language,
description: entry.description,
description_kind: entry.description_kind,
description_xhtml: entry.description_xhtml,
content: entry.content,
content_text: entry.content_text,
content_html: entry.content_html,
content_kind: entry.content_kind,
content_xhtml: entry.content_xhtml,
content_source: entry.content_source,
content_media_type: entry.content_media_type,
atom_body: entry.atom_body,
atom_source: entry.atom_source,
links: entry.links,
comments_url: entry.comments_url,
source_url: entry.source_url,
source_title: entry.source_title,
guid_is_permalink: entry.guid_is_permalink,
rights: Some(rights),
rights_kind: kind,
rights_xhtml: fragment,
authors: entry.authors,
contributors: entry.contributors,
categories: entry.categories,
attachments: entry.attachments,
images: entry.images,
published_at: entry.published_at,
updated_at: entry.updated_at,
}
}
///|
fn atom_location(
field_location : (Int32, Int32)?,
entry_line : Int32,
entry_column : Int32,
) -> (Int32, Int32) {
field_location.unwrap_or((entry_line, entry_column))
}
///|
fn atom_singleton(
element : @xml.NamespaceElement,
seen : Array[String],
entry : Bool,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> Unit {
let field = element.name.local_name
let unique = if entry {
field
is ("id"
| "title"
| "updated"
| "published"
| "summary"
| "content"
| "rights"
| "source")
} else {
field
is ("id"
| "title"
| "updated"
| "subtitle"
| "rights"
| "generator"
| "icon"
| "logo")
}
if !unique || !is_atom_name(element.name, field) {
return
}
for previous in seen {
if previous == field {
diagnostics.push({
kind: DuplicateAtomField(
if entry {
"entry " + field
} else {
"feed " + field
},
),
line,
column,
})
return
}
}
seen.push(field)
}
///|
fn atom_person_singleton(
element : @xml.NamespaceElement,
seen : Array[String],
owner : String,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
) -> Unit {
let field = element.name.local_name
if !is_atom_name(element.name, field) ||
!(field is ("name" | "uri" | "email")) {
return
}
if seen.any(fn(previous) { previous == field }) {
diagnostics.push({
kind: DuplicateAtomField(owner + " " + field),
line,
column,
})
}
seen.push(field)
}
///|
fn add_missing_atom_feed_diagnostics(
id : String?,
title : String?,
updated : String?,
id_location : (Int32, Int32)?,
title_location : (Int32, Int32)?,
updated_location : (Int32, Int32)?,
feed_line : Int32,
feed_column : Int32,
diagnostics : Array[Diagnostic],
) -> Unit {
if id is None {
let (line, column) = atom_location(id_location, feed_line, feed_column)
diagnostics.push({ kind: MissingFeedField("id"), line, column, })
}
if title is None {
let (line, column) = atom_location(title_location, feed_line, feed_column)
diagnostics.push({ kind: MissingFeedField("title"), line, column, })
}
if updated is None {
let (line, column) = atom_location(updated_location, feed_line, feed_column)
diagnostics.push({ kind: MissingFeedField("updated"), line, column, })
}
}
///|
fn add_missing_atom_entry_diagnostics(
index : Int32,
id : String?,
title : String?,
updated : String?,
id_location : (Int32, Int32)?,
title_location : (Int32, Int32)?,
updated_location : (Int32, Int32)?,
entry_line : Int32,
entry_column : Int32,
diagnostics : Array[Diagnostic],
) -> Unit {
if id is None {
let (line, column) = atom_location(id_location, entry_line, entry_column)
diagnostics.push({ kind: MissingEntryField(index, "id"), line, column, })
}
if title is None {
let (line, column) = atom_location(title_location, entry_line, entry_column)
diagnostics.push({ kind: MissingEntryField(index, "title"), line, column, })
}
if updated is None {
let (line, column) = atom_location(
updated_location, entry_line, entry_column,
)
diagnostics.push({
kind: MissingEntryField(index, "updated"),
line,
column,
})
}
}
///|
fn parse_atom(input : String, standalone : Bool) -> Result[Feed, ParseError] {
let reader = @xml.NamespaceReader::from_string(input)
try {
let mut depth = 0
let mut root_seen = false
let mut feed_line = 1
let mut feed_column = 1
let mut feed_id : String? = None
let mut feed_title : String? = None
let mut feed_subtitle : String? = None
let mut feed_updated : String? = None
let mut feed_rights : String? = None
let mut feed_rights_kind = Text
let mut feed_rights_xhtml : XhtmlFragment? = None
let mut feed_generator : String? = None
let mut feed_generator_uri : String? = None
let mut feed_generator_version : String? = None
let mut feed_link : String? = None
let mut feed_language : String? = None
let mut feed_base_references : Array[String] = []
let feed_authors : Array[Author] = []
let feed_contributors : Array[Author] = []
let feed_categories : Array[Category] = []
let feed_links : Array[AtomLink] = []
let mut author_owner : String? = None
let mut author_depth = 0
let mut author_line = 1
let mut author_column = 1
let mut author_name : String? = None
let mut author_uri : String? = None
let mut author_email : String? = None
let mut author_child : String? = None
let mut author_child_depth = 0
let mut author_child_text = ""
let mut author_child_line = 1
let mut author_child_column = 1
let mut author_children_seen : Array[String] = []
let mut feed_title_kind = Text
let mut feed_subtitle_kind = Text
let mut feed_title_xhtml : XhtmlFragment? = None
let mut feed_subtitle_xhtml : XhtmlFragment? = None
let mut feed_id_location : (Int32, Int32)? = None
let mut feed_title_location : (Int32, Int32)? = None
let mut feed_updated_location : (Int32, Int32)? = None
let entries : Array[Entry] = []
let diagnostics : Array[Diagnostic] = []
let entry_needs_feed_authors : Array[Bool] = []
let entry_has_own_author : Array[Bool] = []
let entry_locations : Array[(Int32, Int32)] = []
let feed_singletons : Array[String] = []
let mut entry_singletons : Array[String] = []
let mut entry_open = false
let mut feed_entries_started = false
let mut entry_depth = 0
let mut entry_line = 1
let mut entry_column = 1
let mut entry_id : String? = None
let mut entry_title : String? = None
let mut entry_published : String? = None
let mut entry_updated : String? = None
let mut entry_rights : String? = None
let mut entry_rights_kind = Text
let mut entry_rights_xhtml : XhtmlFragment? = None
let mut entry_summary : String? = None
let mut entry_link : String? = None
let mut entry_language : String? = None
let mut entry_base_references : Array[String] = []
let mut entry_own_authors : Array[Author] = []
let mut entry_contributors : Array[Author] = []
let mut entry_source_authors : Array[Author] = []
let mut entry_atom_source : AtomSource? = None
let mut source_id : String? = None
let mut source_title : String? = None
let mut source_title_kind = Text
let mut source_title_xhtml : XhtmlFragment? = None
let mut source_subtitle : String? = None
let mut source_subtitle_kind = Text
let mut source_subtitle_xhtml : XhtmlFragment? = None
let mut source_updated : String? = None
let mut source_rights : String? = None
let mut source_rights_kind = Text
let mut source_rights_xhtml : XhtmlFragment? = None
let mut source_generator : String? = None
let mut source_generator_uri : String? = None
let mut source_generator_version : String? = None
let mut source_link : String? = None
let mut source_links : Array[AtomLink] = []
let mut source_images : Array[FeedImage] = []
let mut source_contributors : Array[Author] = []
let mut source_categories : Array[Category] = []
let mut source_language : String? = None
let mut source_base_references : Array[String] = []
let mut source_singletons : Array[String] = []
let mut entry_categories : Array[Category] = []
let mut entry_attachments : Array[Attachment] = []
let mut entry_links : Array[AtomLink] = []
let mut source_open = false
let mut source_depth = 0
let mut entry_content_present = false
let mut entry_external_content_location : (Int32, Int32)? = None
let mut entry_content : String? = None
let mut entry_content_source : String? = None
let mut entry_content_media_type : String? = None
let mut entry_title_kind = Text
let mut entry_summary_kind = Text
let mut entry_content_kind = Text
let mut entry_title_xhtml : XhtmlFragment? = None
let mut entry_summary_xhtml : XhtmlFragment? = None
let mut entry_content_xhtml : XhtmlFragment? = None
let mut entry_atom_body : AtomBody? = None
let mut entry_binary_content_location : (Int32, Int32)? = None
let mut entry_id_location : (Int32, Int32)? = None
let mut entry_title_location : (Int32, Int32)? = None
let mut entry_updated_location : (Int32, Int32)? = None
let mut capture_field : String? = None
let mut capture_in_entry = false
let mut capture_in_source = false
let mut capture_depth = 0
let mut captured_text = ""
let mut capture_has_nested_markup = false
let mut capture_unsupported_type = false
let mut capture_text_kind = Text
let mut xhtml_capture : AtomXhtmlCapture? = None
let mut xml_capture : AtomXmlCapture? = None
let mut capture_content_mode = ""
let mut capture_media_type = ""
let mut capture_line = 1
let mut capture_column = 1
// Person fields are XML text-only. Extension elements directly under a
// Person are allowed, but elements inside name/uri/email are not.
let mut author_child_nested = false
for ;; {
let event = reader.read_event()
match event.kind {
@xml.Start(element) => {
if author_child is Some(_) && depth >= author_child_depth &&
!author_child_nested {
diagnostics.push({
kind: InvalidAuthor(
"nested markup in atom person " + author_child.unwrap(),
),
line: event.span.start.line,
column: event.span.start.column,
})
author_child_nested = true
}
if !standalone && root_seen && depth == 1 {
if is_atom_name(element.name, "entry") {
feed_entries_started = true
} else if feed_entries_started {
diagnostics.push({
kind: InvalidAtomOrder(element.name.qualified_name),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
if capture_field is Some(_) && depth >= capture_depth {
match xhtml_capture {
Some(state) => state.node(element, depth == capture_depth, false)
None =>
match xml_capture {
Some(state) => state.node(element, false)
None => capture_has_nested_markup = true
}
}
}
if !root_seen {
root_seen = true
if !is_atom_name(element.name, "feed") &&
!(standalone && is_atom_name(element.name, "entry")) {
return Err(
UnsupportedRoot(
element.name.qualified_name,
element.name.namespace_uri,
),
)
}
feed_line = event.span.start.line
feed_column = event.span.start.column
if !standalone {
feed_language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => None
}
feed_base_references = []
match xml_base_attribute(element) {
Some(value) => feed_base_references.push(value)
None => ()
}
}
}
if (depth == 1 || (standalone && depth == 0)) &&
is_atom_name(element.name, "entry") {
entry_open = true
entry_singletons = []
entry_depth = depth + 1
entry_line = event.span.start.line
entry_column = event.span.start.column
entry_id = None
entry_title = None
entry_published = None
entry_updated = None
entry_rights = None
entry_rights_kind = Text
entry_rights_xhtml = None
entry_summary = None
entry_link = None
entry_language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => feed_language
}
entry_base_references = xhtml_copy_references(feed_base_references)
match xml_base_attribute(element) {
Some(value) => entry_base_references.push(value)
None => ()
}
entry_own_authors = []
entry_contributors = []
entry_source_authors = []
entry_atom_source = None
entry_categories = []
entry_attachments = []
entry_links = []
source_open = false
source_depth = 0
entry_content_present = false
entry_external_content_location = None
entry_content = None
entry_content_source = None
entry_content_media_type = None
entry_title_kind = Text
entry_summary_kind = Text
entry_content_kind = Text
entry_title_xhtml = None
entry_summary_xhtml = None
entry_content_xhtml = None
entry_atom_body = None
entry_binary_content_location = None
entry_id_location = None
entry_title_location = None
entry_updated_location = None
} else if !entry_open &&
depth == 1 &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
author_owner = Some(
if is_atom_name(element.name, "author") {
"feed"
} else {
"feed_contributor"
},
)
author_depth = depth + 1
author_line = event.span.start.line
author_column = event.span.start.column
author_name = None
author_uri = None
author_email = None
author_child = None
author_child_text = ""
author_children_seen = []
} else if entry_open &&
depth == entry_depth &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
author_owner = Some(
if is_atom_name(element.name, "author") {
"entry"
} else {
"entry_contributor"
},
)
author_depth = depth + 1
author_line = event.span.start.line
author_column = event.span.start.column
author_name = None
author_uri = None
author_email = None
author_child = None
author_child_text = ""
author_children_seen = []
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "source") {
source_open = true
source_depth = depth + 1
source_id = None
source_title = None
source_title_kind = Text
source_title_xhtml = None
source_subtitle = None
source_subtitle_kind = Text
source_subtitle_xhtml = None
source_updated = None
source_rights = None
source_rights_kind = Text
source_rights_xhtml = None
source_generator = None
source_generator_uri = None
source_generator_version = None
source_link = None
source_links = []
source_images = []
source_contributors = []
source_categories = []
entry_source_authors = []
source_singletons = []
source_language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => entry_language
}
source_base_references = xhtml_copy_references(
entry_base_references,
)
match xml_base_attribute(element) {
Some(value) => source_base_references.push(value)
None => ()
}
} else if source_open &&
depth == source_depth &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
author_owner = Some(
if is_atom_name(element.name, "author") {
"source"
} else {
"source_contributor"
},
)
author_depth = depth + 1
author_line = event.span.start.line
author_column = event.span.start.column
author_name = None
author_uri = None
author_email = None
author_child = None
author_child_text = ""
author_children_seen = []
} else if capture_field is None &&
source_open &&
depth == source_depth &&
element.name.local_name
is ("id"
| "title"
| "subtitle"
| "updated"
| "rights"
| "generator"
| "icon"
| "logo") &&
is_atom_name(element.name, element.name.local_name) {
let field = element.name.local_name
if source_singletons.any(fn(previous) { previous == field }) {
diagnostics.push({
kind: DuplicateAtomField("source " + field),
line: event.span.start.line,
column: event.span.start.column,
})
}
source_singletons.push(field)
match field {
"id" => source_id = None
"title" => {
source_title = None
source_title_xhtml = None
}
"subtitle" => {
source_subtitle = None
source_subtitle_xhtml = None
}
"rights" => {
source_rights = None
source_rights_xhtml = None
}
"updated" => source_updated = None
"generator" => {
source_generator = None
source_generator_uri = atom_generator_uri(
element, "source generator uri", event.span.start.line,
event.span.start.column, diagnostics,
)
source_generator_version = atom_attribute_value(
element, "version",
)
}
_ => ()
}
capture_field = Some(field)
capture_in_source = true
capture_in_entry = false
capture_depth = depth + 1
captured_text = ""
capture_has_nested_markup = false
capture_text_kind = if field is ("title" | "subtitle" | "rights") {
atom_text_kind(element).unwrap_or(Text)
} else {
Text
}
capture_unsupported_type = field
is ("title" | "subtitle" | "rights") &&
atom_text_kind(element) is None
capture_line = event.span.start.line
capture_column = event.span.start.column
if capture_unsupported_type {
diagnostics.push({
kind: UnsupportedTextConstruct("atom source " + field),
line: capture_line,
column: capture_column,
})
}
xhtml_capture = if field is ("title" | "subtitle" | "rights") &&
capture_text_kind == Xhtml {
let references = xhtml_copy_references(source_base_references)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => source_language
}
Some(AtomXhtmlCapture::new(language, references))
} else {
None
}
} else if source_open &&
depth == source_depth &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => source_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if !entry_open &&
depth == 1 &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => feed_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => entry_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if author_owner is Some(_) &&
depth == author_depth &&
(
is_atom_name(element.name, "name") ||
is_atom_name(element.name, "uri") ||
is_atom_name(element.name, "email")
) {
atom_person_singleton(
element,
author_children_seen,
if author_owner.unwrap_or("").contains("contributor") {
"atom contributor"
} else {
"atom author"
},
event.span.start.line,
event.span.start.column,
diagnostics,
)
author_child = Some(element.name.local_name)
author_child_depth = depth + 1
author_child_text = ""
author_child_nested = false
author_child_line = event.span.start.line
author_child_column = event.span.start.column
} else if source_open &&
depth == source_depth &&
is_atom_name(element.name, "link") {
atom_append_link(
source_links,
element,
"source",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if source_link is None {
source_link = atom_alternate_link(element)
}
} else if !entry_open &&
depth == 1 &&
is_atom_name(element.name, "link") {
atom_append_link(
feed_links,
element,
"feed",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if feed_link is None {
feed_link = atom_alternate_link(element)
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "link") {
atom_append_link(
entry_links,
element,
"entry",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if entry_link is None {
entry_link = atom_alternate_link(element)
}
if atom_element_relation_is(element, "enclosure") {
match
xml_attachment(
element,
Atom1,
"href",
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(attachment) => entry_attachments.push(attachment)
None => ()
}
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "content") {
entry_content_present = true
entry_content = None
entry_content_kind = Text
entry_content_xhtml = None
entry_content_media_type = atom_attribute_value(element, "type")
entry_content_source = atom_attribute_value(element, "src")
entry_external_content_location = None
entry_atom_body = None
match entry_content_source {
Some(_) => {
entry_external_content_location = Some(
(event.span.start.line, event.span.start.column),
)
let mode = atom_content_mode(
atom_attribute_value(element, "type"),
)
if mode == "invalid" ||
mode == "html" ||
mode == "xhtml" ||
atom_attribute_value(element, "type") == Some("text") {
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid external Atom media type",
),
line: event.span.start.line,
column: event.span.start.column,
})
} else {
match atom_external_content(element, entry_base_references) {
Some(reference) =>
entry_atom_body = Some(External(reference))
None =>
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid external Atom src",
),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
capture_field = Some("content")
capture_in_entry = true
capture_depth = depth + 1
captured_text = ""
capture_has_nested_markup = false
capture_unsupported_type = false
capture_content_mode = "external"
capture_line = event.span.start.line
capture_column = event.span.start.column
}
None => {
let mode = atom_content_mode(
atom_attribute_value(element, "type"),
)
if mode == "invalid" {
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid Atom content media type",
),
line: event.span.start.line,
column: event.span.start.column,
})
} else {
let kind = match mode {
"html" => Html
"xhtml" => Xhtml
_ => Text
}
capture_field = Some("content")
capture_in_entry = true
capture_depth = depth + 1
captured_text = ""
capture_has_nested_markup = false
capture_unsupported_type = false
capture_text_kind = kind
capture_content_mode = mode
capture_media_type = atom_content_media_type(
atom_attribute_value(element, "type"),
)
if mode == "binary" {
entry_binary_content_location = Some(
(event.span.start.line, event.span.start.column),
)
}
entry_content_xhtml = None
xhtml_capture = if kind == Xhtml {
let references = xhtml_copy_references(
entry_base_references,
)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => entry_language
}
Some(AtomXhtmlCapture::new(language, references))
} else {
None
}
xml_capture = if mode == "xml" {
let references = xhtml_copy_references(
entry_base_references,
)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => entry_language
}
Some(AtomXmlCapture::new(language, references))
} else {
None
}
capture_line = event.span.start.line
capture_column = event.span.start.column
}
}
}
} else if capture_field is None &&
!entry_open &&
depth == 1 &&
(
is_atom_name(element.name, "id") ||
is_atom_name(element.name, "title") ||
is_atom_name(element.name, "subtitle") ||
is_atom_name(element.name, "rights") ||
is_atom_name(element.name, "generator") ||
is_atom_name(element.name, "updated")
) {
capture_field = Some(element.name.local_name)
capture_in_entry = false
capture_depth = depth + 1
captured_text = ""
capture_has_nested_markup = false
capture_text_kind = if element.name.local_name
is ("title" | "subtitle" | "rights") {
atom_text_kind(element).unwrap_or(Text)
} else {
Text
}
match element.name.local_name {
"title" => {
feed_title = None
feed_title_xhtml = None
}
"subtitle" => {
feed_subtitle = None
feed_subtitle_xhtml = None
}
"rights" => {
feed_rights = None
feed_rights_xhtml = None
}
"generator" => {
feed_generator = None
feed_generator_uri = atom_generator_uri(
element, "feed generator uri", event.span.start.line,
event.span.start.column, diagnostics,
)
feed_generator_version = atom_attribute_value(
element, "version",
)
}
_ => ()
}
xhtml_capture = if capture_text_kind == Xhtml &&
element.name.local_name is ("title" | "subtitle" | "rights") {
let references = xhtml_copy_references(feed_base_references)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => feed_language
}
Some(AtomXhtmlCapture::new(language, references))
} else {
None
}
capture_unsupported_type = element.name.local_name
is ("title" | "subtitle" | "rights") &&
atom_text_kind(element) is None
capture_line = event.span.start.line
capture_column = event.span.start.column
if capture_unsupported_type {
diagnostics.push({
kind: UnsupportedTextConstruct(
"atom " + element.name.local_name,
),
line: capture_line,
column: capture_column,
})
}
match element.name.local_name {
"id" => feed_id_location = Some((capture_line, capture_column))
"title" =>
feed_title_location = Some((capture_line, capture_column))
"updated" =>
feed_updated_location = Some((capture_line, capture_column))
_ => ()
}
} else if capture_field is None &&
entry_open &&
depth == entry_depth &&
(
is_atom_name(element.name, "id") ||
is_atom_name(element.name, "title") ||
is_atom_name(element.name, "published") ||
is_atom_name(element.name, "updated") ||
is_atom_name(element.name, "summary") ||
is_atom_name(element.name, "rights")
) {
capture_field = Some(element.name.local_name)
capture_in_entry = true
capture_depth = depth + 1
captured_text = ""
capture_has_nested_markup = false
capture_text_kind = if element.name.local_name
is ("title" | "summary" | "rights") {
atom_text_kind(element).unwrap_or(Text)
} else {
Text
}
match element.name.local_name {
"title" => {
entry_title = None
entry_title_xhtml = None
}
"summary" => {
entry_summary = None
entry_summary_xhtml = None
}
"rights" => {
entry_rights = None
entry_rights_xhtml = None
}
_ => ()
}
xhtml_capture = if capture_text_kind == Xhtml &&
element.name.local_name is ("title" | "summary" | "rights") {
let references = xhtml_copy_references(entry_base_references)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => entry_language
}
Some(AtomXhtmlCapture::new(language, references))
} else {
None
}
capture_unsupported_type = element.name.local_name
is ("title" | "summary" | "rights") &&
atom_text_kind(element) is None
capture_line = event.span.start.line
capture_column = event.span.start.column
if capture_unsupported_type {
diagnostics.push({
kind: UnsupportedTextConstruct(
"atom entry " + element.name.local_name,
),
line: capture_line,
column: capture_column,
})
}
match element.name.local_name {
"id" => entry_id_location = Some((capture_line, capture_column))
"title" =>
entry_title_location = Some((capture_line, capture_column))
"updated" =>
entry_updated_location = Some((capture_line, capture_column))
_ => ()
}
}
if entry_open && depth == entry_depth {
atom_singleton(
element,
entry_singletons,
true,
event.span.start.line,
event.span.start.column,
diagnostics,
)
} else if !entry_open && depth == 1 {
atom_singleton(
element,
feed_singletons,
false,
event.span.start.line,
event.span.start.column,
diagnostics,
)
}
depth = depth + 1
}
@xml.Empty(element) => {
if author_child is Some(_) && depth >= author_child_depth &&
!author_child_nested {
diagnostics.push({
kind: InvalidAuthor(
"nested markup in atom person " + author_child.unwrap(),
),
line: event.span.start.line,
column: event.span.start.column,
})
author_child_nested = true
}
if !standalone && root_seen && depth == 1 {
if is_atom_name(element.name, "entry") {
feed_entries_started = true
} else if feed_entries_started {
diagnostics.push({
kind: InvalidAtomOrder(element.name.qualified_name),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
if author_owner is Some(_) && depth == author_depth {
atom_person_singleton(
element,
author_children_seen,
if author_owner.unwrap_or("").contains("contributor") {
"atom contributor"
} else {
"atom author"
},
event.span.start.line,
event.span.start.column,
diagnostics,
)
if is_atom_name(element.name, "name") {
author_name = Some("")
} else if is_atom_name(element.name, "uri") {
author_uri = Some("")
} else if is_atom_name(element.name, "email") {
author_email = Some("")
diagnostics.push({
kind: InvalidAuthor("invalid atom person email"),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
if entry_open && depth == entry_depth {
atom_singleton(
element,
entry_singletons,
true,
event.span.start.line,
event.span.start.column,
diagnostics,
)
} else if !entry_open && depth == 1 {
atom_singleton(
element,
feed_singletons,
false,
event.span.start.line,
event.span.start.column,
diagnostics,
)
}
if capture_field is Some(_) && depth >= capture_depth {
match xhtml_capture {
Some(state) => state.node(element, depth == capture_depth, true)
None =>
match xml_capture {
Some(state) => state.node(element, true)
None => capture_has_nested_markup = true
}
}
} else if !root_seen {
root_seen = true
if !is_atom_name(element.name, "feed") {
return Err(
UnsupportedRoot(
element.name.qualified_name,
element.name.namespace_uri,
),
)
}
feed_line = event.span.start.line
feed_column = event.span.start.column
feed_language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => None
}
} else if !entry_open &&
depth == 1 &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
diagnostics.push({
kind: InvalidAuthor(
if is_atom_name(element.name, "author") {
"atom author missing name"
} else {
"atom contributor missing name"
},
),
line: event.span.start.line,
column: event.span.start.column,
})
} else if entry_open &&
depth == entry_depth &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
diagnostics.push({
kind: InvalidAuthor(
if is_atom_name(element.name, "author") {
"atom author missing name"
} else {
"atom contributor missing name"
},
),
line: event.span.start.line,
column: event.span.start.column,
})
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "source") {
entry_atom_source = Some({
identifier: None,
title: None,
title_kind: Text,
title_xhtml: None,
subtitle: None,
subtitle_kind: Text,
subtitle_xhtml: None,
updated_at: None,
rights: None,
rights_kind: Text,
rights_xhtml: None,
generator: None,
generator_uri: None,
generator_version: None,
authors: [],
contributors: [],
categories: [],
link: None,
links: [],
images: [],
})
} else if source_open &&
depth == source_depth &&
(
is_atom_name(element.name, "author") ||
is_atom_name(element.name, "contributor")
) {
diagnostics.push({
kind: InvalidAuthor(
if is_atom_name(element.name, "author") {
"atom author missing name"
} else {
"atom contributor missing name"
},
),
line: event.span.start.line,
column: event.span.start.column,
})
} else if source_open &&
depth == source_depth &&
element.name.local_name
is ("id"
| "title"
| "subtitle"
| "updated"
| "rights"
| "generator"
| "icon"
| "logo") &&
is_atom_name(element.name, element.name.local_name) {
let field = element.name.local_name
if source_singletons.any(fn(previous) { previous == field }) {
diagnostics.push({
kind: DuplicateAtomField("source " + field),
line: event.span.start.line,
column: event.span.start.column,
})
}
source_singletons.push(field)
if field is ("title" | "subtitle" | "rights") {
let kind = atom_text_kind(element).unwrap_or(Text)
if field == "title" {
source_title = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
source_title_kind = kind
source_title_xhtml = None
} else if field == "subtitle" {
source_subtitle = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
source_subtitle_kind = kind
source_subtitle_xhtml = None
} else {
source_rights = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
source_rights_kind = kind
source_rights_xhtml = None
}
if atom_text_kind(element) is None || kind == Xhtml {
diagnostics.push({
kind: UnsupportedTextConstruct("atom source " + field),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if field == "id" {
source_id = None
} else if field == "updated" {
source_updated = atom_date_value(
"", "source updated", event.span.start.line,
event.span.start.column, diagnostics,
)
} else if field == "generator" {
source_generator = Some("")
source_generator_uri = atom_generator_uri(
element, "source generator uri", event.span.start.line,
event.span.start.column, diagnostics,
)
source_generator_version = atom_attribute_value(
element, "version",
)
} else {
source_images.push({
source: Atom1,
role: if field == "icon" { AtomIcon } else { AtomLogo },
url: "",
title: None,
link: None,
description: None,
width: None,
height: None,
})
}
} else if source_open &&
depth == source_depth &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => source_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if !entry_open &&
depth == 1 &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => feed_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "category") {
match
atom_category(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(category) => entry_categories.push(category)
None =>
diagnostics.push({
kind: InvalidCategory("atom category missing term"),
line: event.span.start.line,
column: event.span.start.column,
})
}
} else if source_open &&
depth == source_depth &&
is_atom_name(element.name, "link") {
atom_append_link(
source_links,
element,
"source",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if source_link is None {
source_link = atom_alternate_link(element)
}
} else if !entry_open &&
depth == 1 &&
is_atom_name(element.name, "link") {
atom_append_link(
feed_links,
element,
"feed",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if feed_link is None {
feed_link = atom_alternate_link(element)
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "link") {
atom_append_link(
entry_links,
element,
"entry",
event.span.start.line,
event.span.start.column,
diagnostics,
)
if entry_link is None {
entry_link = atom_alternate_link(element)
}
if atom_element_relation_is(element, "enclosure") {
match
xml_attachment(
element,
Atom1,
"href",
event.span.start.line,
event.span.start.column,
diagnostics,
) {
Some(attachment) => entry_attachments.push(attachment)
None => ()
}
}
} else if entry_open &&
depth == entry_depth &&
is_atom_name(element.name, "content") {
entry_content_present = true
entry_content = None
entry_content_kind = Text
entry_content_xhtml = None
entry_content_media_type = atom_attribute_value(element, "type")
entry_content_source = atom_attribute_value(element, "src")
entry_external_content_location = None
entry_atom_body = None
match entry_content_source {
Some(_) => {
entry_external_content_location = Some(
(event.span.start.line, event.span.start.column),
)
let mode = atom_content_mode(
atom_attribute_value(element, "type"),
)
if mode == "invalid" ||
mode == "html" ||
mode == "xhtml" ||
atom_attribute_value(element, "type") == Some("text") {
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid external Atom media type",
),
line: event.span.start.line,
column: event.span.start.column,
})
} else {
match atom_external_content(element, entry_base_references) {
Some(reference) =>
entry_atom_body = Some(External(reference))
None =>
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid external Atom src",
),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
}
None => {
let mode = atom_content_mode(
atom_attribute_value(element, "type"),
)
match mode {
"xhtml" =>
diagnostics.push({
kind: UnsupportedAtomContent("invalid Atom XHTML div"),
line: event.span.start.line,
column: event.span.start.column,
})
"text" | "text-mime" | "html" => {
entry_content = Some("")
entry_content_kind = if mode == "html" {
Html
} else {
Text
}
entry_atom_body = if mode == "html" {
Some(InlineHtml(""))
} else {
Some(
InlineText(
"",
atom_content_media_type(
atom_attribute_value(element, "type"),
),
),
)
}
}
"xml" => {
let references = xhtml_copy_references(
entry_base_references,
)
match xml_base_attribute(element) {
Some(value) => references.push(value)
None => ()
}
let language = match xml_language_attribute(element) {
Some(value) => non_blank_text(value)
None => entry_language
}
entry_atom_body = Some(
InlineXml(
{ markup: "", language, base_references: references, },
atom_content_media_type(
atom_attribute_value(element, "type"),
),
),
)
}
"binary" => {
entry_binary_content_location = Some(
(event.span.start.line, event.span.start.column),
)
entry_atom_body = Some(
InlineBinary(
b"",
atom_content_media_type(
atom_attribute_value(element, "type"),
),
),
)
}
_ =>
diagnostics.push({
kind: UnsupportedAtomContent(
"invalid Atom content media type",
),
line: event.span.start.line,
column: event.span.start.column,
})
}
}
}
} else if !entry_open &&
depth == 1 &&
(
is_atom_name(element.name, "id") ||
is_atom_name(element.name, "title") ||
is_atom_name(element.name, "subtitle") ||
is_atom_name(element.name, "rights") ||
is_atom_name(element.name, "generator") ||
is_atom_name(element.name, "updated")
) {
if element.name.local_name is ("title" | "subtitle" | "rights") &&
atom_text_kind(element) is None {
diagnostics.push({
kind: UnsupportedTextConstruct(
"atom " + element.name.local_name,
),
line: event.span.start.line,
column: event.span.start.column,
})
}
if element.name.local_name is ("title" | "subtitle" | "rights") &&
atom_text_kind(element) == Some(Xhtml) {
diagnostics.push({
kind: UnsupportedTextConstruct(
"invalid atom XHTML " + element.name.local_name,
),
line: event.span.start.line,
column: event.span.start.column,
})
}
let kind = atom_text_kind(element).unwrap_or(Text)
match element.name.local_name {
"id" => feed_id = None
"title" => {
feed_title = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
feed_title_kind = kind
feed_title_xhtml = None
}
"updated" => feed_updated = atom_date_value(
"", "feed updated", event.span.start.line,
event.span.start.column, diagnostics,
)
"rights" => {
feed_rights = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
feed_rights_kind = kind
feed_rights_xhtml = None
}
"generator" => {
feed_generator = Some("")
feed_generator_uri = atom_generator_uri(
element, "feed generator uri", event.span.start.line,
event.span.start.column, diagnostics,
)
feed_generator_version = atom_attribute_value(
element, "version",
)
}
_ => {
feed_subtitle = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
feed_subtitle_kind = kind
feed_subtitle_xhtml = None
}
}
match element.name.local_name {
"id" =>
feed_id_location = Some(
(event.span.start.line, event.span.start.column),
)
"title" =>
feed_title_location = Some(
(event.span.start.line, event.span.start.column),
)
"updated" =>
feed_updated_location = Some(
(event.span.start.line, event.span.start.column),
)
_ => ()
}
} else if entry_open &&
depth == entry_depth &&
(
is_atom_name(element.name, "id") ||
is_atom_name(element.name, "title") ||
is_atom_name(element.name, "published") ||
is_atom_name(element.name, "updated") ||
is_atom_name(element.name, "summary") ||
is_atom_name(element.name, "rights")
) {
if element.name.local_name is ("title" | "summary" | "rights") &&
atom_text_kind(element) is None {
diagnostics.push({
kind: UnsupportedTextConstruct(
"atom entry " + element.name.local_name,
),
line: event.span.start.line,
column: event.span.start.column,
})
}
if element.name.local_name is ("title" | "summary" | "rights") &&
atom_text_kind(element) == Some(Xhtml) {
diagnostics.push({
kind: UnsupportedTextConstruct(
"invalid atom XHTML " + element.name.local_name,
),
line: event.span.start.line,
column: event.span.start.column,
})
}
let kind = atom_text_kind(element).unwrap_or(Text)
match element.name.local_name {
"id" => entry_id = None
"title" => {
entry_title = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
entry_title_kind = kind
entry_title_xhtml = None
}
"published" => entry_published = atom_date_value(
"", "entry published", event.span.start.line,
event.span.start.column, diagnostics,
)
"updated" => entry_updated = atom_date_value(
"", "entry updated", event.span.start.line,
event.span.start.column, diagnostics,
)
"rights" => {
entry_rights = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
entry_rights_kind = kind
entry_rights_xhtml = None
}
_ => {
entry_summary = if kind != Xhtml &&
atom_text_kind(element) is Some(_) {
Some("")
} else {
None
}
entry_summary_kind = kind
entry_summary_xhtml = None
}
}
match element.name.local_name {
"id" =>
entry_id_location = Some(
(event.span.start.line, event.span.start.column),
)
"title" =>
entry_title_location = Some(
(event.span.start.line, event.span.start.column),
)
"updated" =>
entry_updated_location = Some(
(event.span.start.line, event.span.start.column),
)
_ => ()
}
}
}
@xml.Text(text) | @xml.CData(text) => {
if author_child is Some(_) {
author_child_text = author_child_text + text
}
if capture_field is Some(_) {
match xhtml_capture {
Some(state) => state.text(text, depth > capture_depth)
None => {
captured_text = captured_text + text
match xml_capture {
Some(state) => state.text(text)
None => ()
}
}
}
}
}
@xml.Comment(value) => {
match xhtml_capture {
Some(state) => state.comment(value)
None => ()
}
match xml_capture {
Some(state) => state.comment(value)
None => ()
}
}
@xml.PI(target~, data~) => {
match xhtml_capture {
Some(state) => state.processing_instruction(target, data)
None => ()
}
match xml_capture {
Some(state) => state.processing_instruction(target, data)
None => ()
}
}
@xml.End(name) => {
if capture_field is Some(_) && depth > capture_depth {
match xhtml_capture {
Some(state) => state.end(name, depth == capture_depth + 1)
None =>
match xml_capture {
Some(state) => state.end(name)
None => ()
}
}
}
if author_child is Some(field) &&
author_child_depth == depth &&
is_atom_name(name, field) {
match field {
"name" => author_name = Some(author_child_text)
"uri" => {
author_uri = Some(author_child_text)
if !atom_href_syntax(author_child_text) {
diagnostics.push({
kind: InvalidAuthor("invalid atom person uri"),
line: author_child_line,
column: author_child_column,
})
}
}
_ => {
author_email = Some(author_child_text)
if !atom_email_syntax(author_child_text) {
diagnostics.push({
kind: InvalidAuthor("invalid atom person email"),
line: author_child_line,
column: author_child_column,
})
}
}
}
author_child = None
author_child_text = ""
author_child_nested = false
}
if author_owner is Some(_) &&
author_depth == depth &&
(is_atom_name(name, "author") || is_atom_name(name, "contributor")) {
match author_name {
Some(author_name) => {
let author = {
source: Atom1,
name: Some(author_name),
uri: author_uri,
email: author_email,
avatar: None,
}
match author_owner {
Some("feed") => feed_authors.push(author)
Some("source") => entry_source_authors.push(author)
Some("source_contributor") => source_contributors.push(author)
Some("feed_contributor") => feed_contributors.push(author)
Some("entry_contributor") => entry_contributors.push(author)
_ => entry_own_authors.push(author)
}
}
None =>
diagnostics.push({
kind: InvalidAuthor(
if is_atom_name(name, "contributor") {
"atom contributor missing name"
} else {
"atom author missing name"
},
),
line: author_line,
column: author_column,
})
}
author_owner = None
author_child = None
author_child_text = ""
}
if source_open &&
source_depth == depth &&
is_atom_name(name, "source") {
entry_atom_source = Some({
identifier: source_id,
title: source_title,
title_kind: source_title_kind,
title_xhtml: source_title_xhtml,
subtitle: source_subtitle,
subtitle_kind: source_subtitle_kind,
subtitle_xhtml: source_subtitle_xhtml,
updated_at: source_updated,
rights: source_rights,
rights_kind: source_rights_kind,
rights_xhtml: source_rights_xhtml,
generator: source_generator,
generator_uri: source_generator_uri,
generator_version: source_generator_version,
authors: copy_authors(entry_source_authors),
contributors: source_contributors,
categories: source_categories,
link: source_link,
links: source_links,
images: source_images,
})
source_open = false
}
if capture_field is Some(field) &&
capture_depth == depth &&
is_atom_name(name, field) {
let xhtml_fragment = match xhtml_capture {
Some(state) => state.fragment()
None => None
}
if field == "content" && capture_content_mode == "external" {
if capture_has_nested_markup || !captured_text.is_empty() {
entry_atom_body = None
diagnostics.push({
kind: UnsupportedAtomContent(
"external Atom content must be empty",
),
line: capture_line,
column: capture_column,
})
}
} else if field == "content" && capture_content_mode == "xml" {
match xml_capture {
Some(state) =>
entry_atom_body = Some(
InlineXml(state.fragment(), capture_media_type),
)
None => ()
}
} else if field == "content" &&
capture_content_mode == "binary" &&
!capture_has_nested_markup {
match atom_decode_base64(captured_text) {
Some(bytes) =>
entry_atom_body = Some(
InlineBinary(bytes, capture_media_type),
)
None =>
diagnostics.push({
kind: UnsupportedAtomContent("invalid Atom Base64 content"),
line: capture_line,
column: capture_column,
})
}
} else if capture_text_kind == Xhtml && xhtml_fragment is None {
if field == "content" {
diagnostics.push({
kind: UnsupportedAtomContent("invalid Atom XHTML div"),
line: capture_line,
column: capture_column,
})
} else {
diagnostics.push({
kind: UnsupportedTextConstruct("invalid atom XHTML " + field),
line: capture_line,
column: capture_column,
})
}
} else if capture_unsupported_type || capture_has_nested_markup {
if field == "content" && capture_has_nested_markup {
diagnostics.push({
kind: UnsupportedAtomContent(
"Atom content with nested markup",
),
line: capture_line,
column: capture_column,
})
} else if capture_has_nested_markup && !capture_unsupported_type {
diagnostics.push({
kind: UnsupportedTextConstruct("atom " + field),
line: capture_line,
column: capture_column,
})
}
} else if capture_text_kind == Xhtml {
match xhtml_capture {
Some(state) => {
let plain = state.plain_text
if capture_in_source {
if field == "title" {
source_title = Some(plain)
source_title_kind = Xhtml
source_title_xhtml = xhtml_fragment
} else if field == "subtitle" {
source_subtitle = Some(plain)
source_subtitle_kind = Xhtml
source_subtitle_xhtml = xhtml_fragment
} else {
source_rights = Some(plain)
source_rights_kind = Xhtml
source_rights_xhtml = xhtml_fragment
}
} else if capture_in_entry {
match field {
"title" => {
entry_title = Some(plain)
entry_title_kind = Xhtml
entry_title_xhtml = xhtml_fragment
}
"summary" => {
entry_summary = Some(plain)
entry_summary_kind = Xhtml
entry_summary_xhtml = xhtml_fragment
}
"rights" => {
entry_rights = Some(plain)
entry_rights_kind = Xhtml
entry_rights_xhtml = xhtml_fragment
}
_ => {
entry_content = Some(plain)
entry_content_kind = Xhtml
entry_content_xhtml = xhtml_fragment
match xhtml_fragment {
Some(fragment) =>
entry_atom_body = Some(InlineXhtml(fragment))
None => ()
}
}
}
} else if field == "title" {
feed_title = Some(plain)
feed_title_kind = Xhtml
feed_title_xhtml = xhtml_fragment
} else if field == "rights" {
feed_rights = Some(plain)
feed_rights_kind = Xhtml
feed_rights_xhtml = xhtml_fragment
} else {
feed_subtitle = Some(plain)
feed_subtitle_kind = Xhtml
feed_subtitle_xhtml = xhtml_fragment
}
}
None => ()
}
} else if capture_in_source {
match field {
"id" => source_id = atom_id_value(
captured_text, "source id", capture_line, capture_column,
diagnostics,
)
"title" => {
source_title = Some(captured_text)
source_title_kind = capture_text_kind
}
"subtitle" => {
source_subtitle = Some(captured_text)
source_subtitle_kind = capture_text_kind
}
"rights" => {
source_rights = Some(captured_text)
source_rights_kind = capture_text_kind
}
"updated" => source_updated = atom_date_value(
captured_text, "source updated", capture_line,
capture_column, diagnostics,
)
"generator" => source_generator = Some(captured_text)
"icon" | "logo" =>
if atom_image_iri(
captured_text, "source " + field, capture_line,
capture_column, diagnostics, true,
) {
source_images.push({
source: Atom1,
role: if field == "icon" { AtomIcon } else { AtomLogo },
url: captured_text,
title: None,
link: None,
description: None,
width: None,
height: None,
})
}
_ => ()
}
} else if capture_in_entry {
match field {
"id" => entry_id = atom_id_value(
captured_text, "entry id", capture_line, capture_column,
diagnostics,
)
"title" => {
entry_title = Some(captured_text)
entry_title_kind = capture_text_kind
}
"published" => entry_published = atom_date_value(
captured_text, "entry published", capture_line,
capture_column, diagnostics,
)
"updated" => entry_updated = atom_date_value(
captured_text, "entry updated", capture_line,
capture_column, diagnostics,
)
"rights" => {
entry_rights = Some(captured_text)
entry_rights_kind = capture_text_kind
}
"content" => {
entry_content = Some(captured_text)
entry_content_kind = capture_text_kind
entry_atom_body = if capture_content_mode == "html" {
Some(InlineHtml(captured_text))
} else {
Some(InlineText(captured_text, capture_media_type))
}
}
_ => {
entry_summary = Some(captured_text)
entry_summary_kind = capture_text_kind
}
}
} else {
match field {
"id" => feed_id = atom_id_value(
captured_text, "feed id", capture_line, capture_column,
diagnostics,
)
"title" => {
feed_title = Some(captured_text)
feed_title_kind = capture_text_kind
}
"updated" => feed_updated = atom_date_value(
captured_text, "feed updated", capture_line,
capture_column, diagnostics,
)
"rights" => {
feed_rights = Some(captured_text)
feed_rights_kind = capture_text_kind
}
"generator" => feed_generator = Some(captured_text)
_ => {
feed_subtitle = Some(captured_text)
feed_subtitle_kind = capture_text_kind
}
}
}
capture_field = None
capture_in_source = false
captured_text = ""
capture_has_nested_markup = false
capture_unsupported_type = false
capture_text_kind = Text
xhtml_capture = None
xml_capture = None
capture_content_mode = ""
capture_media_type = ""
}
if entry_open && depth == entry_depth && is_atom_name(name, "entry") {
let index = entries.length()
add_missing_atom_entry_diagnostics(
index, entry_id, entry_title, entry_updated, entry_id_location, entry_title_location,
entry_updated_location, entry_line, entry_column, diagnostics,
)
if !entry_content_present && entry_link is None {
diagnostics.push({
kind: MissingAlternateLink(index),
line: entry_line,
column: entry_column,
})
}
match entry_external_content_location {
Some((content_line, content_column)) if entry_summary is None =>
diagnostics.push({
kind: MissingSummaryForExternalContent(index),
line: content_line,
column: content_column,
})
_ => ()
}
match entry_binary_content_location {
Some((content_line, content_column)) if entry_summary is None =>
diagnostics.push({
kind: MissingSummaryForBinaryContent(index),
line: content_line,
column: content_column,
})
_ => ()
}
let authors = if entry_own_authors.length() > 0 {
copy_authors(entry_own_authors)
} else {
copy_authors(entry_source_authors)
}
entries.push({
identifier: entry_id,
title: entry_title,
title_kind: entry_title_kind,
title_xhtml: entry_title_xhtml,
link: entry_link,
external_url: None,
language: entry_language,
description: entry_summary,
description_kind: entry_summary_kind,
description_xhtml: entry_summary_xhtml,
content: entry_content,
content_text: None,
content_html: None,
content_kind: entry_content_kind,
content_xhtml: entry_content_xhtml,
content_source: entry_content_source,
content_media_type: entry_content_media_type,
atom_body: entry_atom_body,
atom_source: entry_atom_source,
links: entry_links,
comments_url: None,
source_url: None,
source_title: None,
guid_is_permalink: None,
rights: entry_rights,
rights_kind: entry_rights_kind,
rights_xhtml: entry_rights_xhtml,
authors,
contributors: entry_contributors,
categories: entry_categories,
attachments: entry_attachments,
images: [],
published_at: entry_published,
updated_at: entry_updated,
})
entry_needs_feed_authors.push(
entry_own_authors.length() == 0 &&
entry_source_authors.length() == 0,
)
entry_has_own_author.push(entry_own_authors.length() > 0)
entry_locations.push((entry_line, entry_column))
entry_open = false
}
depth = depth - 1
}
@xml.Eof => {
if !standalone {
add_missing_atom_feed_diagnostics(
feed_id, feed_title, feed_updated, feed_id_location, feed_title_location,
feed_updated_location, feed_line, feed_column, diagnostics,
)
if feed_authors.is_empty() &&
(
entries.is_empty() ||
entry_has_own_author.any(fn(value) { !value })
) {
diagnostics.push({
kind: MissingFeedField("author"),
line: feed_line,
column: feed_column,
})
}
}
for index = 0
index < entry_needs_feed_authors.length()
index = index + 1 {
if entry_needs_feed_authors[index] {
if feed_authors.length() > 0 {
entries[index] = with_authors(
entries[index],
copy_authors(feed_authors),
)
} else {
let (line, column) = entry_locations[index]
diagnostics.push({
kind: MissingEntryField(index, "author"),
line,
column,
})
}
}
}
if !standalone && feed_rights is Some(rights) {
for index = 0; index < entries.length(); index = index + 1 {
if entries[index].rights is None {
entries[index] = with_inherited_atom_rights(
entries[index],
rights,
feed_rights_kind,
feed_rights_xhtml,
)
}
}
}
return Ok({
format: Atom1,
identifier: feed_id,
title: feed_title,
title_kind: feed_title_kind,
title_xhtml: feed_title_xhtml,
link: feed_link,
language: feed_language,
description: feed_subtitle,
description_kind: feed_subtitle_kind,
description_xhtml: feed_subtitle_xhtml,
updated_at: feed_updated,
published_at: None,
ttl_minutes: None,
docs_url: None,
rating: None,
cloud: None,
text_input: None,
skip_hours: [],
skip_days: [],
rights: feed_rights,
rights_kind: feed_rights_kind,
rights_xhtml: feed_rights_xhtml,
generator: feed_generator,
generator_uri: feed_generator_uri,
generator_version: feed_generator_version,
managing_editor: None,
webmaster: None,
next_url: None,
expired: None,
hubs: [],
links: feed_links,
authors: feed_authors,
contributors: feed_contributors,
categories: feed_categories,
images: [],
entries,
diagnostics,
})
}
_ => ()
}
}
abort("unreachable")
} catch {
@xml.At(error~, span~) =>
Err(InvalidXml(error.to_string(), span.start.line, span.start.column))
}
}