use crate::ir::Role;
use crate::kfx::schema::SemanticTarget;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum KfxToken {
StartElement(ElementStart),
EndElement,
Text(String),
StartSpan(SpanStart),
EndSpan,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ElementStart {
pub role: Role,
pub id: Option<i64>,
pub semantics: HashMap<SemanticTarget, String>,
pub content_ref: Option<ContentRef>,
pub style_events: Vec<SpanStart>,
pub kfx_attrs: Vec<(u64, String)>,
pub style_symbol: Option<u64>,
pub style_name: Option<String>,
}
impl ElementStart {
pub fn new(role: Role) -> Self {
Self {
role,
id: None,
semantics: HashMap::new(),
content_ref: None,
style_events: Vec::new(),
kfx_attrs: Vec::new(),
style_symbol: None,
style_name: None,
}
}
pub fn get_semantic(&self, target: SemanticTarget) -> Option<&str> {
self.semantics.get(&target).map(|s| s.as_str())
}
pub fn set_semantic(&mut self, target: SemanticTarget, value: String) {
self.semantics.insert(target, value);
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ContentRef {
pub name: String,
pub index: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SpanStart {
pub role: Role,
pub semantics: HashMap<SemanticTarget, String>,
pub offset: usize,
pub length: usize,
pub style_symbol: Option<u64>,
pub kfx_attrs: Vec<(u64, String)>,
}
impl SpanStart {
pub fn new(role: Role, offset: usize, length: usize) -> Self {
Self {
role,
semantics: HashMap::new(),
offset,
length,
style_symbol: None,
kfx_attrs: Vec::new(),
}
}
pub fn get_semantic(&self, target: SemanticTarget) -> Option<&str> {
self.semantics.get(&target).map(|s| s.as_str())
}
pub fn set_semantic(&mut self, target: SemanticTarget, value: String) {
self.semantics.insert(target, value);
}
}
#[derive(Debug, Default)]
pub struct TokenStream {
tokens: Vec<KfxToken>,
}
impl TokenStream {
pub fn new() -> Self {
Self { tokens: Vec::new() }
}
pub fn push(&mut self, token: KfxToken) {
self.tokens.push(token);
}
pub fn start_element(&mut self, role: Role) {
self.tokens
.push(KfxToken::StartElement(ElementStart::new(role)));
}
pub fn start_element_with(
&mut self,
role: Role,
id: Option<i64>,
semantics: HashMap<SemanticTarget, String>,
content_ref: Option<ContentRef>,
style_events: Vec<SpanStart>,
) {
self.tokens.push(KfxToken::StartElement(ElementStart {
role,
id,
semantics,
content_ref,
style_events,
kfx_attrs: Vec::new(),
style_symbol: None,
style_name: None,
}));
}
pub fn end_element(&mut self) {
self.tokens.push(KfxToken::EndElement);
}
pub fn text(&mut self, s: impl Into<String>) {
self.tokens.push(KfxToken::Text(s.into()));
}
pub fn start_span(&mut self, role: Role, semantics: HashMap<SemanticTarget, String>) {
self.tokens.push(KfxToken::StartSpan(SpanStart {
role,
semantics,
offset: 0,
length: 0,
style_symbol: None,
kfx_attrs: Vec::new(),
}));
}
pub fn end_span(&mut self) {
self.tokens.push(KfxToken::EndSpan);
}
pub fn iter(&self) -> impl Iterator<Item = &KfxToken> {
self.tokens.iter()
}
#[allow(clippy::should_implement_trait)]
pub fn into_iter(self) -> impl Iterator<Item = KfxToken> {
self.tokens.into_iter()
}
pub fn len(&self) -> usize {
self.tokens.len()
}
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
}
impl IntoIterator for TokenStream {
type Item = KfxToken;
type IntoIter = std::vec::IntoIter<KfxToken>;
fn into_iter(self) -> Self::IntoIter {
self.tokens.into_iter()
}
}
impl<'a> IntoIterator for &'a TokenStream {
type Item = &'a KfxToken;
type IntoIter = std::slice::Iter<'a, KfxToken>;
fn into_iter(self) -> Self::IntoIter {
self.tokens.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_stream_basic() {
let mut stream = TokenStream::new();
stream.start_element(Role::Paragraph);
stream.text("Hello");
stream.end_element();
assert_eq!(stream.len(), 3);
}
#[test]
fn test_token_stream_with_spans() {
let mut stream = TokenStream::new();
stream.start_element(Role::Paragraph);
stream.text("Click ");
let mut semantics = HashMap::new();
semantics.insert(SemanticTarget::Href, "http://example.com".to_string());
stream.start_span(Role::Link, semantics);
stream.text("here");
stream.end_span();
stream.end_element();
assert_eq!(stream.len(), 6);
}
#[test]
fn test_element_semantics() {
let mut elem = ElementStart::new(Role::Image);
elem.set_semantic(SemanticTarget::Src, "cover.jpg".to_string());
elem.set_semantic(SemanticTarget::Alt, "Cover image".to_string());
assert_eq!(elem.get_semantic(SemanticTarget::Src), Some("cover.jpg"));
assert_eq!(elem.get_semantic(SemanticTarget::Alt), Some("Cover image"));
assert_eq!(elem.get_semantic(SemanticTarget::Href), None);
}
#[test]
fn test_span_semantics() {
let mut span = SpanStart::new(Role::Link, 10, 5);
span.set_semantic(SemanticTarget::Href, "chapter2".to_string());
assert_eq!(span.get_semantic(SemanticTarget::Href), Some("chapter2"));
assert_eq!(span.offset, 10);
assert_eq!(span.length, 5);
}
}