use crate::{Block, EngineDocument, InlineSpan};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LinkStatement {
pub target_url: String,
pub rel: String,
}
pub fn link_statements(doc: &EngineDocument) -> Vec<LinkStatement> {
let mut out = Vec::new();
for block in &doc.blocks {
collect_block(block, &mut out);
}
out
}
fn collect_block(block: &Block, out: &mut Vec<LinkStatement>) {
match block {
Block::Heading { spans, .. } | Block::Paragraph { spans } => {
for span in spans {
collect_span(span, out);
}
}
Block::Quote { blocks } => {
for inner in blocks {
collect_block(inner, out);
}
}
Block::List { items, .. } => {
for item in items {
for inner in item {
collect_block(inner, out);
}
}
}
Block::Table { header, rows, .. } => {
for cell in header.iter().chain(rows.iter().flatten()) {
for span in cell {
collect_span(span, out);
}
}
}
Block::FeedHeader { .. }
| Block::FeedEntry { .. }
| Block::CodeBlock { .. }
| Block::Image { .. }
| Block::Preformatted { .. }
| Block::Rule
| Block::MetadataRow { .. }
| Block::Badge { .. } => {}
}
}
fn collect_span(span: &InlineSpan, out: &mut Vec<LinkStatement>) {
match span {
InlineSpan::Link {
url,
predicate,
spans,
..
} => {
if let Some(rel) = predicate {
out.push(LinkStatement {
target_url: url.clone(),
rel: rel.clone(),
});
}
for inner in spans {
collect_span(inner, out);
}
}
InlineSpan::Emphasis(inner) | InlineSpan::Strong(inner) => {
for s in inner {
collect_span(s, out);
}
}
InlineSpan::Text(_)
| InlineSpan::Code(_)
| InlineSpan::LineBreak
| InlineSpan::SoftBreak => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DocumentProvenance, DocumentTrustState};
fn doc(blocks: Vec<Block>) -> EngineDocument {
EngineDocument {
address: "knot:test".to_string(),
title: None,
content_type: "text/x-knot".to_string(),
lang: None,
provenance: DocumentProvenance::default(),
trust: DocumentTrustState::default(),
diagnostics: Vec::new(),
blocks,
}
}
#[test]
fn link_statements_extracts_predicate_links_only() {
let document = doc(vec![Block::Paragraph {
spans: vec![
InlineSpan::Link {
url: "https://plain.test/".to_string(),
title: None,
spans: vec![InlineSpan::Text("plain".to_string())],
predicate: None,
},
InlineSpan::Link {
url: "mere://node/topic".to_string(),
title: None,
spans: vec![InlineSpan::Text("Topic".to_string())],
predicate: Some("cites".to_string()),
},
],
}]);
assert_eq!(
link_statements(&document),
vec![LinkStatement {
target_url: "mere://node/topic".to_string(),
rel: "cites".to_string(),
}]
);
}
}