diff --git a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
index e7f63af5..2bb64cc6 100644
--- a/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
+++ b/src/WireMock.Net/Transformers/HandleBarsHelpers.cs
@@ -17,6 +17,8 @@ namespace WireMock.Transformers
HandleBarsXeger.Register(handlebarsContext);
+ HandleBarsXPath.Register(handlebarsContext);
+
HandleBarsFile.Register(handlebarsContext, fileSystemHandler);
}
}
diff --git a/src/WireMock.Net/Transformers/HandleBarsXPath.cs b/src/WireMock.Net/Transformers/HandleBarsXPath.cs
new file mode 100644
index 00000000..6118e70a
--- /dev/null
+++ b/src/WireMock.Net/Transformers/HandleBarsXPath.cs
@@ -0,0 +1,102 @@
+using HandlebarsDotNet;
+using System;
+using System.Text;
+using System.Xml;
+using System.Xml.XPath;
+using WireMock.Validation;
+#if !NETSTANDARD1_3
+using Wmhelp.XPath2;
+#endif
+
+namespace WireMock.Transformers
+{
+ internal static class HandleBarsXPath
+ {
+ public static void Register(IHandlebars handlebarsContext)
+ {
+ handlebarsContext.RegisterHelper("XPath.SelectSingleNode", (writer, context, arguments) =>
+ {
+ (XPathNavigator nav, string xpath) = ParseArguments(arguments);
+
+ try
+ {
+#if NETSTANDARD1_3
+ var result = nav.SelectSingleNode(xpath);
+#else
+ var result = nav.XPath2SelectSingleNode(xpath);
+#endif
+ writer.WriteSafeString(result.OuterXml);
+ }
+ catch (Exception)
+ {
+ // Ignore Exception
+ }
+ });
+
+ handlebarsContext.RegisterHelper("XPath.SelectNodes", (writer, context, arguments) =>
+ {
+ (XPathNavigator nav, string xpath) = ParseArguments(arguments);
+
+ try
+ {
+#if NETSTANDARD1_3
+ var result = nav.Select(xpath);
+#else
+ var result = nav.XPath2SelectNodes(xpath);
+#endif
+ var resultXml = new StringBuilder();
+ foreach (XPathNavigator node in result)
+ {
+ resultXml.Append(node.OuterXml);
+ }
+
+ writer.WriteSafeString(resultXml);
+ }
+ catch (Exception)
+ {
+ // Ignore Exception
+ }
+ });
+
+ handlebarsContext.RegisterHelper("XPath.Evaluate", (writer, context, arguments) =>
+ {
+ (XPathNavigator nav, string xpath) = ParseArguments(arguments);
+
+ try
+ {
+#if NETSTANDARD1_3
+ var result = nav.Evaluate(xpath);
+#else
+ var result = nav.XPath2Evaluate(xpath);
+#endif
+ writer.WriteSafeString(result);
+ }
+ catch (Exception)
+ {
+ // Ignore Exception
+ }
+ });
+ }
+
+ private static (XPathNavigator nav, string xpath) ParseArguments(object[] arguments)
+ {
+ Check.Condition(arguments, args => args.Length == 2, nameof(arguments));
+ Check.NotNull(arguments[0], "arguments[0]");
+ Check.NotNullOrEmpty(arguments[1] as string, "arguments[1]");
+
+ XPathNavigator nav;
+
+ switch (arguments[0])
+ {
+ case string stringValue:
+ nav = new XmlDocument { InnerXml = stringValue }.CreateNavigator();
+ break;
+
+ default:
+ throw new NotSupportedException($"The value '{arguments[0]}' with type '{arguments[0]?.GetType()}' cannot be used in Handlebars XPath.");
+ }
+
+ return (nav, (string)arguments[1]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs
new file mode 100644
index 00000000..df9e96d2
--- /dev/null
+++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsXPathTests.cs
@@ -0,0 +1,166 @@
+using System.Threading.Tasks;
+using System.Xml;
+using NFluent;
+using WireMock.Models;
+using WireMock.ResponseBuilders;
+using WireMock.Settings;
+using WireMock.Types;
+using WireMock.Util;
+using Xunit;
+#if !NETSTANDARD1_3
+using Wmhelp.XPath2;
+#endif
+
+namespace WireMock.Net.Tests.ResponseBuilders
+{
+ public class ResponseWithHandlebarsXPathTests
+ {
+ private readonly WireMockServerSettings _settings = new WireMockServerSettings();
+ private const string ClientIp = "::1";
+
+ [Fact]
+ public async Task Response_ProvideResponse_Handlebars_XPath_SelectSingleNode_Request_BodyAsString()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = @"
+ abc
+ def
+ xyz
+ ",
+ DetectedBodyType = BodyType.String
+ };
+
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
+
+ var response = Response.Create()
+ .WithHeader("Content-Type", "application/xml")
+ .WithBody("{{XPath.SelectSingleNode request.body \"/todo-list/todo-item[1]\"}}")
+ .WithTransformer();
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request, _settings);
+
+ // Assert
+ var nav = new XmlDocument { InnerXml = responseMessage.BodyData.BodyAsString }.CreateNavigator();
+ var node = nav.XPath2SelectSingleNode("/response/todo-item");
+ Check.That(node.Value).Equals("abc");
+ Check.That(node.GetAttribute("id", "")).Equals("a1");
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponse_Handlebars_XPath_SelectSingleNode_Text_Request_BodyAsString()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = @"
+ abc
+ def
+ xyz
+ ",
+ DetectedBodyType = BodyType.String
+ };
+
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
+
+ var response = Response.Create()
+ .WithHeader("Content-Type", "application/xml")
+ .WithBody("{{XPath.SelectSingleNode request.body \"/todo-list/todo-item[1]/text()\"}}")
+ .WithTransformer();
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request, _settings);
+
+ // Assert
+ Check.That(responseMessage.BodyData.BodyAsString).IsEqualTo("abc");
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponse_Handlebars_XPath_SelectNodes_Request_BodyAsString()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = @"
+ abc
+ def
+ xyz
+ ",
+ DetectedBodyType = BodyType.String
+ };
+
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
+
+ var response = Response.Create()
+ .WithHeader("Content-Type", "application/xml")
+ .WithBody("{{XPath.SelectNodes request.body \"/todo-list/todo-item\"}}")
+ .WithTransformer();
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request, _settings);
+
+ // Assert
+ var nav = new XmlDocument { InnerXml = responseMessage.BodyData.BodyAsString }.CreateNavigator();
+ var nodes = nav.XPath2SelectNodes("/response/todo-item");
+ Check.That(nodes.Count + 1).IsEqualTo(3);
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponse_Handlebars_XPath_Evaluate_Request_BodyAsString()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = @"
+ abc
+ def
+ xyz
+ ",
+ DetectedBodyType = BodyType.String
+ };
+
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
+
+ var response = Response.Create()
+ .WithHeader("Content-Type", "application/xml")
+ .WithBody("{{XPath.Evaluate request.body \"boolean(/todo-list[count(todo-item) = 3])\"}}")
+ .WithTransformer();
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request, _settings);
+
+ // Assert
+ Check.That(responseMessage.BodyData.BodyAsString).IsEqualIgnoringCase("True");
+ }
+
+ [Fact]
+ public async Task Response_ProvideResponse_Handlebars_XPath_Evaluate_Attribute_Request_BodyAsString()
+ {
+ // Assign
+ var body = new BodyData
+ {
+ BodyAsString = @"
+ abc
+ def
+ xyz
+ ",
+ DetectedBodyType = BodyType.String
+ };
+
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "POST", ClientIp, body);
+
+ var response = Response.Create()
+ .WithHeader("Content-Type", "application/xml")
+ .WithBody("{{XPath.Evaluate request.body \"string(/todo-list/todo-item[1]/@id)\"}}")
+ .WithTransformer();
+
+ // Act
+ var responseMessage = await response.ProvideResponseAsync(request, _settings);
+
+ // Assert
+ Check.That(responseMessage.BodyData.BodyAsString).IsEqualTo("a1");
+ }
+ }
+}