using System.Collections.Generic; using System.IO; using System.Xml.Schema; using System.Xml; using System; using Microsoft.Extensions.Configuration; using Npgsql; namespace LAPS_XMLQC_Service.Services { public class XmlValidatorService { private readonly string _connectionString; public XmlValidatorService(IConfiguration configuration) { _connectionString = configuration.GetConnectionString("DbConnection"); } public string GetDocumentPath(string documentType, int projectDefinitionId) { string filePath = string.Empty; using (var connection = new NpgsqlConnection(_connectionString)) { connection.Open(); using (var command = new NpgsqlCommand("SELECT documentpath FROM tblprojectdocuments WHERE documenttype = @documentType AND projectdefinitionid = @projectDefinitionId", connection)) { command.Parameters.AddWithValue("@documentType", documentType); command.Parameters.AddWithValue("@projectDefinitionId", projectDefinitionId); filePath = Convert.ToString(command.ExecuteScalar()); } } return filePath; } /// /// Validates XML against a DTD file provided via file path. /// /// The XML content as a string. /// The file path to the DTD. /// A list of validation errors (empty if valid). /// /// Validates XML content against a specified DTD file. /// /// The XML content as a string. /// The path to the DTD file. /// A list of validation errors or a success message if valid. public List ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath) { var validationErrors = new List(); try { // Step 1: Check if the XML is well-formed //validationErrors = ValidateXmlAndDetectUnclosedTags(xmlContent); //if (validationErrors.Count > 0) //{ // return validationErrors; // Stop further processing if not well-formed //} // Step 2: Inject the DTD file path if not already present string xmlWithDtd = InjectDtdPath(xmlContent, dtdFilePath); // Step 3: Set up DTD validation settings var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse, ValidationType = ValidationType.DTD, XmlResolver = new XmlUrlResolver() }; // Attach a validation event handler settings.ValidationEventHandler += (sender, e) => { validationErrors.Add($"DTD validation error: {e.Message}"); }; // Step 4: Validate the XML against the DTD using (var stringReader = new StringReader(xmlWithDtd)) using (var xmlReader = XmlReader.Create(stringReader, settings)) { while (xmlReader.Read()) { } // Triggers DTD validation } // If no errors, indicate successful validation if (validationErrors.Count == 0) { validationErrors.Add("XML is valid against the provided DTD."); } } catch (XmlException ex) { validationErrors.Add($"XML parsing error: {ex.Message}"); } catch (Exception ex) { validationErrors.Add($"Unexpected error: {ex.Message}"); } return validationErrors; } //public List FindUnclosedTags(string xmlString) //{ // var unclosedTags = new List(); // var stack = new Stack(); // // Load the XML document // XmlDocument xmlDoc = new XmlDocument(); // xmlDoc.LoadXml(xmlString); // // Traverse all elements in the XML document // XmlNodeList elements = xmlDoc.GetElementsByTagName("*"); // foreach (XmlNode element in elements) // { // // Check for opening tags (elements with children) // if (element.NodeType == XmlNodeType.Element) // { // // If the element is not self-closing, push it onto the stack // if (element.HasChildNodes || !IsSelfClosingTag(element)) // { // stack.Push(element.Name); // } // else // { // // If it's a self-closing tag, it doesn't need to be tracked // // But you could add logic to handle these if needed // } // } // else if (element.NodeType == XmlNodeType.EndElement) // { // // If it's a closing tag, check if it matches the last element in the stack // if (stack.Count > 0 && stack.Peek() == element.Name) // { // stack.Pop(); // Remove matched opening tag // } // else // { // // If there's no match, add the unclosed tag to the list // unclosedTags.Add(element.Name); // } // } // } // // If any opening tags are left in the stack, they are unclosed // while (stack.Count > 0) // { // unclosedTags.Add(stack.Pop()); // } // return unclosedTags; //} //// Helper function to determine if an element is self-closing //private bool IsSelfClosingTag(XmlNode element) //{ // // Example of simple self-closing tag detection, you may need to refine based on your XML format // return element.OuterXml.EndsWith("/>"); //} //public static List ParseXmlWithUnclosedTagDetection(string xmlContent) //{ // var validationErrors = new List(); // var openElements = new Stack(); // Stack to track open elements // var lineNumber = 0; // var linePosition = 0; // try // { // using (var reader = new XmlTextReader(xmlContent)) // { // while (reader.Read()) // { // lineNumber = reader.LineNumber; // linePosition = reader.LinePosition; // // Handle element start // if (reader.NodeType == XmlNodeType.Element) // { // openElements.Push(reader.Name); // } // // Handle element end // else if (reader.NodeType == XmlNodeType.EndElement) // { // // Check if the closing tag matches the most recent opening tag // if (openElements.Count == 0 || openElements.Peek() != reader.Name) // { // validationErrors.Add($"Unmatched closing tag at line {lineNumber}, position {linePosition}."); // } // else // { // openElements.Pop(); // Matched closing tag, pop from stack // } // } // } // } // // After reading, check for any unclosed tags // while (openElements.Count > 0) // { // var unclosedElement = openElements.Pop(); // validationErrors.Add($"Unclosed tag <{unclosedElement}> at line {lineNumber}, position {linePosition}."); // } // if (validationErrors.Count == 0) // { // validationErrors.Add("XML is well-formed."); // } // } // catch (XmlException ex) // { // validationErrors.Add($"XML parsing error: {ex.Message} at line {lineNumber}, position {linePosition}."); // } // catch (Exception ex) // { // validationErrors.Add($"Unexpected error: {ex.Message}"); // } // return validationErrors; //} //public static List ValidateXmlAndDetectUnclosedTags(string xmlContent) //{ // var validationErrors = new List(); // var openElements = new Stack<(string ElementName, int LineNumber, int LinePosition)>(); // Stack to track open elements with line info // try // { // var settings = new XmlReaderSettings // { // DtdProcessing = DtdProcessing.Ignore, // Disable DTD processing // IgnoreComments = true, // Ignore comments // IgnoreWhitespace = true // Ignore insignificant whitespace // }; // using (var stringReader = new StringReader(xmlContent)) // using (var xmlReader = XmlReader.Create(stringReader, settings)) // { // IXmlLineInfo lineInfo = xmlReader as IXmlLineInfo; // while (xmlReader.Read()) // { // // Track opening tags and check for closing tags // if (xmlReader.NodeType == XmlNodeType.Element) // { // // Push start element onto the stack // openElements.Push((xmlReader.Name, lineInfo?.LineNumber ?? -1, lineInfo?.LinePosition ?? -1)); // } // else if (xmlReader.NodeType == XmlNodeType.EndElement) // { // if (openElements.Count == 0 || openElements.Peek().ElementName != xmlReader.Name) // { // // Report unmatched closing tag // validationErrors.Add($"Unmatched closing tag at line {lineInfo?.LineNumber}, position {lineInfo?.LinePosition}."); // } // else // { // // Matched closing tag, pop the stack // openElements.Pop(); // } // } // } // } // // Report unclosed tags remaining in the stack // while (openElements.Count > 0) // { // var (ElementName, LineNumber, LinePosition) = openElements.Pop(); // validationErrors.Add($"Unclosed tag <{ElementName}> at line {LineNumber}, position {LinePosition}."); // } // } // catch (XmlException ex) // { // validationErrors.Add($"XML parsing error: {ex.Message} at line {ex.LineNumber}, position {ex.LinePosition}."); // } // catch (Exception ex) // { // validationErrors.Add($"Unexpected error: {ex.Message}"); // } // if (validationErrors.Count == 0) // { // validationErrors.Add("XML is well-formed."); // } // return validationErrors; //} //public List ValidateXmlAgainstDtd(string xmlContent, string dtdFilePath) //{ // var validationErrors = new List(); // try // { // // Inject the DTD file path into the XML content // string xmlWithDtd = string.Empty; // if (!xmlContent.Contains(" // { // validationErrors.Add($"DTD validation error: {e.Message}"); // }; // // Create an XmlReader for the XML content with the DTD validation settings // using (var stringReader = new StringReader(xmlWithDtd)) // using (var xmlReader = XmlReader.Create(stringReader, settings)) // { // // Read the XML content (this triggers DTD validation) // while (xmlReader.Read()) { } // } // // If there are no errors, validation passed // if (validationErrors.Count == 0) // { // validationErrors.Add("XML is valid against the provided DTD."); // } // } // catch (XmlException ex) // { // validationErrors.Add($"XML is not well-formed: {ex.Message}"); // } // catch (XmlSchemaValidationException ex) // { // validationErrors.Add($"DTD validation error: {ex.Message}"); // } // return validationErrors; //} /// /// Validates XML against an XSD file provided via file path. /// /// The XML content as a string. /// The file path to the XSD. /// A list of validation errors (empty if valid). public List ValidateXmlAgainstXsd(string xmlContent, string xsdFilePath) { var validationErrors = new List(); try { // Load the XSD into an XmlSchemaSet from the file path var schemas = new XmlSchemaSet(); schemas.Add(null, xsdFilePath); // Create XmlReaderSettings and attach the validation event handler var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema, Schemas = schemas }; // ValidationEventHandler to capture validation errors settings.ValidationEventHandler += (sender, e) => { validationErrors.Add($"XSD validation error: {e.Message}"); }; // Create XmlReader for XML validation using (var stringReader = new StringReader(xmlContent)) using (var xmlReader = XmlReader.Create(stringReader, settings)) { // Read the XML (this will trigger validation) while (xmlReader.Read()) { } } // If there are no errors, validation passed if (validationErrors.Count == 0) { validationErrors.Add("XML is valid against the provided XSD."); } } catch (XmlException ex) { validationErrors.Add($"XML is not well-formed: {ex.Message}"); } return validationErrors; } //public List ValidateXmlStructure(string xmlContent) //{ // var errors = new List(); // try // { // var settings = new XmlReaderSettings // { // ConformanceLevel = ConformanceLevel.Document, // Ensures XML is well-formed // DtdProcessing = DtdProcessing.Prohibit // Disables DTD processing for security // }; // // Adding a handler for validation events // settings.ValidationEventHandler += (sender, e) => // { // errors.Add($"Validation Error: {e.Message} at Line {e.Exception.LineNumber}, Position {e.Exception.LinePosition}"); // }; // // Using XmlReader to read the XML content // using var reader = XmlReader.Create(new StringReader(xmlContent), settings); // while (reader.Read()) { } // Reads through the XML document // } // catch (XmlException ex) // { // // Captures XML parsing errors like unclosed tags // errors.Add($"XML Parsing Error: {ex.Message} at Line {ex.LineNumber}, Position {ex.LinePosition}"); // } // catch (Exception ex) // { // // Captures other errors // errors.Add($"Unexpected Error: {ex.Message}"); // } // return errors; // Return all collected validation errors //} /// /// Injects a DOCTYPE declaration with a SYSTEM DTD reference into the XML content. /// /// The XML content as a string. /// The path to the DTD file. /// The modified XML content with the DOCTYPE declaration. private string InjectDtdPath(string xmlContent, string dtdFilePath, string publicId = null) { // Check if the DOCTYPE declaration is already present if (!xmlContent.Contains(""); int searchStartIndex = (xmlDeclarationEnd > -1) ? xmlDeclarationEnd + 2 : 0; // Locate the root element int rootStartIndex = xmlContent.IndexOf('<', searchStartIndex); if (rootStartIndex == -1) { throw new InvalidOperationException("Invalid XML content: No opening tag found."); } int rootEndIndex = xmlContent.IndexOf('>', rootStartIndex); if (rootEndIndex == -1) { throw new InvalidOperationException("Invalid XML content: No closing tag for the root element."); } // Extract the root element name (e.g., "catalog") string rootElement = xmlContent.Substring(rootStartIndex + 1, rootEndIndex - rootStartIndex - 1).Split(' ')[0]; // Construct the DOCTYPE declaration string doctypeDeclaration = string.IsNullOrEmpty(publicId) ? $"" : $""; // Insert the DOCTYPE declaration after the XML declaration (if present) if (xmlDeclarationEnd > -1) // XML declaration exists { return xmlContent.Insert(xmlDeclarationEnd + 2, "\n" + doctypeDeclaration); } // Otherwise, prepend the DOCTYPE declaration to the XML content return doctypeDeclaration + "\n" + xmlContent; } // If DOCTYPE is already present, return the content unchanged return xmlContent; } /// /// Helper method to inject the DTD file path into the XML's DOCTYPE declaration. /// /// The XML content as a string. /// The file path to the DTD. /// The XML content with the DOCTYPE declaration added. //private string InjectDtdPath1(string xmlContent, string dtdFilePath) //{ // const string doctypeDeclaration = ""; // if (xmlContent.Contains(" ValidateXml(string xmlContent, string schemaContent, string schemaType) //{ // var errors = new List(); // try // { // string xmlWithDtd = $"\n{xmlContent}"; // XmlParserContext context = new XmlParserContext(null, null, null, XmlSpace.None); // XmlReaderSettings settings = new XmlReaderSettings // { // // ProhibitDtd = false, // DtdProcessing = DtdProcessing.Prohibit, // ValidationType = ValidationType.DTD // }; // settings.ValidationEventHandler += (sender, args) => // { // errors.Add(args.Message); // }; // using (var reader = XmlReader.Create(new StringReader(xmlWithDtd), settings, context)) // { // while (reader.Read()) { } // } // } // catch (XmlException ex) // { // errors.Add($"XML Exception: {ex.Message}"); // } // catch (Exception ex) // { // errors.Add($"General Error: {ex.Message}"); // } // return errors; //} } }