// Copyright © WireMock.Net using System; using System.Collections.Generic; using System.Linq; using System.Xml; using System.Xml.XPath; using AnyOfTypes; using WireMock.Extensions; using WireMock.Models; using Stef.Validation; using WireMock.Admin.Mappings; using WireMock.Util; //#if !NETSTANDARD1_3 using Wmhelp.XPath2; //#endif namespace WireMock.Matchers; /// /// XPath2Matcher /// /// public class XPathMatcher : IStringMatcher { private readonly AnyOf[] _patterns; /// public MatchBehaviour MatchBehaviour { get; } /// /// Array of namespace prefix and uri. /// public XmlNamespace[]? XmlNamespaceMap { get; private set; } /// /// Initializes a new instance of the class. /// /// The patterns. public XPathMatcher(params AnyOf[] patterns) : this(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, null, patterns) { } /// /// Initializes a new instance of the class. /// /// The match behaviour. /// The to use. (default = "Or") /// The xml namespaces of the xml document. /// The patterns. public XPathMatcher( MatchBehaviour matchBehaviour, MatchOperator matchOperator = MatchOperator.Or, XmlNamespace[]? xmlNamespaceMap = null, params AnyOf[] patterns) { _patterns = Guard.NotNull(patterns); XmlNamespaceMap = xmlNamespaceMap; MatchBehaviour = matchBehaviour; MatchOperator = matchOperator; } /// public MatchResult IsMatch(string? input) { var score = MatchScores.Mismatch; if (input == null) { return CreateMatchResult(score); } try { var xPathEvaluator = new XPathEvaluator(); xPathEvaluator.Load(input); if (!xPathEvaluator.IsXmlDocumentLoaded) { return CreateMatchResult(score); } score = MatchScores.ToScore(xPathEvaluator.Evaluate(_patterns, XmlNamespaceMap), MatchOperator); } catch (Exception exception) { return CreateMatchResult(score, exception); } return CreateMatchResult(score); } /// public AnyOf[] GetPatterns() { return _patterns; } /// public MatchOperator MatchOperator { get; } /// public string Name => nameof(XPathMatcher); /// public string GetCSharpCodeArguments() { return $"new {Name}" + $"(" + $"{MatchBehaviour.GetFullyQualifiedEnumValue()}, " + $"{MatchOperator.GetFullyQualifiedEnumValue()}, " + $"null, " + $"{MappingConverterUtils.ToCSharpCodeArguments(_patterns)}" + $")"; } private MatchResult CreateMatchResult(double score, Exception? exception = null) { return new MatchResult(MatchBehaviourHelper.Convert(MatchBehaviour, score), exception); } private sealed class XPathEvaluator { private XmlDocument? _xmlDocument; private XPathNavigator? _xpathNavigator; public bool IsXmlDocumentLoaded => _xmlDocument != null; public void Load(string input) { try { _xmlDocument = new XmlDocument { InnerXml = input }; _xpathNavigator = _xmlDocument.CreateNavigator(); } catch { _xmlDocument = default; } } public bool[] Evaluate(AnyOf[] patterns, IEnumerable? xmlNamespaceMap) { return _xpathNavigator == null ? [] : patterns.Select(pattern => true.Equals(Evaluate(_xpathNavigator, pattern, xmlNamespaceMap))).ToArray(); } private object Evaluate(XPathNavigator navigator, AnyOf pattern, IEnumerable? xmlNamespaceMap) { var xpath = $"boolean({pattern.GetPattern()})"; var xmlNamespaceManager = GetXmlNamespaceManager(xmlNamespaceMap); if (xmlNamespaceManager == null) { //#if NETSTANDARD1_3 //return navigator.Evaluate(xpath); //#else return navigator.XPath2Evaluate(xpath); //#endif } //#if NETSTANDARD1_3 //return navigator.Evaluate(xpath, xmlNamespaceManager); //#else return navigator.XPath2Evaluate(xpath, xmlNamespaceManager); //#endif } private XmlNamespaceManager? GetXmlNamespaceManager(IEnumerable? xmlNamespaceMap) { if (_xpathNavigator == null || xmlNamespaceMap == null) { return default; } var nsManager = new XmlNamespaceManager(_xpathNavigator.NameTable); foreach (var xmlNamespace in xmlNamespaceMap) { nsManager.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri); } return nsManager; } } }