1 module dhtags.utils.html;
2 
3 string escape(string str) {
4    import std.array : join;
5    import std.conv : to;
6 
7    string[] result;
8    foreach (c; str) {
9       switch (c) {
10          case '"': result ~= """; break;
11          case '&': result ~= "&"; break;
12          case '>': result ~= ">"; break;
13          case '<': result ~= "&lt;"; break;
14          default:  result ~= c.to!string;
15       }
16    }
17    return result.join;
18 }
19 
20 /** Strip the left margin for a multi-line string and remove newlines */
21 string stripMargin(string s) {
22    import std..string : splitLines, stripLeft;
23    import std.algorithm : map;
24    import std.array : join;
25    return s.splitLines.map!(line => line.stripLeft).join;
26 }
27 
28 /**
29  * Convert a value of any type to a string.
30  * We need to handle floating point values separately because the 'to' function can't convert these to a string at compile-time.
31  */
32 string asString(alias value)() {
33    import std.math : floor;
34    import std.conv : to;
35 
36    string doubleToString(double d)() {
37       return d.stringof;
38    }
39 
40    alias TVal = typeof(cast(const) value);
41 
42    if (__ctfe) {
43       static if (is(TVal == const(float)) || is(TVal == const(double))) {
44          enum double val = value;
45          return doubleToString!(val)();
46       } else {
47          return value.to!string;
48       }
49    } else {
50       return value.to!string;
51    }
52 }
53 
54 /**
55  * Convert a lower camelcase string to snake case.
56  * We can't use regex to match at compile-time so we'll iterate through the string and convert it manually.
57  */
58 pure string camelCaseToHyphens(string s) {
59    import std.array : join;
60    import std.range : enumerate;
61    import std.algorithm : map;
62    import std.ascii : isUpper, isLower;
63    import std..string : toLower;
64    import std.conv : to;
65 
66    return s.enumerate.map!((t) {
67       if (isUpper(t.value)) {
68          if (t.index > 0 && (isLower(s[t.index - 1]) || (t.index < s.length - 1 && isLower(s[t.index + 1])))) {
69             return "-" ~ t.value.toLower.to!string;
70          } else {
71             return t.value.toLower.to!string;
72          }
73       } else {
74          return t.value.to!string;
75       }
76    }).join;
77 }
78 
79 /**
80  * Convert a hyphen-separated string to a lower camelcase string.
81  */
82 pure string hyphenToCamelCase(string s) {
83    import std.array : join;
84    import std.ascii : toUpper;
85    import std.algorithm : map;
86    import std.range : enumerate;
87    import std.conv : to;
88 
89    return s.enumerate.map!((t) {
90       if (t.value == '-') {
91          return "";
92       } else {
93          return (t.index > 0 && (s[t.index - 1] == '-')) ? t.value.toUpper.to!string : t.value.to!string;
94       }
95    }).join;
96 }
97 
98 /**
99  * Replace hyphens with underscores so it can be used in mixin as a symbol.
100  */
101 pure string hyphenToUnderscores(string s) {
102    import std..string : replace;
103    return s.replace("-", "_");
104 }
105 
106 /**
107  * Replace underscores with hyphens to get the actual attribute name
108  */
109 pure string underscoreToHyphens(string s) {
110    import std..string : replace;
111    return s.replace("_", "-");
112 }
113 
114 string mixinTempls(List...)() {
115    import std..string : format;
116    static if (List.length) {
117       return format("mixin %s;", List[0].stringof) ~ mixinTempls!(List[1 .. $]);
118    } else { return ""; }
119 }