public static bool Validate(XDocument xDoc, XmlSchemaSet schemaSet, TextWriter output) { var hasError = false; var rules = RuleParser.GetRules(schemaSet, xDoc.Root.GetDefaultNamespace().NamespaceName); xDoc.Validate(schemaSet, OnError, true); void OnError(object sender, ValidationEventArgs args) { hasError = true; output.WriteLine("Error"); output.WriteLine($"- Message: {args.Exception.Message}"); if (sender is XObject xo) { output.WriteLine($"- Position: {XPath(xo)}"); } else { output.WriteLine("- Position: (Unknown)"); } output.WriteLine(); } return(hasError); string XPath(XObject xo) { var sb = new StringBuilder(); var current = xo; while (!(current is null)) { if (current is XElement xe) { if (rules.Contains(xe.Name.LocalName)) { var selector = new StringBuilder(); var pks = rules[xe.Name.LocalName].Where(r => r.IsPrimaryKey).ToList(); if (pks.Any()) { selector.Append('['); foreach (var key in pks) { try { var value = xe.Attribute(key.PropertyName); if (value is null) { continue; } if (selector.Length > 1) { selector.Append(" and "); } selector.Append(value); } catch { } } selector.Append(']'); if (selector.Length > 2) { sb.Insert(0, selector); } } } sb.Insert(0, $"/{xe.Name.LocalName}"); } else if (current is XAttribute xa) { sb.Insert(0, $"/@{xa.Name.LocalName}"); } current = current.Parent; } return(sb.ToString()); } }