Skip to main content

pdf_ast/serialization/
mod.rs

1use crate::ast::{EdgeType, NodeId, PdfAstGraph, PdfDocument};
2use crate::types::PdfValue;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6const AST_SCHEMA_VERSION: &str = "1.1";
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SerializableDocument {
10    pub version: String,
11    pub schema_version: String,
12    pub ast: SerializableGraph,
13    pub catalog: Option<usize>,
14    pub info: Option<usize>,
15    pub trailer: SerializableValue,
16    pub xref_entries: HashMap<String, SerializableXRefEntry>,
17    pub metadata: SerializableDocumentMetadata,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SerializableXRefEntry {
22    pub offset: Option<u64>,
23    pub generation: u16,
24    pub entry_type: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct SerializableDocumentMetadata {
29    pub file_size: Option<u64>,
30    pub linearized: bool,
31    pub encrypted: bool,
32    pub has_forms: bool,
33    pub has_xfa: bool,
34    pub xfa_packets: usize,
35    pub has_xfa_scripts: bool,
36    pub xfa_script_nodes: usize,
37    pub has_hybrid_forms: bool,
38    pub form_field_count: usize,
39    pub has_javascript: bool,
40    pub has_embedded_files: bool,
41    pub has_signatures: bool,
42    pub has_richmedia: bool,
43    pub richmedia_annotations: usize,
44    pub richmedia_assets: usize,
45    pub richmedia_scripts: usize,
46    pub has_3d: bool,
47    pub threed_annotations: usize,
48    pub threed_u3d: usize,
49    pub threed_prc: usize,
50    pub has_audio: bool,
51    pub audio_annotations: usize,
52    pub has_video: bool,
53    pub video_annotations: usize,
54    pub has_dss: bool,
55    pub dss_vri_count: usize,
56    pub dss_certs: usize,
57    pub dss_ocsp: usize,
58    pub dss_crl: usize,
59    pub dss_timestamps: usize,
60    pub page_count: usize,
61    pub producer: Option<String>,
62    pub creator: Option<String>,
63    pub creation_date: Option<String>,
64    pub modification_date: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct SerializableGraph {
69    pub nodes: Vec<SerializableNode>,
70    pub edges: Vec<SerializableEdge>,
71    pub root: Option<usize>,
72    pub metadata: GraphMetadata,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct SerializableNode {
77    pub id: usize,
78    pub node_type: String,
79    pub value: SerializableValue,
80    pub object_id: Option<(u32, u16)>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct SerializableEdge {
85    pub from: usize,
86    pub to: usize,
87    pub edge_type: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct GraphMetadata {
92    pub node_count: usize,
93    pub edge_count: usize,
94    pub is_cyclic: bool,
95    pub serialization_version: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(tag = "type", content = "value")]
100pub enum SerializableValue {
101    Null,
102    Boolean(bool),
103    Integer(i64),
104    Real(f64),
105    String(String),
106    Name(String),
107    Array(Vec<SerializableValue>),
108    Dictionary(HashMap<String, SerializableValue>),
109    Stream {
110        dictionary: HashMap<String, SerializableValue>,
111        data: Vec<u8>,
112        lazy: Option<crate::types::StreamReference>,
113    },
114    Reference {
115        object_id: u32,
116        generation: u16,
117    },
118}
119
120impl SerializableGraph {
121    pub fn from_ast(ast: &PdfAstGraph) -> Self {
122        let serializer = GraphSerializer::new();
123        serializer.serialize(ast)
124    }
125
126    pub fn to_json(&self) -> serde_json::Result<String> {
127        serde_json::to_string_pretty(self)
128    }
129
130    pub fn to_cbor(&self) -> serde_cbor::Result<Vec<u8>> {
131        serde_cbor::to_vec(self)
132    }
133
134    pub fn from_json(json: &str) -> serde_json::Result<Self> {
135        serde_json::from_str(json)
136    }
137
138    pub fn from_cbor(data: &[u8]) -> serde_cbor::Result<Self> {
139        serde_cbor::from_slice(data)
140    }
141}
142
143struct GraphSerializer {
144    nodes: Vec<SerializableNode>,
145    edges: Vec<SerializableEdge>,
146    node_id_map: HashMap<NodeId, usize>,
147    next_serial_id: usize,
148}
149
150impl GraphSerializer {
151    fn new() -> Self {
152        Self {
153            nodes: Vec::new(),
154            edges: Vec::new(),
155            node_id_map: HashMap::new(),
156            next_serial_id: 0,
157        }
158    }
159
160    fn serialize(mut self, ast: &PdfAstGraph) -> SerializableGraph {
161        // Serialize all nodes
162        for node in ast.get_all_nodes() {
163            let serial_id = self.next_serial_id;
164            self.next_serial_id += 1;
165
166            self.node_id_map.insert(node.id, serial_id);
167
168            // Extract object_id if this is an Object node
169            let object_id = match &node.node_type {
170                crate::ast::NodeType::Object(obj_id) => Some((obj_id.number, obj_id.generation)),
171                _ => None,
172            };
173
174            let serialized_node = SerializableNode {
175                id: serial_id,
176                node_type: format!("{:?}", node.node_type)
177                    .split('(')
178                    .next()
179                    .unwrap_or("Unknown")
180                    .to_string(),
181                value: Self::serialize_value(&node.value),
182                object_id,
183            };
184
185            self.nodes.push(serialized_node);
186        }
187
188        // Serialize all edges - FIXED: Now properly serializes edges
189        for edge in ast.get_all_edges() {
190            if let (Some(&from_id), Some(&to_id)) = (
191                self.node_id_map.get(&edge.from),
192                self.node_id_map.get(&edge.to),
193            ) {
194                let serialized_edge = SerializableEdge {
195                    from: from_id,
196                    to: to_id,
197                    edge_type: format!("{:?}", edge.edge_type),
198                };
199                self.edges.push(serialized_edge);
200            }
201        }
202
203        // Find root
204        let root_serial_id = ast
205            .root
206            .and_then(|root_node_id| self.node_id_map.get(&root_node_id).copied());
207
208        SerializableGraph {
209            nodes: self.nodes,
210            edges: self.edges,
211            root: root_serial_id,
212            metadata: GraphMetadata {
213                node_count: ast.node_count(),
214                edge_count: ast.edge_count(),
215                is_cyclic: ast.is_cyclic(),
216                serialization_version: "1.0".to_string(),
217            },
218        }
219    }
220
221    fn serialize_value(value: &PdfValue) -> SerializableValue {
222        match value {
223            PdfValue::Null => SerializableValue::Null,
224            PdfValue::Boolean(b) => SerializableValue::Boolean(*b),
225            PdfValue::Integer(i) => SerializableValue::Integer(*i),
226            PdfValue::Real(r) => SerializableValue::Real(*r),
227            PdfValue::String(s) => SerializableValue::String(s.to_string_lossy()),
228            PdfValue::Name(n) => SerializableValue::Name(n.as_str().to_string()),
229            PdfValue::Array(arr) => {
230                let items: Vec<SerializableValue> = arr.iter().map(Self::serialize_value).collect();
231                SerializableValue::Array(items)
232            }
233            PdfValue::Dictionary(dict) => {
234                let mut map = HashMap::new();
235                for (key, val) in dict.iter() {
236                    map.insert(key.to_string(), Self::serialize_value(val));
237                }
238                SerializableValue::Dictionary(map)
239            }
240            PdfValue::Stream(stream) => {
241                let mut dict_map = HashMap::new();
242                for (key, val) in stream.dict.iter() {
243                    dict_map.insert(key.to_string(), Self::serialize_value(val));
244                }
245                SerializableValue::Stream {
246                    dictionary: dict_map,
247                    data: match &stream.data {
248                        crate::types::StreamData::Raw(bytes) => bytes.clone(),
249                        crate::types::StreamData::Decoded(bytes) => bytes.clone(),
250                        crate::types::StreamData::Lazy(_) => Vec::new(),
251                    },
252                    lazy: match &stream.data {
253                        crate::types::StreamData::Lazy(reference) => Some(reference.clone()),
254                        _ => None,
255                    },
256                }
257            }
258            PdfValue::Reference(r) => SerializableValue::Reference {
259                object_id: r.object_number,
260                generation: r.generation_number,
261            },
262        }
263    }
264}
265
266pub struct GraphDeserializer;
267
268impl GraphDeserializer {
269    pub fn deserialize(serialized: SerializableGraph) -> Result<PdfAstGraph, String> {
270        let mut ast = PdfAstGraph::new();
271        let mut id_map: HashMap<usize, NodeId> = HashMap::new();
272
273        // First pass: create all nodes
274        for serialized_node in &serialized.nodes {
275            let node_type =
276                Self::parse_node_type(&serialized_node.node_type, serialized_node.object_id)?;
277            let value = Self::deserialize_value(&serialized_node.value)?;
278            let node_id = ast.create_node(node_type, value);
279
280            // Object ID mapping would need to be implemented in the AST
281            // if let Some((obj_num, obj_gen)) = serialized_node.object_id {
282            //     ast.set_object_mapping(node_id, object_id);
283            // }
284
285            id_map.insert(serialized_node.id, node_id);
286        }
287
288        // Second pass: create all edges
289        for serialized_edge in &serialized.edges {
290            let from_id = id_map
291                .get(&serialized_edge.from)
292                .ok_or_else(|| format!("Invalid from node ID: {}", serialized_edge.from))?;
293            let to_id = id_map
294                .get(&serialized_edge.to)
295                .ok_or_else(|| format!("Invalid to node ID: {}", serialized_edge.to))?;
296            let edge_type = Self::parse_edge_type(&serialized_edge.edge_type)?;
297
298            ast.add_edge(*from_id, *to_id, edge_type);
299        }
300
301        // Set root if it exists
302        if let Some(root_serial_id) = serialized.root {
303            if let Some(&root_id) = id_map.get(&root_serial_id) {
304                ast.set_root(root_id);
305            }
306        }
307
308        Ok(ast)
309    }
310
311    fn parse_node_type(
312        type_str: &str,
313        object_id: Option<(u32, u16)>,
314    ) -> Result<crate::ast::NodeType, String> {
315        use crate::types::ObjectId;
316        match type_str {
317            "Root" => Ok(crate::ast::NodeType::Root),
318            "Catalog" => Ok(crate::ast::NodeType::Catalog),
319            "Pages" => Ok(crate::ast::NodeType::Pages),
320            "Page" => Ok(crate::ast::NodeType::Page),
321            "Resource" => Ok(crate::ast::NodeType::Resource),
322            "Font" => Ok(crate::ast::NodeType::Font),
323            "Image" => Ok(crate::ast::NodeType::Image),
324            "ContentStream" => Ok(crate::ast::NodeType::ContentStream),
325            "Annotation" => Ok(crate::ast::NodeType::Annotation),
326            "Action" => Ok(crate::ast::NodeType::Action),
327            "Metadata" => Ok(crate::ast::NodeType::Metadata),
328            "EmbeddedFile" => Ok(crate::ast::NodeType::EmbeddedFile),
329            "Signature" => Ok(crate::ast::NodeType::Signature),
330            "Object" => Ok(crate::ast::NodeType::Object(
331                object_id
332                    .map(|(num, gen)| ObjectId::new(num, gen))
333                    .unwrap_or_else(|| ObjectId::new(0, 0)),
334            )),
335            "Unknown" => Ok(crate::ast::NodeType::Unknown),
336            "Stream" => Ok(crate::ast::NodeType::Stream),
337            "FilteredStream" => Ok(crate::ast::NodeType::FilteredStream),
338            "DecodedStream" => Ok(crate::ast::NodeType::DecodedStream),
339            "XObject" => Ok(crate::ast::NodeType::XObject),
340            "FormXObject" => Ok(crate::ast::NodeType::FormXObject),
341            "ImageXObject" => Ok(crate::ast::NodeType::ImageXObject),
342            "Type1Font" => Ok(crate::ast::NodeType::Type1Font),
343            "TrueTypeFont" => Ok(crate::ast::NodeType::TrueTypeFont),
344            "Type3Font" => Ok(crate::ast::NodeType::Type3Font),
345            "CIDFont" => Ok(crate::ast::NodeType::CIDFont),
346            "JavaScriptAction" => Ok(crate::ast::NodeType::JavaScriptAction),
347            "GoToAction" => Ok(crate::ast::NodeType::GoToAction),
348            "URIAction" => Ok(crate::ast::NodeType::URIAction),
349            "LaunchAction" => Ok(crate::ast::NodeType::LaunchAction),
350            "SubmitFormAction" => Ok(crate::ast::NodeType::SubmitFormAction),
351            "AcroForm" => Ok(crate::ast::NodeType::AcroForm),
352            "Field" => Ok(crate::ast::NodeType::Field),
353            "Encrypt" => Ok(crate::ast::NodeType::Encrypt),
354            "Permission" => Ok(crate::ast::NodeType::Permission),
355            "ContentOperator" => Ok(crate::ast::NodeType::ContentOperator),
356            "TextOperator" => Ok(crate::ast::NodeType::TextOperator),
357            "GraphicsOperator" => Ok(crate::ast::NodeType::GraphicsOperator),
358            "EmbeddedJS" => Ok(crate::ast::NodeType::EmbeddedJS),
359            "SuspiciousAction" => Ok(crate::ast::NodeType::SuspiciousAction),
360            "ExternalReference" => Ok(crate::ast::NodeType::ExternalReference),
361            "EncodedContent" => Ok(crate::ast::NodeType::EncodedContent),
362            "Outline" => Ok(crate::ast::NodeType::Outline),
363            "OutlineItem" => Ok(crate::ast::NodeType::OutlineItem),
364            "NameTree" => Ok(crate::ast::NodeType::NameTree),
365            "StructTreeRoot" => Ok(crate::ast::NodeType::StructTreeRoot),
366            "StructElem" => Ok(crate::ast::NodeType::StructElem),
367            "ColorSpace" => Ok(crate::ast::NodeType::ColorSpace),
368            "ICCBased" => Ok(crate::ast::NodeType::ICCBased),
369            "Separation" => Ok(crate::ast::NodeType::Separation),
370            "DeviceN" => Ok(crate::ast::NodeType::DeviceN),
371            "Indexed" => Ok(crate::ast::NodeType::Indexed),
372            "Pattern" => Ok(crate::ast::NodeType::Pattern),
373            "Shading" => Ok(crate::ast::NodeType::Shading),
374            "ExtGState" => Ok(crate::ast::NodeType::ExtGState),
375            "Function" => Ok(crate::ast::NodeType::Function),
376            "CMap" => Ok(crate::ast::NodeType::CMap),
377            "ToUnicode" => Ok(crate::ast::NodeType::ToUnicode),
378            "Encoding" => Ok(crate::ast::NodeType::Encoding),
379            "OCG" => Ok(crate::ast::NodeType::OCG),
380            "OCProperties" => Ok(crate::ast::NodeType::OCProperties),
381            "OCMD" => Ok(crate::ast::NodeType::OCMD),
382            "RichMedia" => Ok(crate::ast::NodeType::RichMedia),
383            "Rendition" => Ok(crate::ast::NodeType::Rendition),
384            "Screen" => Ok(crate::ast::NodeType::Screen),
385            "Sound" => Ok(crate::ast::NodeType::Sound),
386            "Movie" => Ok(crate::ast::NodeType::Movie),
387            "ThreeD" => Ok(crate::ast::NodeType::ThreeD),
388            "U3D" => Ok(crate::ast::NodeType::U3D),
389            "PRC" => Ok(crate::ast::NodeType::PRC),
390            "OutputIntent" => Ok(crate::ast::NodeType::OutputIntent),
391            "LinkAnnotation" => Ok(crate::ast::NodeType::LinkAnnotation),
392            "WidgetAnnotation" => Ok(crate::ast::NodeType::WidgetAnnotation),
393            "FileAttachmentAnnotation" => Ok(crate::ast::NodeType::FileAttachmentAnnotation),
394            "InlineImage" => Ok(crate::ast::NodeType::InlineImage),
395            "Form" => Ok(crate::ast::NodeType::Form),
396            "Structure" => Ok(crate::ast::NodeType::Structure),
397            "Multimedia" => Ok(crate::ast::NodeType::Multimedia),
398            "JavaScript" => Ok(crate::ast::NodeType::JavaScript),
399            "Encryption" => Ok(crate::ast::NodeType::Encryption),
400            "Content" => Ok(crate::ast::NodeType::Content),
401            "Other" => Ok(crate::ast::NodeType::Other),
402            _ => Ok(crate::ast::NodeType::Unknown),
403        }
404    }
405
406    fn parse_edge_type(type_str: &str) -> Result<EdgeType, String> {
407        match type_str {
408            "Child" => Ok(EdgeType::Child),
409            "Reference" => Ok(EdgeType::Reference),
410            "Parent" => Ok(EdgeType::Parent),
411            "Resource" => Ok(EdgeType::Resource),
412            "Annotation" => Ok(EdgeType::Annotation),
413            "Content" => Ok(EdgeType::Content),
414            _ => Err(format!("Unknown edge type: {}", type_str)),
415        }
416    }
417
418    fn deserialize_value(value: &SerializableValue) -> Result<PdfValue, String> {
419        match value {
420            SerializableValue::Null => Ok(PdfValue::Null),
421            SerializableValue::Boolean(b) => Ok(PdfValue::Boolean(*b)),
422            SerializableValue::Integer(i) => Ok(PdfValue::Integer(*i)),
423            SerializableValue::Real(r) => Ok(PdfValue::Real(*r)),
424            SerializableValue::String(s) => Ok(PdfValue::String(
425                crate::types::PdfString::new_literal(s.as_bytes()),
426            )),
427            SerializableValue::Name(n) => Ok(PdfValue::Name(crate::types::PdfName::new(n))),
428            SerializableValue::Array(items) => {
429                let mut array = crate::types::PdfArray::new();
430                for item in items {
431                    array.push(Self::deserialize_value(item)?);
432                }
433                Ok(PdfValue::Array(array))
434            }
435            SerializableValue::Dictionary(map) => {
436                let mut dict = crate::types::PdfDictionary::new();
437                for (key, val) in map {
438                    dict.insert(key.as_str(), Self::deserialize_value(val)?);
439                }
440                Ok(PdfValue::Dictionary(dict))
441            }
442            SerializableValue::Stream {
443                dictionary,
444                data,
445                lazy,
446            } => {
447                let mut dict = crate::types::PdfDictionary::new();
448                for (key, val) in dictionary {
449                    dict.insert(key.as_str(), Self::deserialize_value(val)?);
450                }
451                let stream = if let Some(reference) = lazy {
452                    crate::types::PdfStream::new_lazy(dict, reference.clone())
453                } else {
454                    crate::types::PdfStream {
455                        dict,
456                        data: crate::types::StreamData::Raw(data.clone()),
457                    }
458                };
459                Ok(PdfValue::Stream(stream))
460            }
461            SerializableValue::Reference {
462                object_id,
463                generation,
464            } => Ok(PdfValue::Reference(crate::types::PdfReference {
465                object_number: *object_id,
466                generation_number: *generation,
467            })),
468        }
469    }
470}
471
472/// Convert a PdfDocument to JSON string
473pub fn to_json(document: &PdfDocument) -> Result<String, serde_json::Error> {
474    let serializable = SerializableDocument::from_document(document);
475    serde_json::to_string_pretty(&serializable)
476}
477
478impl SerializableDocument {
479    pub fn from_document(document: &PdfDocument) -> Self {
480        let ast_serializable = SerializableGraph::from_ast(&document.ast);
481
482        // Convert XRef entries
483        let mut xref_entries = HashMap::new();
484        for (obj_id, entry) in &document.xref.entries {
485            let key = format!("{}_{}", obj_id.number, obj_id.generation);
486            let serializable_entry = match entry {
487                crate::ast::document::XRefEntry::InUse { offset, generation } => {
488                    SerializableXRefEntry {
489                        offset: Some(*offset),
490                        generation: *generation,
491                        entry_type: "InUse".to_string(),
492                    }
493                }
494                crate::ast::document::XRefEntry::Free { generation, .. } => SerializableXRefEntry {
495                    offset: None,
496                    generation: *generation,
497                    entry_type: "Free".to_string(),
498                },
499                crate::ast::document::XRefEntry::Compressed { .. } => SerializableXRefEntry {
500                    offset: None,
501                    generation: 0,
502                    entry_type: "Compressed".to_string(),
503                },
504            };
505            xref_entries.insert(key, serializable_entry);
506        }
507
508        // Convert catalog and info to serial IDs
509        let catalog_serial_id = document.catalog.and_then(|node_id| {
510            ast_serializable
511                .nodes
512                .iter()
513                .find(|node| node.id == node_id.0)
514                .map(|node| node.id)
515        });
516
517        let info_serial_id = document.info.and_then(|node_id| {
518            ast_serializable
519                .nodes
520                .iter()
521                .find(|node| node.id == node_id.0)
522                .map(|node| node.id)
523        });
524
525        SerializableDocument {
526            version: document.version.to_string(),
527            schema_version: AST_SCHEMA_VERSION.to_string(),
528            ast: ast_serializable,
529            catalog: catalog_serial_id,
530            info: info_serial_id,
531            trailer: GraphSerializer::serialize_value(&PdfValue::Dictionary(
532                document.trailer.clone(),
533            )),
534            xref_entries,
535            metadata: SerializableDocumentMetadata {
536                file_size: document.metadata.file_size,
537                linearized: document.metadata.linearized,
538                encrypted: document.metadata.encrypted,
539                has_forms: document.metadata.has_forms,
540                has_xfa: document.metadata.has_xfa,
541                xfa_packets: document.metadata.xfa_packets,
542                has_xfa_scripts: document.metadata.has_xfa_scripts,
543                xfa_script_nodes: document.metadata.xfa_script_nodes,
544                has_hybrid_forms: document.metadata.has_hybrid_forms,
545                form_field_count: document.metadata.form_field_count,
546                has_javascript: document.metadata.has_javascript,
547                has_embedded_files: document.metadata.has_embedded_files,
548                has_signatures: document.metadata.has_signatures,
549                has_richmedia: document.metadata.has_richmedia,
550                richmedia_annotations: document.metadata.richmedia_annotations,
551                richmedia_assets: document.metadata.richmedia_assets,
552                richmedia_scripts: document.metadata.richmedia_scripts,
553                has_3d: document.metadata.has_3d,
554                threed_annotations: document.metadata.threed_annotations,
555                threed_u3d: document.metadata.threed_u3d,
556                threed_prc: document.metadata.threed_prc,
557                has_audio: document.metadata.has_audio,
558                audio_annotations: document.metadata.audio_annotations,
559                has_video: document.metadata.has_video,
560                video_annotations: document.metadata.video_annotations,
561                has_dss: document.metadata.has_dss,
562                dss_vri_count: document.metadata.dss_vri_count,
563                dss_certs: document.metadata.dss_certs,
564                dss_ocsp: document.metadata.dss_ocsp,
565                dss_crl: document.metadata.dss_crl,
566                dss_timestamps: document.metadata.dss_timestamps,
567                page_count: document.metadata.page_count,
568                producer: document.metadata.producer.clone(),
569                creator: document.metadata.creator.clone(),
570                creation_date: document.metadata.creation_date.clone(),
571                modification_date: document.metadata.modification_date.clone(),
572            },
573        }
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use crate::ast::{NodeType, PdfAstGraph, PdfDocument, PdfVersion};
581    use crate::types::{PdfDictionary, PdfValue};
582
583    #[test]
584    fn test_graph_serialization() {
585        let mut ast = PdfAstGraph::new();
586        let root_value = PdfValue::Dictionary(PdfDictionary::new());
587        let root_id = ast.create_node(NodeType::Root, root_value);
588        ast.set_root(root_id);
589
590        let serialized = SerializableGraph::from_ast(&ast);
591        assert_eq!(serialized.nodes.len(), 1);
592        assert_eq!(serialized.edges.len(), 0);
593        assert!(serialized.root.is_some());
594
595        let json = serialized.to_json().unwrap();
596        assert!(json.contains("Root"));
597
598        let deserialized = SerializableGraph::from_json(&json).unwrap();
599        assert_eq!(deserialized.nodes.len(), 1);
600    }
601
602    #[test]
603    fn test_document_serialization() {
604        let version = PdfVersion::new(1, 7);
605        let document = PdfDocument::new(version);
606
607        let json = to_json(&document).unwrap();
608        assert!(json.contains("1.7"));
609        assert!(json.contains("ast"));
610        assert!(json.contains("metadata"));
611        assert!(json.contains("schema_version"));
612    }
613
614    #[test]
615    fn test_cbor_serialization() {
616        let mut ast = PdfAstGraph::new();
617        let root_value = PdfValue::Dictionary(PdfDictionary::new());
618        let root_id = ast.create_node(NodeType::Root, root_value);
619        ast.set_root(root_id);
620
621        let serialized = SerializableGraph::from_ast(&ast);
622        let cbor_data = serialized.to_cbor().unwrap();
623        assert!(!cbor_data.is_empty());
624
625        let deserialized = SerializableGraph::from_cbor(&cbor_data).unwrap();
626        assert_eq!(deserialized.nodes.len(), 1);
627    }
628}