///|
fn rss_channel_cloud(
element : @xml.NamespaceElement,
line : Int32,
column : Int32,
diagnostics : Array[Diagnostic],
record_errors : Bool,
) -> RssCloud? {
let domain = match attribute_value(element, "domain") {
Some(value) => non_blank_text(value)
None => None
}
let path = match attribute_value(element, "path") {
Some(value) => non_blank_text(value)
None => None
}
let procedure = match attribute_value(element, "registerProcedure") {
Some(value) => non_blank_text(value)
None => None
}
let protocol = match attribute_value(element, "protocol") {
Some(value) => non_blank_text(value)
None => None
}
let port = match attribute_value(element, "port") {
Some(value) => rss_ttl_minutes(value)
None => None
}
match (domain, port, path, procedure, protocol) {
(Some(domain), Some(port), Some(path), Some(procedure), Some(protocol)) if port >
0 &&
port <= 65535 &&
(protocol == "xml-rpc" || protocol == "soap" || protocol == "http-post") =>
Some({ domain, port, path, register_procedure: procedure, protocol, })
_ => {
if record_errors {
diagnostics.push({ kind: InvalidRssField("cloud"), line, column, })
}
None
}
}
}
///|
fn rss_optional_problem(
diagnostics : Array[Diagnostic],
record_errors : Bool,
field : String,
line : Int32,
column : Int32,
) -> Unit {
if record_errors {
diagnostics.push({ kind: InvalidRssField(field), line, column, })
}
}
///|
fn with_rss_optional_channel(
feed : Feed,
docs_url : String?,
rating : String?,
cloud : RssCloud?,
text_input : RssTextInput?,
) -> Feed {
{
format: feed.format,
identifier: feed.identifier,
title: feed.title,
title_kind: feed.title_kind,
title_xhtml: feed.title_xhtml,
link: feed.link,
language: feed.language,
description: feed.description,
description_kind: feed.description_kind,
description_xhtml: feed.description_xhtml,
updated_at: feed.updated_at,
published_at: feed.published_at,
ttl_minutes: feed.ttl_minutes,
docs_url,
rating,
cloud,
text_input,
skip_hours: feed.skip_hours,
skip_days: feed.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: feed.managing_editor,
webmaster: feed.webmaster,
next_url: feed.next_url,
expired: feed.expired,
hubs: feed.hubs,
links: feed.links,
authors: feed.authors,
contributors: feed.contributors,
categories: feed.categories,
images: feed.images,
entries: feed.entries,
diagnostics: feed.diagnostics,
}
}
///|
/// The core RSS pass owns required fields and entries. This pass reads only
/// direct optional channel children and the four direct textInput children.
fn add_rss_optional_channel(
input : String,
feed : Feed,
source_url : String?,
record_errors : Bool,
) -> Result[Feed, ParseError] {
let reader = @xml.NamespaceReader::from_string(input)
let diagnostics = feed.diagnostics
let mut depth = 0
let mut channel_depth = 0
let mut text_input_depth = 0
let mut text_input_active = false
let mut docs_seen = false
let mut rating_seen = false
let mut cloud_seen = false
let mut text_input_seen = false
let mut docs_url : String? = None
let mut docs_line = 1
let mut docs_column = 1
let mut rating : String? = None
let mut cloud : RssCloud? = None
let mut text_input : RssTextInput? = None
let mut form_title : String? = None
let mut form_description : String? = None
let mut form_name : String? = None
let mut form_link : String? = None
let mut form_line = 1
let mut form_column = 1
let mut capture : String? = None
let mut capture_depth = 0
let mut capture_text = ""
let mut capture_nested = false
let mut capture_line = 1
let mut capture_column = 1
try {
for ;; {
let event = reader.read_event()
match event.kind {
@xml.Start(element) => {
if capture is Some(_) && depth >= capture_depth {
capture_nested = true
}
let name = element.name
if depth == 1 && is_rss_name(name, "channel") {
channel_depth = depth + 1
} else if channel_depth > 0 && depth == channel_depth {
if is_rss_name(name, "docs") ||
is_rss_name(name, "rating") ||
is_rss_name(name, "cloud") ||
is_rss_name(name, "textInput") {
let field = name.local_name
let seen = match field {
"docs" => {
let old = docs_seen
docs_seen = true
old
}
"rating" => {
let old = rating_seen
rating_seen = true
old
}
"cloud" => {
let old = cloud_seen
cloud_seen = true
old
}
_ => {
let old = text_input_seen
text_input_seen = true
old
}
}
if seen {
rss_optional_problem(
diagnostics,
record_errors,
field + " duplicate",
event.span.start.line,
event.span.start.column,
)
} else if field == "cloud" {
cloud = rss_channel_cloud(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
record_errors,
)
} else if field == "textInput" {
text_input_active = true
text_input_depth = depth + 1
form_line = event.span.start.line
form_column = event.span.start.column
} else {
capture = Some(field)
capture_depth = depth + 1
capture_text = ""
capture_nested = false
capture_line = event.span.start.line
capture_column = event.span.start.column
if field == "docs" {
docs_line = capture_line
docs_column = capture_column
}
}
}
} else if text_input_active &&
depth == text_input_depth &&
(
is_rss_name(name, "title") ||
is_rss_name(name, "description") ||
is_rss_name(name, "name") ||
is_rss_name(name, "link")
) {
capture = Some(name.local_name)
capture_depth = depth + 1
capture_text = ""
capture_nested = false
capture_line = event.span.start.line
capture_column = event.span.start.column
}
depth = depth + 1
}
@xml.Empty(element) =>
if capture is Some(_) && depth >= capture_depth {
capture_nested = true
} else if channel_depth > 0 && depth == channel_depth {
if is_rss_name(element.name, "cloud") {
if cloud_seen {
rss_optional_problem(
diagnostics,
record_errors,
"cloud duplicate",
event.span.start.line,
event.span.start.column,
)
} else {
cloud_seen = true
cloud = rss_channel_cloud(
element,
event.span.start.line,
event.span.start.column,
diagnostics,
record_errors,
)
}
} else if is_rss_name(element.name, "docs") ||
is_rss_name(element.name, "rating") ||
is_rss_name(element.name, "textInput") {
let field = element.name.local_name
rss_optional_problem(
diagnostics,
record_errors,
field,
event.span.start.line,
event.span.start.column,
)
if field == "docs" {
docs_seen = true
}
if field == "rating" {
rating_seen = true
}
if field == "textInput" {
text_input_seen = true
}
}
} else if text_input_active &&
depth == text_input_depth &&
(
is_rss_name(element.name, "title") ||
is_rss_name(element.name, "description") ||
is_rss_name(element.name, "name") ||
is_rss_name(element.name, "link")
) {
rss_optional_problem(
diagnostics,
record_errors,
"textInput " + element.name.local_name,
event.span.start.line,
event.span.start.column,
)
}
@xml.Text(value) | @xml.CData(value) =>
if capture is Some(_) {
capture_text = capture_text + value
}
@xml.End(name) => {
if capture is Some(field) &&
depth == capture_depth &&
is_rss_name(name, field) {
if capture_nested {
if record_errors {
diagnostics.push({
kind: UnsupportedNestedMarkup("rss " + field),
line: capture_line,
column: capture_column,
})
}
} else {
let value = non_blank_text(capture_text)
if value is None {
rss_optional_problem(
diagnostics,
record_errors,
if text_input_active {
"textInput " + field
} else {
field
},
capture_line,
capture_column,
)
} else if text_input_active {
let previous = match field {
"title" => form_title
"description" => form_description
"name" => form_name
"link" => form_link
_ => None
}
if previous is Some(_) {
rss_optional_problem(
diagnostics,
record_errors,
"textInput " + field + " duplicate",
capture_line,
capture_column,
)
} else {
match field {
"title" => form_title = value
"description" => form_description = value
"name" => form_name = value
"link" => form_link = value
_ => ()
}
}
} else if field == "docs" {
docs_url = value
} else if field == "rating" {
rating = value
}
}
capture = None
}
if text_input_active &&
depth == text_input_depth &&
is_rss_name(name, "textInput") {
match (form_title, form_description, form_name, form_link) {
(Some(title), Some(description), Some(name), Some(link)) =>
text_input = Some({ title, description, name, link, })
_ =>
rss_optional_problem(
diagnostics, record_errors, "textInput required fields", form_line,
form_column,
)
}
text_input_active = false
}
depth = depth - 1
}
@xml.Eof => {
if source_url is Some(base) {
if docs_url is Some(raw) {
docs_url = Some(
uri_at(
Some(base),
raw,
"rss channel docs",
docs_line,
docs_column,
diagnostics,
),
)
}
if text_input is Some(form) {
text_input = Some({
title: form.title,
description: form.description,
name: form.name,
link: uri_at(
Some(base),
form.link,
"rss channel textInput link",
form_line,
form_column,
diagnostics,
),
})
}
}
return Ok(
with_rss_optional_channel(feed, docs_url, rating, cloud, text_input),
)
}
_ => ()
}
}
abort("unreachable")
} catch {
@xml.At(error~, span~) =>
Err(InvalidXml(error.to_string(), span.start.line, span.start.column))
}
}