1use rusty_xml_tree::{AttrDecl, AttrDefault, ElementDecl, XmlDtd};
4use crate::error::XmlError;
5
6#[doc(alias = "xmlParseDTD")]
8pub fn xml_parse_dtd(
9 buffer: &[u8],
10 public_id: Option<&str>,
11 system_id: Option<&str>,
12) -> Result<XmlDtd, XmlError> {
13 let text = String::from_utf8_lossy(buffer);
14 let mut dtd = parse_dtd_subset(&text)?;
15 dtd.public_id = public_id.map(str::to_string);
16 dtd.system_id = system_id.map(str::to_string);
17 Ok(dtd)
18}
19
20pub fn parse_dtd_subset(src: &str) -> Result<XmlDtd, XmlError> {
22 let expanded = expand_pe(src);
23 let mut dtd = XmlDtd::default();
24 dtd.int_subset = Some(src.to_string());
25 let mut p = DtdParser {
26 src: expanded.as_str(),
27 pos: 0,
28 dtd: &mut dtd,
29 };
30 p.parse_markup()?;
31 Ok(dtd)
32}
33
34fn expand_pe(src: &str) -> String {
35 let mut cur = src.to_string();
37 for _ in 0..16 {
38 let mut pes: std::collections::HashMap<String, String> = std::collections::HashMap::new();
39 harvest_pe(&cur, &mut pes);
40 let next = subst_pe(&cur, &pes);
41 if next == cur {
42 return cur;
43 }
44 cur = next;
45 }
46 cur
47}
48
49fn harvest_pe(src: &str, pes: &mut std::collections::HashMap<String, String>) {
50 let bytes = src.as_bytes();
51 let mut i = 0;
52 while i + 8 < bytes.len() {
53 if bytes[i] == b'<' && bytes.get(i..i + 9) == Some(b"<!ENTITY ") {
54 i += 9;
55 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
56 i += 1;
57 }
58 if i < bytes.len() && bytes[i] == b'%' {
59 i += 1;
60 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
61 i += 1;
62 }
63 let start = i;
64 while i < bytes.len() && !bytes[i].is_ascii_whitespace() && bytes[i] != b'"' && bytes[i] != b'\'' {
65 i += 1;
66 }
67 let name = src[start..i].to_string();
68 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
69 i += 1;
70 }
71 if i < bytes.len() && (bytes[i] == b'"' || bytes[i] == b'\'') {
72 let q = bytes[i];
73 i += 1;
74 let vs = i;
75 while i < bytes.len() && bytes[i] != q {
76 i += 1;
77 }
78 let val = decode_charrefs(&src[vs..i]);
79 pes.insert(name, val);
80 }
81 }
82 } else {
83 i += 1;
84 }
85 }
86}
87
88fn subst_pe(src: &str, pes: &std::collections::HashMap<String, String>) -> String {
89 let mut out = String::new();
90 let mut chars = src.chars().peekable();
91 let mut in_comment = false;
92 while let Some(c) = chars.next() {
93 if in_comment {
94 out.push(c);
95 if c == '-' && chars.peek() == Some(&'-') {
96 out.push(chars.next().unwrap());
97 if chars.peek() == Some(&'>') {
98 out.push(chars.next().unwrap());
99 in_comment = false;
100 }
101 }
102 continue;
103 }
104 if c == '<' && chars.peek() == Some(&'!') {
105 out.push(c);
106 out.push(chars.next().unwrap());
107 if chars.peek() == Some(&'-') {
108 out.push(chars.next().unwrap());
109 if chars.peek() == Some(&'-') {
110 out.push(chars.next().unwrap());
111 in_comment = true;
112 }
113 }
114 continue;
115 }
116 if c == '%' {
117 let mut name = String::new();
118 while let Some(&n) = chars.peek() {
119 if n == ';' {
120 chars.next();
121 break;
122 }
123 if n.is_ascii_whitespace() || n == '"' || n == '\'' {
124 break;
125 }
126 name.push(n);
127 chars.next();
128 }
129 if let Some(v) = pes.get(&name) {
130 out.push_str(v);
131 } else {
132 out.push('%');
133 out.push_str(&name);
134 if !name.is_empty() {
135 out.push(';');
136 }
137 }
138 continue;
139 }
140 out.push(c);
141 }
142 out
143}
144
145fn decode_charrefs(s: &str) -> String {
146 let mut out = String::new();
147 let mut rest = s;
148 while let Some(i) = rest.find("&#") {
149 out.push_str(&rest[..i]);
150 let after = &rest[i + 2..];
151 if let Some(hex) = after.strip_prefix('x').or_else(|| after.strip_prefix('X')) {
152 if let Some(end) = hex.find(';') {
153 if let Ok(v) = u32::from_str_radix(&hex[..end], 16) {
154 if let Some(ch) = char::from_u32(v) {
155 out.push(ch);
156 rest = &hex[end + 1..];
157 continue;
158 }
159 }
160 }
161 } else if let Some(end) = after.find(';') {
162 if let Ok(v) = after[..end].parse::<u32>() {
163 if let Some(ch) = char::from_u32(v) {
164 out.push(ch);
165 rest = &after[end + 1..];
166 continue;
167 }
168 }
169 }
170 out.push_str("&#");
171 rest = after;
172 }
173 out.push_str(rest);
174 out
175}
176
177struct DtdParser<'a> {
178 src: &'a str,
179 pos: usize,
180 dtd: &'a mut XmlDtd,
181}
182
183impl<'a> DtdParser<'a> {
184 fn rest(&self) -> &'a str {
185 &self.src[self.pos..]
186 }
187 fn skip_ws(&mut self) {
192 let r = self.rest();
193 let trimmed = r.trim_start_matches([' ', '\t', '\r', '\n']);
194 self.pos += r.len() - trimmed.len();
195 }
196
197 fn skip_ws_and_comments(&mut self) {
198 loop {
199 let r = self.rest();
200 let trimmed = r.trim_start();
201 let n = r.len() - trimmed.len();
202 self.pos += n;
203 if self.rest().starts_with("<!--") {
204 if let Some(e) = self.rest().find("-->") {
205 self.pos += e + 3;
206 continue;
207 }
208 }
209 if self.rest().starts_with("<?") {
210 if let Some(e) = self.rest().find("?>") {
211 self.pos += e + 2;
212 continue;
213 }
214 }
215 break;
216 }
217 }
218 fn parse_markup(&mut self) -> Result<(), XmlError> {
219 loop {
220 self.skip_ws_and_comments();
221 if self.pos >= self.src.len() {
222 break;
223 }
224 if self.rest().starts_with("<!ELEMENT") {
225 self.parse_element()?;
226 } else if self.rest().starts_with("<!ATTLIST") {
227 self.parse_attlist()?;
228 } else if self.rest().starts_with("<!ENTITY") {
229 self.parse_entity()?;
230 } else if self.rest().starts_with("<!NOTATION") {
231 self.skip_decl()?;
232 } else if self.rest().starts_with("<![") {
233 self.skip_cond()?;
234 } else if self.rest().starts_with('<') {
235 self.skip_decl()?;
236 } else {
237 self.pos += self.rest().chars().next().unwrap().len_utf8();
238 }
239 }
240 Ok(())
241 }
242 fn skip_decl(&mut self) -> Result<(), XmlError> {
243 if let Some(i) = self.rest().find('>') {
244 self.pos += i + 1;
245 Ok(())
246 } else {
247 self.pos = self.src.len();
248 Ok(())
249 }
250 }
251 fn skip_cond(&mut self) -> Result<(), XmlError> {
252 let mut depth = 0i32;
253 let bytes = self.rest().as_bytes();
254 let mut i = 0;
255 while i < bytes.len() {
256 if bytes[i] == b'<' && bytes.get(i..i + 3) == Some(b"<![") {
257 depth += 1;
258 i += 3;
259 continue;
260 }
261 if bytes[i] == b']' && bytes.get(i..i + 3) == Some(b"]]>") {
262 depth -= 1;
263 i += 3;
264 if depth == 0 {
265 self.pos += i;
266 return Ok(());
267 }
268 continue;
269 }
270 i += 1;
271 }
272 self.pos = self.src.len();
273 Ok(())
274 }
275 fn bump(&mut self, n: usize) {
276 self.pos += n;
277 }
278 fn parse_name(&mut self) -> String {
279 self.skip_ws_and_comments();
280 let r = self.rest();
281 let mut n = 0;
282 for (i, c) in r.char_indices() {
283 if i == 0 {
284 if !(c.is_ascii_alphabetic() || c == '_' || c == ':') {
285 break;
286 }
287 } else if !(c.is_ascii_alphanumeric() || "-._:".contains(c)) {
288 n = i;
289 break;
290 }
291 n = i + c.len_utf8();
292 }
293 let s = r[..n].to_string();
294 self.bump(n);
295 s
296 }
297 fn parse_quoted(&mut self) -> Result<String, XmlError> {
307 self.skip_ws_and_comments();
308 let r = self.rest();
309 if r.starts_with('"') || r.starts_with('\'') {
310 let q = r.as_bytes()[0] as char;
311 self.bump(1);
312 if let Some(e) = self.rest().find(q) {
313 let s = decode_charrefs(&self.rest()[..e]);
314 self.bump(e + 1);
315 if let Some(bad) =
316 s.chars().find(|c| !crate::chvalid::xml_is_char(*c as u32))
317 {
318 return Err(XmlError::new(
319 crate::error::XML_ERR_INVALID_CHAR,
320 format!("invalid character 0x{:X} in entity value", bad as u32),
321 0,
322 0,
323 ));
324 }
325 return Ok(s);
326 }
327 }
328 Ok(String::new())
329 }
330 fn parse_element(&mut self) -> Result<(), XmlError> {
331 self.bump("<!ELEMENT".len());
332 let name = self.parse_name();
333 self.skip_ws_and_comments();
334 let decl = if self.rest().starts_with("EMPTY") {
335 self.bump(5);
336 ElementDecl::Empty
337 } else if self.rest().starts_with("ANY") {
338 self.bump(3);
339 ElementDecl::Any
340 } else if self.rest().starts_with('(') {
341 let spec = self.take_until_gt_paren();
342 if spec.contains("#PCDATA") {
343 let mut names = Vec::new();
344 for part in spec.split('|') {
345 let t = part.trim().trim_matches(|c: char| c == '(' || c == ')' || c == '*');
346 if t != "#PCDATA" && !t.is_empty() {
347 names.push(t.to_string());
348 }
349 }
350 ElementDecl::Mixed(names)
351 } else {
352 ElementDecl::Children(spec)
353 }
354 } else {
355 self.skip_decl()?;
356 return Ok(());
357 };
358 self.dtd.elements.insert(name, decl);
359 self.skip_ws_and_comments();
360 if self.rest().starts_with('>') {
361 self.bump(1);
362 } else {
363 self.skip_decl()?;
364 }
365 Ok(())
366 }
367 fn take_until_gt_paren(&mut self) -> String {
368 let r = self.rest();
369 let mut depth = 0i32;
370 let mut i = 0;
371 for (off, c) in r.char_indices() {
372 match c {
373 '(' => depth += 1,
374 ')' => {
375 depth -= 1;
376 if depth == 0 {
377 i = off + 1;
378 break;
379 }
380 }
381 '>' if depth == 0 => {
382 i = off;
383 break;
384 }
385 _ => {}
386 }
387 i = off + c.len_utf8();
388 }
389 let s = r[..i].to_string();
390 self.bump(i);
391 s
392 }
393 fn parse_attlist(&mut self) -> Result<(), XmlError> {
394 self.bump("<!ATTLIST".len());
395 let elem = self.parse_name();
396 loop {
397 self.skip_ws_and_comments();
398 if self.rest().starts_with('>') {
399 self.bump(1);
400 break;
401 }
402 if self.pos >= self.src.len() {
403 break;
404 }
405 let aname = self.parse_name();
406 if aname.is_empty() {
407 self.skip_decl()?;
408 break;
409 }
410 self.skip_ws_and_comments();
411 let mut enumerated = Vec::new();
412 let att_type = if self.rest().starts_with('(') {
413 let spec = self.take_until_gt_paren();
414 let body = spec.trim();
418 if !body.starts_with('(') || !body.ends_with(')') {
419 return Err(self.err("')' required to finish ATTLIST enumeration"));
420 }
421 for part in body[1..body.len() - 1].split('|') {
422 let t = part.trim();
423 if t.is_empty() || !t.chars().all(|c| crate::chvalid::xml_is_name_char(c as u32, false)) {
424 return Err(self.err("')' required to finish ATTLIST enumeration"));
425 }
426 enumerated.push(t.to_string());
427 }
428 "ENUMERATION".into()
429 } else {
430 let t = self.parse_name();
431 const TYPES: &[&str] = &[
434 "CDATA", "ID", "IDREF", "IDREFS", "ENTITY", "ENTITIES", "NMTOKEN",
435 "NMTOKENS", "NOTATION",
436 ];
437 if !TYPES.contains(&t.as_str()) {
438 return Err(self.err("'(' required to start ATTLIST enumeration"));
439 }
440 if t == "NOTATION" {
441 self.skip_ws();
442 if !self.rest().starts_with('(') {
443 return Err(self.err("'(' required to start ATTLIST enumeration"));
444 }
445 let spec = self.take_until_gt_paren();
446 for part in spec.trim().trim_matches(['(', ')']).split('|') {
447 let n = part.trim();
448 if !n.is_empty() {
449 enumerated.push(n.to_string());
450 }
451 }
452 }
453 t
454 };
455 self.skip_ws_and_comments();
456 let (default, default_value) = if self.rest().starts_with("#REQUIRED") {
457 self.bump(9);
458 (AttrDefault::Required, None)
459 } else if self.rest().starts_with("#IMPLIED") {
460 self.bump(8);
461 (AttrDefault::Implied, None)
462 } else if self.rest().starts_with("#FIXED") {
463 self.bump(6);
464 if !self.require_ws() {
465 return Err(self.err("Space required after '#FIXED'"));
466 }
467 if !self.at_quote() {
468 return Err(self.err("AttValue: \" or ' expected"));
469 }
470 (AttrDefault::Fixed, Some(self.parse_quoted()?))
471 } else {
472 if !self.at_quote() {
475 return Err(self.err("AttValue: \" or ' expected"));
476 }
477 (AttrDefault::Value, Some(self.parse_quoted()?))
478 };
479 self.dtd.attributes.insert(
480 (elem.clone(), aname),
481 AttrDecl {
482 att_type,
483 default,
484 default_value,
485 enumerated,
486 },
487 );
488 }
489 Ok(())
490 }
491 fn parse_entity(&mut self) -> Result<(), XmlError> {
492 self.bump("<!ENTITY".len());
493 if !self.require_ws() {
494 return Err(self.err("Space required after '<!ENTITY'"));
495 }
496 let pe = self.rest().starts_with('%');
497 if pe {
498 self.bump(1);
499 if !self.require_ws() {
500 return Err(self.err("Space required after '%'"));
501 }
502 }
503 let name = self.parse_name();
504 if name.is_empty() {
505 return Err(self.err("Entity name expected"));
506 }
507 if !self.require_ws() {
510 return Err(self.err("Space required after the entity name"));
511 }
512 if self.rest().starts_with("SYSTEM") || self.rest().starts_with("PUBLIC") {
513 let public = self.rest().starts_with("PUBLIC");
514 self.bump(6);
515 if !self.require_ws() {
516 return Err(self.err("Space required after the external ID keyword"));
517 }
518 if public {
519 self.parse_quoted()?;
523 if !self.require_ws() {
524 return Err(self.err("Space required after the Public Identifier"));
525 }
526 if !self.at_quote() {
527 return Err(self.err("SystemLiteral expected"));
528 }
529 }
530 self.parse_quoted()?;
531 self.skip_ws_and_comments();
532 if self.rest().starts_with("NDATA") {
534 self.bump(5);
535 if !self.require_ws() {
536 return Err(self.err("Space required after 'NDATA'"));
537 }
538 if self.parse_name().is_empty() {
539 return Err(self.err("Notation name expected after 'NDATA'"));
540 }
541 self.skip_ws();
542 }
543 return self.expect_decl_end("entity");
544 }
545 if !self.at_quote() {
546 return Err(self.err("Entity value expected"));
547 }
548 let val = self.parse_quoted()?;
549 if pe {
550 self.dtd.parameter_entities.insert(name, val);
551 } else {
552 self.dtd.entities.insert(name, val);
553 }
554 self.skip_ws();
555 self.expect_decl_end("entity")
556 }
557
558 fn line_col(&self) -> (u32, u32) {
561 let mut line = 1u32;
562 let mut col = 1u32;
563 for c in self.src[..self.pos.min(self.src.len())].chars() {
564 if c == '\n' {
565 line += 1;
566 col = 1;
567 } else {
568 col += 1;
569 }
570 }
571 (line, col)
572 }
573
574 fn err(&self, msg: &str) -> XmlError {
575 let (line, col) = self.line_col();
576 XmlError::new(crate::error::XML_ERR_SPACE_REQUIRED, msg, line, col)
577 }
578
579 fn require_ws(&mut self) -> bool {
581 let before = self.pos;
582 self.skip_ws();
583 self.pos > before || self.pos >= self.src.len()
584 }
585
586 fn at_quote(&self) -> bool {
587 self.rest().starts_with('"') || self.rest().starts_with('\'')
588 }
589
590 fn expect_decl_end(&mut self, what: &str) -> Result<(), XmlError> {
594 self.skip_ws();
595 if self.rest().starts_with('>') {
596 self.bump(1);
597 Ok(())
598 } else {
599 Err(self.err(&format!("xmlParse{what}Decl: not terminated")))
600 }
601 }
602}
603
604pub fn merge_dtd(dst: &mut XmlDtd, src: XmlDtd) {
606 dst.entities.extend(src.entities);
607 dst.parameter_entities.extend(src.parameter_entities);
608 dst.elements.extend(src.elements);
609 dst.attributes.extend(src.attributes);
610 if dst.public_id.is_none() {
611 dst.public_id = src.public_id;
612 }
613 if dst.system_id.is_none() {
614 dst.system_id = src.system_id;
615 }
616}