From fef34950e944ff21cb4f48a7fdf996653310a134 Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Thu, 11 Jul 2024 09:10:49 +0200 Subject: [PATCH 01/13] Refactor SearchHelper.cs and update field types - Reordered `using` directives to place `System` at the top. - Reformatted `Document` object initialization for clarity. - Changed `StringField` to `TextField` for `request.body`, `response.body`, and `response.bodyAsJson` to handle large text data better. --- .../ViewModels/SearchHelper.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/WireMockInspector/ViewModels/SearchHelper.cs b/src/WireMockInspector/ViewModels/SearchHelper.cs index 1305cbd..05b5323 100644 --- a/src/WireMockInspector/ViewModels/SearchHelper.cs +++ b/src/WireMockInspector/ViewModels/SearchHelper.cs @@ -1,4 +1,5 @@ -using Lucene.Net.Analysis.Standard; +using System; +using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; using Lucene.Net.Index; using Lucene.Net.Store; @@ -29,13 +30,13 @@ public void Load(IReadOnlyList requests) foreach (var request in requests) { var doc = new Document - { - new StringField("_id", request.Raw.Guid.ToString(), Field.Store.YES), - new StringField("clientip", request.Raw.Request.ClientIP.ToLower(), Field.Store.NO), - new StringField("method", request.Method.ToLower(), Field.Store.NO), - new StringField("url", request.Raw.Request.Url.ToLower(), Field.Store.NO), - new StringField("path", request.Path.ToLower(), Field.Store.NO) - }; + { + new StringField("_id", request.Raw.Guid.ToString(), Field.Store.YES), + new StringField("clientip", request.Raw.Request.ClientIP.ToLower(), Field.Store.NO), + new StringField("method", request.Method.ToLower(), Field.Store.NO), + new StringField("url", request.Raw.Request.Url.ToLower(), Field.Store.NO), + new StringField("path", request.Path.ToLower(), Field.Store.NO) + }; if (request.Raw.Request.Headers is { } headers) foreach (var (key, val) in headers) @@ -60,13 +61,13 @@ public void Load(IReadOnlyList requests) doc.Add(new StringField("param", key.ToLower(), Field.Store.NO)); foreach (var v in val) { - doc.Add(new StringField( ToFieldName("param",key), v.ToLower(), Field.Store.NO)); + doc.Add(new StringField(ToFieldName("param", key), v.ToLower(), Field.Store.NO)); } } if (request.Raw.Request.Body is { } requestBody) { - doc.Add(new StringField("request.body", requestBody.ToLower(), Field.Store.NO)); + doc.Add(new TextField("request.body", requestBody.ToLower(), Field.Store.NO)); } doc.Add(new StringField("status", request.Raw.Response.StatusCode?.ToString()?.ToLower() ?? "", Field.Store.NO)); @@ -83,11 +84,11 @@ public void Load(IReadOnlyList requests) if (request.Raw.Response.Body is { } responseBody) { - doc.Add(new StringField("response.body", responseBody.ToLower(), Field.Store.NO)); + doc.Add(new TextField("response.body", responseBody.ToLower(), Field.Store.NO)); } else if (request.Raw.Response.BodyAsJson is { } responseBodyAsJson) { - doc.Add(new StringField("response.body", responseBodyAsJson.ToString()?.ToLower() ?? string.Empty, Field.Store.NO)); + doc.Add(new TextField("response.body", responseBodyAsJson.ToString()?.ToLower() ?? string.Empty, Field.Store.NO)); } indexWriter.AddDocument(doc); From 63123bc025b26ce8e587f93c39546abd5fe8dbeb Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Thu, 11 Jul 2024 09:14:21 +0200 Subject: [PATCH 02/13] rollback formatting --- src/WireMockInspector/ViewModels/SearchHelper.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/WireMockInspector/ViewModels/SearchHelper.cs b/src/WireMockInspector/ViewModels/SearchHelper.cs index 05b5323..93e1881 100644 --- a/src/WireMockInspector/ViewModels/SearchHelper.cs +++ b/src/WireMockInspector/ViewModels/SearchHelper.cs @@ -30,13 +30,13 @@ public void Load(IReadOnlyList requests) foreach (var request in requests) { var doc = new Document - { - new StringField("_id", request.Raw.Guid.ToString(), Field.Store.YES), - new StringField("clientip", request.Raw.Request.ClientIP.ToLower(), Field.Store.NO), - new StringField("method", request.Method.ToLower(), Field.Store.NO), - new StringField("url", request.Raw.Request.Url.ToLower(), Field.Store.NO), - new StringField("path", request.Path.ToLower(), Field.Store.NO) - }; + { + new StringField("_id", request.Raw.Guid.ToString(), Field.Store.YES), + new StringField("clientip", request.Raw.Request.ClientIP.ToLower(), Field.Store.NO), + new StringField("method", request.Method.ToLower(), Field.Store.NO), + new StringField("url", request.Raw.Request.Url.ToLower(), Field.Store.NO), + new StringField("path", request.Path.ToLower(), Field.Store.NO) + }; if (request.Raw.Request.Headers is { } headers) foreach (var (key, val) in headers) From ed9238a11daa76e4949bb1526ba50f0aca839e6a Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Thu, 11 Jul 2024 09:26:43 +0200 Subject: [PATCH 03/13] Update package dependencies in project files Updated the following package versions in `WireMock.Net.Extensions.WireMockInspector.csproj`: - `WireMock.Net.Abstractions` from `1.5.24` to `1.5.60`. Updated the following package versions in `WireMockInspector.csproj`: - `Avalonia` from `11.0.6` to `11.0.11`. - `Avalonia.AvaloniaEdit` from `11.0.5` to `11.0.6`. - `Avalonia.Controls.DataGrid` from `11.0.6` to `11.0.11`. - `Avalonia.Desktop` from `11.0.6` to `11.0.11`. - `Avalonia.Svg` from `11.0.0.9` to `11.0.0.18`. - `Avalonia.Themes.Fluent` from `11.0.6` to `11.0.11`. - Conditional package `Avalonia.Diagnostics` for `Debug` configuration from `11.0.6` to `11.0.11`. - `Avalonia.ReactiveUI` from `11.0.6` to `11.0.11`. - `AvaloniaEdit.TextMate` from `11.0.5` to `11.0.6`. - `Fluid.Core` from `2.5.0` to `2.10.0`. - `TextMateSharp.Grammars` from `1.0.56` to `1.0.58`. - `WireMock.Net.RestClient` from `1.5.46` to `1.5.60`. These updates ensure the projects use the latest features, improvements, and bug fixes provided by these packages. --- ...ck.Net.Extensions.WireMockInspector.csproj | 2 +- .../WireMockInspector.csproj | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/WireMock.Net.Extensions.WireMockInspector/WireMock.Net.Extensions.WireMockInspector.csproj b/src/WireMock.Net.Extensions.WireMockInspector/WireMock.Net.Extensions.WireMockInspector.csproj index c3ffe63..241386a 100644 --- a/src/WireMock.Net.Extensions.WireMockInspector/WireMock.Net.Extensions.WireMockInspector.csproj +++ b/src/WireMock.Net.Extensions.WireMockInspector/WireMock.Net.Extensions.WireMockInspector.csproj @@ -27,6 +27,6 @@ - + diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index 8a06080..9988e31 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -40,18 +40,18 @@ - - - - - - + + + + + + - - - + + + - + @@ -60,8 +60,8 @@ - - + + From c189384a4eec3c6ff4881774c4316d661908c03b Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Thu, 11 Jul 2024 10:14:28 +0200 Subject: [PATCH 04/13] Refactor: Change namespace of CSharpFormatter class The namespace of the `CSharpFormatter` class has been changed from `WireMockInspector.ViewModels` to `WireMockInspector.CodeGenerators`. This reorganization better categorizes the class under a namespace that aligns with its functionality related to code generation. --- src/WireMockInspector/CodeGenerators/CSharpFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs b/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs index 4a1e6bc..3e47aa0 100644 --- a/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs +++ b/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs @@ -4,7 +4,7 @@ using System.Linq; using Newtonsoft.Json.Linq; -namespace WireMockInspector.ViewModels; +namespace WireMockInspector.CodeGenerators; internal static class CSharpFormatter { From 405ae15f9d7b1abc5b22b3cd85addd054e28dc9f Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Fri, 12 Jul 2024 11:48:34 +0200 Subject: [PATCH 05/13] Refactor and enhance code generation and UI - Added `FormatArray` method to `CSharpFormatter`. - Removed `MappingCodeGenerator` class and its methods. - Cleared `default_template.liquid` content. - Changed `EndNode` class visibility to private in `GraphConverter`. - Made several methods in `GraphConverter` private. - Removed unused imports from `MainWindowViewModel`. - Updated `SelectedTemplate` property initialization in `MainWindowViewModel`. - Added `JsonGenerator` property to `MainWindowViewModel`. - Added `CopyActualValue` command to `MappingCodeGeneratorViewModel`. - Updated `OutputCode` property in `MappingCodeGeneratorViewModel`. - Made formatting and visibility changes in `MatchDetailsViewModel`. - Made `luceneVersion` constant private in `SearchHelper`. - Updated XAML layouts in `MappingPage` and `RequestPage`. - Added `Definition` tab in `RequestPage` for JSON generator options. --- .../CodeGenerators/CSharpFormatter.cs | 1 + .../Code/MappingCodeGenerator.cs | 108 ++++++++++ .../{ => Code}/default_template.liquid | 0 .../CodeGenerators/CodeGenerator.cs | 87 ++++++++ .../Json/MappingJsonGenerator.cs | 101 ++++++++++ .../Json/default_template.liquid | 115 +++++++++++ .../CodeGenerators/MappingCodeGenerator.cs | 189 ------------------ .../ViewModels/GraphConverter.cs | 6 +- .../ViewModels/MainWindowViewModel.cs | 48 +++-- .../MappingCodeGeneratorViewModel.cs | 19 +- .../MappingJsonGeneratorViewModel.cs | 58 ++++++ .../ViewModels/MatchDetailsViewModel.cs | 88 ++++---- .../ViewModels/SearchHelper.cs | 2 +- src/WireMockInspector/Views/MappingPage.axaml | 8 +- src/WireMockInspector/Views/RequestPage.axaml | 84 +++++--- .../WireMockInspector.csproj | 12 +- 16 files changed, 635 insertions(+), 291 deletions(-) create mode 100644 src/WireMockInspector/CodeGenerators/Code/MappingCodeGenerator.cs rename src/WireMockInspector/CodeGenerators/{ => Code}/default_template.liquid (100%) create mode 100644 src/WireMockInspector/CodeGenerators/CodeGenerator.cs create mode 100644 src/WireMockInspector/CodeGenerators/Json/MappingJsonGenerator.cs create mode 100644 src/WireMockInspector/CodeGenerators/Json/default_template.liquid delete mode 100644 src/WireMockInspector/CodeGenerators/MappingCodeGenerator.cs create mode 100644 src/WireMockInspector/ViewModels/MappingJsonGeneratorViewModel.cs diff --git a/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs b/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs index 3e47aa0..c3f43eb 100644 --- a/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs +++ b/src/WireMockInspector/CodeGenerators/CSharpFormatter.cs @@ -185,6 +185,7 @@ private static string FormatObject(JObject jObject, int ind) } + } private static string FormatArray(JArray jArray, int ind) diff --git a/src/WireMockInspector/CodeGenerators/Code/MappingCodeGenerator.cs b/src/WireMockInspector/CodeGenerators/Code/MappingCodeGenerator.cs new file mode 100644 index 0000000..eb87587 --- /dev/null +++ b/src/WireMockInspector/CodeGenerators/Code/MappingCodeGenerator.cs @@ -0,0 +1,108 @@ +using System.IO; +using System.Linq; +using Fluid; +using Fluid.Values; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WireMock.Admin.Requests; +using WireMockInspector.ViewModels; + +namespace WireMockInspector.CodeGenerators.Code; + +public class MappingCodeGenerator : CodeGenerator +{ + public static string EscapeString(string value) => CSharpFormatter.ToCSharpStringLiteral(value); + + public static string Generate(LogRequestModel logRequest, LogResponseModel logResponse, MappingCodeGeneratorConfigViewModel config) + { + var options = new TemplateOptions(); + options.ValueConverters.Add(o => o is JToken t ? t.ToString() : null); + options.Filters.AddFilter("escape_string_for_csharp", (input, arguments, templateContext) => new StringValue(EscapeString(input.ToStringValue()))); + options.Filters.AddFilter("format_as_anonymous_object", (input, arguments, templateContext) => + { + var ind = arguments.Values.FirstOrDefault() is NumberValue nv ? (int)nv.ToNumberValue() : 0; + + return input switch + { + StringValue dv => CSharpFormatter.TryToConvertJsonToAnonymousObject(dv.ToStringValue(), ind) switch + { + { } s => new StringValue(s), + _ => NilValue.Instance, + }, + _ => input + }; + }); + var parser = new FluidParser(); + + var templateCode = ""; + if (config.SelectedTemplate == DefaultTemplateName) + { + templateCode = ReadEmbeddedResource("WireMockInspector.CodeGenerators.Code.default_template.liquid"); + } + else if (string.IsNullOrWhiteSpace(config.SelectedTemplate) == false) + { + var templatePath = Path.Combine(PathHelper.GetTemplateDir(), config.SelectedTemplate); + if (File.Exists(templatePath)) + { + templateCode = File.ReadAllText(templatePath); + } + } + + if (parser.TryParse(templateCode, out var ftemplate, out var error)) + { + var reader = new JsonDataSourceReader(); + + var data = reader.Read(JsonConvert.SerializeObject( + new + { + request = new + { + logRequest.ClientIP, + logRequest.DateTime, + logRequest.Path, + FullPath = GetFullPath(logRequest), + logRequest.AbsolutePath, + logRequest.Url, + logRequest.AbsoluteUrl, + logRequest.ProxyUrl, + Query = logRequest.Query.OrNullWhenEmpty(), + logRequest.Method, + Headers = logRequest.Headers.OrNullWhenEmpty(), + Cookies = logRequest.Cookies.OrNullWhenEmpty(), + logRequest.Body, + BodyAsJson = (TryParseJson(logRequest.Body) ?? logRequest.BodyAsJson)?.ToString(), + logRequest.BodyAsBytes, + logRequest.BodyEncoding, + logRequest.DetectedBodyType, + logRequest.DetectedBodyTypeFromContentType + }, + response = new + { + logResponse.StatusCode, + Headers = logResponse.Headers.OrNullWhenEmpty(), + logResponse.BodyDestination, + logResponse.Body, + BodyAsJson = (TryParseJson(logResponse.Body) ?? logResponse.BodyAsJson)?.ToString(), + logResponse.BodyAsBytes, + logResponse.BodyAsFile, + logResponse.BodyAsFileIsCached, + logResponse.BodyOriginal, + logResponse.BodyEncoding, + logResponse.DetectedBodyType, + logResponse.DetectedBodyTypeFromContentType, + logResponse.FaultType, + logResponse.FaultPercentage + }, + config + })); + var result = ftemplate.Render(new TemplateContext(new + { + data + }, options)); + return result; + } + + return error; + + } +} \ No newline at end of file diff --git a/src/WireMockInspector/CodeGenerators/default_template.liquid b/src/WireMockInspector/CodeGenerators/Code/default_template.liquid similarity index 100% rename from src/WireMockInspector/CodeGenerators/default_template.liquid rename to src/WireMockInspector/CodeGenerators/Code/default_template.liquid diff --git a/src/WireMockInspector/CodeGenerators/CodeGenerator.cs b/src/WireMockInspector/CodeGenerators/CodeGenerator.cs new file mode 100644 index 0000000..63144d7 --- /dev/null +++ b/src/WireMockInspector/CodeGenerators/CodeGenerator.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Web; +using Newtonsoft.Json.Linq; +using WireMock.Admin.Requests; + +namespace WireMockInspector.CodeGenerators; + +public class CodeGenerator +{ + public class JsonDataSourceReader + { + private static object? ConvertJsonToObject(JToken xDocument) + { + return xDocument switch + { + JArray jArray => jArray.Select(ConvertJsonToObject).ToArray(), + JObject jObject => jObject.Properties().ToDictionary(x => x.Name, x => ConvertJsonToObject(x.Value)), + JValue jValue => jValue.Value, + _ => null + }; + } + + public object? Read(string content) + { + var json = JToken.Parse(content); + + if (json is JObject jo && jo.ContainsKey("$schema")) + { + jo.Remove("$schema"); + } + + return ConvertJsonToObject(json); + } + } + + public static string ReadEmbeddedResource(string resourceName) + { + // Get the current assembly + Assembly assembly = Assembly.GetExecutingAssembly(); + + // Using stream to read the embedded file. + using var stream = assembly.GetManifestResourceStream(resourceName); + // Make sure the resource is available + if (stream == null) throw new FileNotFoundException("The specified embedded resource cannot be found.", resourceName); + using StreamReader reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + public const string DefaultTemplateName = "(default)"; + + + public static JToken? TryParseJson(string? payload) + { + try + { + return payload switch + { + { } => JToken.Parse(payload), + _ => null + }; + } + catch (Exception e) + { + return null; + } + } + + public static string GetFullPath(LogRequestModel logRequest) + { + var query = HttpUtility.ParseQueryString(""); + if (logRequest.Query is { } requestQuery) + { + foreach (var p in requestQuery) + { + query[p.Key] = p.Value.ToString(); + } + } + var fullQuery = query.ToString(); + if (string.IsNullOrWhiteSpace(fullQuery) == false) + { + return $"{logRequest.Path}?{fullQuery}"; + } + return logRequest.Path; + } +} \ No newline at end of file diff --git a/src/WireMockInspector/CodeGenerators/Json/MappingJsonGenerator.cs b/src/WireMockInspector/CodeGenerators/Json/MappingJsonGenerator.cs new file mode 100644 index 0000000..2b2695d --- /dev/null +++ b/src/WireMockInspector/CodeGenerators/Json/MappingJsonGenerator.cs @@ -0,0 +1,101 @@ +using System.IO; +using System.Text.RegularExpressions; +using Fluid; +using Fluid.Values; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WireMock.Admin.Requests; +using WireMockInspector.ViewModels; + +namespace WireMockInspector.CodeGenerators.Json; + +public class MappingJsonGenerator : CodeGenerator +{ + public static string EscapeString(string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + return JsonConvert.SerializeObject(value); + } + + public static string Generate(LogRequestModel logRequest, LogResponseModel logResponse, MappingCodeGeneratorConfigViewModel config) + { + var options = new TemplateOptions(); + options.ValueConverters.Add(o => o is JToken t ? t.ToString() : null); + options.Filters.AddFilter("escape_json", (input, arguments, templateContext) => new StringValue(EscapeString(input.ToStringValue()))); + var parser = new FluidParser(); + + var templateCode = ""; + if (config.SelectedTemplate == DefaultTemplateName) + { + templateCode = ReadEmbeddedResource("WireMockInspector.CodeGenerators.Json.default_template.liquid"); + } + else if (string.IsNullOrWhiteSpace(config.SelectedTemplate) == false) + { + var templatePath = Path.Combine(PathHelper.GetTemplateDir(), config.SelectedTemplate); + if (File.Exists(templatePath)) + { + templateCode = File.ReadAllText(templatePath); + } + } + + if (parser.TryParse(templateCode, out var ftemplate, out var error)) + { + var reader = new JsonDataSourceReader(); + var data = reader.Read(JsonConvert.SerializeObject( + new + { + request = new + { + logRequest.ClientIP, + logRequest.DateTime, + logRequest.Path, + FullPath = GetFullPath(logRequest), + logRequest.AbsolutePath, + logRequest.Url, + logRequest.AbsoluteUrl, + logRequest.ProxyUrl, + Query = logRequest.Query.OrNullWhenEmpty(), + logRequest.Method, + Headers = logRequest.Headers.OrNullWhenEmpty(), + Cookies = logRequest.Cookies.OrNullWhenEmpty(), + logRequest.Body, + BodyAsJson = (TryParseJson(logRequest.Body) ?? logRequest.BodyAsJson)?.ToString(), + logRequest.BodyAsBytes, + logRequest.BodyEncoding, + logRequest.DetectedBodyType, + logRequest.DetectedBodyTypeFromContentType + }, + response = new + { + logResponse.StatusCode, + Headers = logResponse.Headers.OrNullWhenEmpty(), + logResponse.BodyDestination, + logResponse.Body, + BodyAsJson = (TryParseJson(logResponse.Body) ?? logResponse.BodyAsJson)?.ToString(), + logResponse.BodyAsBytes, + logResponse.BodyAsFile, + logResponse.BodyAsFileIsCached, + logResponse.BodyOriginal, + logResponse.BodyEncoding, + logResponse.DetectedBodyType, + logResponse.DetectedBodyTypeFromContentType, + logResponse.FaultType, + logResponse.FaultPercentage + }, + config + })); + var result = ftemplate.Render(new TemplateContext(new + { + data + }, options)); + + return Regex.Replace(result, @",(?=\s*[\]}])", ""); + } + + return error; + } +} \ No newline at end of file diff --git a/src/WireMockInspector/CodeGenerators/Json/default_template.liquid b/src/WireMockInspector/CodeGenerators/Json/default_template.liquid new file mode 100644 index 0000000..39f7c00 --- /dev/null +++ b/src/WireMockInspector/CodeGenerators/Json/default_template.liquid @@ -0,0 +1,115 @@ +{%- assign request = data.request -%} +{%- assign response = data.response -%} +{%- assign config = data.config -%} +{ + "Priority": 0, + "Request": { + {%- if config.IncludePath %} + "Path": { + "Matchers": [ + { + "Name": "WildcardMatcher", + "Pattern": {{ request.Path | escape_json }} + } + ] + }, + {%- endif %} + {%- if config.IncludeMethod %} + "Methods": [ + {{ request.Method | escape_json }} + ], + {%- endif %} + {%- if config.IncludeClientIP and request.ClientIP %} + "ClientIP": {{ request.ClientIP | escape_json }}, + {%- endif %} + {%- if config.IncludeAbsolutePath and request.AbsolutePath %} + "AbsolutePath": {{ request.AbsolutePath | escape_json }}, + {%- endif %} + {%- if config.IncludeUrl and request.Url %} + "Url": {{ request.Url | escape_json }}, + {%- endif %} + {%- if config.IncludeAbsoluteUrl and request.AbsoluteUrl %} + "AbsoluteUrl": {{ request.AbsoluteUrl | escape_json }}, + {%- endif %} + {%- if config.IncludeProxyUrl and request.ProxyUrl %} + "ProxyUrl": {{ request.ProxyUrl | escape_json }}, + {%- endif %} + {%- if config.IncludeQuery and request.Query %} + "Params": [ + {%- for item in request.Query %} + {%- assign query_key = item[0] %} + {%- assign query_values = item[1] %} + { + "Name": {{ query_key | escape_json }}, + "Matchers": [ + {%- for value in query_values %} + { + "Name": "ExactMatcher", + "Pattern": {{ value | escape_json }} + }{%- if forloop.last == false %},{% endif %} + {%- endfor %} + ] + }{%- if forloop.last == false %},{% endif %} + {%- endfor %} + ], + {%- endif %} + {%- if config.IncludeHeaders and request.Headers %} + "Headers": [ + {%- for item in request.Headers %} + {%- assign header_key = item[0] %} + {%- assign header_values = item[1] | join: "\", \"" | escape_json %} + { + "Name": {{ header_key | escape_json }}, + "Matchers": [ + { + "Name": "ExactMatcher", + "Pattern": {{ header_values }} + } + ] + }{%- if forloop.last == false %},{% endif %} + {%- endfor %} + ], + {%- endif %} + + {%- if config.IncludeBody %} + {%- if request.BodyAsJson %} + "Body": { + "Matcher": { + "Name": "WildcardMatcher", + "Pattern": {{ request.BodyAsJson | escape_json }} + } + }, + {%- elsif request.Body %} + "Body": { + "Matcher": { + "Name": "WildcardMatcher", + "Pattern": {{ request.Body | escape_json }} + } + }, + {%- endif %} + {%- endif %} + }, + "Response": { + {%- if config.IncludeStatusCode and response.StatusCode %} + "StatusCode": {{ response.StatusCode }}, + {%- endif %} + "BodyDestination": "SameAsSource", + {%- if config.IncludeHeadersResponse and response.Headers %} + "Headers": { + {%- for item in response.Headers %} + {%- assign header_key = item[0] %} + {%- assign header_values = item[1] | join: ", " | escape_json %} + {{ header_key | escape_json }}: {{ header_values }}{%- if forloop.last == false %},{% endif %} + {%- endfor %} + }, + {%- endif %} + {%- if config.IncludeBodyResponse %} + {%- if response.BodyAsJson %} + "BodyAsJson": {{ response.BodyAsJson | escape_json }}, + {%- elsif response.Body %} + {%- assign body = response.Body | escape_json %} + "Body": {{ body }} + {%- endif %} + {%- endif %} + } +} \ No newline at end of file diff --git a/src/WireMockInspector/CodeGenerators/MappingCodeGenerator.cs b/src/WireMockInspector/CodeGenerators/MappingCodeGenerator.cs deleted file mode 100644 index 76f86ef..0000000 --- a/src/WireMockInspector/CodeGenerators/MappingCodeGenerator.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Web; -using Fluid; -using Fluid.Values; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using WireMock.Admin.Requests; -using WireMockInspector.ViewModels; - -namespace WireMockInspector.CodeGenerators; - -public static class MappingCodeGenerator -{ - - class JsonDataSourceReader - { - object? ConvertJsonToObject(JToken xDocument) - { - return xDocument switch - { - JArray jArray => jArray.Select(ConvertJsonToObject).ToArray(), - JObject jObject => jObject.Properties().ToDictionary(x => x.Name, x => ConvertJsonToObject(x.Value)), - JValue jValue => jValue.Value, - _ => null - }; - } - - public object? Read(string content) - { - var json = JToken.Parse(content); - - if (json is JObject jo && jo.ContainsKey("$schema")) - { - jo.Remove("$schema"); - } - - return ConvertJsonToObject(json); - } - } - - public static string EscapeStringForCSharp(string value) => CSharpFormatter.ToCSharpStringLiteral(value); - - private static string ReadEmbeddedResource(string resourceName) - { - // Get the current assembly - Assembly assembly = Assembly.GetExecutingAssembly(); - - // Using stream to read the embedded file. - using var stream = assembly.GetManifestResourceStream(resourceName); - // Make sure the resource is available - if (stream == null) throw new FileNotFoundException("The specified embedded resource cannot be found.", resourceName); - using StreamReader reader = new StreamReader(stream); - return reader.ReadToEnd(); - } - public const string DefaultTemplateName = "(default)"; - - - private static JToken? TryParseJson(string? payload) - { - try - { - return payload switch - { - { } => JToken.Parse(payload), - _ => null - }; - } - catch (Exception e) - { - return null; - } - } - - public static string GenerateCSharpCode(LogRequestModel logRequest, LogResponseModel logResponse, MappingCodeGeneratorConfigViewModel config) - { - var options = new TemplateOptions(); - options.ValueConverters.Add(o => o is JToken t? t.ToString(): null ); - options.Filters.AddFilter("escape_string_for_csharp", (input, arguments, templateContext) => new StringValue(EscapeStringForCSharp(input.ToStringValue()) )); - options.Filters.AddFilter("format_as_anonymous_object", (input, arguments, templateContext) => - { - var ind = arguments.Values.FirstOrDefault() is NumberValue nv ? (int)nv.ToNumberValue() : 0; - - return input switch - { - StringValue dv => CSharpFormatter.TryToConvertJsonToAnonymousObject(dv.ToStringValue(), ind) switch - { - { } s => new StringValue(s), - _ => NilValue.Instance, - }, - _ => input - }; - }); - var parser = new FluidParser(); - - var templateCode =""; - if (config.SelectedTemplate == DefaultTemplateName) - { - templateCode = ReadEmbeddedResource("WireMockInspector.CodeGenerators.default_template.liquid"); - } - else if(string.IsNullOrWhiteSpace(config.SelectedTemplate) == false) - { - var templatePath = Path.Combine(PathHelper.GetTemplateDir(), config.SelectedTemplate); - if (File.Exists(templatePath)) - { - templateCode = File.ReadAllText(templatePath); - } - } - - - if (parser.TryParse(templateCode, out var ftemplate, out var error)) - { - var reader = new JsonDataSourceReader(); - - var data = reader.Read(JsonConvert.SerializeObject( - new - { - request = new - { - ClientIP = logRequest.ClientIP, - DateTime = logRequest.DateTime, - Path = logRequest.Path, - FullPath = GetFullPath(logRequest), - AbsolutePath = logRequest.AbsolutePath, - Url = logRequest.Url, - AbsoluteUrl = logRequest.AbsoluteUrl, - ProxyUrl = logRequest.ProxyUrl, - Query = logRequest.Query.OrNullWhenEmpty(), - Method = logRequest.Method, - Headers = logRequest.Headers.OrNullWhenEmpty(), - Cookies = logRequest.Cookies.OrNullWhenEmpty(), - Body = logRequest.Body, - BodyAsJson = (TryParseJson(logRequest.Body) ?? logRequest.BodyAsJson)?.ToString(), - BodyAsBytes = logRequest.BodyAsBytes, - BodyEncoding = logRequest.BodyEncoding, - DetectedBodyType = logRequest.DetectedBodyType, - DetectedBodyTypeFromContentType = logRequest.DetectedBodyTypeFromContentType - }, - response = new - { - StatusCode = logResponse.StatusCode, - Headers = logResponse.Headers.OrNullWhenEmpty(), - BodyDestination = logResponse.BodyDestination, - Body = logResponse.Body, - BodyAsJson = (TryParseJson(logResponse.Body) ?? logResponse.BodyAsJson)?.ToString(), - BodyAsBytes = logResponse.BodyAsBytes, - BodyAsFile = logResponse.BodyAsFile, - BodyAsFileIsCached = logResponse.BodyAsFileIsCached, - BodyOriginal = logResponse.BodyOriginal, - BodyEncoding = logResponse.BodyEncoding, - DetectedBodyType = logResponse.DetectedBodyType, - DetectedBodyTypeFromContentType = logResponse.DetectedBodyTypeFromContentType, - FaultType = logResponse.FaultType, - FaultPercentage = logResponse.FaultPercentage - }, - config - })); - var result = ftemplate.Render(new TemplateContext(new - { - data = data - }, options)); - return result; - } - - return error; - - } - - private static string GetFullPath(LogRequestModel logRequest) - { - var query = HttpUtility.ParseQueryString(""); - if (logRequest.Query is { } requestQuery) - { - foreach (var p in requestQuery) - { - query[p.Key] = p.Value.ToString(); - } - } - var fullQuery = query.ToString(); - if (string.IsNullOrWhiteSpace(fullQuery) == false) - { - return $"{logRequest.Path}?{fullQuery}"; - } - return logRequest.Path; - } -} \ No newline at end of file diff --git a/src/WireMockInspector/ViewModels/GraphConverter.cs b/src/WireMockInspector/ViewModels/GraphConverter.cs index db36b30..a189bc8 100644 --- a/src/WireMockInspector/ViewModels/GraphConverter.cs +++ b/src/WireMockInspector/ViewModels/GraphConverter.cs @@ -20,7 +20,7 @@ public class GraphConverter : IValueConverter { public static readonly GraphConverter Instance = new (); - class EndNode : Microsoft.Msagl.Drawing.Node + private class EndNode : Microsoft.Msagl.Drawing.Node { public EndNode(string id) : base(id) { @@ -146,7 +146,7 @@ public EndNode(string id) : base(id) } - static string PrintSvgAsString(Graph drawingGraph) + private static string PrintSvgAsString(Graph drawingGraph) { var ms = new MemoryStream(); var writer = new StreamWriter(ms); @@ -159,7 +159,7 @@ static string PrintSvgAsString(Graph drawingGraph) .Replace("font-family=\"Arial\"", "font-family=\"Consolas\""); } - static void AssignLabelsDimensions(Graph drawingGraph, int maxTextLength) + private static void AssignLabelsDimensions(Graph drawingGraph, int maxTextLength) { // In general, the label dimensions should depend on the viewer foreach (var na in drawingGraph.Nodes) diff --git a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs index 60dc086..b053758 100644 --- a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs +++ b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs @@ -7,24 +7,20 @@ using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; -using System.Xml; using DiffPlex; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; using DynamicData; -using JsonDiffPatchDotNet; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ReactiveUI; using RestEase; -using TextMateSharp.Internal.Grammars.Parser; using WireMock.Admin.Mappings; using WireMock.Admin.Requests; using WireMock.Admin.Scenarios; using WireMock.Admin.Settings; using WireMock.Client; using WireMock.Types; -using WireMockInspector.CodeGenerators; using ChangeType = DiffPlex.DiffBuilder.Model.ChangeType; using Formatting = Newtonsoft.Json.Formatting; @@ -40,10 +36,6 @@ public bool DataLoaded } private bool _dataLoaded; - - - - public string AdminUrl { get => _adminUrl; @@ -459,7 +451,7 @@ await _settingsManager.UpdateSettings(settings => Config = { - SelectedTemplate = MappingCodeGenerator.DefaultTemplateName, + SelectedTemplate = CodeGenerators.CodeGenerator.DefaultTemplateName, Templates = GetAvailableTemplates().ToList(), IncludeClientIP = false, IncludePath = true, @@ -475,17 +467,39 @@ await _settingsManager.UpdateSettings(settings => } }; }).ToProperty(this, x=>x.CodeGenerator, out _codeGenerator); - } - - + this.WhenAnyValue(x => x.SelectedRequest) + .Where(x=>x is not null) + .Select(model => + { + return new MappingJsonGeneratorViewModel() + { + Request = model.Raw.Request, + Response = model.Raw.Response, + Config = + { + SelectedTemplate = CodeGenerators.CodeGenerator.DefaultTemplateName, + Templates = GetAvailableTemplates().ToList(), + IncludeClientIP = false, + IncludePath = true, + IncludeUrl = false, + IncludeQuery = true, + IncludeMethod = true, + IncludeHeaders = true, + IncludeCookies = true, + IncludeBody = true, + IncludeStatusCode = true, + IncludeHeadersResponse = true, + IncludeBodyResponse = true + } + }; + }).ToProperty(this, x=>x.JsonGenerator, out _jsonGenerator); + } private IEnumerable GetAvailableTemplates() { var templateDir = PathHelper.GetTemplateDir(); - - yield return MappingCodeGenerator.DefaultTemplateName; - + yield return CodeGenerators.CodeGenerator.DefaultTemplateName; foreach (var file in Directory.GetFiles(templateDir, "*.liquid")) { yield return Path.GetFileName(file); @@ -495,6 +509,10 @@ private IEnumerable GetAvailableTemplates() private readonly ObservableAsPropertyHelper _codeGenerator; public MappingCodeGeneratorViewModel CodeGenerator => _codeGenerator.Value; + + private readonly ObservableAsPropertyHelper _jsonGenerator; + public MappingJsonGeneratorViewModel JsonGenerator => _jsonGenerator.Value; + private static List MapToLogEntries(IEnumerable logs) { return logs diff --git a/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs b/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs index 4756289..448d225 100644 --- a/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs +++ b/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs @@ -1,14 +1,16 @@ -using System.Collections.Generic; -using System.Reactive.Linq; +using System.Reactive.Linq; +using System.Windows.Input; +using Avalonia.Controls.ApplicationLifetimes; using DynamicData.Binding; using ReactiveUI; using WireMock.Admin.Requests; -using WireMockInspector.CodeGenerators; +using WireMockInspector.CodeGenerators.Code; namespace WireMockInspector.ViewModels; public class MappingCodeGeneratorViewModel : ViewModelBase { + public ICommand CopyActualValue { get; set; } private LogRequestModel _request; public LogRequestModel Request { @@ -36,12 +38,19 @@ public MappingCodeGeneratorConfigViewModel Config public MappingCodeGeneratorViewModel() { - + CopyActualValue = ReactiveCommand.Create(async () => + { + if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + await desktop.MainWindow.Clipboard.SetTextAsync(_outputCode.Value.rawValue); + } + }); + Config.WhenAnyPropertyChanged() .Where(x=> x is not null) .Select(x => { - var code = MappingCodeGenerator.GenerateCSharpCode(Request, Response, x); + var code = MappingCodeGenerator.Generate(Request, Response, x); return new MarkdownCode("cs", code); }).ToProperty(this, x => x.OutputCode, out _outputCode); } diff --git a/src/WireMockInspector/ViewModels/MappingJsonGeneratorViewModel.cs b/src/WireMockInspector/ViewModels/MappingJsonGeneratorViewModel.cs new file mode 100644 index 0000000..099d871 --- /dev/null +++ b/src/WireMockInspector/ViewModels/MappingJsonGeneratorViewModel.cs @@ -0,0 +1,58 @@ +using System.Reactive.Linq; +using System.Windows.Input; +using Avalonia.Controls.ApplicationLifetimes; +using DynamicData.Binding; +using ReactiveUI; +using WireMock.Admin.Requests; +using WireMockInspector.CodeGenerators.Json; + +namespace WireMockInspector.ViewModels; + +public class MappingJsonGeneratorViewModel : ViewModelBase +{ + public ICommand CopyActualValue { get; set; } + + private LogRequestModel _request; + public LogRequestModel Request + { + get => _request; + set => this.RaiseAndSetIfChanged(ref _request, value); + } + + private LogResponseModel _response; + public LogResponseModel Response + { + get => _response; + set => this.RaiseAndSetIfChanged(ref _response, value); + } + + private MappingCodeGeneratorConfigViewModel _config; + + public MappingCodeGeneratorConfigViewModel Config + { + get; + private set; + } = new MappingCodeGeneratorConfigViewModel(); + + private readonly ObservableAsPropertyHelper _outputCode; + public MarkdownCode OutputCode => _outputCode.Value; + + public MappingJsonGeneratorViewModel() + { + CopyActualValue = ReactiveCommand.Create(async () => + { + if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + await desktop.MainWindow.Clipboard.SetTextAsync(_outputCode.Value.rawValue); + } + }); + + Config.WhenAnyPropertyChanged() + .Where(x => x is not null) + .Select(x => + { + var code = MappingJsonGenerator.Generate(Request, Response, x); + return new MarkdownCode("json", code); + }).ToProperty(this, x => x.OutputCode, out _outputCode); + } +} \ No newline at end of file diff --git a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs index 0a49661..a91a669 100644 --- a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs +++ b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs @@ -14,21 +14,21 @@ namespace WireMockInspector.ViewModels; public class ExpectationMatcher { - - public IReadOnlyList> Attributes { get; set; } + + public IReadOnlyList> Attributes { get; set; } public List Tags { get; set; } public List Patterns { get; set; } } public abstract class ExpectationsModel { - + } -public class SimpleKeyValueExpectations: ExpectationsModel +public class SimpleKeyValueExpectations : ExpectationsModel { - public IReadOnlyList> Items { get; set; } - + public IReadOnlyList> Items { get; set; } + } public class GridExpectationItem @@ -36,28 +36,28 @@ public class GridExpectationItem public string Name { get; set; } public IReadOnlyList Matchers { get; set; } } -public class GridExpectations: ExpectationsModel +public class GridExpectations : ExpectationsModel { public IReadOnlyList Items { get; set; } - + } -class SimpleStringExpectations : ExpectationsModel +internal class SimpleStringExpectations : ExpectationsModel { public string Value { get; set; } } -public class MissingExpectations:ExpectationsModel +public class MissingExpectations : ExpectationsModel { public static readonly MissingExpectations Instance = new MissingExpectations(); } -public class RawExpectations:ExpectationsModel +public class RawExpectations : ExpectationsModel { public MarkdownCode Definition { get; set; } } -public class RichExpectations:ExpectationsModel +public class RichExpectations : ExpectationsModel { public MarkdownCode Definition { get; set; } public string? Operator { get; set; } @@ -69,13 +69,13 @@ public class CodeDiffViewModel { public MarkdownCode Left { get; set; } public MarkdownCode Right { get; set; } - + } -public class MatchDetailsViewModel:ViewModelBase +public class MatchDetailsViewModel : ViewModelBase { private ActualValue _actualValue; - + public string RuleName { get; set; } public bool? Matched { get; set; } public bool NoExpectations { get; set; } @@ -108,7 +108,7 @@ public CodeDiffViewModel Diff public ICommand ReformatActualValue { get; set; } public ICommand CopyActualValue { get; set; } - + public MatchDetailsViewModel() { @@ -116,57 +116,54 @@ public MatchDetailsViewModel() { if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - if (ActualValue is MarkdownActualValue {Value: {rawValue: var raw}}) + if (ActualValue is MarkdownActualValue { Value: { rawValue: var raw } }) { await desktop.MainWindow.Clipboard.SetTextAsync(raw); } - else if (ActualValue is SimpleActualValue {Value: var simpleValue} ) + else if (ActualValue is SimpleActualValue { Value: var simpleValue }) { await desktop.MainWindow.Clipboard.SetTextAsync(simpleValue); } - else if(ActualValue is KeyValueListActualValue{SelectedActualValueGridItem: {} selectedRow}) + else if (ActualValue is KeyValueListActualValue { SelectedActualValueGridItem: { } selectedRow }) { await desktop.MainWindow.Clipboard.SetTextAsync($"{selectedRow.Key}:{selectedRow.Value}"); - } + } } - - + + }); - - - + + + ReformatActualValue = ReactiveCommand.Create(() => { - if (this.ActualValue is MarkdownActualValue {Value: {} rawValue} markdown) + if (this.ActualValue is MarkdownActualValue { Value: { } rawValue } markdown) { ActualValue = new MarkdownActualValue() { Value = rawValue.TryToReformat() }; } - }, - this.WhenAnyValue(x=>x.ActualValue).Select(x => + }, + this.WhenAnyValue(x => x.ActualValue).Select(x => { - return x is MarkdownActualValue {Value: { } va} && va.IsJsonMarkdown(); + return x is MarkdownActualValue { Value: { } va } && va.IsJsonMarkdown(); })); - } - - } -public abstract class ActualValue:ViewModelBase +public abstract class ActualValue : ViewModelBase { - + } -public class SimpleActualValue:ActualValue +public class SimpleActualValue : ActualValue { public string Value { get; set; } } -public class MarkdownActualValue:ActualValue +public class MarkdownActualValue : ActualValue { public MarkdownCode Value { get; set; } public string MarkdownValue { get; set; } @@ -174,35 +171,36 @@ public class MarkdownActualValue:ActualValue public record Text(); -public record SimpleText(string Value):Text; -public record MarkdownCode(string lang, string rawValue, List? oldTextLines = null):Text +public record SimpleText(string Value) : Text; + +public record MarkdownCode(string lang, string rawValue, List? oldTextLines = null) : Text { public string AsMarkdownSyntax() { if (string.IsNullOrWhiteSpace(rawValue) == false) { - return $"```{lang}\r\n{rawValue}\r\n```"; + return $"```{lang}\r\n{rawValue}\r\n```"; } return string.Empty; } public override string ToString() => AsMarkdownSyntax(); - - public MarkdownCode TryToReformat() + + public MarkdownCode TryToReformat() { if (IsJsonMarkdown()) { try { - + var formatted = JToken.Parse(this.rawValue).ToString(Formatting.Indented); return new MarkdownCode("json", formatted); } catch (Exception e) { - + } } @@ -215,10 +213,10 @@ public bool IsJsonMarkdown() } }; -public class KeyValueListActualValue:ActualValue +public class KeyValueListActualValue : ActualValue { public KeyValuePair SelectedActualValueGridItem { get; set; } = new KeyValuePair(); - public IReadOnlyList> Items { get; set; } + public IReadOnlyList> Items { get; set; } } diff --git a/src/WireMockInspector/ViewModels/SearchHelper.cs b/src/WireMockInspector/ViewModels/SearchHelper.cs index 93e1881..4935d4f 100644 --- a/src/WireMockInspector/ViewModels/SearchHelper.cs +++ b/src/WireMockInspector/ViewModels/SearchHelper.cs @@ -12,7 +12,7 @@ namespace WireMockInspector.ViewModels; public class SearchHelper { - const LuceneVersion luceneVersion = LuceneVersion.LUCENE_48; + private const LuceneVersion luceneVersion = LuceneVersion.LUCENE_48; private readonly IndexWriter indexWriter; private readonly StandardAnalyzer standardAnalyzer; diff --git a/src/WireMockInspector/Views/MappingPage.axaml b/src/WireMockInspector/Views/MappingPage.axaml index dce14bd..a60e021 100644 --- a/src/WireMockInspector/Views/MappingPage.axaml +++ b/src/WireMockInspector/Views/MappingPage.axaml @@ -4,7 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" xmlns:views="clr-namespace:WireMockInspector.Views" - xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="viewModels:MainWindowViewModel" x:Class="WireMockInspector.Views.MappingPage"> @@ -96,7 +95,6 @@ - @@ -145,9 +143,9 @@ - - - + + + diff --git a/src/WireMockInspector/Views/RequestPage.axaml b/src/WireMockInspector/Views/RequestPage.axaml index bf9b8df..59d59f2 100644 --- a/src/WireMockInspector/Views/RequestPage.axaml +++ b/src/WireMockInspector/Views/RequestPage.axaml @@ -45,32 +45,64 @@ - - - - Show options - - - - - - - - - - - - - - - - - - - - - - + + + + + Show options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Show options + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index 9988e31..74db7f3 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -21,12 +21,19 @@ - + + + + + + + + @@ -75,6 +82,7 @@ - + + From 89f88c71a8fa6d2de4978e6171908cf4b53ef5fc Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Fri, 12 Jul 2024 12:35:14 +0200 Subject: [PATCH 06/13] Refactor code and update C# syntax highlighting - Updated `MarkdownCode` language identifier to "csharp" in `MainWindowViewModel.cs` and `MappingCodeGeneratorViewModel.cs`. - Refactored `CodeBlockViewer.cs` for better readability: - Restructured class to organize constructor and methods. - Updated `SetMarkdown` method to use `CSharpSyntaxHighlighter`. - Moved `DiffLineBackgroundRenderer` class to a separate file. - Added `CSharpSyntaxHighlighter` class in `CSharpSyntaxHighlighter.cs`. - Moved and refactored `DiffLineBackgroundRenderer` class to `DiffLineBackgroundRenderer.cs` for improved clarity and separation of concerns. --- .../ViewModels/MainWindowViewModel.cs | 2 +- .../MappingCodeGeneratorViewModel.cs | 2 +- .../ViewModels/MatchDetailsViewModel.cs | 1 - .../Views/CodeBlockViewer.cs | 183 +++++++----------- .../Transformer/CSharpSyntaxHighlighter.cs | 87 +++++++++ .../Transformer/DiffLineBackgroundRenderer.cs | 43 ++++ 6 files changed, 207 insertions(+), 111 deletions(-) create mode 100644 src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs create mode 100644 src/WireMockInspector/Views/Transformer/DiffLineBackgroundRenderer.cs diff --git a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs index b053758..592b59a 100644 --- a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs +++ b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs @@ -437,7 +437,7 @@ await _settingsManager.UpdateSettings(settings => .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(code => { - SelectedMapping.Code = new MarkdownCode("cs", code); + SelectedMapping.Code = new MarkdownCode("csharp", code); }); this.WhenAnyValue(x => x.SelectedRequest) diff --git a/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs b/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs index 448d225..9ec0d4e 100644 --- a/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs +++ b/src/WireMockInspector/ViewModels/MappingCodeGeneratorViewModel.cs @@ -51,7 +51,7 @@ public MappingCodeGeneratorViewModel() .Select(x => { var code = MappingCodeGenerator.Generate(Request, Response, x); - return new MarkdownCode("cs", code); + return new MarkdownCode("csharp", code); }).ToProperty(this, x => x.OutputCode, out _outputCode); } } \ No newline at end of file diff --git a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs index a91a669..e10f6e0 100644 --- a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs +++ b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs @@ -191,7 +191,6 @@ public MarkdownCode TryToReformat() { if (IsJsonMarkdown()) { - try { diff --git a/src/WireMockInspector/Views/CodeBlockViewer.cs b/src/WireMockInspector/Views/CodeBlockViewer.cs index 7c4c976..8ddb4b3 100644 --- a/src/WireMockInspector/Views/CodeBlockViewer.cs +++ b/src/WireMockInspector/Views/CodeBlockViewer.cs @@ -1,141 +1,108 @@ using System; -using System.Collections.Generic; using System.Linq; using Avalonia; using Avalonia.Media; using AvaloniaEdit; using AvaloniaEdit.Document; -using AvaloniaEdit.Rendering; using AvaloniaEdit.TextMate; -using DiffPlex.DiffBuilder.Model; using TextMateSharp.Grammars; using WireMockInspector.ViewModels; +using WireMockInspector.Views.Transformer; -namespace WireMockInspector.Views; - -public class CodeBlockViewer : TextEditor +namespace WireMockInspector.Views { - protected override Type StyleKeyOverride { get; } = typeof(TextEditor); - - public CodeBlockViewer() + public class CodeBlockViewer : TextEditor { - this._registryOptions = new RegistryOptions(ThemeName.DarkPlus); - this._textMateInstallation = this.InstallTextMate(_registryOptions); - } + protected override Type StyleKeyOverride { get; } = typeof(TextEditor); - protected override void OnInitialized() - { - base.OnInitialized(); - this.Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)); - this.TextArea.TextView.Margin = new Thickness(10, 0); - this.ShowLineNumbers = true; - this.IsReadOnly = true; - this.FontFamily = "Cascadia Code,Consolas,Menlo,Monospace"; - } + public CodeBlockViewer() + { + this._registryOptions = new RegistryOptions(ThemeName.DarkPlus); + this._textMateInstallation = this.InstallTextMate(_registryOptions); + } - static CodeBlockViewer() - { - CodeProperty.Changed.Subscribe(OnCodeChanged); - } + protected override void OnInitialized() + { + base.OnInitialized(); + this.Background = new SolidColorBrush(Color.FromRgb(30, 30, 30)); + this.TextArea.TextView.Margin = new Thickness(10, 0); + this.ShowLineNumbers = true; + this.IsReadOnly = true; + this.FontFamily = "Cascadia Code,Consolas,Menlo,Monospace"; + } - private static void OnCodeChanged(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as CodeBlockViewer)?.OnCodeChanged((ViewModels.MarkdownCode)e.OldValue, (ViewModels.MarkdownCode)e.NewValue); - } + static CodeBlockViewer() + { + CodeProperty.Changed.Subscribe(OnCodeChanged); + } - private void OnCodeChanged(MarkdownCode newValue, MarkdownCode NewValue) - { - SetMarkdown(NewValue); - } + private static void OnCodeChanged(AvaloniaPropertyChangedEventArgs e) + { + (e.Sender as CodeBlockViewer)?.OnCodeChanged((ViewModels.MarkdownCode)e.OldValue, + (ViewModels.MarkdownCode)e.NewValue); + } + private void OnCodeChanged(MarkdownCode newValue, MarkdownCode NewValue) + { + SetMarkdown(NewValue); + } - private void SetMarkdown(ViewModels.MarkdownCode md) - { - if (md is not null) + private void SetMarkdown(ViewModels.MarkdownCode md) { - if (this.Document is not null) - { - this.Document.Text = md.rawValue; - } - else + if (md is not null) { - this.Document = new TextDocument(md.rawValue); - } - if (_currentLang != md.lang) - { - _currentLang = md.lang; - CodeBlockViewer _textEditor = this; - - if (_registryOptions.GetLanguageByExtension("." + md.lang) is { } languageByExtension) + if (this.Document is not null) { - _textMateInstallation.SetGrammar(_registryOptions.GetScopeByLanguageId(languageByExtension.Id)); - - + this.Document.Text = md.rawValue; + } + else + { + this.Document = new TextDocument(md.rawValue); } - - if (this.TextArea.TextView.LineTransformers.OfType().FirstOrDefault() is { } existing) + if (_currentLang != md.lang) { - this.TextArea.TextView.LineTransformers.Remove(existing); + _currentLang = md.lang; + CodeBlockViewer _textEditor = this; + + if (_registryOptions.GetLanguageByExtension("." + md.lang) is { } languageByExtension) + { + _textMateInstallation.SetGrammar(_registryOptions.GetScopeByLanguageId(languageByExtension.Id)); + } + + if (this.TextArea.TextView.LineTransformers.OfType().FirstOrDefault() is + { } existing) + { + this.TextArea.TextView.LineTransformers.Remove(existing); + } + + this.TextArea.TextView.LineTransformers.Add(new DiffLineBackgroundRenderer(md.oldTextLines)); + + // Adding syntax highlighter for C# + if (md.lang == "csharp") + { + this.TextArea.TextView.LineTransformers.Add(new CSharpSyntaxHighlighter()); + } } - this.TextArea.TextView.LineTransformers.Add(new DiffLineBackgroundRenderer(md.oldTextLines)); + } + else + { + this.Document = new TextDocument(""); } } - else - { - this.Document = new TextDocument(""); - } - - } - - private string? _currentLang; - public ViewModels.MarkdownCode Code - { - get { return GetValue(CodeProperty); } - set { SetValue(CodeProperty, value); } - } - - public static readonly StyledProperty CodeProperty = AvaloniaProperty.Register(nameof(Code)); - private readonly TextMate.Installation _textMateInstallation; - private readonly RegistryOptions _registryOptions; -} + private string? _currentLang; - -public class DiffLineBackgroundRenderer : GenericLineTransformer -{ - private readonly List? _mdOldTextLines; - - - protected override void TransformLine(DocumentLine line, ITextRunConstructionContext context) - { - if (_mdOldTextLines is { } li) + public ViewModels.MarkdownCode Code { - var index = line.LineNumber -1; - if (index is > -1 && index < li.Count) - { - var brush = li[index].Type switch - { - ChangeType.Deleted => new SolidColorBrush(Colors.Red, 0.5), - ChangeType.Inserted => new SolidColorBrush(Colors.Green, 0.5), - ChangeType.Imaginary => new SolidColorBrush(Colors.Gray, 0.5), - ChangeType.Modified => new SolidColorBrush(Colors.Orange, 0.5), - _ => null - }; - if (brush != null) - { - this.SetTextStyle(line,0, line.Length, null, brush, context.GlobalTextRunProperties.Typeface.Style, context.GlobalTextRunProperties.Typeface.Weight, false); - } - } - + get { return GetValue(CodeProperty); } + set { SetValue(CodeProperty, value); } } - - - } - public DiffLineBackgroundRenderer(List? mdOldTextLines) : base((_)=>{}) - { - _mdOldTextLines = mdOldTextLines; - } -} + public static readonly StyledProperty CodeProperty = + AvaloniaProperty.Register(nameof(Code)); + private readonly TextMate.Installation _textMateInstallation; + private readonly RegistryOptions _registryOptions; + } +} \ No newline at end of file diff --git a/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs new file mode 100644 index 0000000..c43e399 --- /dev/null +++ b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs @@ -0,0 +1,87 @@ +using System; +using System.Text.RegularExpressions; +using Avalonia.Media; +using AvaloniaEdit.Document; +using AvaloniaEdit.Rendering; + +namespace WireMockInspector.Views.Transformer; + +public class CSharpSyntaxHighlighter : DocumentColorizingTransformer +{ + // List of C# keywords to highlight + private static readonly string[] Keywords = new string[] + { + "var", "public", "private", "protected", "class", "void", "int", "string", "bool", "new", "namespace", "using" + }; + + // Matches method calls like `.UsingMethod("....")` + private static readonly Regex MethodCallRegex = new Regex(@"\.(\w+)\(", RegexOptions.Compiled); + + // Matches string literals + private static readonly Regex StringLiteralRegex = new Regex(@"""(\\.|[^\\\""])*""", RegexOptions.Compiled); + + // Custom list for additional class names to be highlighted + private static readonly string[] CustomClasses = new string[] + { + "MappingBuilder", "Request", "Response" + }; + + // Colors + private static readonly SolidColorBrush KeywordBrush = new SolidColorBrush(Colors.Blue); + private static readonly SolidColorBrush MethodBrush = new SolidColorBrush(Colors.Yellow); + private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Colors.LightBlue); + private static readonly SolidColorBrush ClassNameBrush = new SolidColorBrush(Colors.Teal); + + protected override void ColorizeLine(DocumentLine line) + { + var lineText = CurrentContext.Document.GetText(line); + int lineStartOffset = line.Offset; + + // Highlight keywords + foreach (var keyword in Keywords) + { + HighlightWord(lineText, lineStartOffset, keyword, KeywordBrush); + } + + // Highlight method calls + foreach (Match match in MethodCallRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index + 1, // Start offset (skip initial '.') + lineStartOffset + match.Index + match.Length - 1, // End offset (skip '(') + element => element.TextRunProperties.SetForegroundBrush(MethodBrush)); + } + + // Highlight string literals + foreach (Match match in StringLiteralRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(StringLiteralBrush)); + } + + // Highlight custom class names + foreach (var className in CustomClasses) + { + HighlightWord(lineText, lineStartOffset, className, ClassNameBrush); + } + } + + private void HighlightWord(string text, int offset, string word, SolidColorBrush brush) + { + int start = 0; + while ((start = text.IndexOf(word, start, StringComparison.Ordinal)) >= 0) + { + if ((start == 0 || !char.IsLetterOrDigit(text[start - 1])) && + (start + word.Length == text.Length || !char.IsLetterOrDigit(text[start + word.Length]))) + { + ChangeLinePart( + offset + start, // Start offset + offset + start + word.Length, // End offset + element => element.TextRunProperties.SetForegroundBrush(brush)); + } + start += word.Length; + } + } +} \ No newline at end of file diff --git a/src/WireMockInspector/Views/Transformer/DiffLineBackgroundRenderer.cs b/src/WireMockInspector/Views/Transformer/DiffLineBackgroundRenderer.cs new file mode 100644 index 0000000..fc86e05 --- /dev/null +++ b/src/WireMockInspector/Views/Transformer/DiffLineBackgroundRenderer.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Avalonia.Media; +using AvaloniaEdit.Document; +using AvaloniaEdit.Rendering; +using AvaloniaEdit.TextMate; +using DiffPlex.DiffBuilder.Model; + +namespace WireMockInspector.Views.Transformer; + +public class DiffLineBackgroundRenderer : GenericLineTransformer +{ + private readonly List? _mdOldTextLines; + + protected override void TransformLine(DocumentLine line, ITextRunConstructionContext context) + { + if (_mdOldTextLines is { } li) + { + var index = line.LineNumber - 1; + if (index is > -1 && index < li.Count) + { + var brush = li[index].Type switch + { + ChangeType.Deleted => new SolidColorBrush(Colors.Red, 0.5), + ChangeType.Inserted => new SolidColorBrush(Colors.Green, 0.5), + ChangeType.Imaginary => new SolidColorBrush(Colors.Gray, 0.5), + ChangeType.Modified => new SolidColorBrush(Colors.Orange, 0.5), + _ => null + }; + if (brush != null) + { + SetTextStyle(line, 0, line.Length, null, brush, + context.GlobalTextRunProperties.Typeface.Style, + context.GlobalTextRunProperties.Typeface.Weight, false); + } + } + } + } + + public DiffLineBackgroundRenderer(List? mdOldTextLines) : base((_) => { }) + { + _mdOldTextLines = mdOldTextLines; + } +} \ No newline at end of file From 5c031caf792e437a09e727be7473ae48f27dce71 Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Fri, 12 Jul 2024 13:41:29 +0200 Subject: [PATCH 07/13] Cleanup --- .../Views/CodeBlockViewer.cs | 6 +- .../Transformer/CSharpSyntaxHighlighter.cs | 8 +- .../Transformer/JsonSyntaxHighlighter.cs | 75 +++++++++++++++++++ .../WireMockInspector.csproj | 7 -- 4 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs diff --git a/src/WireMockInspector/Views/CodeBlockViewer.cs b/src/WireMockInspector/Views/CodeBlockViewer.cs index 8ddb4b3..71e1750 100644 --- a/src/WireMockInspector/Views/CodeBlockViewer.cs +++ b/src/WireMockInspector/Views/CodeBlockViewer.cs @@ -77,12 +77,14 @@ private void SetMarkdown(ViewModels.MarkdownCode md) } this.TextArea.TextView.LineTransformers.Add(new DiffLineBackgroundRenderer(md.oldTextLines)); - - // Adding syntax highlighter for C# if (md.lang == "csharp") { this.TextArea.TextView.LineTransformers.Add(new CSharpSyntaxHighlighter()); } + if (md.lang == "json") + { + this.TextArea.TextView.LineTransformers.Add(new JsonSyntaxHighlighter()); + } } } else diff --git a/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs index c43e399..28e5633 100644 --- a/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs +++ b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs @@ -27,10 +27,10 @@ public class CSharpSyntaxHighlighter : DocumentColorizingTransformer }; // Colors - private static readonly SolidColorBrush KeywordBrush = new SolidColorBrush(Colors.Blue); - private static readonly SolidColorBrush MethodBrush = new SolidColorBrush(Colors.Yellow); - private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Colors.LightBlue); - private static readonly SolidColorBrush ClassNameBrush = new SolidColorBrush(Colors.Teal); + private static readonly SolidColorBrush KeywordBrush = new SolidColorBrush(Color.Parse("#3988D6")); + private static readonly SolidColorBrush MethodBrush = new SolidColorBrush(Color.Parse("#ADD795")); + private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Color.Parse("#D6936B")); + private static readonly SolidColorBrush ClassNameBrush = new SolidColorBrush(Color.Parse("#41C2B0")); protected override void ColorizeLine(DocumentLine line) { diff --git a/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs new file mode 100644 index 0000000..60f9109 --- /dev/null +++ b/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs @@ -0,0 +1,75 @@ +using System.Text.RegularExpressions; +using Avalonia.Media; +using AvaloniaEdit.Document; +using AvaloniaEdit.Rendering; + +namespace WireMockInspector.Views.Transformer +{ + public class JsonSyntaxHighlighter : DocumentColorizingTransformer + { + // Regex patterns for different JSON components + private static readonly Regex PropertyNameRegex = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*:)", RegexOptions.Compiled); + private static readonly Regex StringLiteralRegex = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*[,}\]])(?!\s*:)", RegexOptions.Compiled); + private static readonly Regex NumberRegex = new Regex(@"\b\d+(\.\d+)?\b", RegexOptions.Compiled); + private static readonly Regex BooleanNullRegex = new Regex(@"\b(true|false|null)\b", RegexOptions.Compiled); + private static readonly Regex PunctuationRegex = new Regex(@"[\[\]{}:,]", RegexOptions.Compiled); + + // Colors using the same palette as CSharpSyntaxHighlighter + private static readonly SolidColorBrush PropertyNameBrush = new SolidColorBrush(Color.Parse("#ADD795")); + private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Color.Parse("#D6936B")); + private static readonly SolidColorBrush NumberBrush = new SolidColorBrush(Color.Parse("#ADD795")); + private static readonly SolidColorBrush BooleanNullBrush = new SolidColorBrush(Color.Parse("#41C2B0")); + private static readonly SolidColorBrush PunctuationBrush = new SolidColorBrush(Color.Parse("#3988D6")); + + protected override void ColorizeLine(DocumentLine line) + { + var lineText = CurrentContext.Document.GetText(line); + int lineStartOffset = line.Offset; + + // Highlight property names + foreach (Match match in PropertyNameRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(PropertyNameBrush)); + } + + // Highlight string literals + foreach (Match match in StringLiteralRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(StringLiteralBrush)); + } + + // Highlight numbers + foreach (Match match in NumberRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(NumberBrush)); + } + + // Highlight boolean and null values + foreach (Match match in BooleanNullRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(BooleanNullBrush)); + } + + // Highlight punctuation + foreach (Match match in PunctuationRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(PunctuationBrush)); + } + } + } +} \ No newline at end of file diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index 74db7f3..11310c9 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -24,13 +24,6 @@ - - - - - - - From a2d21257b57af8a46b4c004389600f84ae7f7a10 Mon Sep 17 00:00:00 2001 From: Mark Heinis Date: Fri, 12 Jul 2024 13:42:01 +0200 Subject: [PATCH 08/13] Cleanup --- src/WireMockInspector/WireMockInspector.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index 11310c9..f66f625 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -26,7 +26,6 @@ - From d98826fe806f14afd1f0ac11e8bc19c407b2a0cb Mon Sep 17 00:00:00 2001 From: Galadril Date: Fri, 12 Jul 2024 23:40:58 +0200 Subject: [PATCH 09/13] Refactor converters and add XML highlighting - Removed `HttpStatusCodeToDescriptionConverter`, `RequestMethodToColorConverter`, and `ResponseCodeToColorConverter` from `WireMockInspector.ViewModels`. - Re-added the above converters in `WireMockInspector.Converters` for better modularity. - Updated namespace references in XAML files from `viewModels` to `converters`. - Introduced `XmlSyntaxHighlighter` in `WireMockInspector.Views.Transformer` for XML syntax highlighting. - Enhanced `CodeBlockViewer.cs` to support XML highlighting, improving readability. - Adjusted XAML files to use the new converter namespace, ensuring UI consistency. --- .../HttpStatusCodeToDescriptionConverter.cs | 2 +- .../RequestMethodToColorConverter.cs | 4 +- .../ResponseCodeToColorConverter.cs | 4 +- .../ViewModels/MainWindowViewModel.cs | 5 +- .../Views/CodeBlockViewer.cs | 6 +- src/WireMockInspector/Views/MappingPage.axaml | 3 +- src/WireMockInspector/Views/RequestList.axaml | 5 +- src/WireMockInspector/Views/RequestLogs.axaml | 5 +- .../Views/Transformer/XmlSyntaxHighlighter.cs | 70 +++++++++++++++++++ 9 files changed, 92 insertions(+), 12 deletions(-) rename src/WireMockInspector/{ViewModels => Converters}/HttpStatusCodeToDescriptionConverter.cs (98%) rename src/WireMockInspector/{ViewModels => Converters}/RequestMethodToColorConverter.cs (97%) rename src/WireMockInspector/{ViewModels => Converters}/ResponseCodeToColorConverter.cs (92%) create mode 100644 src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs diff --git a/src/WireMockInspector/ViewModels/HttpStatusCodeToDescriptionConverter.cs b/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs similarity index 98% rename from src/WireMockInspector/ViewModels/HttpStatusCodeToDescriptionConverter.cs rename to src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs index 59cba87..c8c81eb 100644 --- a/src/WireMockInspector/ViewModels/HttpStatusCodeToDescriptionConverter.cs +++ b/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs @@ -2,7 +2,7 @@ using System.Globalization; using Avalonia.Data.Converters; -namespace WireMockInspector.ViewModels; +namespace WireMockInspector.Converters; public class HttpStatusCodeToDescriptionConverter : IValueConverter { diff --git a/src/WireMockInspector/ViewModels/RequestMethodToColorConverter.cs b/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs similarity index 97% rename from src/WireMockInspector/ViewModels/RequestMethodToColorConverter.cs rename to src/WireMockInspector/Converters/RequestMethodToColorConverter.cs index 719f1fe..b95c657 100644 --- a/src/WireMockInspector/ViewModels/RequestMethodToColorConverter.cs +++ b/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs @@ -3,12 +3,12 @@ using Avalonia.Data.Converters; using Avalonia.Media; -namespace WireMockInspector.ViewModels; +namespace WireMockInspector.Converters; public class RequestMethodToColorConverter : IValueConverter { public static readonly RequestMethodToColorConverter Instance = new RequestMethodToColorConverter(); - + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string requestMethod) diff --git a/src/WireMockInspector/ViewModels/ResponseCodeToColorConverter.cs b/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs similarity index 92% rename from src/WireMockInspector/ViewModels/ResponseCodeToColorConverter.cs rename to src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs index dca94a4..27bd8c9 100644 --- a/src/WireMockInspector/ViewModels/ResponseCodeToColorConverter.cs +++ b/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs @@ -3,7 +3,7 @@ using Avalonia.Data.Converters; using Avalonia.Media; -namespace WireMockInspector.ViewModels; +namespace WireMockInspector.Converters; public class ResponseCodeToColorConverter : IValueConverter { @@ -14,7 +14,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { var color = (statusCode / 100) switch { - 1 => Color.FromRgb(173, 216, 230), + 1 => Color.FromRgb(173, 216, 230), 2 => Color.FromRgb(0, 150, 0), 3 => Color.FromRgb(255, 165, 0), 4 => Color.FromRgb(255, 0, 0), diff --git a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs index 592b59a..6458c04 100644 --- a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs +++ b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs @@ -21,6 +21,7 @@ using WireMock.Admin.Settings; using WireMock.Client; using WireMock.Types; +using WireMockInspector.Converters; using ChangeType = DiffPlex.DiffBuilder.Model.ChangeType; using Formatting = Newtonsoft.Json.Formatting; @@ -966,6 +967,7 @@ private static MatchDetailsViewModel MapToRequestBodyViewModel(RequestViewModel { "String" or "FormUrlEncoded" => WrapBodyInMarkdown(req.Raw.Request.Body?? string.Empty), "Json" => new MarkdownCode("json", req.Raw.Request.BodyAsJson?.ToString() ?? string.Empty), + "Xml" => new MarkdownCode("xml", req.Raw.Request.Body?.ToString() ?? string.Empty), "Bytes" => new MarkdownCode("plaintext", req.Raw.Request.BodyAsBytes?.ToString()?? string.Empty), "File" => new MarkdownCode("plaintext","[FileContent]"), _ => new MarkdownCode("plaintext", "") @@ -1167,7 +1169,8 @@ private static MarkdownCode GetActualForRequestBody(RequestViewModel req) return req.Raw.Response?.DetectedBodyType.ToString() switch { "Json" => new MarkdownCode("json",req.Raw.Response.BodyAsJson?.ToString() ?? string.Empty), - "Bytes" => new MarkdownCode("plaintext", req.Raw.Response.BodyAsBytes?.ToString()?? string.Empty), + "Xml" => new MarkdownCode("xml", req.Raw.Response.Body?.ToString() ?? string.Empty), + "Bytes" => new MarkdownCode("plaintext", req.Raw.Response.BodyAsBytes?.ToString() ?? string.Empty), "File" => new MarkdownCode("plaintext",req.Raw.Response.BodyAsFile?.ToString() ?? string.Empty), _ => WrapBodyInMarkdown( req.Raw.Response?.Body?? string.Empty), }; diff --git a/src/WireMockInspector/Views/CodeBlockViewer.cs b/src/WireMockInspector/Views/CodeBlockViewer.cs index 71e1750..25a9b08 100644 --- a/src/WireMockInspector/Views/CodeBlockViewer.cs +++ b/src/WireMockInspector/Views/CodeBlockViewer.cs @@ -81,10 +81,14 @@ private void SetMarkdown(ViewModels.MarkdownCode md) { this.TextArea.TextView.LineTransformers.Add(new CSharpSyntaxHighlighter()); } - if (md.lang == "json") + else if (md.lang == "json") { this.TextArea.TextView.LineTransformers.Add(new JsonSyntaxHighlighter()); } + else if (md.lang == "xml") + { + this.TextArea.TextView.LineTransformers.Add(new XmlSyntaxHighlighter()); + } } } else diff --git a/src/WireMockInspector/Views/MappingPage.axaml b/src/WireMockInspector/Views/MappingPage.axaml index a60e021..a4f447c 100644 --- a/src/WireMockInspector/Views/MappingPage.axaml +++ b/src/WireMockInspector/Views/MappingPage.axaml @@ -3,6 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" xmlns:views="clr-namespace:WireMockInspector.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="viewModels:MainWindowViewModel" @@ -84,7 +85,7 @@ - + diff --git a/src/WireMockInspector/Views/RequestList.axaml b/src/WireMockInspector/Views/RequestList.axaml index 4fdb97c..99e2d46 100644 --- a/src/WireMockInspector/Views/RequestList.axaml +++ b/src/WireMockInspector/Views/RequestList.axaml @@ -3,6 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="viewModels:MainWindowViewModel" x:Class="WireMockInspector.Views.RequestList"> @@ -29,11 +30,11 @@ - + - + diff --git a/src/WireMockInspector/Views/RequestLogs.axaml b/src/WireMockInspector/Views/RequestLogs.axaml index bb86863..e3c1130 100644 --- a/src/WireMockInspector/Views/RequestLogs.axaml +++ b/src/WireMockInspector/Views/RequestLogs.axaml @@ -3,6 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" xmlns:views="clr-namespace:WireMockInspector.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -15,11 +16,11 @@ - + - + diff --git a/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs new file mode 100644 index 0000000..8ab8560 --- /dev/null +++ b/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs @@ -0,0 +1,70 @@ +using System.Text.RegularExpressions; +using Avalonia.Media; +using AvaloniaEdit.Document; +using AvaloniaEdit.Rendering; + +namespace WireMockInspector.Views.Transformer +{ + public class XmlSyntaxHighlighter : DocumentColorizingTransformer + { + // Regex patterns for XML elements + private static readonly Regex TagRegex = new Regex(@"<(/?[\w\s]*)(.*?)>", RegexOptions.Compiled); + private static readonly Regex AttributeRegex = new Regex(@"\b(\w+)(\s*=\s*)(""[^""]*"")", RegexOptions.Compiled); + private static readonly Regex CommentRegex = new Regex(@"", RegexOptions.Compiled); + + // Colors + private static readonly SolidColorBrush TagBrush = new SolidColorBrush(Color.Parse("#3988D6")); + private static readonly SolidColorBrush AttributeNameBrush = new SolidColorBrush(Color.Parse("#41C2B0")); + private static readonly SolidColorBrush AttributeValueBrush = new SolidColorBrush(Color.Parse("#D6936B")); + private static readonly SolidColorBrush CommentBrush = new SolidColorBrush(Color.Parse("#ADD795")); + + protected override void ColorizeLine(DocumentLine line) + { + var lineText = CurrentContext.Document.GetText(line); + int lineStartOffset = line.Offset; + + // Highlight XML tags + foreach (Match match in TagRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Groups[1].Length + 1, // Tag name + element => element.TextRunProperties.SetForegroundBrush(TagBrush)); + if (match.Groups[2].Success) + { + HighlightAttributes(lineText, lineStartOffset + match.Index + match.Groups[1].Length + 1, match.Groups[2].Value); + } + } + + // Highlight comments + foreach (Match match in CommentRegex.Matches(lineText)) + { + ChangeLinePart( + lineStartOffset + match.Index, + lineStartOffset + match.Index + match.Length, + element => element.TextRunProperties.SetForegroundBrush(CommentBrush)); + } + } + + private void HighlightAttributes(string text, int offset, string attributesText) + { + foreach (Match match in AttributeRegex.Matches(attributesText)) + { + // Highlight attribute name + ChangeLinePart( + offset + match.Index, + offset + match.Index + match.Groups[1].Length, + element => element.TextRunProperties.SetForegroundBrush(AttributeNameBrush)); + + // Highlight attribute value + if (match.Groups[3].Success) + { + ChangeLinePart( + offset + match.Index + match.Groups[1].Length + match.Groups[2].Length, + offset + match.Index + match.Groups[1].Length + match.Groups[2].Length + match.Groups[3].Length, + element => element.TextRunProperties.SetForegroundBrush(AttributeValueBrush)); + } + } + } + } +} From 9c372bf7ed8218092f612a7fa61930bf162c1e6f Mon Sep 17 00:00:00 2001 From: Galadril Date: Fri, 12 Jul 2024 23:49:12 +0200 Subject: [PATCH 10/13] Refactor and enhance converter usage This commit represents a significant refactoring and enhancement of the converter usage within the WireMockInspector application. Key changes include the relocation of converter classes (`EnumItemsConverter`, `GraphConverter`, `StringListConverter`, `StringMatchConverter`) from the `WireMockInspector.ViewModels` namespace to a new `WireMockInspector.Converters` namespace, improving modularity and clarity. New converter classes have been added, encapsulating the conversion logic for better reusability. Updates in various XAML files reflect these namespace changes, ensuring correct converter references in data-binding expressions. Additionally, unused `using` directives have been cleaned up, and a new `SettingsWrapperTemplateSelector` class has been introduced to enhance dynamic UI generation capabilities. These changes collectively aim to improve the maintainability and functionality of the application's UI data presentation. --- .../Converters/EnumItemsConverter.cs | 32 +++++ .../GraphConverter.cs | 29 +++-- .../Converters/StringListConverter.cs | 29 +++++ .../Converters/StringMatchConverter.cs | 22 ++++ .../SettingsWrapperTemplateSelector.cs | 50 ++++++++ .../ViewModels/StringMatchConverter.cs | 121 ------------------ src/WireMockInspector/Views/MainWindow.axaml | 5 +- .../Views/MainWindow.axaml.cs | 1 - src/WireMockInspector/Views/MappingPage.axaml | 8 +- .../Views/RequestDetails.axaml | 7 +- .../Views/ScenarioPage.axaml | 9 +- 11 files changed, 164 insertions(+), 149 deletions(-) create mode 100644 src/WireMockInspector/Converters/EnumItemsConverter.cs rename src/WireMockInspector/{ViewModels => Converters}/GraphConverter.cs (91%) create mode 100644 src/WireMockInspector/Converters/StringListConverter.cs create mode 100644 src/WireMockInspector/Converters/StringMatchConverter.cs create mode 100644 src/WireMockInspector/ViewModels/SettingsWrapperTemplateSelector.cs delete mode 100644 src/WireMockInspector/ViewModels/StringMatchConverter.cs diff --git a/src/WireMockInspector/Converters/EnumItemsConverter.cs b/src/WireMockInspector/Converters/EnumItemsConverter.cs new file mode 100644 index 0000000..9a81c93 --- /dev/null +++ b/src/WireMockInspector/Converters/EnumItemsConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace WireMockInspector.Converters; + +public class EnumItemsConverter : IValueConverter +{ + public static readonly EnumItemsConverter Instance = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is Type t) + { + if (t.Name == "Nullable`1" && t.GenericTypeArguments[0].IsEnum) + { + t = t.GenericTypeArguments[0]; + } + + if (t.IsEnum) + { + return t.GetEnumValues(); + } + } + return Array.Empty(); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; + } +} diff --git a/src/WireMockInspector/ViewModels/GraphConverter.cs b/src/WireMockInspector/Converters/GraphConverter.cs similarity index 91% rename from src/WireMockInspector/ViewModels/GraphConverter.cs rename to src/WireMockInspector/Converters/GraphConverter.cs index a189bc8..906838d 100644 --- a/src/WireMockInspector/ViewModels/GraphConverter.cs +++ b/src/WireMockInspector/Converters/GraphConverter.cs @@ -12,19 +12,20 @@ using Microsoft.Msagl.Miscellaneous; using Svg; using Svg.Model; +using WireMockInspector.ViewModels; using Color = Microsoft.Msagl.Drawing.Color; -namespace WireMockInspector.ViewModels; +namespace WireMockInspector.Converters; public class GraphConverter : IValueConverter { - public static readonly GraphConverter Instance = new (); + public static readonly GraphConverter Instance = new(); - private class EndNode : Microsoft.Msagl.Drawing.Node + private class EndNode : Node { public EndNode(string id) : base(id) { - Attr.Shape = Microsoft.Msagl.Drawing.Shape.Circle; + Attr.Shape = Shape.Circle; } } @@ -36,16 +37,16 @@ public EndNode(string id) : base(id) CurrentState: var currentState }) { - Microsoft.Msagl.Drawing.Graph drawingGraph = new Microsoft.Msagl.Drawing.Graph("graph"); + Graph drawingGraph = new Graph("graph"); var currentNodeColor = new Color(0, 120, 215); var nodeCache = new Dictionary(); - foreach (var n in transitions.SelectMany(x => new[] {x.From, x.To}).OfType().DistinctBy(x=>x.Id)) + foreach (var n in transitions.SelectMany(x => new[] { x.From, x.To }).OfType().DistinctBy(x => x.Id)) { - + var node = n switch { ScenarioEdgeNode => new EndNode(n.Id), - _ => new Microsoft.Msagl.Drawing.Node(n.Id) + _ => new Node(n.Id) }; nodeCache[n.Id] = node; @@ -62,19 +63,19 @@ public EndNode(string id) : base(id) foreach (var scenarioTransition in transitions) { var edge = drawingGraph.AddEdge(scenarioTransition.From.Id, scenarioTransition.Id, scenarioTransition.To.Id); - + if (scenarioTransition.Hit) { if (nodeCache.TryGetValue(scenarioTransition.From.Id, out var fromNode)) { - if (scenarioTransition.From is ScenarioEdgeNode {Current: true}) + if (scenarioTransition.From is ScenarioEdgeNode { Current: true }) { fromNode.Attr.FillColor = currentNodeColor; } else { - fromNode.Attr.FillColor = visitedColor; - } + fromNode.Attr.FillColor = visitedColor; + } } if (nodeCache.TryGetValue(scenarioTransition.To.Id, out var toNode) && toNode is EndNode) @@ -82,7 +83,7 @@ public EndNode(string id) : base(id) toNode.Attr.FillColor = currentNodeColor; } } - + if (scenarioTransition.Id == currentTransition) { edge.Attr.Color = Color.Orange; @@ -109,7 +110,7 @@ public EndNode(string id) : base(id) { if (n.Attr.Id == currentState) { - + n.Attr.FillColor = currentNodeColor; } diff --git a/src/WireMockInspector/Converters/StringListConverter.cs b/src/WireMockInspector/Converters/StringListConverter.cs new file mode 100644 index 0000000..7ee1c37 --- /dev/null +++ b/src/WireMockInspector/Converters/StringListConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace WireMockInspector.Converters; + +public class StringListConverter : IValueConverter +{ + public static readonly StringListConverter Instance = new(); + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is string[] sl) + { + return string.Join(",", sl); + } + + return string.Empty; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is string s) + { + return s.Split(","); + } + + return null; + } +} diff --git a/src/WireMockInspector/Converters/StringMatchConverter.cs b/src/WireMockInspector/Converters/StringMatchConverter.cs new file mode 100644 index 0000000..8a23ad4 --- /dev/null +++ b/src/WireMockInspector/Converters/StringMatchConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace WireMockInspector.Converters; + +public class StringMatchConverter : IValueConverter +{ + public static readonly StringMatchConverter Instance = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var parameterString = parameter?.ToString() ?? "null"; + var valueString = value?.ToString() ?? "null"; + return parameterString.Equals(valueString, StringComparison.InvariantCultureIgnoreCase); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/src/WireMockInspector/ViewModels/SettingsWrapperTemplateSelector.cs b/src/WireMockInspector/ViewModels/SettingsWrapperTemplateSelector.cs new file mode 100644 index 0000000..edf7d19 --- /dev/null +++ b/src/WireMockInspector/ViewModels/SettingsWrapperTemplateSelector.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Metadata; + +namespace WireMockInspector.ViewModels; + +public class SettingsWrapperTemplateSelector : IDataTemplate +{ + [Content] + public Dictionary AvailableTemplates { get; } = new(); + + public Control? Build(object? param) + { + + if (param is SettingsWrapper w) + { + var templateName = w.Type switch + { + { IsEnum: true } => "Enum", + { Name: "Nullable`1" } n when n.GenericTypeArguments[0].IsEnum => "Enum", + var x => x.ToString() switch + { + "System.String" => "string", + "System.Boolean" => "bool", + "System.Nullable`1[System.Boolean]" => "bool?", + "System.Int32" => "int", + "System.Nullable`1[System.Int32]" => "int?", + "System.String[]" => "stringlist", + _ => "object" + } + }; + + return AvailableTemplates[templateName].Build(param); + } + + throw new ArgumentNullException(nameof(param)); + } + + public bool Match(object? data) + { + if (data is SettingsWrapper w) + { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/WireMockInspector/ViewModels/StringMatchConverter.cs b/src/WireMockInspector/ViewModels/StringMatchConverter.cs deleted file mode 100644 index 1a5f447..0000000 --- a/src/WireMockInspector/ViewModels/StringMatchConverter.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Avalonia.Controls; -using Avalonia.Controls.Templates; -using Avalonia.Data.Converters; -using Avalonia.Metadata; - -namespace WireMockInspector.ViewModels; - -public class StringMatchConverter : IValueConverter -{ - public static readonly StringMatchConverter Instance = new(); - - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - var parameterString = parameter?.ToString() ?? "null"; - var valueString = value?.ToString() ?? "null"; - return parameterString.Equals(valueString, StringComparison.InvariantCultureIgnoreCase); - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} - -public class EnumItemsConverter: IValueConverter -{ - public static readonly EnumItemsConverter Instance = new(); - - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is Type t) - { - if (t.Name == "Nullable`1" && t.GenericTypeArguments[0].IsEnum) - { - t = t.GenericTypeArguments[0]; - } - - if (t.IsEnum) - { - return t.GetEnumValues(); - } - } - return Array.Empty(); - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - return null; - } -} - -public class StringListConverter: IValueConverter -{ - public static readonly StringListConverter Instance = new(); - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is string[] sl) - { - return string.Join(",", sl); - } - - return string.Empty; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is string s) - { - return s.Split(","); - } - - return null; - } -} - - -public class SettingsWrapperTemplateSelector : IDataTemplate -{ - [Content] - public Dictionary AvailableTemplates { get; } = new(); - - public Control? Build(object? param) - { - - if (param is SettingsWrapper w) - { - var templateName = w.Type switch - { - {IsEnum: true} => "Enum", - {Name: "Nullable`1" } n when n.GenericTypeArguments[0].IsEnum => "Enum", - var x => x.ToString() switch - { - "System.String" => "string", - "System.Boolean" => "bool", - "System.Nullable`1[System.Boolean]" => "bool?", - "System.Int32" => "int", - "System.Nullable`1[System.Int32]" => "int?", - "System.String[]" => "stringlist", - _ => "object" - } - }; - - return AvailableTemplates[templateName].Build(param); - } - - throw new ArgumentNullException(nameof(param)); - } - - public bool Match(object? data) - { - if (data is SettingsWrapper w) - { - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/src/WireMockInspector/Views/MainWindow.axaml b/src/WireMockInspector/Views/MainWindow.axaml index 79512ab..ecc001f 100644 --- a/src/WireMockInspector/Views/MainWindow.axaml +++ b/src/WireMockInspector/Views/MainWindow.axaml @@ -6,6 +6,7 @@ xmlns:views="clr-namespace:WireMockInspector.Views" xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="650" x:Class="WireMockInspector.Views.MainWindow" @@ -89,14 +90,14 @@ - + - + diff --git a/src/WireMockInspector/Views/MainWindow.axaml.cs b/src/WireMockInspector/Views/MainWindow.axaml.cs index 7fc1516..ad8d61e 100644 --- a/src/WireMockInspector/Views/MainWindow.axaml.cs +++ b/src/WireMockInspector/Views/MainWindow.axaml.cs @@ -20,7 +20,6 @@ public partial class MainWindow : ReactiveWindow public MainWindow() { - InitializeComponent(); this.WhenActivated((disposables) => { diff --git a/src/WireMockInspector/Views/MappingPage.axaml b/src/WireMockInspector/Views/MappingPage.axaml index a4f447c..31341eb 100644 --- a/src/WireMockInspector/Views/MappingPage.axaml +++ b/src/WireMockInspector/Views/MappingPage.axaml @@ -61,9 +61,9 @@ @@ -141,7 +141,7 @@ - + diff --git a/src/WireMockInspector/Views/RequestDetails.axaml b/src/WireMockInspector/Views/RequestDetails.axaml index 14641cc..bc5e824 100644 --- a/src/WireMockInspector/Views/RequestDetails.axaml +++ b/src/WireMockInspector/Views/RequestDetails.axaml @@ -5,6 +5,7 @@ xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" xmlns:generic="clr-namespace:System.Collections.Generic;assembly=System.Collections" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" xmlns:views="clr-namespace:WireMockInspector.Views" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:DataType="viewModels:MatchDetailsList" @@ -47,9 +48,9 @@ diff --git a/src/WireMockInspector/Views/ScenarioPage.axaml b/src/WireMockInspector/Views/ScenarioPage.axaml index 3e359b3..9ceca31 100644 --- a/src/WireMockInspector/Views/ScenarioPage.axaml +++ b/src/WireMockInspector/Views/ScenarioPage.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:WireMockInspector.Views" xmlns:viewModels="clr-namespace:WireMockInspector.ViewModels" + xmlns:converters="clr-namespace:WireMockInspector.Converters" xmlns:md="clr-namespace:Markdown.Avalonia;assembly=Markdown.Avalonia" x:DataType="viewModels:MainWindowViewModel" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" @@ -61,9 +62,9 @@ @@ -88,7 +89,7 @@ - + From c00f8eb2ab8e6aff4f4ac09921707e238b77c32d Mon Sep 17 00:00:00 2001 From: Galadril Date: Fri, 12 Jul 2024 23:51:46 +0200 Subject: [PATCH 11/13] spaces --- src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs index e10f6e0..a564081 100644 --- a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs +++ b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs @@ -133,8 +133,6 @@ public MatchDetailsViewModel() }); - - ReformatActualValue = ReactiveCommand.Create(() => { if (this.ActualValue is MarkdownActualValue { Value: { } rawValue } markdown) @@ -210,6 +208,11 @@ public bool IsJsonMarkdown() { return this.lang == "json"; } + + public bool IsXmlMarkdown() + { + return this.lang == "xml"; + } }; public class KeyValueListActualValue : ActualValue From d27b6b58d4ba259c8e4f1fd1972a30fa436d56dd Mon Sep 17 00:00:00 2001 From: Galadril Date: Fri, 12 Jul 2024 23:53:23 +0200 Subject: [PATCH 12/13] .. --- src/WireMockInspector/WireMockInspector.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index f66f625..fa26f65 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -50,7 +50,7 @@ - + From c495fb046c006ee6ea0fd9442d3ed920ed7c9826 Mon Sep 17 00:00:00 2001 From: Galadril Date: Sat, 13 Jul 2024 14:02:05 +0200 Subject: [PATCH 13/13] Refactor codebase and enhance XML handling - Adopted target-typed `new` expressions across various files for cleaner instantiation. - Enhanced graph drawing in `GraphConverter.cs` with streamlined object creation. - Simplified `XmlDocument` object instantiation in multiple files for conciseness. - Updated singleton patterns in converters and formatters to modern C# practices. - Improved XML content formatting in markdown with `.TryToReformat()` in `MainWindowViewModel.cs`. - Refined syntax highlighting in `CSharpSyntaxHighlighter.cs`, `JsonSyntaxHighlighter.cs`, and `XmlSyntaxHighlighter.cs` with updated regex patterns and object instantiation. - Added `XmlFormatter.cs` for better XML handling, including escaping, pretty-printing, and conversion utilities. - Adjusted `Fluid.Core` package version in `WireMockInspector.csproj` for compatibility or feature requirements. --- .../CodeGenerators/CodeGenerator.cs | 2 +- .../CodeGenerators/XmlFormatter.cs | 71 +++++++++++++++++++ .../Converters/GraphConverter.cs | 4 +- .../HttpStatusCodeToDescriptionConverter.cs | 2 +- .../RequestMethodToColorConverter.cs | 2 +- .../ResponseCodeToColorConverter.cs | 2 +- src/WireMockInspector/ViewModels/JsonDiff.cs | 2 +- .../ViewModels/MainWindowViewModel.cs | 8 +-- .../ViewModels/MatchDetailsViewModel.cs | 16 +++-- .../Transformer/CSharpSyntaxHighlighter.cs | 12 ++-- .../Transformer/JsonSyntaxHighlighter.cs | 20 +++--- .../Views/Transformer/XmlSyntaxHighlighter.cs | 15 ++-- .../WireMockInspector.csproj | 2 +- 13 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 src/WireMockInspector/CodeGenerators/XmlFormatter.cs diff --git a/src/WireMockInspector/CodeGenerators/CodeGenerator.cs b/src/WireMockInspector/CodeGenerators/CodeGenerator.cs index 63144d7..b3912df 100644 --- a/src/WireMockInspector/CodeGenerators/CodeGenerator.cs +++ b/src/WireMockInspector/CodeGenerators/CodeGenerator.cs @@ -45,7 +45,7 @@ public static string ReadEmbeddedResource(string resourceName) using var stream = assembly.GetManifestResourceStream(resourceName); // Make sure the resource is available if (stream == null) throw new FileNotFoundException("The specified embedded resource cannot be found.", resourceName); - using StreamReader reader = new StreamReader(stream); + using StreamReader reader = new(stream); return reader.ReadToEnd(); } public const string DefaultTemplateName = "(default)"; diff --git a/src/WireMockInspector/CodeGenerators/XmlFormatter.cs b/src/WireMockInspector/CodeGenerators/XmlFormatter.cs new file mode 100644 index 0000000..8e044d5 --- /dev/null +++ b/src/WireMockInspector/CodeGenerators/XmlFormatter.cs @@ -0,0 +1,71 @@ +using System; +using System.Globalization; +using System.Security; +using System.Text; +using System.Xml.Linq; + +namespace WireMockInspector.CodeGenerators; + +internal static class XmlFormatter +{ + public static string EscapeXmlValue(string value) + { + // Escapes special XML characters in a string value + return SecurityElement.Escape(value); + } + + public static string PrettyPrintXml(string xml) + { + try + { + var xDocument = XDocument.Parse(xml); + return xDocument.ToString(); + } + catch (Exception) + { + // If parsing fails, return the original XML string + return xml; + } + } + + public static string ToXmlStringLiteral(string value) + { + // Converts a string to a C# literal suitable for XML content + if (string.IsNullOrEmpty(value)) + { + return "\"\""; + } + + var stringBuilder = new StringBuilder(value.Length + 2); + stringBuilder.Append('"'); + foreach (var ch in value) + { + switch (ch) + { + case '\\': stringBuilder.Append(@"\\"); break; + case '\"': stringBuilder.Append("\\\""); break; + case '\0': stringBuilder.Append(@"\0"); break; + case '\a': stringBuilder.Append(@"\a"); break; + case '\b': stringBuilder.Append(@"\b"); break; + case '\f': stringBuilder.Append(@"\f"); break; + case '\n': stringBuilder.Append(@"\n"); break; + case '\r': stringBuilder.Append(@"\r"); break; + case '\t': stringBuilder.Append(@"\t"); break; + case '\v': stringBuilder.Append(@"\v"); break; + default: + if (char.IsControl(ch) || char.IsHighSurrogate(ch) || char.IsLowSurrogate(ch)) + { + stringBuilder.Append(@"\u"); + stringBuilder.Append(((int)ch).ToString("x4", CultureInfo.InvariantCulture)); + } + else + { + stringBuilder.Append(ch); + } + break; + } + } + stringBuilder.Append('"'); + return stringBuilder.ToString(); + } +} diff --git a/src/WireMockInspector/Converters/GraphConverter.cs b/src/WireMockInspector/Converters/GraphConverter.cs index 906838d..48eb1a9 100644 --- a/src/WireMockInspector/Converters/GraphConverter.cs +++ b/src/WireMockInspector/Converters/GraphConverter.cs @@ -37,7 +37,7 @@ public EndNode(string id) : base(id) CurrentState: var currentState }) { - Graph drawingGraph = new Graph("graph"); + Graph drawingGraph = new("graph"); var currentNodeColor = new Color(0, 120, 215); var nodeCache = new Dictionary(); foreach (var n in transitions.SelectMany(x => new[] { x.From, x.To }).OfType().DistinctBy(x => x.Id)) @@ -132,7 +132,7 @@ public EndNode(string id) : base(id) var svg = PrintSvgAsString(drawingGraph); var al = new AvaloniaAssetLoader(); - XmlDocument xml = new XmlDocument(); + XmlDocument xml = new(); xml.LoadXml(svg); return new Avalonia.Svg.SvgImage() { diff --git a/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs b/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs index c8c81eb..3de8f88 100644 --- a/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs +++ b/src/WireMockInspector/Converters/HttpStatusCodeToDescriptionConverter.cs @@ -6,7 +6,7 @@ namespace WireMockInspector.Converters; public class HttpStatusCodeToDescriptionConverter : IValueConverter { - public static readonly HttpStatusCodeToDescriptionConverter Instance = new HttpStatusCodeToDescriptionConverter(); + public static readonly HttpStatusCodeToDescriptionConverter Instance = new(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return TranslateStatusCode(value); diff --git a/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs b/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs index b95c657..1f045ee 100644 --- a/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs +++ b/src/WireMockInspector/Converters/RequestMethodToColorConverter.cs @@ -7,7 +7,7 @@ namespace WireMockInspector.Converters; public class RequestMethodToColorConverter : IValueConverter { - public static readonly RequestMethodToColorConverter Instance = new RequestMethodToColorConverter(); + public static readonly RequestMethodToColorConverter Instance = new(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs b/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs index 27bd8c9..18afc42 100644 --- a/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs +++ b/src/WireMockInspector/Converters/ResponseCodeToColorConverter.cs @@ -7,7 +7,7 @@ namespace WireMockInspector.Converters; public class ResponseCodeToColorConverter : IValueConverter { - public static readonly ResponseCodeToColorConverter Instance = new ResponseCodeToColorConverter(); + public static readonly ResponseCodeToColorConverter Instance = new(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is string responseCode && int.TryParse(responseCode, out int statusCode)) diff --git a/src/WireMockInspector/ViewModels/JsonDiff.cs b/src/WireMockInspector/ViewModels/JsonDiff.cs index a4a3504..9eb3d2e 100644 --- a/src/WireMockInspector/ViewModels/JsonDiff.cs +++ b/src/WireMockInspector/ViewModels/JsonDiff.cs @@ -6,7 +6,7 @@ namespace WireMockInspector.ViewModels; public class AlphabeticallySortedJsonDiffFormatter { - public static readonly AlphabeticallySortedJsonDiffFormatter Instance = new AlphabeticallySortedJsonDiffFormatter(); + public static readonly AlphabeticallySortedJsonDiffFormatter Instance = new(); public string Format(JToken? diff) { diff --git a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs index 6458c04..9657058 100644 --- a/src/WireMockInspector/ViewModels/MainWindowViewModel.cs +++ b/src/WireMockInspector/ViewModels/MainWindowViewModel.cs @@ -105,7 +105,7 @@ public MappingViewModel SelectedMapping public NewVersionInfoViewModel NewVersion => _newVersion.Value; - private GithubUpdater _githubUpdater = new GithubUpdater("cezarypiatek/WireMockInspector"); + private GithubUpdater _githubUpdater = new("cezarypiatek/WireMockInspector"); private WireMockInspectorSettingsManager _settingsManager = new(); public ObservableCollection Settings { get; set; } = new(); @@ -967,7 +967,7 @@ private static MatchDetailsViewModel MapToRequestBodyViewModel(RequestViewModel { "String" or "FormUrlEncoded" => WrapBodyInMarkdown(req.Raw.Request.Body?? string.Empty), "Json" => new MarkdownCode("json", req.Raw.Request.BodyAsJson?.ToString() ?? string.Empty), - "Xml" => new MarkdownCode("xml", req.Raw.Request.Body?.ToString() ?? string.Empty), + "Xml" => new MarkdownCode("xml", req.Raw.Request.Body?.ToString() ?? string.Empty).TryToReformat(), "Bytes" => new MarkdownCode("plaintext", req.Raw.Request.BodyAsBytes?.ToString()?? string.Empty), "File" => new MarkdownCode("plaintext","[FileContent]"), _ => new MarkdownCode("plaintext", "") @@ -1169,7 +1169,7 @@ private static MarkdownCode GetActualForRequestBody(RequestViewModel req) return req.Raw.Response?.DetectedBodyType.ToString() switch { "Json" => new MarkdownCode("json",req.Raw.Response.BodyAsJson?.ToString() ?? string.Empty), - "Xml" => new MarkdownCode("xml", req.Raw.Response.Body?.ToString() ?? string.Empty), + "Xml" => new MarkdownCode("xml", req.Raw.Response.Body?.ToString() ?? string.Empty).TryToReformat(), "Bytes" => new MarkdownCode("plaintext", req.Raw.Response.BodyAsBytes?.ToString() ?? string.Empty), "File" => new MarkdownCode("plaintext",req.Raw.Response.BodyAsFile?.ToString() ?? string.Empty), _ => WrapBodyInMarkdown( req.Raw.Response?.Body?? string.Empty), @@ -1186,7 +1186,7 @@ private static MarkdownCode WrapBodyInMarkdown(string bodyResponse) } if (cleanBody.StartsWith("<")) { - return new MarkdownCode("xml", bodyResponse); + return new MarkdownCode("xml", bodyResponse).TryToReformat(); } return new MarkdownCode("plaintext", bodyResponse); diff --git a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs index a564081..e49df13 100644 --- a/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs +++ b/src/WireMockInspector/ViewModels/MatchDetailsViewModel.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ReactiveUI; +using WireMockInspector.CodeGenerators; namespace WireMockInspector.ViewModels; @@ -49,7 +50,7 @@ internal class SimpleStringExpectations : ExpectationsModel public class MissingExpectations : ExpectationsModel { - public static readonly MissingExpectations Instance = new MissingExpectations(); + public static readonly MissingExpectations Instance = new(); } public class RawExpectations : ExpectationsModel @@ -187,19 +188,20 @@ public string AsMarkdownSyntax() public MarkdownCode TryToReformat() { - if (IsJsonMarkdown()) + try { - try + if (IsJsonMarkdown()) { - var formatted = JToken.Parse(this.rawValue).ToString(Formatting.Indented); - return new MarkdownCode("json", formatted); + return new MarkdownCode(lang, formatted); } - catch (Exception e) + else if (IsXmlMarkdown()) { - + return new MarkdownCode(lang, XmlFormatter.PrettyPrintXml(rawValue)); } } + catch (Exception) + {} return this; } diff --git a/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs index 28e5633..2d7d87e 100644 --- a/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs +++ b/src/WireMockInspector/Views/Transformer/CSharpSyntaxHighlighter.cs @@ -15,10 +15,10 @@ public class CSharpSyntaxHighlighter : DocumentColorizingTransformer }; // Matches method calls like `.UsingMethod("....")` - private static readonly Regex MethodCallRegex = new Regex(@"\.(\w+)\(", RegexOptions.Compiled); + private static readonly Regex MethodCallRegex = new(@"\.(\w+)\(", RegexOptions.Compiled); // Matches string literals - private static readonly Regex StringLiteralRegex = new Regex(@"""(\\.|[^\\\""])*""", RegexOptions.Compiled); + private static readonly Regex StringLiteralRegex = new(@"""(\\.|[^\\\""])*""", RegexOptions.Compiled); // Custom list for additional class names to be highlighted private static readonly string[] CustomClasses = new string[] @@ -27,10 +27,10 @@ public class CSharpSyntaxHighlighter : DocumentColorizingTransformer }; // Colors - private static readonly SolidColorBrush KeywordBrush = new SolidColorBrush(Color.Parse("#3988D6")); - private static readonly SolidColorBrush MethodBrush = new SolidColorBrush(Color.Parse("#ADD795")); - private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Color.Parse("#D6936B")); - private static readonly SolidColorBrush ClassNameBrush = new SolidColorBrush(Color.Parse("#41C2B0")); + private static readonly SolidColorBrush KeywordBrush = new(Color.Parse("#3988D6")); + private static readonly SolidColorBrush MethodBrush = new(Color.Parse("#ADD795")); + private static readonly SolidColorBrush StringLiteralBrush = new(Color.Parse("#D6936B")); + private static readonly SolidColorBrush ClassNameBrush = new(Color.Parse("#41C2B0")); protected override void ColorizeLine(DocumentLine line) { diff --git a/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs index 60f9109..9811d35 100644 --- a/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs +++ b/src/WireMockInspector/Views/Transformer/JsonSyntaxHighlighter.cs @@ -8,18 +8,18 @@ namespace WireMockInspector.Views.Transformer public class JsonSyntaxHighlighter : DocumentColorizingTransformer { // Regex patterns for different JSON components - private static readonly Regex PropertyNameRegex = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*:)", RegexOptions.Compiled); - private static readonly Regex StringLiteralRegex = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*[,}\]])(?!\s*:)", RegexOptions.Compiled); - private static readonly Regex NumberRegex = new Regex(@"\b\d+(\.\d+)?\b", RegexOptions.Compiled); - private static readonly Regex BooleanNullRegex = new Regex(@"\b(true|false|null)\b", RegexOptions.Compiled); - private static readonly Regex PunctuationRegex = new Regex(@"[\[\]{}:,]", RegexOptions.Compiled); + private static readonly Regex PropertyNameRegex = new(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*:)", RegexOptions.Compiled); + private static readonly Regex StringLiteralRegex = new(@"""[^""\\]*(?:\\.[^""\\]*)*""(?=\s*[,}\]])(?!\s*:)", RegexOptions.Compiled); + private static readonly Regex NumberRegex = new(@"\b\d+(\.\d+)?\b", RegexOptions.Compiled); + private static readonly Regex BooleanNullRegex = new(@"\b(true|false|null)\b", RegexOptions.Compiled); + private static readonly Regex PunctuationRegex = new(@"[\[\]{}:,]", RegexOptions.Compiled); // Colors using the same palette as CSharpSyntaxHighlighter - private static readonly SolidColorBrush PropertyNameBrush = new SolidColorBrush(Color.Parse("#ADD795")); - private static readonly SolidColorBrush StringLiteralBrush = new SolidColorBrush(Color.Parse("#D6936B")); - private static readonly SolidColorBrush NumberBrush = new SolidColorBrush(Color.Parse("#ADD795")); - private static readonly SolidColorBrush BooleanNullBrush = new SolidColorBrush(Color.Parse("#41C2B0")); - private static readonly SolidColorBrush PunctuationBrush = new SolidColorBrush(Color.Parse("#3988D6")); + private static readonly SolidColorBrush PropertyNameBrush = new(Color.Parse("#ADD795")); + private static readonly SolidColorBrush StringLiteralBrush = new(Color.Parse("#D6936B")); + private static readonly SolidColorBrush NumberBrush = new(Color.Parse("#ADD795")); + private static readonly SolidColorBrush BooleanNullBrush = new(Color.Parse("#41C2B0")); + private static readonly SolidColorBrush PunctuationBrush = new(Color.Parse("#3988D6")); protected override void ColorizeLine(DocumentLine line) { diff --git a/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs b/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs index 8ab8560..01ae1e6 100644 --- a/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs +++ b/src/WireMockInspector/Views/Transformer/XmlSyntaxHighlighter.cs @@ -8,12 +8,12 @@ namespace WireMockInspector.Views.Transformer public class XmlSyntaxHighlighter : DocumentColorizingTransformer { // Regex patterns for XML elements - private static readonly Regex TagRegex = new Regex(@"<(/?[\w\s]*)(.*?)>", RegexOptions.Compiled); - private static readonly Regex AttributeRegex = new Regex(@"\b(\w+)(\s*=\s*)(""[^""]*"")", RegexOptions.Compiled); - private static readonly Regex CommentRegex = new Regex(@"", RegexOptions.Compiled); + private static readonly Regex TagRegex = new(@"<(/?[\w\s-]+)(.*?)>", RegexOptions.Compiled); + private static readonly Regex AttributeRegex = new(@"\b(\w+)(\s*=\s*)(""[^""]*"")", RegexOptions.Compiled); + private static readonly Regex CommentRegex = new(@"", RegexOptions.Compiled); // Colors - private static readonly SolidColorBrush TagBrush = new SolidColorBrush(Color.Parse("#3988D6")); + private static readonly SolidColorBrush TagBrush = new SolidColorBrush(Color.Parse("#ADD795")); private static readonly SolidColorBrush AttributeNameBrush = new SolidColorBrush(Color.Parse("#41C2B0")); private static readonly SolidColorBrush AttributeValueBrush = new SolidColorBrush(Color.Parse("#D6936B")); private static readonly SolidColorBrush CommentBrush = new SolidColorBrush(Color.Parse("#ADD795")); @@ -26,10 +26,13 @@ protected override void ColorizeLine(DocumentLine line) // Highlight XML tags foreach (Match match in TagRegex.Matches(lineText)) { + // Highlight the tag name ChangeLinePart( lineStartOffset + match.Index, - lineStartOffset + match.Index + match.Groups[1].Length + 1, // Tag name + lineStartOffset + match.Index + match.Groups[1].Length + 2, // Tag name element => element.TextRunProperties.SetForegroundBrush(TagBrush)); + + // Highlight attributes within the tag if (match.Groups[2].Success) { HighlightAttributes(lineText, lineStartOffset + match.Index + match.Groups[1].Length + 1, match.Groups[2].Value); @@ -46,7 +49,7 @@ protected override void ColorizeLine(DocumentLine line) } } - private void HighlightAttributes(string text, int offset, string attributesText) + private void HighlightAttributes(string lineText, int offset, string attributesText) { foreach (Match match in AttributeRegex.Matches(attributesText)) { diff --git a/src/WireMockInspector/WireMockInspector.csproj b/src/WireMockInspector/WireMockInspector.csproj index fa26f65..f66f625 100644 --- a/src/WireMockInspector/WireMockInspector.csproj +++ b/src/WireMockInspector/WireMockInspector.csproj @@ -50,7 +50,7 @@ - +