| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <AK/LexicalPath.h>
 | 
					
						
							|  |  |  | #include <AK/Queue.h>
 | 
					
						
							|  |  |  | #include <AK/URL.h>
 | 
					
						
							|  |  |  | #include <AK/URLParser.h>
 | 
					
						
							|  |  |  | #include <LibCore/ArgsParser.h>
 | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  | #include <LibCore/File.h>
 | 
					
						
							| 
									
										
										
										
											2023-05-20 10:43:37 +02:00
										 |  |  | #include <LibFileSystem/FileSystem.h>
 | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | #include <LibMain/Main.h>
 | 
					
						
							|  |  |  | #include <LibXML/DOM/Document.h>
 | 
					
						
							|  |  |  | #include <LibXML/DOM/Node.h>
 | 
					
						
							|  |  |  | #include <LibXML/Parser/Parser.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool g_color = false; | 
					
						
							|  |  |  | static bool g_only_contents = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum class ColorRole { | 
					
						
							|  |  |  |     PITag, | 
					
						
							|  |  |  |     PITarget, | 
					
						
							|  |  |  |     PIData, | 
					
						
							|  |  |  |     AttributeName, | 
					
						
							|  |  |  |     Eq, | 
					
						
							|  |  |  |     AttributeValue, | 
					
						
							|  |  |  |     Tag, | 
					
						
							|  |  |  |     Text, | 
					
						
							|  |  |  |     Comment, | 
					
						
							|  |  |  |     Reset, | 
					
						
							|  |  |  |     Doctype, | 
					
						
							|  |  |  |     Keyword, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | static void color(ColorRole role) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!g_color) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (role) { | 
					
						
							|  |  |  |     case ColorRole::PITag: | 
					
						
							|  |  |  |     case ColorRole::Doctype: | 
					
						
							|  |  |  |         out("\x1b[{};{}m", 1, "38;5;223"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::PITarget: | 
					
						
							|  |  |  |         out("\x1b[{};{}m", 1, "38;5;23"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::PIData: | 
					
						
							|  |  |  |         out("\x1b[{};{}m", 1, "38;5;43"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::AttributeName: | 
					
						
							|  |  |  |         out("\x1b[38;5;27m"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Eq: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::AttributeValue: | 
					
						
							|  |  |  |         out("\x1b[38;5;46m"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Tag: | 
					
						
							|  |  |  |         out("\x1b[{};{}m", 1, "38;5;220"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Text: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Comment: | 
					
						
							|  |  |  |         out("\x1b[{};{}m", 3, "38;5;250"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Reset: | 
					
						
							|  |  |  |         out("\x1b[0m"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case ColorRole::Keyword: | 
					
						
							|  |  |  |         out("\x1b[38;5;40m"); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dump(XML::Node const& node) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     node.content.visit( | 
					
						
							|  |  |  |         [](XML::Node::Text const& text) { | 
					
						
							|  |  |  |             out("{}", text.builder.string_view()); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         [](XML::Node::Comment const& comment) { | 
					
						
							|  |  |  |             color(ColorRole::Comment); | 
					
						
							|  |  |  |             out("<!--{}-->", comment.text); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         [](XML::Node::Element const& element) { | 
					
						
							|  |  |  |             color(ColorRole::Tag); | 
					
						
							|  |  |  |             out("<{}", element.name); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (!element.attributes.is_empty()) { | 
					
						
							|  |  |  |                 for (auto& attribute : element.attributes) { | 
					
						
							|  |  |  |                     auto quote = attribute.value.contains('"') ? '\'' : '"'; | 
					
						
							|  |  |  |                     color(ColorRole::AttributeName); | 
					
						
							|  |  |  |                     out(" {}", attribute.key); | 
					
						
							|  |  |  |                     color(ColorRole::Eq); | 
					
						
							|  |  |  |                     out("="); | 
					
						
							|  |  |  |                     color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                     out("{}{}{}", quote, attribute.value, quote); | 
					
						
							|  |  |  |                     color(ColorRole::Reset); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (element.children.is_empty()) { | 
					
						
							|  |  |  |                 color(ColorRole::Tag); | 
					
						
							|  |  |  |                 out("/>"); | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 color(ColorRole::Tag); | 
					
						
							|  |  |  |                 out(">"); | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 for (auto& node : element.children) | 
					
						
							| 
									
										
										
										
											2023-03-06 17:16:25 +01:00
										 |  |  |                     dump(*node); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |                 color(ColorRole::Tag); | 
					
						
							|  |  |  |                 out("</{}>", element.name); | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dump(XML::Document& document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!g_only_contents) { | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             color(ColorRole::PITag); | 
					
						
							|  |  |  |             out("<?"); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |             color(ColorRole::PITarget); | 
					
						
							|  |  |  |             out("xml"); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |             color(ColorRole::PIData); | 
					
						
							|  |  |  |             out(" version='{}'", document.version() == XML::Version::Version10 ? "1.0" : "1.1"); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |             color(ColorRole::PITag); | 
					
						
							|  |  |  |             outln("?>"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (auto& pi : document.processing_instructions()) { | 
					
						
							|  |  |  |             color(ColorRole::PITag); | 
					
						
							|  |  |  |             out("<?"); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |             color(ColorRole::PITarget); | 
					
						
							|  |  |  |             out("{}", pi.key); | 
					
						
							|  |  |  |             color(ColorRole::Reset); | 
					
						
							|  |  |  |             if (!pi.value.is_empty()) { | 
					
						
							|  |  |  |                 color(ColorRole::PIData); | 
					
						
							|  |  |  |                 out(" {}", pi.value); | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             color(ColorRole::PITag); | 
					
						
							|  |  |  |             outln("?>"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (auto maybe_doctype = document.doctype(); maybe_doctype.has_value()) { | 
					
						
							|  |  |  |             auto& doctype = *maybe_doctype; | 
					
						
							|  |  |  |             color(ColorRole::Doctype); | 
					
						
							|  |  |  |             out("<!DOCTYPE "); | 
					
						
							|  |  |  |             color(ColorRole::Tag); | 
					
						
							|  |  |  |             out("{}", doctype.type); | 
					
						
							|  |  |  |             if (!doctype.markup_declarations.is_empty()) { | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  |                 out(" [\n"); | 
					
						
							|  |  |  |                 for (auto& entry : doctype.markup_declarations) { | 
					
						
							|  |  |  |                     entry.visit( | 
					
						
							|  |  |  |                         [&](XML::ElementDeclaration const& element) { | 
					
						
							|  |  |  |                             color(ColorRole::Doctype); | 
					
						
							|  |  |  |                             out("    <!ELEMENT "); | 
					
						
							|  |  |  |                             color(ColorRole::Tag); | 
					
						
							|  |  |  |                             out("{} ", element.type); | 
					
						
							|  |  |  |                             element.content_spec.visit( | 
					
						
							|  |  |  |                                 [&](XML::ElementDeclaration::Empty const&) { | 
					
						
							|  |  |  |                                     color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                     out("EMPTY"); | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                                 [&](XML::ElementDeclaration::Any const&) { | 
					
						
							|  |  |  |                                     color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                     out("ANY"); | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                                 [&](XML::ElementDeclaration::Mixed const&) { | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                                 [&](XML::ElementDeclaration::Children const&) { | 
					
						
							|  |  |  |                                 }); | 
					
						
							|  |  |  |                             color(ColorRole::Doctype); | 
					
						
							|  |  |  |                             outln(">"); | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         [&](XML::AttributeListDeclaration const& list) { | 
					
						
							|  |  |  |                             color(ColorRole::Doctype); | 
					
						
							|  |  |  |                             out("    <!ATTLIST "); | 
					
						
							|  |  |  |                             color(ColorRole::Tag); | 
					
						
							|  |  |  |                             out("{}", list.type); | 
					
						
							|  |  |  |                             for (auto& attribute : list.attributes) { | 
					
						
							|  |  |  |                                 color(ColorRole::AttributeName); | 
					
						
							|  |  |  |                                 out(" {} ", attribute.name); | 
					
						
							|  |  |  |                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                 attribute.type.visit( | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::StringType) { | 
					
						
							|  |  |  |                                         out("CDATA"); | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::TokenizedType type) { | 
					
						
							|  |  |  |                                         switch (type) { | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::ID: | 
					
						
							|  |  |  |                                             out("ID"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::IDRef: | 
					
						
							|  |  |  |                                             out("IDREF"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::IDRefs: | 
					
						
							|  |  |  |                                             out("IDREFS"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::Entity: | 
					
						
							|  |  |  |                                             out("ENTITY"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::Entities: | 
					
						
							|  |  |  |                                             out("ENTITIES"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::NMToken: | 
					
						
							|  |  |  |                                             out("NMTOKEN"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         case XML::AttributeListDeclaration::TokenizedType::NMTokens: | 
					
						
							|  |  |  |                                             out("NMTOKENS"); | 
					
						
							|  |  |  |                                             break; | 
					
						
							|  |  |  |                                         } | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::NotationType const& type) { | 
					
						
							|  |  |  |                                         out("NOTATION "); | 
					
						
							|  |  |  |                                         color(ColorRole::Reset); | 
					
						
							|  |  |  |                                         out("( "); | 
					
						
							|  |  |  |                                         bool first = true; | 
					
						
							|  |  |  |                                         for (auto& name : type.names) { | 
					
						
							|  |  |  |                                             color(ColorRole::Reset); | 
					
						
							|  |  |  |                                             if (first) | 
					
						
							|  |  |  |                                                 first = false; | 
					
						
							|  |  |  |                                             else | 
					
						
							|  |  |  |                                                 out(" | "); | 
					
						
							|  |  |  |                                             color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                             out("{}", name); | 
					
						
							|  |  |  |                                         } | 
					
						
							|  |  |  |                                         color(ColorRole::Reset); | 
					
						
							|  |  |  |                                         out(" )"); | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::Enumeration const& type) { | 
					
						
							|  |  |  |                                         color(ColorRole::Reset); | 
					
						
							|  |  |  |                                         out("( "); | 
					
						
							|  |  |  |                                         bool first = true; | 
					
						
							|  |  |  |                                         for (auto& name : type.tokens) { | 
					
						
							|  |  |  |                                             color(ColorRole::Reset); | 
					
						
							|  |  |  |                                             if (first) | 
					
						
							|  |  |  |                                                 first = false; | 
					
						
							|  |  |  |                                             else | 
					
						
							|  |  |  |                                                 out(" | "); | 
					
						
							|  |  |  |                                             color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                             out("{}", name); | 
					
						
							|  |  |  |                                         } | 
					
						
							|  |  |  |                                         color(ColorRole::Reset); | 
					
						
							|  |  |  |                                         out(" )"); | 
					
						
							|  |  |  |                                     }); | 
					
						
							|  |  |  |                                 out(" "); | 
					
						
							|  |  |  |                                 attribute.default_.visit( | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::Required) { | 
					
						
							|  |  |  |                                         color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                         out("#REQUIRED"); | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::Implied) { | 
					
						
							|  |  |  |                                         color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                         out("#IMPLIED"); | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::Fixed const& fixed) { | 
					
						
							|  |  |  |                                         color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                         out("#FIXED "); | 
					
						
							|  |  |  |                                         color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                         out("\"{}\"", fixed.value); | 
					
						
							|  |  |  |                                     }, | 
					
						
							|  |  |  |                                     [](XML::AttributeListDeclaration::DefaultValue const& default_) { | 
					
						
							|  |  |  |                                         color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                         out("\"{}\"", default_.value); | 
					
						
							|  |  |  |                                     }); | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                             color(ColorRole::Doctype); | 
					
						
							|  |  |  |                             outln(">"); | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         [&](XML::EntityDeclaration const& entity) { | 
					
						
							|  |  |  |                             color(ColorRole::Doctype); | 
					
						
							|  |  |  |                             out("    <!ENTITY "); | 
					
						
							|  |  |  |                             entity.visit( | 
					
						
							|  |  |  |                                 [](XML::GEDeclaration const& declaration) { | 
					
						
							|  |  |  |                                     color(ColorRole::Tag); | 
					
						
							|  |  |  |                                     out("{} ", declaration.name); | 
					
						
							|  |  |  |                                     declaration.definition.visit( | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |                                         [](ByteString const& value) { | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                                             color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                             out("\"{}\"", value); | 
					
						
							|  |  |  |                                         }, | 
					
						
							|  |  |  |                                         [](XML::EntityDefinition const& definition) { | 
					
						
							|  |  |  |                                             if (definition.id.public_id.has_value()) { | 
					
						
							|  |  |  |                                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                                 out("PUBLIC "); | 
					
						
							|  |  |  |                                                 color(ColorRole::PITarget); | 
					
						
							|  |  |  |                                                 out("\"{}\" ", definition.id.public_id->public_literal); | 
					
						
							|  |  |  |                                             } else { | 
					
						
							|  |  |  |                                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                                 out("SYSTEM "); | 
					
						
							|  |  |  |                                             } | 
					
						
							|  |  |  |                                             color(ColorRole::PITarget); | 
					
						
							|  |  |  |                                             out("\"{}\" ", definition.id.system_id.system_literal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                                             if (definition.notation.has_value()) { | 
					
						
							|  |  |  |                                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                                 out(" NDATA "); | 
					
						
							|  |  |  |                                                 color(ColorRole::PITarget); | 
					
						
							|  |  |  |                                                 out("{}", *definition.notation); | 
					
						
							|  |  |  |                                             } | 
					
						
							|  |  |  |                                         }); | 
					
						
							|  |  |  |                                     color(ColorRole::Tag); | 
					
						
							|  |  |  |                                     outln(">"); | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                                 [](XML::PEDeclaration const& declaration) { | 
					
						
							|  |  |  |                                     color(ColorRole::Tag); | 
					
						
							|  |  |  |                                     out("{} ", declaration.name); | 
					
						
							|  |  |  |                                     declaration.definition.visit( | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |                                         [](ByteString const& value) { | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                                             color(ColorRole::AttributeValue); | 
					
						
							|  |  |  |                                             out("\"{}\"", value); | 
					
						
							|  |  |  |                                         }, | 
					
						
							|  |  |  |                                         [](XML::ExternalID const& id) { | 
					
						
							|  |  |  |                                             if (id.public_id.has_value()) { | 
					
						
							|  |  |  |                                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                                 out("PUBLIC "); | 
					
						
							|  |  |  |                                                 color(ColorRole::PITarget); | 
					
						
							|  |  |  |                                                 out("\"{}\" ", id.public_id->public_literal); | 
					
						
							|  |  |  |                                             } else { | 
					
						
							|  |  |  |                                                 color(ColorRole::Keyword); | 
					
						
							|  |  |  |                                                 out("SYSTEM "); | 
					
						
							|  |  |  |                                             } | 
					
						
							|  |  |  |                                             color(ColorRole::PITarget); | 
					
						
							|  |  |  |                                             out("\"{}\"", id.system_id.system_literal); | 
					
						
							|  |  |  |                                         }); | 
					
						
							|  |  |  |                                     color(ColorRole::Tag); | 
					
						
							|  |  |  |                                     outln(">"); | 
					
						
							|  |  |  |                                 }); | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         [&](XML::NotationDeclaration const&) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 color(ColorRole::Reset); | 
					
						
							|  |  |  |                 out("]"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             color(ColorRole::Doctype); | 
					
						
							|  |  |  |             outln(">"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     dump(document.root()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 10:43:37 +02:00
										 |  |  | static String s_path; | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | static auto parse(StringView contents) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return XML::Parser { | 
					
						
							|  |  |  |         contents, | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             .preserve_comments = true, | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |             .resolve_external_resource = [&](XML::SystemID const& system_id, Optional<XML::PublicID> const&) -> ErrorOr<ByteString> { | 
					
						
							|  |  |  |                 auto base = URL::create_with_file_scheme(s_path.to_byte_string()); | 
					
						
							| 
									
										
										
										
											2023-07-15 14:29:20 +12:00
										 |  |  |                 auto url = URLParser::basic_parse(system_id.system_literal, base); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 if (!url.is_valid()) | 
					
						
							|  |  |  |                     return Error::from_string_literal("Invalid URL"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (url.scheme() != "file") | 
					
						
							|  |  |  |                     return Error::from_string_literal("NYI: Nonlocal entity"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                 auto file = TRY(Core::File::open(url.serialize_path(), Core::File::OpenMode::Read)); | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |                 return ByteString::copy(TRY(file->read_until_eof())); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             }, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum class TestResult { | 
					
						
							|  |  |  |     Passed, | 
					
						
							|  |  |  |     Failed, | 
					
						
							|  |  |  |     RunnerFailed, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  | static HashMap<ByteString, TestResult> s_test_results {}; | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | static void do_run_tests(XML::Document& document) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     auto& root = document.root().content.get<XML::Node::Element>(); | 
					
						
							|  |  |  |     VERIFY(root.name == "TESTSUITE"); | 
					
						
							|  |  |  |     Queue<XML::Node*> suites; | 
					
						
							|  |  |  |     auto dump_cases = [&](auto& root) { | 
					
						
							|  |  |  |         for (auto& node : root.children) { | 
					
						
							| 
									
										
										
										
											2023-03-06 17:16:25 +01:00
										 |  |  |             auto element = node->content.template get_pointer<XML::Node::Element>(); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             if (!element) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             if (element->name != "TESTCASES" && element->name != "TEST") | 
					
						
							|  |  |  |                 continue; | 
					
						
							| 
									
										
										
										
											2023-03-06 17:16:25 +01:00
										 |  |  |             suites.enqueue(node); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dump_cases(root); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |     auto base_path = LexicalPath::dirname(s_path.to_byte_string()); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     while (!suites.is_empty()) { | 
					
						
							|  |  |  |         auto& node = *suites.dequeue(); | 
					
						
							|  |  |  |         auto& suite = node.content.get<XML::Node::Element>(); | 
					
						
							|  |  |  |         if (suite.name == "TESTCASES") { | 
					
						
							|  |  |  |             dump_cases(suite); | 
					
						
							|  |  |  |             continue; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (suite.name == "TEST") { | 
					
						
							|  |  |  |             Vector<StringView> bases; | 
					
						
							|  |  |  |             for (auto* parent = node.parent; parent; parent = parent->parent) { | 
					
						
							|  |  |  |                 auto& attributes = parent->content.get<XML::Node::Element>().attributes; | 
					
						
							|  |  |  |                 auto it = attributes.find("xml:base"); | 
					
						
							|  |  |  |                 if (it == attributes.end()) | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 bases.append(it->value); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auto type = suite.attributes.find("TYPE")->value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             StringBuilder path_builder; | 
					
						
							|  |  |  |             path_builder.append(base_path); | 
					
						
							| 
									
										
										
										
											2022-07-11 20:10:18 +00:00
										 |  |  |             path_builder.append('/'); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             for (auto& entry : bases.in_reverse()) { | 
					
						
							|  |  |  |                 path_builder.append(entry); | 
					
						
							| 
									
										
										
										
											2022-07-11 20:10:18 +00:00
										 |  |  |                 path_builder.append('/'); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-12-16 17:49:34 +03:30
										 |  |  |             auto test_base_path = path_builder.to_byte_string(); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |             path_builder.append(suite.attributes.find("URI")->value); | 
					
						
							|  |  |  |             auto url = URL::create_with_file_scheme(path_builder.string_view()); | 
					
						
							|  |  |  |             if (!url.is_valid()) { | 
					
						
							|  |  |  |                 warnln("Invalid URL {}", path_builder.string_view()); | 
					
						
							|  |  |  |                 s_test_results.set(path_builder.string_view(), TestResult::RunnerFailed); | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |             auto file_path = url.serialize_path(); | 
					
						
							|  |  |  |             auto file_result = Core::File::open(file_path, Core::File::OpenMode::Read); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             if (file_result.is_error()) { | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                 warnln("Read error for {}: {}", file_path, file_result.error()); | 
					
						
							|  |  |  |                 s_test_results.set(file_path, TestResult::RunnerFailed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |             warnln("Running test {}", file_path); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-11 17:49:00 +01:00
										 |  |  |             auto contents = file_result.value()->read_until_eof(); | 
					
						
							| 
									
										
										
										
											2022-09-14 17:46:00 +01:00
										 |  |  |             if (contents.is_error()) { | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                 warnln("Read error for {}: {}", file_path, contents.error()); | 
					
						
							|  |  |  |                 s_test_results.set(file_path, TestResult::RunnerFailed); | 
					
						
							| 
									
										
										
										
											2022-09-14 17:46:00 +01:00
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             auto parser = parse(contents.value()); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             auto doc_or_error = parser.parse(); | 
					
						
							|  |  |  |             if (doc_or_error.is_error()) { | 
					
						
							|  |  |  |                 if (type == "invalid" || type == "error" || type == "not-wf") | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::Passed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 else | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::Failed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auto out = suite.attributes.find("OUTPUT"); | 
					
						
							|  |  |  |             if (out != suite.attributes.end()) { | 
					
						
							|  |  |  |                 auto out_path = LexicalPath::join(test_base_path, out->value).string(); | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |                 auto file_result = Core::File::open(out_path, Core::File::OpenMode::Read); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 if (file_result.is_error()) { | 
					
						
							|  |  |  |                     warnln("Read error for {}: {}", out_path, file_result.error()); | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::RunnerFailed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-12-11 17:49:00 +01:00
										 |  |  |                 auto contents = file_result.value()->read_until_eof(); | 
					
						
							| 
									
										
										
										
											2022-09-14 17:46:00 +01:00
										 |  |  |                 if (contents.is_error()) { | 
					
						
							|  |  |  |                     warnln("Read error for {}: {}", out_path, contents.error()); | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::RunnerFailed); | 
					
						
							| 
									
										
										
										
											2022-09-14 17:46:00 +01:00
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 auto parser = parse(contents.value()); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                 auto out_doc_or_error = parser.parse(); | 
					
						
							|  |  |  |                 if (out_doc_or_error.is_error()) { | 
					
						
							|  |  |  |                     warnln("Parse error for {}: {}", out_path, out_doc_or_error.error()); | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::RunnerFailed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 auto out_doc = out_doc_or_error.release_value(); | 
					
						
							|  |  |  |                 if (out_doc.root() != doc_or_error.value().root()) { | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                     s_test_results.set(file_path, TestResult::Failed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (type == "invalid" || type == "error" || type == "not-wf") | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                 s_test_results.set(file_path, TestResult::Failed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |             else | 
					
						
							| 
									
										
										
										
											2023-04-14 20:12:03 +01:00
										 |  |  |                 s_test_results.set(file_path, TestResult::Passed); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ErrorOr<int> serenity_main(Main::Arguments arguments) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     StringView filename; | 
					
						
							|  |  |  |     bool run_tests { false }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Core::ArgsParser parser; | 
					
						
							|  |  |  |     parser.set_general_help("Parse and dump XML files"); | 
					
						
							|  |  |  |     parser.add_option(g_color, "Syntax highlight the output", "color", 'c'); | 
					
						
							|  |  |  |     parser.add_option(g_only_contents, "Only display markup and text", "only-contents", 'o'); | 
					
						
							|  |  |  |     parser.add_option(run_tests, "Run tests", "run-tests", 't'); | 
					
						
							|  |  |  |     parser.add_positional_argument(filename, "File to read from", "file"); | 
					
						
							|  |  |  |     parser.parse(arguments); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-20 10:43:37 +02:00
										 |  |  |     s_path = TRY(FileSystem::real_path(filename)); | 
					
						
							| 
									
										
										
										
											2023-02-09 03:02:46 +01:00
										 |  |  |     auto file = TRY(Core::File::open(s_path, Core::File::OpenMode::Read)); | 
					
						
							| 
									
										
										
										
											2022-12-11 17:49:00 +01:00
										 |  |  |     auto contents = TRY(file->read_until_eof()); | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     auto xml_parser = parse(contents); | 
					
						
							|  |  |  |     auto result = xml_parser.parse(); | 
					
						
							|  |  |  |     if (result.is_error()) { | 
					
						
							| 
									
										
										
										
											2022-05-08 12:57:36 +04:30
										 |  |  |         if (xml_parser.parse_error_causes().is_empty()) { | 
					
						
							|  |  |  |             warnln("{}", result.error()); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             warnln("{}; caused by:", result.error()); | 
					
						
							|  |  |  |             for (auto const& cause : xml_parser.parse_error_causes()) | 
					
						
							|  |  |  |                 warnln("    {}", cause); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return 1; | 
					
						
							| 
									
										
										
										
											2022-03-26 21:32:57 +04:30
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto doc = result.release_value(); | 
					
						
							|  |  |  |     if (run_tests) { | 
					
						
							|  |  |  |         do_run_tests(doc); | 
					
						
							|  |  |  |         size_t passed = 0; | 
					
						
							|  |  |  |         size_t failed = 0; | 
					
						
							|  |  |  |         size_t runner_error = 0; | 
					
						
							|  |  |  |         size_t total = 0; | 
					
						
							|  |  |  |         for (auto& entry : s_test_results) { | 
					
						
							|  |  |  |             total++; | 
					
						
							|  |  |  |             switch (entry.value) { | 
					
						
							|  |  |  |             case TestResult::Passed: | 
					
						
							|  |  |  |                 passed++; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case TestResult::Failed: | 
					
						
							|  |  |  |                 failed++; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             case TestResult::RunnerFailed: | 
					
						
							|  |  |  |                 runner_error++; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         outln("{} passed, {} failed, {} runner failed of {} tests run.", passed, failed, runner_error, total); | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dump(doc); | 
					
						
							|  |  |  |     if (!g_only_contents) | 
					
						
							|  |  |  |         outln(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } |