Skip to content

Commit 0a29fb4

Browse files
Add filters for .properties files (#43)
* Initial work on .properties file support. * Preserve leading spaces in property values (#44) * Preserve leading spaces in property values * For symmetry, class both control character ranges as ISO-8859-1 Co-authored-by: David Young <thedewi@users.noreply.github.com>
1 parent ee78f53 commit 0a29fb4

File tree

3 files changed

+127
-9
lines changed

3 files changed

+127
-9
lines changed

source/Octostache.Tests/FiltersFixture.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using Xunit;
54
using FluentAssertions;
65
using YamlDotNet.Serialization;
@@ -92,7 +91,7 @@ public void JsonIsEscaped(string input, string expectedResult, string testName)
9291
var result = Evaluate("#{Foo | JsonEscape}", new Dictionary<string, string> { { "Foo", input } });
9392
result.Should().Be(expectedResult);
9493
}
95-
94+
9695
[Theory]
9796
[InlineData("single'quote", "single''quote")]
9897
[InlineData("\\'", "\\''")]
@@ -197,6 +196,47 @@ public void YamlSingleQuotedStringsCanRoundTripWithSideEffects(string input, str
197196
doc.Key.Should().Be(normalisedExpected);
198197
}
199198

199+
[Theory]
200+
[InlineData(" ", "\\ ")]
201+
[InlineData(":", "\\:")]
202+
[InlineData("=", "\\=")]
203+
[InlineData("\\", "\\\\")]
204+
[InlineData("\r", "\\r")]
205+
[InlineData("\n", "\\n")]
206+
[InlineData("\t", "\\t")]
207+
[InlineData(" a \n b ", "\\ a\\ \\n\\ b\\ ")]
208+
[InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz")]
209+
[InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")]
210+
[InlineData("0123456789", "0123456789")]
211+
[InlineData("我叫章鱼", "\\u6211\\u53eb\\u7ae0\\u9c7c")]
212+
[InlineData("֟", "֟")]
213+
public void PropertiesKeyIsEscaped(string input, string expected)
214+
{
215+
var result = Evaluate("#{Foo | PropertiesKeyEscape}", new Dictionary<string, string> {{"Foo", input}});
216+
result.Should().Be(expected);
217+
}
218+
219+
[Theory]
220+
[InlineData(":", ":")]
221+
[InlineData("=", "=")]
222+
[InlineData(" ", "\\ ")]
223+
[InlineData("a ", "a ")]
224+
[InlineData("\\", "\\\\")]
225+
[InlineData("\r", "\\r")]
226+
[InlineData("\n", "\\n")]
227+
[InlineData("\t", "\\t")]
228+
[InlineData(" a \n b ", "\\ a \\n b ")]
229+
[InlineData("abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz")]
230+
[InlineData("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")]
231+
[InlineData("0123456789", "0123456789")]
232+
[InlineData("我叫章鱼", "\\u6211\\u53eb\\u7ae0\\u9c7c")]
233+
[InlineData("֟", "֟")]
234+
public void PropertiesValueIsEscaped(string input, string expected)
235+
{
236+
var result = Evaluate("#{Foo | PropertiesValueEscape}", new Dictionary<string, string> {{"Foo", input}});
237+
result.Should().Be(expected);
238+
}
239+
200240
[Theory]
201241
[InlineData("#{Foo | Markdown}")]
202242
[InlineData("#{Foo | MarkdownToHtml}")]

source/Octostache/Templates/BuiltInFunctions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ static class BuiltInFunctions
1919
{"jsonescape", TextEscapeFunction.JsonEscape },
2020
{"yamlsinglequoteescape", TextEscapeFunction.YamlSingleQuoteEscape },
2121
{"yamldoublequoteescape", TextEscapeFunction.YamlDoubleQuoteEscape },
22+
{"propertieskeyescape", TextEscapeFunction.PropertiesKeyEscape },
23+
{"propertiesvalueescape", TextEscapeFunction.PropertiesValueEscape },
2224
{"markdown", TextEscapeFunction.MarkdownToHtml },
2325
{"markdowntohtml", TextEscapeFunction.MarkdownToHtml },
2426
{"nowdate", DateFunction.NowDate },

source/Octostache/Templates/Functions/TextEscapeFunction.cs

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Linq.Expressions;
54
using System.Text.RegularExpressions;
65
using Markdig;
76

@@ -74,7 +73,23 @@ private static string HandleSingleQuoteYamlNewLines(string input)
7473

7574
return output;
7675
}
77-
76+
77+
public static string PropertiesKeyEscape(string argument, string[] options)
78+
{
79+
if (options.Any())
80+
return null;
81+
82+
return Escape(argument, PropertiesKeyMap);
83+
}
84+
85+
public static string PropertiesValueEscape(string argument, string[] options)
86+
{
87+
if (options.Any())
88+
return null;
89+
90+
return Escape(argument, PropertiesValueMap);
91+
}
92+
7893
[Obsolete("Please use MarkdownToHtml instead.")]
7994
public static string Markdown(string argument, string[] options)
8095
{
@@ -137,7 +152,15 @@ static string Escape(string raw, Func<char, string> mapping)
137152

138153
return string.Join("", raw.Select(mapping));
139154
}
140-
155+
156+
static string Escape(string raw, Func<char, int, string> mapping)
157+
{
158+
if (raw == null)
159+
return null;
160+
161+
return string.Join("", raw.Select(mapping));
162+
}
163+
141164
static readonly IDictionary<char, string> HtmlEntityMap = new Dictionary<char, string>
142165
{
143166
{ '&', "&amp;" },
@@ -177,12 +200,17 @@ static bool IsAsciiPrintable(char ch)
177200
return ch >= 0x20 && ch <= 0x7E;
178201
}
179202

180-
static string EncodeUnicodeCharForYaml(char ch)
203+
static bool IsIso88591Compatible(char ch)
204+
{
205+
return ch >= 0x00 && ch < 0xFF;
206+
}
207+
208+
static string EscapeUnicodeCharForYamlOrProperties(char ch)
181209
{
182210
var hex = ((int)ch).ToString("x4");
183211
return $"\\u{hex}";
184212
}
185-
213+
186214
static string YamlDoubleQuoteMap(char ch)
187215
{
188216
// Yaml supports multiple ways to encode newlines. One method we tried
@@ -202,7 +230,55 @@ static string YamlDoubleQuoteMap(char ch)
202230
case '"':
203231
return "\\\"";
204232
default:
205-
return IsAsciiPrintable(ch) ? ch.ToString() : EncodeUnicodeCharForYaml(ch);
233+
return IsAsciiPrintable(ch) ? ch.ToString() : EscapeUnicodeCharForYamlOrProperties(ch);
234+
}
235+
}
236+
237+
static string CommonPropertiesMap(char ch)
238+
{
239+
switch (ch)
240+
{
241+
case '\\':
242+
return "\\\\";
243+
case '\r':
244+
return "\\r";
245+
case '\n':
246+
return "\\n";
247+
case '\t':
248+
// In some contexts a tab can get treated as non-semantic whitespace,
249+
// or as part of the separator between keys and values. It's safer to
250+
// always encode tabs.
251+
return "\\t";
252+
default:
253+
return IsIso88591Compatible(ch)
254+
? ch.ToString()
255+
: EscapeUnicodeCharForYamlOrProperties(ch);
256+
}
257+
}
258+
259+
static string PropertiesKeyMap(char ch)
260+
{
261+
switch (ch)
262+
{
263+
case ' ':
264+
return "\\ ";
265+
case ':':
266+
return "\\:";
267+
case '=':
268+
return "\\=";
269+
default:
270+
return CommonPropertiesMap(ch);
271+
}
272+
}
273+
274+
static string PropertiesValueMap(char ch, int index)
275+
{
276+
switch (ch)
277+
{
278+
case ' ' when index == 0:
279+
return "\\ ";
280+
default:
281+
return CommonPropertiesMap(ch);
206282
}
207283
}
208284
}

0 commit comments

Comments
 (0)