Define core data models for book and theme

This commit is contained in:
Developer
2026-05-13 23:02:04 +08:00
parent 8b42a3d679
commit 072a47378d
2 changed files with 169 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
pub fn strip_html(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut in_tag = false;
let mut in_entity = false;
let mut entity = String::new();
for c in input.chars() {
match c {
'<' => in_tag = true,
'>' if in_tag => in_tag = false,
'&' if !in_tag => {
in_entity = true;
entity.clear();
}
';' if in_entity => {
in_entity = false;
let decoded = match entity.as_str() {
"amp" => "&",
"lt" => "<",
"gt" => ">",
"quot" => "\"",
"nbsp" => " ",
_ => "",
};
result.push_str(decoded);
}
c if !in_tag && !in_entity => result.push(c),
c if in_entity => entity.push(c),
_ => {}
}
}
result
}
#[derive(Debug, Clone)]
pub struct TocEntry {
pub label: String,
pub section: usize,
pub children: Vec<TocEntry>,
}
#[derive(Debug, Clone)]
pub struct Section {
pub title: String,
pub content: String,
pub pages: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct Book {
pub title: String,
pub author: String,
pub cover: Option<Vec<u8>>,
pub sections: Vec<Section>,
pub toc: Vec<TocEntry>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_html_plain_text() {
assert_eq!(strip_html("Hello World"), "Hello World");
}
#[test]
fn test_strip_html_simple_tags() {
assert_eq!(strip_html("<p>Hello</p>"), "Hello");
}
#[test]
fn test_strip_html_nested_tags() {
assert_eq!(
strip_html("<div><p>Hello <b>World</b></p></div>"),
"Hello World"
);
}
#[test]
fn test_strip_html_html_entities() {
assert_eq!(strip_html("Hello &amp; World"), "Hello & World");
assert_eq!(strip_html("Hello&nbsp;World"), "Hello World");
}
#[test]
fn test_strip_html_empty() {
assert_eq!(strip_html(""), "");
}
}

View File

@@ -0,0 +1,79 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Theme {
Light,
Dark,
}
impl Default for Theme {
fn default() -> Self {
Theme::Light
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Settings {
pub font_size: f32,
pub theme: Theme,
pub recent_files: Vec<String>,
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
pub window_size: Option<(f32, f32)>,
}
impl Default for Settings {
fn default() -> Self {
Self {
font_size: 20.0,
theme: Theme::Light,
recent_files: Vec::new(),
reading_positions: std::collections::HashMap::new(),
bookmarks: std::collections::HashMap::new(),
window_size: None,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct ReadingPosition {
pub section: usize,
pub page: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bookmark {
pub section: usize,
pub page: usize,
pub label: String,
pub timestamp: i64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_settings_default() {
let s = Settings::default();
assert_eq!(s.font_size, 20.0);
assert_eq!(s.theme, Theme::Light);
}
#[test]
fn test_settings_round_trip() {
let s = Settings::default();
let json = serde_json::to_string(&s).unwrap();
let restored: Settings = serde_json::from_str(&json).unwrap();
assert_eq!(restored.font_size, s.font_size);
assert_eq!(restored.theme, s.theme);
}
#[test]
fn test_theme_serialize() {
let json = serde_json::to_string(&Theme::Dark).unwrap();
assert_eq!(json, "\"Dark\"");
let restored: Theme = serde_json::from_str(&json).unwrap();
assert_eq!(restored, Theme::Dark);
}
}