use crate::lex::ast::elements::annotation::Annotation;
use crate::lex::ast::elements::blank_line_group::BlankLineGroup;
use crate::lex::ast::elements::content_item::ContentItem;
use crate::lex::ast::elements::definition::Definition;
use crate::lex::ast::elements::list::{List, ListItem};
use crate::lex::ast::elements::paragraph::{Paragraph, TextLine};
use crate::lex::ast::elements::sequence_marker::DecorationStyle;
use crate::lex::ast::elements::session::Session;
use crate::lex::ast::elements::table::{Table, TableCell, TableCellAlignment};
use crate::lex::ast::elements::verbatim::Verbatim;
use crate::lex::ast::elements::verbatim_line::VerbatimLine;
use crate::lex::ast::Document;
use lex_extension::wire::{
WireFootnote, WireInline, WireListItem, WireNode, WireRow, WireTableCell,
};
use serde_json::{Map, Value};
use super::inline::text_content_to_wire;
use super::range::{origin_string, range_to_wire};
pub fn to_wire_document(doc: &Document) -> WireNode {
let children = doc
.root
.children
.iter()
.map(to_wire_node)
.collect::<Vec<_>>();
WireNode::Document {
range: range_to_wire(&doc.root.location),
origin: origin_string(&doc.root.location),
children,
}
}
pub fn to_wire_node(item: &ContentItem) -> WireNode {
match item {
ContentItem::Paragraph(p) => paragraph_to_wire(p),
ContentItem::BlankLineGroup(blg) => blank_to_wire(blg),
ContentItem::Annotation(a) => annotation_to_wire(a),
ContentItem::Session(s) => session_to_wire(s),
ContentItem::Definition(d) => definition_to_wire(d),
ContentItem::List(l) => list_to_wire(l),
ContentItem::ListItem(li) => list_item_standalone_to_wire(li),
ContentItem::Table(t) => table_to_wire(t),
ContentItem::VerbatimBlock(v) => verbatim_to_wire(v),
ContentItem::VerbatimLine(vl) => verbatim_line_standalone_to_wire(vl),
ContentItem::TextLine(tl) => text_line_standalone_to_wire(tl),
}
}
fn paragraph_to_wire(p: &Paragraph) -> WireNode {
let mut inlines = Vec::new();
let mut first_line = true;
for line_item in &p.lines {
if let ContentItem::TextLine(line) = line_item {
if !first_line {
inlines.push(WireInline::Text { text: "\n".into() });
}
inlines.extend(text_content_to_wire(&line.content));
first_line = false;
}
}
WireNode::Paragraph {
range: range_to_wire(&p.location),
origin: origin_string(&p.location),
inlines,
}
}
fn blank_to_wire(blg: &BlankLineGroup) -> WireNode {
WireNode::Blank {
range: range_to_wire(&blg.location),
origin: origin_string(&blg.location),
}
}
fn annotation_to_wire(a: &Annotation) -> WireNode {
let label = a.data.label.value.clone();
let params = parameters_to_json(&a.data.parameters);
let body = annotation_body_to_json(a);
WireNode::Annotation {
range: range_to_wire(&a.location),
origin: origin_string(&a.location),
label,
params,
body,
}
}
fn session_to_wire(s: &Session) -> WireNode {
let children = s.children.iter().map(to_wire_node).collect::<Vec<_>>();
WireNode::Session {
range: range_to_wire(&s.location),
origin: origin_string(&s.location),
title: s.full_title().to_string(),
marker: s.marker.as_ref().map(|m| m.as_str().to_string()),
children,
}
}
fn definition_to_wire(d: &Definition) -> WireNode {
let children = d.children.iter().map(to_wire_node).collect::<Vec<_>>();
WireNode::Definition {
range: range_to_wire(&d.location),
origin: origin_string(&d.location),
subject: d.subject.as_string().to_string(),
children,
}
}
fn list_to_wire(l: &List) -> WireNode {
let marker_style = l
.marker
.as_ref()
.map(|m| decoration_style_name(m.style))
.unwrap_or("dash")
.to_string();
let items = l
.items
.iter()
.filter_map(|item| match item {
ContentItem::ListItem(li) => Some(list_item_to_wire(li)),
_ => None,
})
.collect();
WireNode::List {
range: range_to_wire(&l.location),
origin: origin_string(&l.location),
marker_style,
items,
}
}
fn list_item_to_wire(li: &ListItem) -> WireListItem {
let mut inlines = Vec::new();
for (i, tc) in li.text.iter().enumerate() {
if i > 0 {
inlines.push(WireInline::Text { text: "\n".into() });
}
inlines.extend(text_content_to_wire(tc));
}
let children = li.children.iter().map(to_wire_node).collect();
WireListItem {
range: range_to_wire(&li.location),
inlines,
children,
}
}
fn list_item_standalone_to_wire(li: &ListItem) -> WireNode {
WireNode::List {
range: range_to_wire(&li.location),
origin: origin_string(&li.location),
marker_style: "dash".into(),
items: vec![list_item_to_wire(li)],
}
}
fn table_to_wire(t: &Table) -> WireNode {
if t.cell_children_iter().next().is_some() {
return WireNode::Verbatim {
range: range_to_wire(&t.location),
origin: origin_string(&t.location),
label: "lex.internal.unsupported.table_block_cells".into(),
params: Value::Object(Map::new()),
body_text: String::new(),
subject: String::new(),
mode: "inflow".into(),
};
}
let header_rows = u32::try_from(t.header_rows.len()).unwrap_or(u32::MAX);
let align = table_align_summary(t);
let rows = t
.all_rows()
.map(|row| WireRow {
cells: row.cells.iter().map(table_cell_to_wire).collect(),
})
.collect();
let footnotes = t
.footnotes
.as_deref()
.map(table_footnotes_to_wire)
.unwrap_or_default();
WireNode::Table {
range: range_to_wire(&t.location),
origin: origin_string(&t.location),
caption: t.subject.as_string().to_string(),
header_rows,
align,
rows,
footnotes,
}
}
fn table_cell_to_wire(cell: &TableCell) -> WireTableCell {
debug_assert!(
!cell.has_block_content(),
"table_to_wire must short-circuit block-content cells before reaching table_cell_to_wire"
);
WireTableCell {
inlines: text_content_to_wire(&cell.content),
colspan: u32::try_from(cell.colspan).unwrap_or(1),
rowspan: u32::try_from(cell.rowspan).unwrap_or(1),
}
}
fn table_align_summary(t: &Table) -> String {
for row in &t.body_rows {
for cell in &row.cells {
match cell.align {
TableCellAlignment::Left => return "left".into(),
TableCellAlignment::Center => return "center".into(),
TableCellAlignment::Right => return "right".into(),
TableCellAlignment::None => {}
}
}
}
String::new()
}
fn table_footnotes_to_wire(footnotes: &List) -> Vec<WireFootnote> {
footnotes
.items
.iter()
.filter_map(|item| match item {
ContentItem::ListItem(li) => Some(WireFootnote {
marker: li.marker.as_string().to_string(),
inlines: li
.text
.first()
.map(text_content_to_wire)
.unwrap_or_default(),
}),
_ => None,
})
.collect()
}
fn verbatim_to_wire(v: &Verbatim) -> WireNode {
let label = v.closing_data.label.value.clone();
let params = parameters_to_json(&v.closing_data.parameters);
let body_text = v
.children
.iter()
.filter_map(|item| match item {
ContentItem::VerbatimLine(vl) => Some(vl.content.as_string().to_string()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");
WireNode::Verbatim {
range: range_to_wire(&v.location),
origin: origin_string(&v.location),
label,
params,
body_text,
subject: v.subject.as_string().to_string(),
mode: verbatim_mode_name(v.mode).to_string(),
}
}
fn verbatim_line_standalone_to_wire(vl: &VerbatimLine) -> WireNode {
WireNode::Verbatim {
range: range_to_wire(&vl.location),
origin: origin_string(&vl.location),
label: String::new(),
params: Value::Object(Map::new()),
body_text: vl.content.as_string().to_string(),
subject: String::new(),
mode: "inflow".into(),
}
}
fn text_line_standalone_to_wire(tl: &TextLine) -> WireNode {
WireNode::Paragraph {
range: range_to_wire(&tl.location),
origin: origin_string(&tl.location),
inlines: text_content_to_wire(&tl.content),
}
}
fn parameters_to_json(params: &[crate::lex::ast::elements::parameter::Parameter]) -> Value {
let mut obj = Map::with_capacity(params.len());
for p in params {
obj.insert(p.key.clone(), Value::String(p.value.clone()));
}
Value::Object(obj)
}
fn annotation_body_to_json(a: &Annotation) -> Value {
let children = a.children.iter().collect::<Vec<_>>();
if children.is_empty() {
return Value::Null;
}
let wire_children: Vec<Value> = children
.iter()
.map(|c| serde_json::to_value(to_wire_node(c)).expect("wire node serialises"))
.collect();
let mut obj = Map::with_capacity(2);
obj.insert("kind".into(), Value::String("block".into()));
obj.insert("children".into(), Value::Array(wire_children));
Value::Object(obj)
}
fn decoration_style_name(style: DecorationStyle) -> &'static str {
match style {
DecorationStyle::Plain => "dash",
DecorationStyle::Numerical => "numerical",
DecorationStyle::Alphabetical => "alphabetical",
DecorationStyle::Roman => "roman",
}
}
fn verbatim_mode_name(
mode: crate::lex::ast::elements::verbatim::VerbatimBlockMode,
) -> &'static str {
use crate::lex::ast::elements::verbatim::VerbatimBlockMode;
match mode {
VerbatimBlockMode::Inflow => "inflow",
VerbatimBlockMode::Fullwidth => "fullwidth",
}
}