| 1 | module xml |
| 2 | |
| 3 | fn (node XMLNode) validate(elements map[string]DTDElement, entities map[string]string) !XMLNode { |
| 4 | mut children := []XMLNodeContents{cap: node.children.len} |
| 5 | |
| 6 | valid_elements := elements[node.name].definition |
| 7 | mut validate_node_children := node.name in elements |
| 8 | |
| 9 | // Check if the node will match everything |
| 10 | if valid_elements.len == 1 && valid_elements[0] == '#PCDATA' { |
| 11 | validate_node_children = false |
| 12 | } |
| 13 | |
| 14 | for child in node.children { |
| 15 | match child { |
| 16 | XMLNode { |
| 17 | if validate_node_children { |
| 18 | name := child.name |
| 19 | if name !in valid_elements { |
| 20 | return error('Invalid child element ${name} for ${node.name}') |
| 21 | } |
| 22 | } |
| 23 | children << child.validate(elements, entities)! |
| 24 | } |
| 25 | string { |
| 26 | children << unescape_text(child, entities: entities)! |
| 27 | } |
| 28 | else { |
| 29 | // Ignore other nodes |
| 30 | children << child |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | return XMLNode{ |
| 36 | name: node.name |
| 37 | attributes: node.attributes |
| 38 | children: children |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | // validate checks the document is well-formed and valid. It returns a new |
| 43 | // document with the parsed entities expanded when validation is successful. |
| 44 | // Otherwise it returns an error. |
| 45 | pub fn (doc XMLDocument) validate() !XMLDocument { |
| 46 | // The document is well-formed because we were able to parse it properly. |
| 47 | match doc.doctype.dtd { |
| 48 | DocumentTypeDefinition { |
| 49 | // Store the element and entity definitions |
| 50 | mut elements := map[string]DTDElement{} |
| 51 | mut entities := default_entities.clone() |
| 52 | mut reverse_entities := default_entities_reverse.clone() |
| 53 | |
| 54 | for item in doc.doctype.dtd.list { |
| 55 | match item { |
| 56 | DTDElement { |
| 57 | name := item.name |
| 58 | if name in elements { |
| 59 | return error('Duplicate element definition for ${name}') |
| 60 | } |
| 61 | elements[name] = item |
| 62 | } |
| 63 | DTDEntity { |
| 64 | name := item.name |
| 65 | if name in entities { |
| 66 | return error('Duplicate entity definition for ${name}') |
| 67 | } |
| 68 | entities[name] = item.value |
| 69 | reverse_entities[item.value] = name |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // Now validate the document against the elements and entities. |
| 75 | new_root := doc.root.validate(elements, entities)! |
| 76 | |
| 77 | // Check the DOCTYPE name matches the root name |
| 78 | if doc.doctype.name != '' && doc.doctype.name != new_root.name { |
| 79 | return error('Root element ${new_root.name} does not match DOCTYPE ${doc.doctype.name}') |
| 80 | } |
| 81 | |
| 82 | return XMLDocument{ |
| 83 | version: doc.version |
| 84 | encoding: doc.encoding |
| 85 | doctype: doc.doctype |
| 86 | comments: doc.comments |
| 87 | root: new_root |
| 88 | parsed_reverse_entities: reverse_entities |
| 89 | } |
| 90 | } |
| 91 | string { |
| 92 | // TODO: Validate the document against the DTD string. |
| 93 | return doc |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |