use super::content::{parse_content, slugify};
use super::document::{Document, HeadingNode};
use super::output::*;
use super::utils::get_heading_level;
use std::path::Path;
pub fn build_json_output(doc: &Document, source_path: Option<&Path>) -> DocumentOutput {
let tree = doc.build_tree();
let max_depth = calculate_max_depth(&tree);
let word_count = count_words(&doc.content);
let metadata = DocumentMetadata {
source: source_path.map(|p| p.to_string_lossy().to_string()),
heading_count: doc.headings.len(),
max_depth,
word_count,
};
let sections = tree
.iter()
.map(|node| build_section(node, &doc.content))
.collect();
DocumentOutput {
document: DocumentRoot { metadata, sections },
}
}
fn build_section(node: &HeadingNode, full_content: &str) -> Section {
let heading = &node.heading;
let (raw_content, offset, line) = extract_section_content(heading, full_content);
let blocks = parse_content(&raw_content, line);
let children = node
.children
.iter()
.map(|child| build_section(child, full_content))
.collect();
Section {
id: slugify(&heading.text),
level: heading.level,
title: heading.text.clone(),
slug: slugify(&heading.text),
position: Position { line, offset },
content: Content {
raw: raw_content,
blocks,
},
children,
}
}
fn extract_section_content(
heading: &super::document::Heading,
full_content: &str,
) -> (String, usize, usize) {
let offset = heading.offset;
let line = full_content[..offset].lines().count() + 1;
let after_heading = &full_content[offset..];
let content_start = after_heading.find('\n').map(|i| i + 1).unwrap_or(0);
let section_content = &after_heading[content_start..];
let end = find_next_heading(section_content);
(
section_content[..end].trim().to_string(),
offset + content_start,
line + 1,
)
}
fn find_next_heading(content: &str) -> usize {
let mut in_code_block = false;
let mut pos = 0;
for line in content.lines() {
if line.trim_start().starts_with("```") {
in_code_block = !in_code_block;
}
if !in_code_block && get_heading_level(line).is_some() {
return pos;
}
pos += line.len() + 1; }
content.len()
}
fn calculate_max_depth(tree: &[HeadingNode]) -> usize {
tree.iter()
.map(|node| 1 + calculate_max_depth(&node.children))
.max()
.unwrap_or(0)
}
fn count_words(content: &str) -> usize {
content.split_whitespace().count()
}