Skip to content

Commit b3a65fd

Browse files
committed
JSON: Table writer and reader
1 parent ce933f9 commit b3a65fd

File tree

8 files changed

+398
-50
lines changed

8 files changed

+398
-50
lines changed

crates/quarto-markdown-pandoc/src/pandoc/treesitter.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,26 @@ fn process_native_inline<T: Write>(
301301
PandocNativeIntermediate::IntermediateBaseText(text, range) => {
302302
if let Some(_) = whitespace_re.find(&text) {
303303
Inline::Space(Space {
304-
source_info: SourceInfo::new(if context.filenames.is_empty() { None } else { Some(0) }, range),
304+
source_info: SourceInfo::new(
305+
if context.filenames.is_empty() {
306+
None
307+
} else {
308+
Some(0)
309+
},
310+
range,
311+
),
305312
})
306313
} else {
307314
Inline::Str(Str {
308315
text: apply_smart_quotes(text),
309-
source_info: SourceInfo::new(if context.filenames.is_empty() { None } else { Some(0) }, range),
316+
source_info: SourceInfo::new(
317+
if context.filenames.is_empty() {
318+
None
319+
} else {
320+
Some(0)
321+
},
322+
range,
323+
),
310324
})
311325
}
312326
}
@@ -365,12 +379,26 @@ fn process_native_inlines<T: Write>(
365379
PandocNativeIntermediate::IntermediateBaseText(text, range) => {
366380
if let Some(_) = whitespace_re.find(&text) {
367381
inlines.push(Inline::Space(Space {
368-
source_info: SourceInfo::new(if context.filenames.is_empty() { None } else { Some(0) }, range),
382+
source_info: SourceInfo::new(
383+
if context.filenames.is_empty() {
384+
None
385+
} else {
386+
Some(0)
387+
},
388+
range,
389+
),
369390
}))
370391
} else {
371392
inlines.push(Inline::Str(Str {
372393
text,
373-
source_info: SourceInfo::new(if context.filenames.is_empty() { None } else { Some(0) }, range),
394+
source_info: SourceInfo::new(
395+
if context.filenames.is_empty() {
396+
None
397+
} else {
398+
Some(0)
399+
},
400+
range,
401+
),
374402
}))
375403
}
376404
}

crates/quarto-markdown-pandoc/src/readers/json.rs

Lines changed: 246 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use crate::pandoc::ast_context::ASTContext;
77
use crate::pandoc::block::MetaBlock;
88
use crate::pandoc::location::{Location, Range, SourceInfo};
9+
use crate::pandoc::table::{
10+
Alignment, Cell, ColSpec, ColWidth, Row, Table, TableBody, TableFoot, TableHead,
11+
};
912
use crate::pandoc::{
1013
Attr, Block, BlockQuote, BulletList, Caption, Citation, CitationMode, Cite, Code, CodeBlock,
1114
DefinitionList, Div, Emph, Figure, Header, HorizontalRule, Image, Inline, Inlines, LineBlock,
@@ -73,7 +76,8 @@ fn read_location(value: &Value) -> Option<(Option<usize>, Range)> {
7376
column: end_obj.get("column")?.as_u64()? as usize,
7477
};
7578

76-
let filename_index = obj.get("filenameIndex")
79+
let filename_index = obj
80+
.get("filenameIndex")
7781
.and_then(|v| v.as_u64())
7882
.map(|i| i as usize);
7983

@@ -182,23 +186,26 @@ fn read_inline(value: &Value) -> Result<Inline> {
182186
}))
183187
}
184188
"Space" => {
185-
let (filename_index, range) = obj.get("l")
189+
let (filename_index, range) = obj
190+
.get("l")
186191
.and_then(read_location)
187192
.unwrap_or_else(|| (None, empty_range()));
188193
Ok(Inline::Space(Space {
189194
source_info: SourceInfo::new(filename_index, range),
190195
}))
191196
}
192197
"LineBreak" => {
193-
let (filename_index, range) = obj.get("l")
198+
let (filename_index, range) = obj
199+
.get("l")
194200
.and_then(read_location)
195201
.unwrap_or_else(|| (None, empty_range()));
196202
Ok(Inline::LineBreak(crate::pandoc::inline::LineBreak {
197203
source_info: SourceInfo::new(filename_index, range),
198204
}))
199205
}
200206
"SoftBreak" => {
201-
let (filename_index, range) = obj.get("l")
207+
let (filename_index, range) = obj
208+
.get("l")
202209
.and_then(read_location)
203210
.unwrap_or_else(|| (None, empty_range()));
204211
Ok(Inline::SoftBreak(SoftBreak {
@@ -616,7 +623,8 @@ fn read_ast_context(value: &Value) -> Result<ASTContext> {
616623
.as_object()
617624
.ok_or_else(|| JsonReadError::InvalidType("Expected object for ASTContext".to_string()))?;
618625

619-
let filenames_val = obj.get("filenames")
626+
let filenames_val = obj
627+
.get("filenames")
620628
.ok_or_else(|| JsonReadError::MissingField("filenames".to_string()))?;
621629

622630
let filenames_arr = filenames_val
@@ -773,6 +781,197 @@ fn read_blocks(value: &Value) -> Result<Vec<Block>> {
773781
arr.iter().map(read_block).collect()
774782
}
775783

784+
fn read_alignment(value: &Value) -> Result<Alignment> {
785+
let obj = value
786+
.as_object()
787+
.ok_or_else(|| JsonReadError::InvalidType("Expected object for Alignment".to_string()))?;
788+
let t = obj
789+
.get("t")
790+
.and_then(|v| v.as_str())
791+
.ok_or_else(|| JsonReadError::MissingField("t".to_string()))?;
792+
793+
match t {
794+
"AlignLeft" => Ok(Alignment::Left),
795+
"AlignCenter" => Ok(Alignment::Center),
796+
"AlignRight" => Ok(Alignment::Right),
797+
"AlignDefault" => Ok(Alignment::Default),
798+
_ => Err(JsonReadError::UnsupportedVariant(format!(
799+
"Alignment: {}",
800+
t
801+
))),
802+
}
803+
}
804+
805+
fn read_colwidth(value: &Value) -> Result<ColWidth> {
806+
let obj = value
807+
.as_object()
808+
.ok_or_else(|| JsonReadError::InvalidType("Expected object for ColWidth".to_string()))?;
809+
let t = obj
810+
.get("t")
811+
.and_then(|v| v.as_str())
812+
.ok_or_else(|| JsonReadError::MissingField("t".to_string()))?;
813+
814+
match t {
815+
"ColWidthDefault" => Ok(ColWidth::Default),
816+
"ColWidth" => {
817+
let c = obj
818+
.get("c")
819+
.ok_or_else(|| JsonReadError::MissingField("c".to_string()))?;
820+
let percentage = c.as_f64().ok_or_else(|| {
821+
JsonReadError::InvalidType("ColWidth percentage must be number".to_string())
822+
})?;
823+
Ok(ColWidth::Percentage(percentage))
824+
}
825+
_ => Err(JsonReadError::UnsupportedVariant(format!(
826+
"ColWidth: {}",
827+
t
828+
))),
829+
}
830+
}
831+
832+
fn read_colspec(value: &Value) -> Result<ColSpec> {
833+
let arr = value
834+
.as_array()
835+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for ColSpec".to_string()))?;
836+
837+
if arr.len() != 2 {
838+
return Err(JsonReadError::InvalidType(
839+
"ColSpec array must have 2 elements".to_string(),
840+
));
841+
}
842+
843+
let alignment = read_alignment(&arr[0])?;
844+
let colwidth = read_colwidth(&arr[1])?;
845+
Ok((alignment, colwidth))
846+
}
847+
848+
fn read_cell(value: &Value) -> Result<Cell> {
849+
let arr = value
850+
.as_array()
851+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for Cell".to_string()))?;
852+
853+
if arr.len() != 5 {
854+
return Err(JsonReadError::InvalidType(
855+
"Cell array must have 5 elements".to_string(),
856+
));
857+
}
858+
859+
let attr = read_attr(&arr[0])?;
860+
let alignment = read_alignment(&arr[1])?;
861+
let row_span = arr[2]
862+
.as_u64()
863+
.ok_or_else(|| JsonReadError::InvalidType("Cell row_span must be number".to_string()))?
864+
as usize;
865+
let col_span = arr[3]
866+
.as_u64()
867+
.ok_or_else(|| JsonReadError::InvalidType("Cell col_span must be number".to_string()))?
868+
as usize;
869+
let content = read_blocks(&arr[4])?;
870+
871+
Ok(Cell {
872+
attr,
873+
alignment,
874+
row_span,
875+
col_span,
876+
content,
877+
})
878+
}
879+
880+
fn read_row(value: &Value) -> Result<Row> {
881+
let arr = value
882+
.as_array()
883+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for Row".to_string()))?;
884+
885+
if arr.len() != 2 {
886+
return Err(JsonReadError::InvalidType(
887+
"Row array must have 2 elements".to_string(),
888+
));
889+
}
890+
891+
let attr = read_attr(&arr[0])?;
892+
let cells_arr = arr[1]
893+
.as_array()
894+
.ok_or_else(|| JsonReadError::InvalidType("Row cells must be array".to_string()))?;
895+
let cells = cells_arr
896+
.iter()
897+
.map(read_cell)
898+
.collect::<Result<Vec<_>>>()?;
899+
900+
Ok(Row { attr, cells })
901+
}
902+
903+
fn read_table_head(value: &Value) -> Result<TableHead> {
904+
let arr = value
905+
.as_array()
906+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for TableHead".to_string()))?;
907+
908+
if arr.len() != 2 {
909+
return Err(JsonReadError::InvalidType(
910+
"TableHead array must have 2 elements".to_string(),
911+
));
912+
}
913+
914+
let attr = read_attr(&arr[0])?;
915+
let rows_arr = arr[1]
916+
.as_array()
917+
.ok_or_else(|| JsonReadError::InvalidType("TableHead rows must be array".to_string()))?;
918+
let rows = rows_arr.iter().map(read_row).collect::<Result<Vec<_>>>()?;
919+
920+
Ok(TableHead { attr, rows })
921+
}
922+
923+
fn read_table_body(value: &Value) -> Result<TableBody> {
924+
let arr = value
925+
.as_array()
926+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for TableBody".to_string()))?;
927+
928+
if arr.len() != 4 {
929+
return Err(JsonReadError::InvalidType(
930+
"TableBody array must have 4 elements".to_string(),
931+
));
932+
}
933+
934+
let attr = read_attr(&arr[0])?;
935+
let rowhead_columns = arr[1].as_u64().ok_or_else(|| {
936+
JsonReadError::InvalidType("TableBody rowhead_columns must be number".to_string())
937+
})? as usize;
938+
let head_arr = arr[2]
939+
.as_array()
940+
.ok_or_else(|| JsonReadError::InvalidType("TableBody head must be array".to_string()))?;
941+
let head = head_arr.iter().map(read_row).collect::<Result<Vec<_>>>()?;
942+
let body_arr = arr[3]
943+
.as_array()
944+
.ok_or_else(|| JsonReadError::InvalidType("TableBody body must be array".to_string()))?;
945+
let body = body_arr.iter().map(read_row).collect::<Result<Vec<_>>>()?;
946+
947+
Ok(TableBody {
948+
attr,
949+
rowhead_columns,
950+
head,
951+
body,
952+
})
953+
}
954+
955+
fn read_table_foot(value: &Value) -> Result<TableFoot> {
956+
let arr = value
957+
.as_array()
958+
.ok_or_else(|| JsonReadError::InvalidType("Expected array for TableFoot".to_string()))?;
959+
960+
if arr.len() != 2 {
961+
return Err(JsonReadError::InvalidType(
962+
"TableFoot array must have 2 elements".to_string(),
963+
));
964+
}
965+
966+
let attr = read_attr(&arr[0])?;
967+
let rows_arr = arr[1]
968+
.as_array()
969+
.ok_or_else(|| JsonReadError::InvalidType("TableFoot rows must be array".to_string()))?;
970+
let rows = rows_arr.iter().map(read_row).collect::<Result<Vec<_>>>()?;
971+
972+
Ok(TableFoot { attr, rows })
973+
}
974+
776975
fn read_block(value: &Value) -> Result<Block> {
777976
let obj = value
778977
.as_object()
@@ -783,7 +982,8 @@ fn read_block(value: &Value) -> Result<Block> {
783982
.ok_or_else(|| JsonReadError::MissingField("t".to_string()))?;
784983

785984
// Extract location information if present
786-
let (filename_index, range) = obj.get("l")
985+
let (filename_index, range) = obj
986+
.get("l")
787987
.and_then(read_location)
788988
.unwrap_or_else(|| (None, empty_range()));
789989

@@ -993,6 +1193,46 @@ fn read_block(value: &Value) -> Result<Block> {
9931193
source_info: SourceInfo::new(filename_index, range),
9941194
}))
9951195
}
1196+
"Table" => {
1197+
let c = obj
1198+
.get("c")
1199+
.ok_or_else(|| JsonReadError::MissingField("c".to_string()))?;
1200+
let arr = c.as_array().ok_or_else(|| {
1201+
JsonReadError::InvalidType("Table content must be array".to_string())
1202+
})?;
1203+
if arr.len() != 6 {
1204+
return Err(JsonReadError::InvalidType(
1205+
"Table array must have 6 elements".to_string(),
1206+
));
1207+
}
1208+
let attr = read_attr(&arr[0])?;
1209+
let caption = read_caption(&arr[1])?;
1210+
let colspec_arr = arr[2].as_array().ok_or_else(|| {
1211+
JsonReadError::InvalidType("Table colspec must be array".to_string())
1212+
})?;
1213+
let colspec = colspec_arr
1214+
.iter()
1215+
.map(read_colspec)
1216+
.collect::<Result<Vec<_>>>()?;
1217+
let head = read_table_head(&arr[3])?;
1218+
let bodies_arr = arr[4].as_array().ok_or_else(|| {
1219+
JsonReadError::InvalidType("Table bodies must be array".to_string())
1220+
})?;
1221+
let bodies = bodies_arr
1222+
.iter()
1223+
.map(read_table_body)
1224+
.collect::<Result<Vec<_>>>()?;
1225+
let foot = read_table_foot(&arr[5])?;
1226+
Ok(Block::Table(Table {
1227+
attr,
1228+
caption,
1229+
colspec,
1230+
head,
1231+
bodies,
1232+
foot,
1233+
source_info: SourceInfo::new(filename_index, range),
1234+
}))
1235+
}
9961236
"Div" => {
9971237
let c = obj
9981238
.get("c")

0 commit comments

Comments
 (0)