XmlValidatorService.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Xml.Schema;
  4. using System.Xml;
  5. using System;
  6. using Microsoft.Extensions.Configuration;
  7. using Npgsql;
  8. namespace LAPS_XMLQC_Service.Services
  9. {
  10. public class XmlValidatorService
  11. {
  12. private readonly string _connectionString;
  13. public XmlValidatorService(IConfiguration configuration)
  14. {
  15. _connectionString = configuration.GetConnectionString("DbConnection");
  16. }
  17. public string GetDocumentPath(string documentType, int projectDefinitionId)
  18. {
  19. string filePath = string.Empty;
  20. using (var connection = new NpgsqlConnection(_connectionString))
  21. {
  22. connection.Open();
  23. using (var command = new NpgsqlCommand("SELECT documentpath FROM tblprojectdocuments WHERE documenttype = @documentType AND projectdefinitionid = @projectDefinitionId", connection))
  24. {
  25. command.Parameters.AddWithValue("@documentType", documentType);
  26. command.Parameters.AddWithValue("@projectDefinitionId", projectDefinitionId);
  27. filePath = Convert.ToString(command.ExecuteScalar());
  28. }
  29. }
  30. return filePath;
  31. }
  32. /// <summary>
  33. /// Validates XML against a DTD file provided via file path.
  34. /// </summary>
  35. /// <param name="xmlContent">The XML content as a string.</param>
  36. /// <param name="dtdFilePath">The file path to the DTD.</param>
  37. /// <returns>A list of validation errors (empty if valid).</returns>
  38. /// <summary>
  39. /// Validates XML content against a specified DTD file.
  40. /// </summary>
  41. /// <param name="xmlContent">The XML content as a string.</param>
  42. /// <param name="dtdFilePath">The path to the DTD file.</param>
  43. /// <returns>A list of validation errors or a success message if valid.</returns>
  44. public List<string> ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath)
  45. {
  46. var validationErrors = new List<string>();
  47. try
  48. {
  49. // Step 1: Check if the XML is well-formed
  50. //validationErrors = ValidateXmlAndDetectUnclosedTags(xmlContent);
  51. //if (validationErrors.Count > 0)
  52. //{
  53. // return validationErrors; // Stop further processing if not well-formed
  54. //}
  55. // Step 2: Inject the DTD file path if not already present
  56. string xmlWithDtd = InjectDtdPath(xmlContent, dtdFilePath);
  57. // Step 3: Set up DTD validation settings
  58. var settings = new XmlReaderSettings
  59. {
  60. DtdProcessing = DtdProcessing.Parse,
  61. ValidationType = ValidationType.DTD,
  62. XmlResolver = new XmlUrlResolver()
  63. };
  64. // Attach a validation event handler
  65. settings.ValidationEventHandler += (sender, e) =>
  66. {
  67. validationErrors.Add($"DTD validation error: {e.Message}");
  68. };
  69. // Step 4: Validate the XML against the DTD
  70. using (var stringReader = new StringReader(xmlWithDtd))
  71. using (var xmlReader = XmlReader.Create(stringReader, settings))
  72. {
  73. while (xmlReader.Read()) { } // Triggers DTD validation
  74. }
  75. // If no errors, indicate successful validation
  76. if (validationErrors.Count == 0)
  77. {
  78. validationErrors.Add("XML is valid against the provided DTD.");
  79. }
  80. }
  81. catch (XmlException ex)
  82. {
  83. validationErrors.Add($"XML parsing error: {ex.Message}");
  84. }
  85. catch (Exception ex)
  86. {
  87. validationErrors.Add($"Unexpected error: {ex.Message}");
  88. }
  89. return validationErrors;
  90. }
  91. //public List<string> FindUnclosedTags(string xmlString)
  92. //{
  93. // var unclosedTags = new List<string>();
  94. // var stack = new Stack<string>();
  95. // // Load the XML document
  96. // XmlDocument xmlDoc = new XmlDocument();
  97. // xmlDoc.LoadXml(xmlString);
  98. // // Traverse all elements in the XML document
  99. // XmlNodeList elements = xmlDoc.GetElementsByTagName("*");
  100. // foreach (XmlNode element in elements)
  101. // {
  102. // // Check for opening tags (elements with children)
  103. // if (element.NodeType == XmlNodeType.Element)
  104. // {
  105. // // If the element is not self-closing, push it onto the stack
  106. // if (element.HasChildNodes || !IsSelfClosingTag(element))
  107. // {
  108. // stack.Push(element.Name);
  109. // }
  110. // else
  111. // {
  112. // // If it's a self-closing tag, it doesn't need to be tracked
  113. // // But you could add logic to handle these if needed
  114. // }
  115. // }
  116. // else if (element.NodeType == XmlNodeType.EndElement)
  117. // {
  118. // // If it's a closing tag, check if it matches the last element in the stack
  119. // if (stack.Count > 0 && stack.Peek() == element.Name)
  120. // {
  121. // stack.Pop(); // Remove matched opening tag
  122. // }
  123. // else
  124. // {
  125. // // If there's no match, add the unclosed tag to the list
  126. // unclosedTags.Add(element.Name);
  127. // }
  128. // }
  129. // }
  130. // // If any opening tags are left in the stack, they are unclosed
  131. // while (stack.Count > 0)
  132. // {
  133. // unclosedTags.Add(stack.Pop());
  134. // }
  135. // return unclosedTags;
  136. //}
  137. //// Helper function to determine if an element is self-closing
  138. //private bool IsSelfClosingTag(XmlNode element)
  139. //{
  140. // // Example of simple self-closing tag detection, you may need to refine based on your XML format
  141. // return element.OuterXml.EndsWith("/>");
  142. //}
  143. //public static List<string> ParseXmlWithUnclosedTagDetection(string xmlContent)
  144. //{
  145. // var validationErrors = new List<string>();
  146. // var openElements = new Stack<string>(); // Stack to track open elements
  147. // var lineNumber = 0;
  148. // var linePosition = 0;
  149. // try
  150. // {
  151. // using (var reader = new XmlTextReader(xmlContent))
  152. // {
  153. // while (reader.Read())
  154. // {
  155. // lineNumber = reader.LineNumber;
  156. // linePosition = reader.LinePosition;
  157. // // Handle element start
  158. // if (reader.NodeType == XmlNodeType.Element)
  159. // {
  160. // openElements.Push(reader.Name);
  161. // }
  162. // // Handle element end
  163. // else if (reader.NodeType == XmlNodeType.EndElement)
  164. // {
  165. // // Check if the closing tag matches the most recent opening tag
  166. // if (openElements.Count == 0 || openElements.Peek() != reader.Name)
  167. // {
  168. // validationErrors.Add($"Unmatched closing tag </{reader.Name}> at line {lineNumber}, position {linePosition}.");
  169. // }
  170. // else
  171. // {
  172. // openElements.Pop(); // Matched closing tag, pop from stack
  173. // }
  174. // }
  175. // }
  176. // }
  177. // // After reading, check for any unclosed tags
  178. // while (openElements.Count > 0)
  179. // {
  180. // var unclosedElement = openElements.Pop();
  181. // validationErrors.Add($"Unclosed tag <{unclosedElement}> at line {lineNumber}, position {linePosition}.");
  182. // }
  183. // if (validationErrors.Count == 0)
  184. // {
  185. // validationErrors.Add("XML is well-formed.");
  186. // }
  187. // }
  188. // catch (XmlException ex)
  189. // {
  190. // validationErrors.Add($"XML parsing error: {ex.Message} at line {lineNumber}, position {linePosition}.");
  191. // }
  192. // catch (Exception ex)
  193. // {
  194. // validationErrors.Add($"Unexpected error: {ex.Message}");
  195. // }
  196. // return validationErrors;
  197. //}
  198. //public static List<string> ValidateXmlAndDetectUnclosedTags(string xmlContent)
  199. //{
  200. // var validationErrors = new List<string>();
  201. // var openElements = new Stack<(string ElementName, int LineNumber, int LinePosition)>(); // Stack to track open elements with line info
  202. // try
  203. // {
  204. // var settings = new XmlReaderSettings
  205. // {
  206. // DtdProcessing = DtdProcessing.Ignore, // Disable DTD processing
  207. // IgnoreComments = true, // Ignore comments
  208. // IgnoreWhitespace = true // Ignore insignificant whitespace
  209. // };
  210. // using (var stringReader = new StringReader(xmlContent))
  211. // using (var xmlReader = XmlReader.Create(stringReader, settings))
  212. // {
  213. // IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo;
  214. // while (xmlReader.Read())
  215. // {
  216. // // Track opening tags and check for closing tags
  217. // if (xmlReader.NodeType == XmlNodeType.Element)
  218. // {
  219. // // Push start element onto the stack
  220. // openElements.Push((xmlReader.Name, lineInfo?.LineNumber ?? -1, lineInfo?.LinePosition ?? -1));
  221. // }
  222. // else if (xmlReader.NodeType == XmlNodeType.EndElement)
  223. // {
  224. // if (openElements.Count == 0 || openElements.Peek().ElementName != xmlReader.Name)
  225. // {
  226. // // Report unmatched closing tag
  227. // validationErrors.Add($"Unmatched closing tag </{xmlReader.Name}> at line {lineInfo?.LineNumber}, position {lineInfo?.LinePosition}.");
  228. // }
  229. // else
  230. // {
  231. // // Matched closing tag, pop the stack
  232. // openElements.Pop();
  233. // }
  234. // }
  235. // }
  236. // }
  237. // // Report unclosed tags remaining in the stack
  238. // while (openElements.Count > 0)
  239. // {
  240. // var (ElementName, LineNumber, LinePosition) = openElements.Pop();
  241. // validationErrors.Add($"Unclosed tag <{ElementName}> at line {LineNumber}, position {LinePosition}.");
  242. // }
  243. // }
  244. // catch (XmlException ex)
  245. // {
  246. // validationErrors.Add($"XML parsing error: {ex.Message} at line {ex.LineNumber}, position {ex.LinePosition}.");
  247. // }
  248. // catch (Exception ex)
  249. // {
  250. // validationErrors.Add($"Unexpected error: {ex.Message}");
  251. // }
  252. // if (validationErrors.Count == 0)
  253. // {
  254. // validationErrors.Add("XML is well-formed.");
  255. // }
  256. // return validationErrors;
  257. //}
  258. //public List<string> ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath)
  259. //{
  260. // var validationErrors = new List<string>();
  261. // try
  262. // {
  263. // // Inject the DTD file path into the XML content
  264. // string xmlWithDtd = string.Empty;
  265. // if (!xmlContent.Contains("<!DOCTYPE"))
  266. // {
  267. // xmlWithDtd = InjectDtdPath(xmlContent, dtdFilePath);
  268. // }
  269. // else
  270. // {
  271. // xmlWithDtd = xmlContent;
  272. // }
  273. // var settings = new XmlReaderSettings
  274. // {
  275. // DtdProcessing = DtdProcessing.Parse, // Enable DTD parsing
  276. // ValidationType = ValidationType.DTD, // Set validation type to DTD
  277. // XmlResolver = new XmlUrlResolver() // Enable resolution of external DTD files
  278. // };
  279. // // Attach a validation event handler to capture errors
  280. // settings.ValidationEventHandler += (sender, e) =>
  281. // {
  282. // validationErrors.Add($"DTD validation error: {e.Message}");
  283. // };
  284. // // Create an XmlReader for the XML content with the DTD validation settings
  285. // using (var stringReader = new StringReader(xmlWithDtd))
  286. // using (var xmlReader = XmlReader.Create(stringReader, settings))
  287. // {
  288. // // Read the XML content (this triggers DTD validation)
  289. // while (xmlReader.Read()) { }
  290. // }
  291. // // If there are no errors, validation passed
  292. // if (validationErrors.Count == 0)
  293. // {
  294. // validationErrors.Add("XML is valid against the provided DTD.");
  295. // }
  296. // }
  297. // catch (XmlException ex)
  298. // {
  299. // validationErrors.Add($"XML is not well-formed: {ex.Message}");
  300. // }
  301. // catch (XmlSchemaValidationException ex)
  302. // {
  303. // validationErrors.Add($"DTD validation error: {ex.Message}");
  304. // }
  305. // return validationErrors;
  306. //}
  307. /// <summary>
  308. /// Validates XML against an XSD file provided via file path.
  309. /// </summary>
  310. /// <param name="xmlContent">The XML content as a string.</param>
  311. /// <param name="xsdFilePath">The file path to the XSD.</param>
  312. /// <returns>A list of validation errors (empty if valid).</returns>
  313. public List<string> ValidateXmlAgainstXsd(string xmlContent, string xsdFilePath)
  314. {
  315. var validationErrors = new List<string>();
  316. try
  317. {
  318. // Load the XSD into an XmlSchemaSet from the file path
  319. var schemas = new XmlSchemaSet();
  320. schemas.Add(null, xsdFilePath);
  321. // Create XmlReaderSettings and attach the validation event handler
  322. var settings = new XmlReaderSettings
  323. {
  324. ValidationType = ValidationType.Schema,
  325. Schemas = schemas
  326. };
  327. // ValidationEventHandler to capture validation errors
  328. settings.ValidationEventHandler += (sender, e) =>
  329. {
  330. validationErrors.Add($"XSD validation error: {e.Message}");
  331. };
  332. // Create XmlReader for XML validation
  333. using (var stringReader = new StringReader(xmlContent))
  334. using (var xmlReader = XmlReader.Create(stringReader, settings))
  335. {
  336. // Read the XML (this will trigger validation)
  337. while (xmlReader.Read()) { }
  338. }
  339. // If there are no errors, validation passed
  340. if (validationErrors.Count == 0)
  341. {
  342. validationErrors.Add("XML is valid against the provided XSD.");
  343. }
  344. }
  345. catch (XmlException ex)
  346. {
  347. validationErrors.Add($"XML is not well-formed: {ex.Message}");
  348. }
  349. return validationErrors;
  350. }
  351. //public List<string> ValidateXmlStructure(string xmlContent)
  352. //{
  353. // var errors = new List<string>();
  354. // try
  355. // {
  356. // var settings = new XmlReaderSettings
  357. // {
  358. // ConformanceLevel = ConformanceLevel.Document, // Ensures XML is well-formed
  359. // DtdProcessing = DtdProcessing.Prohibit // Disables DTD processing for security
  360. // };
  361. // // Adding a handler for validation events
  362. // settings.ValidationEventHandler += (sender, e) =>
  363. // {
  364. // errors.Add($"Validation Error: {e.Message} at Line {e.Exception.LineNumber}, Position {e.Exception.LinePosition}");
  365. // };
  366. // // Using XmlReader to read the XML content
  367. // using var reader = XmlReader.Create(new StringReader(xmlContent), settings);
  368. // while (reader.Read()) { } // Reads through the XML document
  369. // }
  370. // catch (XmlException ex)
  371. // {
  372. // // Captures XML parsing errors like unclosed tags
  373. // errors.Add($"XML Parsing Error: {ex.Message} at Line {ex.LineNumber}, Position {ex.LinePosition}");
  374. // }
  375. // catch (Exception ex)
  376. // {
  377. // // Captures other errors
  378. // errors.Add($"Unexpected Error: {ex.Message}");
  379. // }
  380. // return errors; // Return all collected validation errors
  381. //}
  382. /// <summary>
  383. /// Injects a DOCTYPE declaration with a SYSTEM DTD reference into the XML content.
  384. /// </summary>
  385. /// <param name="xmlContent">The XML content as a string.</param>
  386. /// <param name="dtdFilePath">The path to the DTD file.</param>
  387. /// <returns>The modified XML content with the DOCTYPE declaration.</returns>
  388. private string InjectDtdPath(string xmlContent, string dtdFilePath, string publicId = null)
  389. {
  390. // Check if the DOCTYPE declaration is already present
  391. if (!xmlContent.Contains("<!DOCTYPE"))
  392. {
  393. // Skip over the XML declaration if it exists
  394. int xmlDeclarationEnd = xmlContent.IndexOf("?>");
  395. int searchStartIndex = (xmlDeclarationEnd > -1) ? xmlDeclarationEnd + 2 : 0;
  396. // Locate the root element
  397. int rootStartIndex = xmlContent.IndexOf('<', searchStartIndex);
  398. if (rootStartIndex == -1)
  399. {
  400. throw new InvalidOperationException("Invalid XML content: No opening tag found.");
  401. }
  402. int rootEndIndex = xmlContent.IndexOf('>', rootStartIndex);
  403. if (rootEndIndex == -1)
  404. {
  405. throw new InvalidOperationException("Invalid XML content: No closing tag for the root element.");
  406. }
  407. // Extract the root element name (e.g., "catalog")
  408. string rootElement = xmlContent.Substring(rootStartIndex + 1, rootEndIndex - rootStartIndex - 1).Split(' ')[0];
  409. // Construct the DOCTYPE declaration
  410. string doctypeDeclaration = string.IsNullOrEmpty(publicId)
  411. ? $"<!DOCTYPE {rootElement} SYSTEM \"{dtdFilePath}\">"
  412. : $"<!DOCTYPE {rootElement} PUBLIC \"{publicId}\" \"{dtdFilePath}\">";
  413. // Insert the DOCTYPE declaration after the XML declaration (if present)
  414. if (xmlDeclarationEnd > -1) // XML declaration exists
  415. {
  416. return xmlContent.Insert(xmlDeclarationEnd + 2, "\n" + doctypeDeclaration);
  417. }
  418. // Otherwise, prepend the DOCTYPE declaration to the XML content
  419. return doctypeDeclaration + "\n" + xmlContent;
  420. }
  421. // If DOCTYPE is already present, return the content unchanged
  422. return xmlContent;
  423. }
  424. /// <summary>
  425. /// Helper method to inject the DTD file path into the XML's DOCTYPE declaration.
  426. /// </summary>
  427. /// <param name="xmlContent">The XML content as a string.</param>
  428. /// <param name="dtdFilePath">The file path to the DTD.</param>
  429. /// <returns>The XML content with the DOCTYPE declaration added.</returns>
  430. //private string InjectDtdPath1(string xmlContent, string dtdFilePath)
  431. //{
  432. // const string doctypeDeclaration = "<!DOCTYPE catalog SYSTEM \"{0}\">";
  433. // if (xmlContent.Contains("<?xml"))
  434. // {
  435. // string formattedDoctype = string.Format(doctypeDeclaration, dtdFilePath);
  436. // // Find the position of the first newline character after the XML declaration
  437. // int firstNewlineIndex = xmlContent.IndexOf('\n');
  438. // if (firstNewlineIndex != -1)
  439. // {
  440. // // Insert the DOCTYPE declaration after the XML declaration
  441. // return xmlContent.Insert(firstNewlineIndex + 1, formattedDoctype + "\n");
  442. // }
  443. // else
  444. // {
  445. // // If no newline is found, assume it's a single line XML and append after the XML declaration
  446. // return xmlContent + "\n" + formattedDoctype;
  447. // }
  448. // }
  449. // else
  450. // {
  451. // return $"{string.Format(doctypeDeclaration, dtdFilePath)}\n{xmlContent}";
  452. // }
  453. //}
  454. //public List<string> ValidateXml(string xmlContent, string schemaContent, string schemaType)
  455. //{
  456. // var errors = new List<string>();
  457. // try
  458. // {
  459. // string xmlWithDtd = $"<!DOCTYPE bookstore SYSTEM \"bookstore.dtd\">\n{xmlContent}";
  460. // XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None);
  461. // XmlReaderSettings settings = new XmlReaderSettings
  462. // {
  463. // // ProhibitDtd = false,
  464. // DtdProcessing = DtdProcessing.Prohibit,
  465. // ValidationType = ValidationType.DTD
  466. // };
  467. // settings.ValidationEventHandler += (sender, args) =>
  468. // {
  469. // errors.Add(args.Message);
  470. // };
  471. // using (var reader = XmlReader.Create(new StringReader(xmlWithDtd), settings, context))
  472. // {
  473. // while (reader.Read()) { }
  474. // }
  475. // }
  476. // catch (XmlException ex)
  477. // {
  478. // errors.Add($"XML Exception: {ex.Message}");
  479. // }
  480. // catch (Exception ex)
  481. // {
  482. // errors.Add($"General Error: {ex.Message}");
  483. // }
  484. // return errors;
  485. //}
  486. }
  487. }