1#![forbid(unsafe_code)]
4
5mod c14n;
6mod rng;
7mod xsd;
8mod schematron;
9
10use rusty_xml_tree::{AttrDefault, ElementDecl, NodeId, NodeKind, XmlDoc, XmlDtd};
11
12pub use c14n::*;
13pub use rng::*;
14pub use xsd::*;
15pub use schematron::*;
16
17#[doc(alias = "xmlValidateDocument")]
19pub fn xml_validate_document(doc: &XmlDoc) -> Result<(), String> {
20 let dtd = doc.dtd.as_ref().ok_or("no DTD")?;
21 xml_validate_dtd(doc, dtd)
22}
23
24#[doc(alias = "xmlValidateDtd")]
26pub fn xml_validate_dtd(doc: &XmlDoc, dtd: &XmlDtd) -> Result<(), String> {
27 let root = doc
28 .xml_doc_get_root_element()
29 .ok_or("document has no root")?;
30 if let Some(n) = &dtd.name {
31 if doc.name(root) != n.as_str() {
32 return Err(format!("root element {} does not match DOCTYPE {n}", doc.name(root)));
33 }
34 }
35 if let Some(dup) = dtd.duplicate_elements.first() {
41 return Err(format!("Redefinition of element {dup}"));
42 }
43 for (elem, decl) in &dtd.elements {
46 if let ElementDecl::Mixed(names) = decl {
47 let mut seen = std::collections::HashSet::new();
48 for n in names {
49 if !seen.insert(n) {
50 return Err(format!(
51 "Definition of {elem} has duplicate references of {n}"
52 ));
53 }
54 }
55 }
56 }
57 for ((elem, aname), ad) in &dtd.attributes {
58 let mut seen = std::collections::HashSet::new();
60 for e in &ad.enumerated {
61 if !seen.insert(e) {
62 return Err(format!(
63 "attribute {aname} of {elem}: enumeration value token {e} duplicated"
64 ));
65 }
66 }
67 if ad.att_type == "ID"
71 && matches!(ad.default, AttrDefault::Value | AttrDefault::Fixed)
72 {
73 return Err(format!(
74 "ID attribute {aname} of {elem} is not valid, must be #IMPLIED or #REQUIRED"
75 ));
76 }
77 if ad.att_type == "NOTATION" && matches!(dtd.elements.get(elem), Some(ElementDecl::Empty)) {
81 return Err(format!(
82 "NOTATION attribute type declared for EMPTY element {elem}"
83 ));
84 }
85 let Some(def) = ad.default_value.as_deref() else {
88 continue;
89 };
90 let ok = match ad.att_type.as_str() {
91 "ID" | "IDREF" | "ENTITY" => is_name(def),
92 "IDREFS" | "ENTITIES" => {
93 def.split_ascii_whitespace().next().is_some()
94 && def.split_ascii_whitespace().all(is_name)
95 }
96 "NMTOKEN" => is_nmtoken(def),
97 "NMTOKENS" => {
98 def.split_ascii_whitespace().next().is_some()
99 && def.split_ascii_whitespace().all(is_nmtoken)
100 }
101 _ => true,
102 };
103 if !ok {
104 return Err(format!("invalid default value for attribute {aname} of {elem}"));
105 }
106 if !ad.enumerated.is_empty() && !ad.enumerated.iter().any(|e| e == def) {
107 return Err(format!("invalid default value for attribute {aname} of {elem}"));
108 }
109 }
110
111 let mut ids: std::collections::HashMap<String, ()> = Default::default();
114 let mut idrefs: Vec<String> = Vec::new();
115 let mut stack = vec![root];
116 while let Some(id) = stack.pop() {
117 validate_element(doc, id, dtd, &mut ids, &mut idrefs)?;
118 let mut c = doc.last_child(id);
119 while let Some(x) = c {
120 if doc.kind(x) == NodeKind::Element {
121 stack.push(x);
122 }
123 c = doc.prev_sibling(x);
124 }
125 }
126 for r in &idrefs {
129 if !ids.contains_key(r) {
130 return Err(format!("IDREF attribute references unknown ID \"{r}\""));
131 }
132 }
133 Ok(())
134}
135
136fn is_name(v: &str) -> bool {
138 let mut cs = v.chars();
139 match cs.next() {
140 Some(c) if rusty_xml_parser::chvalid::xml_is_name_start_char(c as u32, false) => {}
141 _ => return false,
142 }
143 cs.all(|c| rusty_xml_parser::chvalid::xml_is_name_char(c as u32, false))
144}
145
146fn is_nmtoken(v: &str) -> bool {
148 !v.is_empty()
149 && v.chars()
150 .all(|c| rusty_xml_parser::chvalid::xml_is_name_char(c as u32, false))
151}
152
153fn validate_element(
154 doc: &XmlDoc,
155 id: NodeId,
156 dtd: &XmlDtd,
157 ids: &mut std::collections::HashMap<String, ()>,
158 idrefs: &mut Vec<String>,
159) -> Result<(), String> {
160 if doc.kind(id) != NodeKind::Element {
161 return Ok(());
162 }
163 let name = doc.name(id).to_string();
164 if !dtd.elements.is_empty() && !dtd.elements.contains_key(&name) {
168 return Err(format!("No declaration for element {name}"));
169 }
170 if let Some(decl) = dtd.elements.get(&name) {
171 match decl {
172 ElementDecl::Empty => {
173 if doc.first_child(id).is_some() {
174 return Err(format!("element {name} must be EMPTY"));
175 }
176 }
177 ElementDecl::Any => {}
178 ElementDecl::Mixed(_) => {
179 let mut c = doc.first_child(id);
180 while let Some(x) = c {
181 match doc.kind(x) {
182 NodeKind::Element => {
183 if let ElementDecl::Mixed(allowed) = decl {
184 if !allowed.is_empty() && !allowed.iter().any(|n| n == doc.name(x)) {
185 return Err(format!("element {} not allowed in mixed {name}", doc.name(x)));
186 }
187 }
188 }
189 NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi => {}
190 _ => {}
191 }
192 c = doc.next_sibling(x);
193 }
194 }
195 ElementDecl::Children(spec) => {
196 let kids: Vec<String> = {
197 let mut v = Vec::new();
198 let mut c = doc.first_child(id);
199 while let Some(x) = c {
200 if doc.kind(x) == NodeKind::Element {
201 v.push(doc.name(x).to_string());
202 } else if doc.kind(x) == NodeKind::Text && !doc.xml_is_blank_node(x) {
203 return Err(format!("character data not allowed in {name}"));
204 } else if doc.kind(x) == NodeKind::CData {
205 return Err(format!("character data not allowed in {name}"));
211 }
212 c = doc.next_sibling(x);
213 }
214 v
215 };
216 if !match_children_spec(spec, &kids) {
217 return Err(format!("content of {name} does not match {spec}"));
218 }
219 }
220 }
221 }
222 for ((elem, aname), ad) in &dtd.attributes {
223 if elem != &name {
224 continue;
225 }
226 let have = {
232 let mut found = None;
233 let mut a = doc.first_attr(id);
234 while let Some(x) = a {
235 if doc.qname(x) == *aname {
236 found = Some(doc.content(x).to_string());
237 break;
238 }
239 a = doc.next_sibling(x);
240 }
241 found
242 };
243 match ad.default {
244 AttrDefault::Required if have.is_none() => {
245 return Err(format!("attribute {aname} of {name} is required"));
246 }
247 AttrDefault::Fixed => {
248 if let (Some(v), Some(fix)) = (&have, &ad.default_value) {
249 if v != fix {
250 return Err(format!("attribute {aname} must be {fix}"));
251 }
252 }
253 }
254 _ => {}
255 }
256 if let Some(v) = &have {
257 if !ad.enumerated.is_empty() && !ad.enumerated.iter().any(|e| e == v) {
258 return Err(format!("attribute {aname} value not in enumeration"));
259 }
260 match ad.att_type.as_str() {
264 "ID" | "IDREF" => {
265 if !is_name(v) {
266 return Err(format!(
267 "Syntax of value for attribute {aname} of {name} is not valid"
268 ));
269 }
270 if ad.att_type == "ID" {
271 if ids.insert(v.clone(), ()).is_some() {
272 return Err(format!("ID {v} already defined"));
273 }
274 } else {
275 idrefs.push(v.clone());
276 }
277 }
278 "IDREFS" => {
279 let mut any = false;
280 for part in v.split_ascii_whitespace() {
281 any = true;
282 if !is_name(part) {
283 return Err(format!(
284 "Syntax of value for attribute {aname} of {name} is not valid"
285 ));
286 }
287 idrefs.push(part.to_string());
288 }
289 if !any {
290 return Err(format!(
291 "Syntax of value for attribute {aname} of {name} is not valid"
292 ));
293 }
294 }
295 "NMTOKEN" => {
296 if !is_nmtoken(v) {
297 return Err(format!(
298 "Syntax of value for attribute {aname} of {name} is not valid"
299 ));
300 }
301 }
302 "NMTOKENS" => {
303 if v.split_ascii_whitespace().next().is_none()
304 || !v.split_ascii_whitespace().all(is_nmtoken)
305 {
306 return Err(format!(
307 "Syntax of value for attribute {aname} of {name} is not valid"
308 ));
309 }
310 }
311 "ENTITY" | "ENTITIES" => {
312 for part in v.split_ascii_whitespace() {
313 if !is_name(part) {
314 return Err(format!(
315 "Syntax of value for attribute {aname} of {name} is not valid"
316 ));
317 }
318 if !dtd.unparsed_entities.contains(part) {
319 return Err(format!(
320 "ENTITY attribute {aname} references an unknown entity \"{part}\""
321 ));
322 }
323 }
324 }
325 _ => {}
326 }
327 }
328 }
329 if !dtd.elements.is_empty() {
333 let mut a = doc.first_attr(id);
334 while let Some(x) = a {
335 let q = doc.qname(x);
336 if !dtd.attributes.contains_key(&(name.clone(), q.clone())) {
337 return Err(format!("No declaration for attribute {q} of element {name}"));
338 }
339 a = doc.next_sibling(x);
340 }
341 }
342 let id_attrs = dtd
345 .attributes
346 .iter()
347 .filter(|((e, _), ad)| e == &name && ad.att_type == "ID")
348 .count();
349 if id_attrs > 1 {
350 return Err(format!("Element {name} has {id_attrs} ID attributes"));
351 }
352 Ok(())
353}
354
355fn match_children_spec(spec: &str, kids: &[String]) -> bool {
356 let toks = tokenize_content(spec);
357 match_seq(&toks, kids, 0).contains(&kids.len())
358}
359
360#[derive(Clone, Debug)]
361enum Tok {
362 Name(String),
363 Seq(Vec<Tok>),
364 Choice(Vec<Tok>),
365 Star,
366 Plus,
367 Q,
368}
369
370fn tokenize_content(spec: &str) -> Vec<Tok> {
371 let p = spec.trim();
373 fn parse_choice<'a>(p: &mut &'a str) -> Vec<Tok> {
374 let mut alts = vec![Tok::Seq(parse_seq(p))];
375 loop {
376 skip(p);
377 if p.starts_with('|') {
378 *p = &p[1..];
379 alts.push(Tok::Seq(parse_seq(p)));
380 } else {
381 break;
382 }
383 }
384 alts
385 }
386 fn parse_seq<'a>(p: &mut &'a str) -> Vec<Tok> {
387 let mut v = Vec::new();
388 loop {
389 skip(p);
390 if p.is_empty() || p.starts_with('|') || p.starts_with(')') {
391 break;
392 }
393 if p.starts_with(',') {
394 *p = &p[1..];
395 continue;
396 }
397 let before = p.len();
406 let particle = parse_particle(p);
407 if p.len() == before {
408 break;
409 }
410 v.push(particle);
411 }
412 v
413 }
414 fn parse_particle<'a>(p: &mut &'a str) -> Tok {
415 skip(p);
416 let mut inner = if p.starts_with('(') {
417 *p = &p[1..];
418 let c = parse_choice(p);
419 skip(p);
420 if p.starts_with(')') {
421 *p = &p[1..];
422 }
423 if c.len() == 1 {
424 Tok::Seq(match c.into_iter().next().unwrap() {
425 Tok::Seq(s) => s,
426 other => vec![other],
427 })
428 } else {
429 Tok::Choice(c)
430 }
431 } else {
432 let name = take_name(p);
433 Tok::Name(name)
434 };
435 skip(p);
436 inner = match p.chars().next() {
437 Some('*') => {
438 *p = &p[1..];
439 Tok::Seq(vec![inner, Tok::Star])
440 }
441 Some('+') => {
442 *p = &p[1..];
443 Tok::Seq(vec![inner, Tok::Plus])
444 }
445 Some('?') => {
446 *p = &p[1..];
447 Tok::Seq(vec![inner, Tok::Q])
448 }
449 _ => inner,
450 };
451 inner
452 }
453 fn take_name<'a>(p: &mut &'a str) -> String {
454 let bytes = p.as_bytes();
455 let mut i = 0;
456 while i < bytes.len() {
457 let c = bytes[i] as char;
458 if c.is_ascii_alphanumeric() || "-._:".contains(c) {
459 i += 1;
460 } else {
461 break;
462 }
463 }
464 let s = p[..i].to_string();
465 *p = &p[i..];
466 s
467 }
468 fn skip(p: &mut &str) {
469 *p = p.trim_start();
470 }
471 let mut tmp = p;
472 parse_choice(&mut tmp)
473}
474
475fn match_seq(toks: &[Tok], kids: &[String], i: usize) -> Vec<usize> {
476 if toks.is_empty() {
477 return vec![i];
478 }
479 match &toks[0] {
480 Tok::Star => {
481 let rest = &toks[1..];
482 match_seq(rest, kids, i)
484 }
485 Tok::Plus | Tok::Q => match_seq(&toks[1..], kids, i),
486 Tok::Name(n) => {
487 if i < kids.len() && &kids[i] == n {
488 match_seq(&toks[1..], kids, i + 1)
489 } else {
490 vec![]
491 }
492 }
493 Tok::Seq(inner) => {
494 let (body, quant) = split_quant(inner);
495 apply_quant(body, quant, &toks[1..], kids, i)
496 }
497 Tok::Choice(alts) => {
498 let mut out = Vec::new();
499 for a in alts {
500 let one = match_seq(&[a.clone()], kids, i);
501 for pos in one {
502 out.extend(match_seq(&toks[1..], kids, pos));
503 }
504 }
505 out.sort();
506 out.dedup();
507 out
508 }
509 }
510}
511
512enum Quant {
513 One,
514 Q,
515 Star,
516 Plus,
517}
518
519fn split_quant(inner: &[Tok]) -> (&[Tok], Quant) {
520 if inner.len() >= 2 {
521 match inner.last() {
522 Some(Tok::Star) => return (&inner[..inner.len() - 1], Quant::Star),
523 Some(Tok::Plus) => return (&inner[..inner.len() - 1], Quant::Plus),
524 Some(Tok::Q) => return (&inner[..inner.len() - 1], Quant::Q),
525 _ => {}
526 }
527 }
528 (inner, Quant::One)
529}
530
531fn apply_quant(body: &[Tok], q: Quant, rest: &[Tok], kids: &[String], i: usize) -> Vec<usize> {
532 match q {
533 Quant::One => {
534 let mut out = Vec::new();
535 for p in match_seq(body, kids, i) {
536 out.extend(match_seq(rest, kids, p));
537 }
538 out
539 }
540 Quant::Q => {
541 let mut out = match_seq(rest, kids, i);
542 for p in match_seq(body, kids, i) {
543 out.extend(match_seq(rest, kids, p));
544 }
545 out.sort();
546 out.dedup();
547 out
548 }
549 Quant::Star => {
550 let mut out = match_seq(rest, kids, i);
551 let mut frontier = vec![i];
552 while let Some(p) = frontier.pop() {
553 for n in match_seq(body, kids, p) {
554 if n > p {
555 out.extend(match_seq(rest, kids, n));
556 frontier.push(n);
557 }
558 }
559 }
560 out.sort();
561 out.dedup();
562 out
563 }
564 Quant::Plus => {
565 let mut out = Vec::new();
566 for p in match_seq(body, kids, i) {
567 out.extend(apply_quant(body, Quant::Star, rest, kids, p));
568 }
569 out.sort();
570 out.dedup();
571 out
572 }
573 }
574}