use crate::parser::{CommentParser, ElementParser, Parser, PiParser}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum DtdParser { /// If inside a PubidLiteral or SystemLiteral, it holds the quote type (either `'` or `"`). /// Otherwise, it holds `0` (this is an initial state). /// /// ```text /// [28] doctypedecl ::= '' /// ``` BeforeInternalSubset(u8), /// Inside of the `intSubset` rule. /// /// ```text /// [28a] DeclSep ::= PEReference | S /// [28b] intSubset ::= (markupdecl | DeclSep)* /// [29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment /// ``` InsideOfInternalSubset, /// After `]` but before `>`. AfterInternalSubset, InComment(CommentParser), InPi(PiParser), /// ```text /// [45] elementdecl ::= '' /// ``` InElementDecl, /// This state handles ATTLIST, ENTITY and NOTATION elements, i.e. all elements that can have /// quotes strings (`'...'` or `"..."`) inside their markup, in which `>` should not be threated /// as the end of the markup. /// /// This state handles the following productions from XML grammar: /// /// ### ATTLIST /// /// ```text /// [52] AttlistDecl ::= '' /// [53] AttDef ::= S Name S AttType S DefaultDecl /// [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue) /// ``` /// /// ### ENTITY /// /// ```text /// [70] EntityDecl ::= GEDecl | PEDecl /// [71] GEDecl ::= '' /// [72] PEDecl ::= '' /// [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) /// [74] PEDef ::= EntityValue | ExternalID /// [75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral /// [76] NDataDecl ::= S 'NDATA' S Name /// ``` /// /// ### NOTATION /// /// ```text /// [82] NotationDecl ::= '' /// ``` InQuoteSensitive(ElementParser), /// The state where it was not possible to determine which markup it was during the previous iteration. \ /// It holds the number of bytes read since the start of the markup. UndecidedMarkup(usize), Finished, } impl DtdParser { /// Skip DTD contents. /// /// # Parameters (as same as `reader::BangType::parse`) /// - `buf`: buffer with data consumed on previous iterations /// - `chunk`: data read on current iteration and not yet consumed from reader pub fn feed(&mut self, buf: &[u8], chunk: &[u8]) -> Option { // This method assumes the DTD is well-formed. // Since this crate does not support parsing DTDs, the inability to read non-well-formed DTDs // is not particularly problematic; the only point of interest is reporting well-formed DTDs // to the user without errors. let mut cur = chunk; while !cur.is_empty() { match *self { Self::BeforeInternalSubset(0) => { // Find the // - start of quoted string ('...' or "...") // - start of internal subset ([...]) // - end of DOCTYPE declaration (>) if let Some(i) = cur .iter() .position(|&b| matches!(b, b'\'' | b'"' | b'[' | b'>')) { let b = cur[i]; match b { b'\'' | b'"' => { // SystemLiteral or PubidLiteral *self = Self::BeforeInternalSubset(b); cur = &cur[i + 1..]; // +1 to skip `'` or `"` continue; } b'[' => { *self = Self::InsideOfInternalSubset; cur = &cur[i + 1..]; // +1 to skip `[` continue; } b'>' => { *self = Self::Finished; return Some(chunk.len() - cur.len() + i); } _ => {} } continue; } break; } // Inside the quoted string (this is PubidLiteral or SystemLiteral) we do not want to // recognize other special characters (namely [ and >). Find only the closing quote Self::BeforeInternalSubset(quote) => { // ExternalID handling if let Some(i) = memchr::memchr(quote, cur) { *self = Self::BeforeInternalSubset(0); cur = &cur[i + 1..]; continue; } break; } Self::InsideOfInternalSubset => { // Find the end of internal subset (]) or the start of the markup inside (<) if let Some(i) = memchr::memchr2(b']', b'<', cur) { if cur[i] == b']' { *self = Self::AfterInternalSubset; cur = &cur[i + 1..]; // +1 to skip `]` continue; } // +1 to start after `<` if let Some(skip) = self.switch(&cur[i + 1..]) { cur = &cur[i + 1 + skip..]; // +1 to skip `<` continue; } // Keep the number of already looked bytes (started from byte after `<`, so -1), // try to decide after feeding the new chunk. let skipped = cur.len() - i - 1; // The 9-byte work buffer in `UndecidedMarkup` is sized // for `!NOTATION` (the longest keyword). If the chunk // already gave us 9+ bytes after `<` and `switch()` // returned `None`, the markup is definitively not one // of `