1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Error)]
10pub enum Error {
11 #[error("package error: {0}")]
13 Package(#[from] ooxml_opc::Error),
14
15 #[error("XML error: {0}")]
17 Xml(#[from] quick_xml::Error),
18
19 #[error("{context}: {message}")]
21 Parse {
22 context: String,
24 message: String,
26 position: Option<u64>,
28 },
29
30 #[error("invalid document: {0}")]
32 Invalid(String),
33
34 #[error("unsupported: {0}")]
36 Unsupported(String),
37
38 #[error("missing part: {0}")]
40 MissingPart(String),
41
42 #[error("UTF-8 error: {0}")]
44 Utf8(#[from] std::string::FromUtf8Error),
45
46 #[error("I/O error: {0}")]
48 Io(#[from] std::io::Error),
49
50 #[error("raw XML error: {0}")]
52 RawXml(#[from] ooxml_xml::Error),
53}
54
55impl From<crate::generated_serializers::SerializeError> for Error {
56 fn from(e: crate::generated_serializers::SerializeError) -> Self {
57 match e {
58 crate::generated_serializers::SerializeError::Xml(x) => Error::Xml(x),
59 crate::generated_serializers::SerializeError::Io(io) => Error::Io(io),
60 crate::generated_serializers::SerializeError::RawXml(r) => Error::RawXml(r),
61 }
62 }
63}
64
65impl From<crate::generated_parsers::ParseError> for Error {
66 fn from(e: crate::generated_parsers::ParseError) -> Self {
67 match e {
68 crate::generated_parsers::ParseError::Xml(x) => Error::Xml(x),
69 crate::generated_parsers::ParseError::RawXml(r) => Error::RawXml(r),
70 crate::generated_parsers::ParseError::UnexpectedElement(msg) => Error::Invalid(msg),
71 crate::generated_parsers::ParseError::MissingAttribute(msg) => Error::Invalid(msg),
72 crate::generated_parsers::ParseError::InvalidValue(msg) => Error::Invalid(msg),
73 }
74 }
75}
76
77impl Error {
78 pub fn parse(context: impl Into<String>, message: impl Into<String>) -> Self {
80 Self::Parse {
81 context: context.into(),
82 message: message.into(),
83 position: None,
84 }
85 }
86
87 pub fn parse_at(context: impl Into<String>, message: impl Into<String>, position: u64) -> Self {
89 Self::Parse {
90 context: context.into(),
91 message: message.into(),
92 position: Some(position),
93 }
94 }
95
96 pub fn with_context(self, context: impl Into<String>) -> Self {
98 match self {
99 Self::Xml(e) => Self::Parse {
100 context: context.into(),
101 message: e.to_string(),
102 position: None,
103 },
104 Self::Parse {
105 message, position, ..
106 } => Self::Parse {
107 context: context.into(),
108 message,
109 position,
110 },
111 other => other,
112 }
113 }
114
115 pub fn at_position(self, position: u64) -> Self {
117 match self {
118 Self::Parse {
119 context, message, ..
120 } => Self::Parse {
121 context,
122 message,
123 position: Some(position),
124 },
125 Self::Xml(e) => Self::Parse {
126 context: String::new(),
127 message: e.to_string(),
128 position: Some(position),
129 },
130 other => other,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Default)]
139pub struct ParseContext {
140 pub file_path: Option<String>,
142 pub element_stack: Vec<String>,
144}
145
146impl ParseContext {
147 pub fn new(file_path: impl Into<String>) -> Self {
149 Self {
150 file_path: Some(file_path.into()),
151 element_stack: Vec::new(),
152 }
153 }
154
155 pub fn push(&mut self, element: impl Into<String>) {
157 self.element_stack.push(element.into());
158 }
159
160 pub fn pop(&mut self) {
162 self.element_stack.pop();
163 }
164
165 pub fn describe(&self) -> String {
167 let mut parts = Vec::new();
168 if let Some(ref path) = self.file_path {
169 parts.push(path.clone());
170 }
171 if !self.element_stack.is_empty() {
172 parts.push(format!("in <{}>", self.element_stack.join("/")));
173 }
174 if parts.is_empty() {
175 "unknown location".to_string()
176 } else {
177 parts.join(" ")
178 }
179 }
180
181 pub fn error(&self, message: impl Into<String>) -> Error {
183 Error::parse(self.describe(), message)
184 }
185
186 pub fn error_at(&self, message: impl Into<String>, position: u64) -> Error {
188 Error::parse_at(self.describe(), message, position)
189 }
190}
191
192pub fn position_to_line_col(content: &[u8], position: u64) -> (usize, usize) {
196 let position = position as usize;
197 let content = if position <= content.len() {
198 &content[..position]
199 } else {
200 content
201 };
202
203 let mut line = 1;
204 let mut col = 1;
205
206 for &byte in content {
207 if byte == b'\n' {
208 line += 1;
209 col = 1;
210 } else {
211 col += 1;
212 }
213 }
214
215 (line, col)
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_position_to_line_col() {
224 let content = b"line1\nline2\nline3";
225 assert_eq!(position_to_line_col(content, 0), (1, 1));
226 assert_eq!(position_to_line_col(content, 5), (1, 6)); assert_eq!(position_to_line_col(content, 6), (2, 1)); assert_eq!(position_to_line_col(content, 12), (3, 1)); }
230
231 #[test]
232 fn test_parse_context() {
233 let mut ctx = ParseContext::new("word/document.xml");
234 assert_eq!(ctx.describe(), "word/document.xml");
235
236 ctx.push("w:body");
237 assert_eq!(ctx.describe(), "word/document.xml in <w:body>");
238
239 ctx.push("w:p");
240 assert_eq!(ctx.describe(), "word/document.xml in <w:body/w:p>");
241
242 ctx.pop();
243 assert_eq!(ctx.describe(), "word/document.xml in <w:body>");
244 }
245
246 #[test]
247 fn test_error_with_context() {
248 let err = Error::parse("word/document.xml", "unexpected element");
249 assert!(err.to_string().contains("word/document.xml"));
250 assert!(err.to_string().contains("unexpected element"));
251 }
252}