CadParamPluging/Common/NoteTemplateEngine.cs

294 lines
8.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CadParamPluging.Common
{
public static class NoteTemplateEngine
{
public sealed class PlaceholderOccurrence
{
public int Index { get; set; }
public int Start { get; set; }
public int LineNumber { get; set; }
public int ColumnInLine { get; set; }
public string LineText { get; set; }
}
public static int CountPlaceholders(string text)
{
if (string.IsNullOrEmpty(text))
{
return 0;
}
var count = 0;
for (var i = 0; i < text.Length;)
{
if (text[i] != '*')
{
i++;
continue;
}
var j = i;
while (j < text.Length && text[j] == '*')
{
j++;
}
var len = j - i;
if (len != 4)
{
count += len;
}
i = j;
}
return count;
}
public static List<PlaceholderOccurrence> ParseOccurrences(string text)
{
var result = new List<PlaceholderOccurrence>();
if (string.IsNullOrEmpty(text))
{
return result;
}
var s = NormalizeNewLines(text);
// Keep empty lines for correct line number mapping.
var lines = s.Split(new[] { '\n' }, StringSplitOptions.None);
// Build line start offsets.
var lineStartOffsets = new int[lines.Length];
var offset = 0;
for (var i = 0; i < lines.Length; i++)
{
lineStartOffsets[i] = offset;
offset += (lines[i] ?? string.Empty).Length;
if (i < lines.Length - 1)
{
offset += 1; // '\n'
}
}
var placeholderIndex = 0;
var lineIdx = 0;
var lineStart = lines.Length > 0 ? lineStartOffsets[0] : 0;
for (var i = 0; i < s.Length;)
{
// Move line index if needed.
while (lineIdx + 1 < lineStartOffsets.Length && i >= lineStartOffsets[lineIdx] + (lines[lineIdx] ?? string.Empty).Length + 1)
{
lineIdx++;
lineStart = lineStartOffsets[lineIdx];
}
if (s[i] != '*')
{
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
i = j;
continue;
}
var lineText = (lineIdx >= 0 && lineIdx < lines.Length) ? (lines[lineIdx] ?? string.Empty) : string.Empty;
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
var pos = i + k;
var col = pos - lineStart;
if (col < 0) col = 0;
result.Add(new PlaceholderOccurrence
{
Index = placeholderIndex,
Start = pos,
LineNumber = lineIdx + 1,
ColumnInLine = col,
LineText = lineText
});
}
i = j;
}
return result;
}
public static string BuildNumberedPreviewText(string templateText)
{
templateText = templateText ?? string.Empty;
var s = NormalizeNewLines(templateText);
var sb = new StringBuilder(s.Length + 64);
var placeholderIndex = 0;
for (var i = 0; i < s.Length;)
{
if (s[i] != '*')
{
sb.Append(s[i]);
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
sb.Append("****");
i = j;
continue;
}
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
sb.Append("【#");
sb.Append(placeholderIndex);
sb.Append("】");
}
i = j;
}
return sb.ToString();
}
public static string Render(string templateText, IEnumerable<NotePlaceholderBinding> bindings, Func<string, string> getValue)
{
templateText = templateText ?? string.Empty;
var s = NormalizeNewLines(templateText);
var map = new Dictionary<int, string>();
foreach (var b in bindings ?? Enumerable.Empty<NotePlaceholderBinding>())
{
if (b == null)
{
continue;
}
if (b.Index <= 0)
{
continue;
}
var key = (b.ParamKey ?? string.Empty).Trim();
if (key.Length == 0)
{
continue;
}
if (!map.ContainsKey(b.Index))
{
map[b.Index] = key;
}
}
var sb = new StringBuilder(s.Length + 64);
var placeholderIndex = 0;
for (var i = 0; i < s.Length;)
{
if (s[i] != '*')
{
sb.Append(s[i]);
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
sb.Append("****");
i = j;
continue;
}
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
if (map.TryGetValue(placeholderIndex, out var key) && getValue != null)
{
var v = getValue(key);
if (!string.IsNullOrWhiteSpace(v))
{
sb.Append(v);
continue;
}
}
sb.Append('*');
}
i = j;
}
return sb.ToString();
}
/// <summary>
/// When the same ParamKey is bound by multiple placeholders, each placeholder should have its own value key,
/// otherwise values would overwrite each other.
/// This method returns an equivalent bindings list where ParamKey is replaced by an effective value key.
/// </summary>
public static List<NotePlaceholderBinding> BuildEffectiveValueKeyBindings(IEnumerable<NotePlaceholderBinding> bindings)
{
var list = (bindings ?? Enumerable.Empty<NotePlaceholderBinding>())
.Where(b => b != null && b.Index > 0 && !string.IsNullOrWhiteSpace(b.ParamKey))
.Select(b => new NotePlaceholderBinding { Index = b.Index, ParamKey = b.ParamKey.Trim() })
.ToList();
var dup = list
.GroupBy(b => b.ParamKey, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.OrdinalIgnoreCase);
foreach (var b in list)
{
if (dup.TryGetValue(b.ParamKey, out var cnt) && cnt > 1)
{
b.ParamKey = string.Format("{0}@{1}", b.ParamKey, b.Index);
}
}
return list;
}
private static string NormalizeNewLines(string text)
{
return (text ?? string.Empty)
.Replace("\r\n", "\n")
.Replace("\r", "\n");
}
}
}