use std::collections::HashMap;
use super::node::{NodeId, TextRange};
#[derive(Debug, Default, Clone)]
pub struct SemanticMap {
buffer: String,
href: HashMap<NodeId, TextRange>,
src: HashMap<NodeId, TextRange>,
alt: HashMap<NodeId, TextRange>,
id: HashMap<NodeId, TextRange>,
title: HashMap<NodeId, TextRange>,
lang: HashMap<NodeId, TextRange>,
epub_type: HashMap<NodeId, TextRange>,
aria_role: HashMap<NodeId, TextRange>,
datetime: HashMap<NodeId, TextRange>,
list_start: HashMap<NodeId, u32>,
row_span: HashMap<NodeId, u32>,
col_span: HashMap<NodeId, u32>,
is_header_cell: HashMap<NodeId, bool>,
language: HashMap<NodeId, TextRange>,
}
impl SemanticMap {
pub fn new() -> Self {
Self::default()
}
fn append(&mut self, s: &str) -> TextRange {
let start = self.buffer.len() as u32;
self.buffer.push_str(s);
TextRange::new(start, s.len() as u32)
}
fn get_str(&self, range: TextRange) -> &str {
let start = range.start as usize;
let end = (range.start + range.len) as usize;
&self.buffer[start..end]
}
pub fn set_href(&mut self, node: NodeId, href: &str) {
if !href.is_empty() {
let range = self.append(href);
self.href.insert(node, range);
}
}
pub fn href(&self, node: NodeId) -> Option<&str> {
self.href.get(&node).map(|r| self.get_str(*r))
}
pub fn set_src(&mut self, node: NodeId, src: &str) {
if !src.is_empty() {
let range = self.append(src);
self.src.insert(node, range);
}
}
pub fn src(&self, node: NodeId) -> Option<&str> {
self.src.get(&node).map(|r| self.get_str(*r))
}
pub fn set_alt(&mut self, node: NodeId, alt: &str) {
if !alt.is_empty() {
let range = self.append(alt);
self.alt.insert(node, range);
}
}
pub fn alt(&self, node: NodeId) -> Option<&str> {
self.alt.get(&node).map(|r| self.get_str(*r))
}
pub fn set_id(&mut self, node: NodeId, id: &str) {
if !id.is_empty() {
let range = self.append(id);
self.id.insert(node, range);
}
}
pub fn id(&self, node: NodeId) -> Option<&str> {
self.id.get(&node).map(|r| self.get_str(*r))
}
pub fn set_title(&mut self, node: NodeId, title: &str) {
if !title.is_empty() {
let range = self.append(title);
self.title.insert(node, range);
}
}
pub fn title(&self, node: NodeId) -> Option<&str> {
self.title.get(&node).map(|r| self.get_str(*r))
}
pub fn set_lang(&mut self, node: NodeId, lang: &str) {
if !lang.is_empty() {
let range = self.append(lang);
self.lang.insert(node, range);
}
}
pub fn lang(&self, node: NodeId) -> Option<&str> {
self.lang.get(&node).map(|r| self.get_str(*r))
}
pub fn set_epub_type(&mut self, node: NodeId, epub_type: &str) {
if !epub_type.is_empty() {
let range = self.append(epub_type);
self.epub_type.insert(node, range);
}
}
pub fn epub_type(&self, node: NodeId) -> Option<&str> {
self.epub_type.get(&node).map(|r| self.get_str(*r))
}
pub fn set_aria_role(&mut self, node: NodeId, role: &str) {
if !role.is_empty() {
let range = self.append(role);
self.aria_role.insert(node, range);
}
}
pub fn aria_role(&self, node: NodeId) -> Option<&str> {
self.aria_role.get(&node).map(|r| self.get_str(*r))
}
pub fn set_datetime(&mut self, node: NodeId, datetime: &str) {
if !datetime.is_empty() {
let range = self.append(datetime);
self.datetime.insert(node, range);
}
}
pub fn datetime(&self, node: NodeId) -> Option<&str> {
self.datetime.get(&node).map(|r| self.get_str(*r))
}
pub fn set_list_start(&mut self, node: NodeId, start: u32) {
if start != 1 {
self.list_start.insert(node, start);
}
}
pub fn list_start(&self, node: NodeId) -> Option<u32> {
self.list_start.get(&node).copied()
}
pub fn set_row_span(&mut self, node: NodeId, span: u32) {
if span > 1 {
self.row_span.insert(node, span);
}
}
pub fn row_span(&self, node: NodeId) -> Option<u32> {
self.row_span.get(&node).copied()
}
pub fn set_col_span(&mut self, node: NodeId, span: u32) {
if span > 1 {
self.col_span.insert(node, span);
}
}
pub fn col_span(&self, node: NodeId) -> Option<u32> {
self.col_span.get(&node).copied()
}
pub fn set_header_cell(&mut self, node: NodeId, is_header: bool) {
if is_header {
self.is_header_cell.insert(node, true);
}
}
pub fn is_header_cell(&self, node: NodeId) -> bool {
self.is_header_cell.get(&node).copied().unwrap_or(false)
}
pub fn set_language(&mut self, node: NodeId, language: &str) {
if !language.is_empty() {
let range = self.append(language);
self.language.insert(node, range);
}
}
pub fn language(&self, node: NodeId) -> Option<&str> {
self.language.get(&node).map(|r| self.get_str(*r))
}
pub fn get_attr(&self, node: NodeId, name: &str) -> Option<&str> {
match name {
"href" => self.href(node),
"src" => self.src(node),
"alt" => self.alt(node),
"id" => self.id(node),
"title" => self.title(node),
"lang" | "xml:lang" => self.lang(node),
"epub:type" => self.epub_type(node),
"role" => self.aria_role(node),
"datetime" => self.datetime(node),
_ => None,
}
}
pub fn set_attr(&mut self, node: NodeId, name: &str, value: &str) -> bool {
match name {
"href" => {
self.set_href(node, value);
true
}
"src" => {
self.set_src(node, value);
true
}
"alt" => {
self.set_alt(node, value);
true
}
"id" => {
self.set_id(node, value);
true
}
"title" => {
self.set_title(node, value);
true
}
"lang" | "xml:lang" => {
self.set_lang(node, value);
true
}
"epub:type" => {
self.set_epub_type(node, value);
true
}
"role" => {
self.set_aria_role(node, value);
true
}
"datetime" => {
self.set_datetime(node, value);
true
}
_ => false,
}
}
pub fn len(&self) -> usize {
self.href.len()
+ self.src.len()
+ self.alt.len()
+ self.id.len()
+ self.title.len()
+ self.lang.len()
+ self.epub_type.len()
+ self.aria_role.len()
+ self.datetime.len()
+ self.list_start.len()
+ self.row_span.len()
+ self.col_span.len()
+ self.is_header_cell.len()
+ self.language.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn resolve_paths<F>(&mut self, resolver: F)
where
F: Fn(&str) -> String,
{
let src_updates: Vec<_> = self
.src
.iter()
.map(|(&node, &range)| {
let old_value = self.get_str(range);
(node, resolver(old_value))
})
.collect();
for (node, new_value) in src_updates {
let range = self.append(&new_value);
self.src.insert(node, range);
}
let href_updates: Vec<_> = self
.href
.iter()
.filter_map(|(&node, &range)| {
let old_value = self.get_str(range);
if !old_value.contains("://") && !old_value.starts_with("mailto:") {
Some((node, resolver(old_value)))
} else {
None
}
})
.collect();
for (node, new_value) in href_updates {
let range = self.append(&new_value);
self.href.insert(node, range);
}
}
}