Skip to main content

winprint_ext/ticket/document/
reader.rs

1use super::{
2    ParameterDef, ParameterInit, PrintCapabilitiesDocument, PrintFeature, PrintFeatureOption,
3    PrintSchemaDocument, PrintTicketDocument, Property, PropertyValue, ScoredProperty, NS_PSF,
4    NS_XSD, NS_XSI,
5};
6use std::{fmt::Debug, io::Cursor};
7use thiserror::Error;
8use xml::{
9    common::{Position, TextPosition},
10    name::OwnedName,
11    namespace::Namespace,
12    reader::XmlEvent,
13    EventReader,
14};
15
16#[derive(Error, Debug)]
17/// Represents an error occurred while parsing print schema.
18pub enum ParsePrintSchemaError {
19    /// Invalid XML.
20    #[error("Invalid xml")]
21    InvalidXml(#[source] xml::reader::Error),
22    /// Invalid print schema.
23    #[error("Invalid print schema: (at {pos}) {reason}")]
24    InvalidPrintSchema {
25        /// Position in the document.
26        pos: TextPosition,
27        /// Reason of the error.
28        reason: String,
29    },
30    /// Wrong document type.
31    #[error("Wrong document type: expected {expected} but found {found}")]
32    WrongDocumentType {
33        /// Expected document type.
34        expected: &'static str,
35        /// Found document type.
36        found: &'static str,
37    },
38}
39
40/// Represents a root element which can be parsed from XML.
41pub trait ParsableXmlDocument: Sized {
42    /// The error type that can be returned when parsing fails.
43    type Error;
44
45    /// Parse the XML document from the given XML reader.
46    fn parse<R>(reader: &mut EventReader<R>) -> Result<Self, Self::Error>
47    where
48        R: std::io::Read;
49
50    /// Parse the XML document from the given bytes.
51    fn parse_from_bytes(xml: impl AsRef<[u8]>) -> Result<Self, Self::Error> {
52        let mut reader = EventReader::new(Cursor::new(xml));
53        Self::parse(&mut reader)
54    }
55}
56
57fn parse_qname(namespace: &Namespace, value: &str) -> OwnedName {
58    let prefix_index = value.find(':');
59    if let Some(prefix_index) = prefix_index {
60        let (prefix, local_name) = value.split_at(prefix_index);
61        let local_name = &local_name[1..];
62        OwnedName {
63            local_name: local_name.to_string(),
64            namespace: namespace.get(prefix).map(str::to_string),
65            prefix: Some(prefix.to_string()),
66        }
67    } else {
68        OwnedName {
69            local_name: value.to_string(),
70            namespace: None,
71            prefix: None,
72        }
73    }
74}
75
76fn parse_name_attribute(
77    attributes: &[xml::attribute::OwnedAttribute],
78    namespace: &Namespace,
79) -> Option<OwnedName> {
80    let attribute_value = attributes
81        .iter()
82        .find(|x| x.name.local_name == "name" && x.name.namespace.is_none())
83        .map(|x| x.value.clone());
84    attribute_value.map(|x| parse_qname(namespace, &x))
85}
86
87fn parse_type_attribute(
88    attributes: &[xml::attribute::OwnedAttribute],
89    namespace: &Namespace,
90) -> Option<OwnedName> {
91    let attribute_value = attributes
92        .iter()
93        .find(|x| x.name.local_name == "type" && x.name.namespace_ref() == Some(NS_XSI))
94        .map(|x| x.value.clone());
95    attribute_value.map(|x| parse_qname(namespace, &x))
96}
97
98struct PsfValueContext {
99    pos: TextPosition,
100    value: String,
101    value_type: OwnedName,
102    namespace: Namespace,
103}
104
105impl PsfValueContext {
106    fn parse(self) -> Result<PropertyValue, ParsePrintSchemaError> {
107        if self.value_type.namespace_ref() == Some(NS_XSD) {
108            match self.value_type.local_name.as_str() {
109                "string" => return Ok(PropertyValue::String(self.value)),
110                "integer" => {
111                    return self.value.trim().parse().map(PropertyValue::Integer).map_err(|_| {
112                        ParsePrintSchemaError::InvalidPrintSchema {
113                            pos: self.pos,
114                            reason: "Invalid integer".to_string(),
115                        }
116                    })
117                }
118                "QName" => {
119                    return Ok(PropertyValue::QName(parse_qname(
120                        &self.namespace,
121                        &self.value,
122                    )))
123                }
124                _ => {}
125            };
126        }
127        Ok(PropertyValue::Unknown(self.value_type, self.value))
128    }
129}
130
131impl ParsableXmlDocument for PrintSchemaDocument {
132    type Error = ParsePrintSchemaError;
133    fn parse<R>(reader: &mut EventReader<R>) -> Result<Self, Self::Error>
134    where
135        R: std::io::Read,
136    {
137        let mut depth: usize = 0;
138
139        let mut option_name: Option<OwnedName> = None;
140        let mut parameter_ref: Option<OwnedName> = None;
141
142        let mut parameter_def_name: Option<OwnedName> = None;
143        let mut parameter_def_container: Option<Vec<ParameterDef>> = None;
144
145        let mut parameter_init_name: Option<OwnedName> = None;
146        let mut parameter_init_container: Option<Vec<ParameterInit>> = None;
147
148        let mut feature_name: Vec<OwnedName> = Vec::new();
149        let mut feature_containers: Vec<Vec<PrintFeature>> = Vec::new();
150
151        let mut option_containers: Vec<Vec<PrintFeatureOption>> = Vec::new();
152
153        let mut property_name: Vec<OwnedName> = Vec::new();
154        let mut property_containers: Vec<Vec<Property>> = Vec::new();
155
156        let mut scored_property_name: Vec<Option<OwnedName>> = Vec::new();
157        let mut scored_property_containers: Vec<Vec<ScoredProperty>> = Vec::new();
158
159        let mut value_context: Option<PsfValueContext> = None;
160        let mut parsed_value: Option<PropertyValue> = None;
161
162        loop {
163            let e = match reader.next() {
164                Ok(e) => e,
165                Err(e) => return Err(ParsePrintSchemaError::InvalidXml(e)),
166            };
167            match e {
168                XmlEvent::StartElement {
169                    name,
170                    attributes,
171                    namespace,
172                } => {
173                    depth += 1;
174
175                    if name.namespace_ref() == Some(NS_PSF) {
176                        match name.local_name.as_str() {
177                            "PrintCapabilities" => {
178                                if depth > 1 {
179                                    return Err(ParsePrintSchemaError::InvalidPrintSchema {
180                                        pos: reader.position(),
181                                        reason: "PrintCapabilities should be root element"
182                                            .to_string(),
183                                    });
184                                }
185                                // root container
186                                feature_containers.push(Vec::new());
187                                property_containers.push(Vec::new());
188                                parameter_def_container.replace(Vec::new());
189                            }
190                            "PrintTicket" => {
191                                if depth > 1 {
192                                    return Err(ParsePrintSchemaError::InvalidPrintSchema {
193                                        pos: reader.position(),
194                                        reason: "PrintTicket should be root element".to_string(),
195                                    });
196                                }
197                                // root container
198                                feature_containers.push(Vec::new());
199                                property_containers.push(Vec::new());
200                                parameter_init_container.replace(Vec::new());
201                            }
202                            "ParameterDef" => {
203                                parameter_def_name = parse_name_attribute(&attributes, &namespace);
204                                property_containers.push(Vec::new());
205                            }
206                            "ParameterInit" => {
207                                parameter_init_name = parse_name_attribute(&attributes, &namespace);
208                            }
209                            "Feature" => {
210                                feature_name.push(
211                                    parse_name_attribute(&attributes, &namespace).ok_or_else(
212                                        || ParsePrintSchemaError::InvalidPrintSchema {
213                                            pos: reader.position(),
214                                            reason: "Feature name not found".to_string(),
215                                        },
216                                    )?,
217                                );
218
219                                // for sub-elements
220                                feature_containers.push(Vec::new());
221                                property_containers.push(Vec::new());
222                                option_containers.push(Vec::new());
223                            }
224                            "Option" => {
225                                option_name = parse_name_attribute(&attributes, &namespace);
226                                property_containers.push(Vec::new());
227                                scored_property_containers.push(Vec::new());
228                            }
229                            "ParameterRef" => {
230                                parameter_ref = parse_name_attribute(&attributes, &namespace);
231                            }
232                            "ScoredProperty" => {
233                                scored_property_name
234                                    .push(parse_name_attribute(&attributes, &namespace));
235
236                                // for sub-elements
237                                property_containers.push(Vec::new());
238                                scored_property_containers.push(Vec::new());
239
240                                // clear previous value
241                                parsed_value.take();
242                                parameter_ref.take();
243                            }
244                            "Property" => {
245                                property_name.push(
246                                    parse_name_attribute(&attributes, &namespace).ok_or_else(
247                                        || ParsePrintSchemaError::InvalidPrintSchema {
248                                            pos: reader.position(),
249                                            reason: "Property name not found".to_string(),
250                                        },
251                                    )?,
252                                );
253
254                                // for sub-elements
255                                property_containers.push(Vec::new());
256
257                                // clear previous value
258                                parsed_value.take();
259                            }
260                            "Value" => {
261                                if let Some(value_type) =
262                                    parse_type_attribute(&attributes, &namespace)
263                                {
264                                    value_context.replace(PsfValueContext {
265                                        pos: reader.position(),
266                                        value: String::new(),
267                                        value_type,
268                                        namespace,
269                                    });
270                                }
271                            }
272                            _ => {
273                                return Err(ParsePrintSchemaError::InvalidPrintSchema {
274                                    pos: reader.position(),
275                                    reason: format!("Invalid element: {}", name),
276                                })
277                            }
278                        }
279                    }
280                }
281                XmlEvent::EndElement { name } => {
282                    depth -= 1;
283
284                    if name.namespace_ref() == Some(NS_PSF) {
285                        match name.local_name.as_str() {
286                            "PrintCapabilities" => {
287                                return Ok(PrintCapabilitiesDocument {
288                                    parameter_defs: parameter_def_container.unwrap(),
289                                    features: feature_containers.pop().unwrap(),
290                                    properties: property_containers.pop().unwrap(),
291                                }
292                                .into());
293                            }
294                            "PrintTicket" => {
295                                return Ok(PrintTicketDocument {
296                                    parameter_inits: parameter_init_container.unwrap(),
297                                    features: feature_containers.pop().unwrap(),
298                                    properties: property_containers.pop().unwrap(),
299                                }
300                                .into());
301                            }
302                            "ParameterDef" => {
303                                // element should be paired, so it's safe to unwrap
304                                let parameter_def_name = parameter_def_name.take().unwrap();
305                                let properties = property_containers.pop().unwrap();
306
307                                let parent = parameter_def_container.as_mut().ok_or_else(|| {
308                                    ParsePrintSchemaError::InvalidPrintSchema {
309                                        pos: reader.position(),
310                                        reason: "ParameterDef cannot be here".to_string(),
311                                    }
312                                })?;
313                                parent.push(ParameterDef {
314                                    name: parameter_def_name,
315                                    properties,
316                                });
317                            }
318                            "ParameterInit" => {
319                                // element should be paired, so it's safe to unwrap
320                                let parameter_init_name = parameter_init_name.take().unwrap();
321
322                                // value may not be found
323                                // check it, and if not found, return error
324                                let value = parsed_value.take().ok_or_else(|| {
325                                    ParsePrintSchemaError::InvalidPrintSchema {
326                                        pos: reader.position(),
327                                        reason: "ParameterInit value not found".to_string(),
328                                    }
329                                })?;
330
331                                let parent =
332                                    parameter_init_container.as_mut().ok_or_else(|| {
333                                        ParsePrintSchemaError::InvalidPrintSchema {
334                                            pos: reader.position(),
335                                            reason: "ParameterInit cannot be here".to_string(),
336                                        }
337                                    })?;
338                                parent.push(ParameterInit {
339                                    name: parameter_init_name,
340                                    value,
341                                });
342                            }
343                            "Feature" => {
344                                // element should be paired, so it's safe to unwrap
345                                let frature_name = feature_name.pop().unwrap();
346                                let features = feature_containers.pop().unwrap();
347                                let properties = property_containers.pop().unwrap();
348                                let options = option_containers.pop().unwrap();
349
350                                let parent = feature_containers.last_mut().ok_or_else(|| {
351                                    ParsePrintSchemaError::InvalidPrintSchema {
352                                        pos: reader.position(),
353                                        reason: "Feature cannot be here".to_string(),
354                                    }
355                                })?;
356                                parent.push(PrintFeature {
357                                    name: frature_name,
358                                    properties,
359                                    options,
360                                    features,
361                                });
362                            }
363                            "Option" => {
364                                // element should be paired, so it's safe to unwrap
365                                let option_name = option_name.take();
366                                let properties = property_containers.pop().unwrap();
367                                let scored_properties = scored_property_containers.pop().unwrap();
368
369                                let parent = option_containers.last_mut().ok_or_else(|| {
370                                    ParsePrintSchemaError::InvalidPrintSchema {
371                                        pos: reader.position(),
372                                        reason: "Option cannot be here".to_string(),
373                                    }
374                                })?;
375                                parent.push(PrintFeatureOption {
376                                    name: option_name,
377                                    scored_properties,
378                                    properties,
379                                });
380                            }
381                            "ScoredProperty" => {
382                                // element should be paired, so it's safe to unwrap
383                                let scored_property_name = scored_property_name.pop().unwrap();
384                                let properties = property_containers.pop().unwrap();
385                                let scored_properties = scored_property_containers.pop().unwrap();
386
387                                let parent =
388                                    scored_property_containers.last_mut().ok_or_else(|| {
389                                        ParsePrintSchemaError::InvalidPrintSchema {
390                                            pos: reader.position(),
391                                            reason: "ScoredProperty cannot be here".to_string(),
392                                        }
393                                    })?;
394                                parent.push(ScoredProperty {
395                                    name: scored_property_name,
396                                    parameter_ref: parameter_ref.take(),
397                                    value: parsed_value.take(),
398                                    properties,
399                                    scored_properties,
400                                });
401                            }
402                            "Property" => {
403                                // element should be paired, so it's safe to unwrap
404                                let property_name = property_name.pop().unwrap();
405                                let properties = property_containers.pop().unwrap();
406
407                                let parent = property_containers.last_mut().ok_or_else(|| {
408                                    ParsePrintSchemaError::InvalidPrintSchema {
409                                        pos: reader.position(),
410                                        reason: "Property cannot be here".to_string(),
411                                    }
412                                })?;
413                                parent.push(Property {
414                                    name: property_name,
415                                    value: parsed_value.take(),
416                                    properties,
417                                });
418                            }
419                            "Value" => {
420                                if let Some(value_context) = value_context.take() {
421                                    parsed_value.replace(value_context.parse()?);
422                                }
423                            }
424                            _ => {}
425                        }
426                    }
427                }
428                XmlEvent::Characters(s) => {
429                    if let Some(c) = value_context.as_mut() {
430                        c.value.push_str(&s);
431                    }
432                }
433                XmlEvent::Whitespace(_) => {
434                    if let Some(c) = value_context.as_mut() {
435                        c.value.push(' ');
436                    }
437                }
438                XmlEvent::CData(s) => {
439                    if let Some(c) = value_context.as_mut() {
440                        c.value.push_str(&s);
441                    }
442                }
443                XmlEvent::EndDocument => break,
444                _ => {}
445            }
446        }
447
448        Err(ParsePrintSchemaError::InvalidPrintSchema {
449            pos: reader.position(),
450            reason: "No valid root element found".to_string(),
451        })
452    }
453}
454
455impl ParsableXmlDocument for PrintCapabilitiesDocument {
456    type Error = ParsePrintSchemaError;
457    fn parse<R>(reader: &mut EventReader<R>) -> Result<Self, Self::Error>
458    where
459        R: std::io::Read,
460    {
461        PrintSchemaDocument::parse(reader).and_then(|x| match x {
462            PrintSchemaDocument::PrintCapabilities(document) => Ok(document),
463            PrintSchemaDocument::PrintTicket(_) => Err(ParsePrintSchemaError::WrongDocumentType {
464                expected: "PrintCapabilities",
465                found: "PrintTicket",
466            }),
467        })
468    }
469}
470
471impl ParsableXmlDocument for PrintTicketDocument {
472    type Error = ParsePrintSchemaError;
473    fn parse<R>(reader: &mut EventReader<R>) -> Result<Self, Self::Error>
474    where
475        R: std::io::Read,
476    {
477        PrintSchemaDocument::parse(reader).and_then(|x| match x {
478            PrintSchemaDocument::PrintTicket(document) => Ok(document),
479            PrintSchemaDocument::PrintCapabilities(_) => {
480                Err(ParsePrintSchemaError::WrongDocumentType {
481                    expected: "PrintTicket",
482                    found: "PrintCapabilities",
483                })
484            }
485        })
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::{ParsableXmlDocument, ParsePrintSchemaError};
492    use crate::ticket::document::{
493        PrintCapabilitiesDocument, PrintSchemaDocument, PrintTicketDocument,
494    };
495
496    #[test]
497    fn wrong_type_should_return_error() {
498        let xml = include_bytes!("../../../test_data/print_ticket.xml");
499        let result = PrintCapabilitiesDocument::parse_from_bytes(xml);
500        assert!(matches!(
501            result,
502            Err(ParsePrintSchemaError::WrongDocumentType { .. })
503        ));
504
505        let xml = include_bytes!("../../../test_data/print_capabilities.xml");
506        let result = PrintTicketDocument::parse_from_bytes(xml);
507        assert!(matches!(
508            result,
509            Err(ParsePrintSchemaError::WrongDocumentType { .. })
510        ));
511    }
512
513    #[test]
514    fn parameter_def_should_not_in_print_ticket() {
515        let xml = r#"<psf:PrintTicket version="1"
516    xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" 
517    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
518    xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords">
519    <psf:ParameterDef name="psk:JobCopiesAllDocuments">
520        <psf:Property name="psf:DataType">
521            <psf:Value xsi:type="xsd:QName">xsd:integer</psf:Value>
522        </psf:Property>
523        <psf:Property name="psf:UnitType">
524            <psf:Value xsi:type="xsd:string">copies</psf:Value>
525        </psf:Property>
526        <psf:Property name="psf:Multiple">
527            <psf:Value xsi:type="xsd:integer">1</psf:Value>
528        </psf:Property>
529        <psf:Property name="psf:MaxValue">
530            <psf:Value xsi:type="xsd:integer">9999</psf:Value>
531        </psf:Property>
532        <psf:Property name="psf:MinValue">
533            <psf:Value xsi:type="xsd:integer">1</psf:Value>
534        </psf:Property>
535        <psf:Property name="psf:DefaultValue">
536            <psf:Value xsi:type="xsd:integer">1</psf:Value>
537        </psf:Property>
538        <psf:Property name="psf:Mandatory">
539            <psf:Value xsi:type="xsd:QName">psk:Unconditional</psf:Value>
540        </psf:Property>
541        <psf:Property name="psk:DisplayName">
542            <psf:Value xsi:type="xsd:string">份数</psf:Value>
543        </psf:Property>
544    </psf:ParameterDef>
545</psf:PrintTicket>"#;
546        let result = PrintSchemaDocument::parse_from_bytes(xml);
547        assert!(matches!(
548            result,
549            Err(ParsePrintSchemaError::InvalidPrintSchema { .. })
550        ));
551    }
552
553    #[test]
554    fn parameter_init_should_not_in_print_capabilities() {
555        let xml = r#"<psf:PrintCapabilities version="1"
556    xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" 
557    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
558    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
559    xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords">
560    <psf:ParameterInit name="psk:PageMediaSizeMediaSizeWidth">
561        <psf:Value xsi:type="xsd:integer">2540</psf:Value>
562    </psf:ParameterInit>
563</psf:PrintCapabilities>"#;
564        let result = PrintSchemaDocument::parse_from_bytes(xml);
565        assert!(matches!(
566            result,
567            Err(ParsePrintSchemaError::InvalidPrintSchema { .. })
568        ));
569    }
570
571    #[test]
572    fn parse_print_ticket() {
573        let xml = include_bytes!("../../../test_data/print_ticket.xml");
574        let _document = PrintTicketDocument::parse_from_bytes(xml).unwrap();
575    }
576
577    #[test]
578    fn parse_print_capabilities() {
579        let xml = include_bytes!("../../../test_data/print_capabilities.xml");
580        let _document = PrintCapabilitiesDocument::parse_from_bytes(xml).unwrap();
581    }
582}