@oino-ts/common 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /*
3
+ * This Source Code Form is subject to the terms of the Mozilla Public
4
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.OINO_EMPTY_FORMATTER = exports.OINOFormatter = void 0;
9
+ const index_js_1 = require("./index.js");
10
+ /**
11
+ * Class for formatting strings and values.
12
+ *
13
+ */
14
+ class OINOFormatter {
15
+ static OINO_FORMATTER_REGEXP = /\s?(trim(\(\))?|trimLeft(\(\))?|trimRight(\(\))?|toUpper(\(\))?|toLower(\(\))?|cropLeft\((\d+)\)|cropRight\((\d+)\)|cropToDelimiter\(([^\(\),]+),(\d+)\)|cropFromDelimiter\(([^\(\),]+),(\d+)\)|substring\((\d+),(\d+)\)|replace\(([^\(\),]+),([^\(\),]+)\))\s?$/i;
16
+ _types;
17
+ _params;
18
+ /**
19
+ * Constructor of `OINOFormatter`
20
+ * @param types array of formatter types
21
+ * @param params array of formatter parameters according to type
22
+ */
23
+ constructor(types, params) {
24
+ this._types = types;
25
+ this._params = params;
26
+ }
27
+ /**
28
+ * Constructor for `OINOFormatter` as parser of http parameter.
29
+ *
30
+ * @param formatters string or array of strings of serialized representation of formatters with following functions
31
+ * - trim()
32
+ * - trimLeft()
33
+ * - trimRight()
34
+ * - toUpper()
35
+ * - toLower()
36
+ * - cropLeft(charsToCrop)
37
+ * - cropRight(charsToCrop)
38
+ * - cropToDelimiter(delimiter,offsetChars)
39
+ * - cropFromDelimiter(delimiter,offsetChars)
40
+ * - substring(start,end)
41
+ * - replace(search,replace)
42
+ */
43
+ static parse(formatters) {
44
+ if (typeof formatters === "string") {
45
+ formatters = [formatters];
46
+ }
47
+ if (!formatters || formatters.length === 0) {
48
+ return exports.OINO_EMPTY_FORMATTER;
49
+ }
50
+ else {
51
+ const types = [];
52
+ const params = [];
53
+ for (let i = 0; i < formatters.length; i++) {
54
+ let match = formatters[i]?.match(this.OINO_FORMATTER_REGEXP);
55
+ if (!match) {
56
+ index_js_1.OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Invalid formatter string", { formatter: formatters[i] });
57
+ throw new Error(index_js_1.OINO_ERROR_PREFIX + "Invalid formatter: " + formatters[i]);
58
+ }
59
+ else {
60
+ const formatter_type = match[1].toLowerCase().substring(0, match[1].indexOf('('));
61
+ const formatter_params = [];
62
+ if ((formatter_type === "trim") || (formatter_type === "trimleft") || (formatter_type === "trimright") || (formatter_type === "toupper") || (formatter_type === "tolower")) {
63
+ // no parameters
64
+ }
65
+ else if (formatter_type === "cropleft") {
66
+ formatter_params.push(parseInt(match[7]));
67
+ }
68
+ else if (formatter_type === "cropright") {
69
+ formatter_params.push(parseInt(match[8]));
70
+ }
71
+ else if (formatter_type === "croptodelimiter") {
72
+ formatter_params.push(decodeURIComponent(match[9]), parseInt(match[10]));
73
+ }
74
+ else if (formatter_type === "cropfromdelimiter") {
75
+ formatter_params.push(decodeURIComponent(match[11]), parseInt(match[12]));
76
+ }
77
+ else if (formatter_type === "substring") {
78
+ formatter_params.push(parseInt(match[13]), parseInt(match[14]));
79
+ }
80
+ else if (formatter_type === "replace") {
81
+ formatter_params.push(decodeURIComponent(match[15]), decodeURIComponent(match[16]));
82
+ }
83
+ else {
84
+ index_js_1.OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Unknown formatter type", { formatter: formatters[i] });
85
+ throw new Error(index_js_1.OINO_ERROR_PREFIX + "Unsupported formatter: " + formatters[i]);
86
+ }
87
+ types.push(formatter_type);
88
+ params.push(formatter_params);
89
+ }
90
+ }
91
+ return new OINOFormatter(types, params);
92
+ }
93
+ }
94
+ /**
95
+ * Does formatter include any operations.
96
+ * @return true if formatter is empty
97
+ */
98
+ isEmpty() {
99
+ return this._types.length === 0;
100
+ }
101
+ /**
102
+ * Applies all formatters in order to given value.
103
+ *
104
+ * @param value string value to be formatted
105
+ * @returns formatted string value
106
+ */
107
+ format(value) {
108
+ let formatted = value;
109
+ for (let i = 0; i < this._types.length; i++) {
110
+ const formatter_type = this._types[i];
111
+ const formatter_params = this._params[i];
112
+ if (formatter_type === "trim") {
113
+ formatted = formatted.trim();
114
+ }
115
+ else if (formatter_type === "trimleft") {
116
+ formatted = formatted.trimStart();
117
+ }
118
+ else if (formatter_type === "trimright") {
119
+ formatted = formatted.trimEnd();
120
+ }
121
+ else if (formatter_type === "toupper") {
122
+ formatted = formatted.toUpperCase();
123
+ }
124
+ else if (formatter_type === "tolower") {
125
+ formatted = formatted.toLowerCase();
126
+ }
127
+ else if (formatter_type === "cropleft") {
128
+ formatted = formatted.slice(formatter_params[0]);
129
+ }
130
+ else if (formatter_type === "cropright") {
131
+ formatted = formatted.slice(0, formatted.length - formatter_params[0]);
132
+ }
133
+ else if (formatter_type === "croptodelimiter") {
134
+ const to_demilimiter_idx = formatted.indexOf(formatter_params[0]);
135
+ if (to_demilimiter_idx >= 0) {
136
+ formatted = formatted.slice(Math.max(to_demilimiter_idx + formatter_params[0].length + formatter_params[1], 0));
137
+ }
138
+ }
139
+ else if (formatter_type === "cropfromdelimiter") {
140
+ const from_demilimiter_idx = formatted.indexOf(formatter_params[0]);
141
+ if (from_demilimiter_idx >= 0) {
142
+ formatted = formatted.slice(0, Math.max(from_demilimiter_idx + formatter_params[1], 0));
143
+ }
144
+ }
145
+ else if (formatter_type === "substring") {
146
+ const start = formatter_params[0] ? parseInt(formatter_params[0]) : 0;
147
+ const end = formatter_params[1] ? parseInt(formatter_params[1]) : formatted.length;
148
+ formatted = formatted.substring(start, end);
149
+ }
150
+ else if (formatter_type === "replace") {
151
+ const search = formatter_params[0];
152
+ const replacement = formatter_params[1];
153
+ formatted = formatted.replaceAll(search, replacement);
154
+ }
155
+ // console.log("formatter:", formatter_type, "params:", formatter_params, "formatted:", formatted)
156
+ }
157
+ return formatted;
158
+ }
159
+ }
160
+ exports.OINOFormatter = OINOFormatter;
161
+ exports.OINO_EMPTY_FORMATTER = new OINOFormatter([], []);
@@ -2,13 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OINOHtmlTemplate = void 0;
4
4
  const _1 = require(".");
5
+ const OINOFormatter_1 = require("./OINOFormatter");
5
6
  /**
6
7
  * Class for rendering HTML from data.
7
8
  */
8
9
  class OINOHtmlTemplate {
9
- _tag;
10
- _tagCleanRegex;
10
+ _tagOpen;
11
+ _tagClose;
11
12
  _variables = {};
13
+ _tagStart = [];
14
+ _tagEnd = [];
15
+ _tagVariable = [];
16
+ _tagFormatters = [];
17
+ _tagCount = 0;
12
18
  /** HTML template string */
13
19
  template;
14
20
  /** Cache modified value for template */
@@ -22,12 +28,13 @@ class OINOHtmlTemplate {
22
28
  * @param tag tag to identify variables in template
23
29
  *
24
30
  */
25
- constructor(template, tag = "###") {
31
+ constructor(template, tagOpen = "{{{", tagClose = "}}}") {
26
32
  this.template = template;
27
33
  this.modified = 0;
28
34
  this.expires = 0;
29
- this._tag = tag;
30
- this._tagCleanRegex = new RegExp(tag + ".*" + tag, "g");
35
+ this._tagOpen = tagOpen;
36
+ this._tagClose = tagClose;
37
+ this._parseTemplate();
31
38
  }
32
39
  /**
33
40
  * @returns whether template is empty
@@ -35,10 +42,30 @@ class OINOHtmlTemplate {
35
42
  isEmpty() {
36
43
  return this.template == "";
37
44
  }
38
- _createHttpResult(html, removeUnusedTags) {
39
- if (removeUnusedTags) {
40
- html = html.replace(this._tagCleanRegex, "");
45
+ _parseTemplate() {
46
+ const tag_open_length = this._tagOpen.length;
47
+ const tag_close_length = this._tagClose.length;
48
+ let tag_start_pos = this.template.indexOf(this._tagOpen, 0);
49
+ let tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length;
50
+ while ((tag_start_pos >= 0) && (tag_end_pos > tag_start_pos)) {
51
+ this._tagStart.push(tag_start_pos);
52
+ this._tagEnd.push(tag_end_pos);
53
+ let variable = this.template.slice(tag_start_pos + tag_open_length, tag_end_pos - tag_close_length);
54
+ const variable_parts = variable.split("|");
55
+ if (variable_parts.length > 1) {
56
+ const formatter = OINOFormatter_1.OINOFormatter.parse(variable_parts.slice(1));
57
+ this._tagFormatters.push(formatter);
58
+ }
59
+ else {
60
+ this._tagFormatters.push(OINOFormatter_1.OINO_EMPTY_FORMATTER);
61
+ }
62
+ this._tagVariable.push(variable_parts[0]);
63
+ this._tagCount = this._tagCount + 1;
64
+ tag_start_pos = this.template.indexOf(this._tagOpen, tag_end_pos);
65
+ tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length;
41
66
  }
67
+ }
68
+ _createHttpResult(html) {
42
69
  const result = new _1.OINOHttpResult(html);
43
70
  if (this.expires >= 1) {
44
71
  result.expires = Math.round(this.expires);
@@ -49,11 +76,22 @@ class OINOHtmlTemplate {
49
76
  return result;
50
77
  }
51
78
  _renderHtml() {
52
- let html = this.template;
53
- for (let key in this._variables) {
54
- const value = this._variables[key];
55
- html = html.replaceAll(this._tag + key + this._tag, value);
79
+ let html = "";
80
+ let start_pos = 0;
81
+ let end_pos = 0;
82
+ for (let i = 0; i < this._tagCount; i++) {
83
+ end_pos = this._tagStart[i];
84
+ const key = this._tagVariable[i];
85
+ const value = this._tagFormatters[i].format(this._variables[key] || "");
86
+ html += this.template.slice(start_pos, end_pos) + value;
87
+ start_pos = this._tagEnd[i];
56
88
  }
89
+ html += this.template.slice(start_pos);
90
+ // let html:string = this.template
91
+ // for (let key in this._variables) {
92
+ // const value = this._variables[key]
93
+ // html = html.replaceAll(this._tag + key + this._tag, value)
94
+ // }
57
95
  return html;
58
96
  }
59
97
  /**
@@ -99,26 +137,23 @@ class OINOHtmlTemplate {
99
137
  /**
100
138
  * Creates HTML Response from set variables.
101
139
  *
102
- * @param removeUnusedTags whether to remove unused tags
103
- *
104
140
  */
105
- render(removeUnusedTags = true) {
141
+ render() {
106
142
  const html = this._renderHtml();
107
143
  this.clearVariables(); // clear variables after rendering
108
- return this._createHttpResult(html, removeUnusedTags);
144
+ return this._createHttpResult(html);
109
145
  }
110
146
  /**
111
147
  * Creates HTML Response from a key-value-pair.
112
148
  *
113
149
  * @param key key
114
150
  * @param value value
115
- * @param removeUnusedTags whether to remove unused tags
116
151
  *
117
152
  */
118
- renderFromKeyValue(key, value, removeUnusedTags = true) {
153
+ renderFromKeyValue(key, value) {
119
154
  _1.OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromKeyValue");
120
155
  this.setVariableFromValue(key, value);
121
- const result = this.render(removeUnusedTags);
156
+ const result = this.render();
122
157
  _1.OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromKeyValue");
123
158
  return result;
124
159
  }
@@ -126,13 +161,12 @@ class OINOHtmlTemplate {
126
161
  * Creates HTML Response from object properties.
127
162
  *
128
163
  * @param object object
129
- * @param removeUnusedTags whether to remove unused tags
130
164
  *
131
165
  */
132
- renderFromObject(object, removeUnusedTags = true) {
166
+ renderFromObject(object = true) {
133
167
  _1.OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromObject");
134
168
  this.setVariableFromProperties(object);
135
- const result = this.render(removeUnusedTags);
169
+ const result = this.render();
136
170
  _1.OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromObject");
137
171
  return result;
138
172
  }
@@ -140,7 +174,6 @@ class OINOHtmlTemplate {
140
174
  * Creates HTML Response from API result.
141
175
  *
142
176
  * @param result OINOResult-object
143
- * @param removeUnusedTags whether to remove unused tags
144
177
  * @param messageSeparator HTML separator for messages
145
178
  * @param includeErrorMessages include debug messages in result
146
179
  * @param includeWarningMessages include debug messages in result
@@ -148,7 +181,7 @@ class OINOHtmlTemplate {
148
181
  * @param includeDebugMessages include debug messages in result
149
182
  *
150
183
  */
151
- renderFromResult(result, removeUnusedTags = true, messageSeparator = "", includeErrorMessages = false, includeWarningMessages = false, includeInfoMessages = false, includeDebugMessages = false) {
184
+ renderFromResult(result, messageSeparator = "", includeErrorMessages = false, includeWarningMessages = false, includeInfoMessages = false, includeDebugMessages = false) {
152
185
  _1.OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromResult");
153
186
  this.setVariableFromValue("statusCode", result.statusCode.toString());
154
187
  this.setVariableFromValue("statusMessage", result.statusMessage.toString());
@@ -170,7 +203,7 @@ class OINOHtmlTemplate {
170
203
  if (messageSeparator && (messages.length > 0)) {
171
204
  this.setVariableFromValue("messages", messages.join(messageSeparator), false); // messages have been escaped already
172
205
  }
173
- const http_result = this.render(removeUnusedTags);
206
+ const http_result = this.render();
174
207
  _1.OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromResult");
175
208
  return http_result;
176
209
  }
package/dist/cjs/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OINOContentType = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINOHtmlTemplate = exports.OINOStr = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = void 0;
3
+ exports.OINOContentType = exports.OINO_DEBUG_PREFIX = exports.OINO_INFO_PREFIX = exports.OINO_WARNING_PREFIX = exports.OINO_ERROR_PREFIX = exports.OINO_EMPTY_FORMATTER = exports.OINOFormatter = exports.OINOHtmlTemplate = exports.OINOStr = exports.OINOHttpResult = exports.OINOResult = exports.OINOConsoleLog = exports.OINOLogLevel = exports.OINOLog = exports.OINOMemoryBenchmark = exports.OINOBenchmark = void 0;
4
4
  var OINOBenchmark_js_1 = require("./OINOBenchmark.js");
5
5
  Object.defineProperty(exports, "OINOBenchmark", { enumerable: true, get: function () { return OINOBenchmark_js_1.OINOBenchmark; } });
6
6
  Object.defineProperty(exports, "OINOMemoryBenchmark", { enumerable: true, get: function () { return OINOBenchmark_js_1.OINOMemoryBenchmark; } });
@@ -15,6 +15,9 @@ var OINOStr_js_1 = require("./OINOStr.js");
15
15
  Object.defineProperty(exports, "OINOStr", { enumerable: true, get: function () { return OINOStr_js_1.OINOStr; } });
16
16
  var OINOHtmlTemplate_js_1 = require("./OINOHtmlTemplate.js");
17
17
  Object.defineProperty(exports, "OINOHtmlTemplate", { enumerable: true, get: function () { return OINOHtmlTemplate_js_1.OINOHtmlTemplate; } });
18
+ var OINOFormatter_js_1 = require("./OINOFormatter.js");
19
+ Object.defineProperty(exports, "OINOFormatter", { enumerable: true, get: function () { return OINOFormatter_js_1.OINOFormatter; } });
20
+ Object.defineProperty(exports, "OINO_EMPTY_FORMATTER", { enumerable: true, get: function () { return OINOFormatter_js_1.OINO_EMPTY_FORMATTER; } });
18
21
  /** OINO error message prefix */
19
22
  exports.OINO_ERROR_PREFIX = "OINO ERROR";
20
23
  /** OINO warning message prefix */
@@ -0,0 +1,157 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+ import { OINO_ERROR_PREFIX, OINOLog } from "./index.js";
7
+ /**
8
+ * Class for formatting strings and values.
9
+ *
10
+ */
11
+ export class OINOFormatter {
12
+ static OINO_FORMATTER_REGEXP = /\s?(trim(\(\))?|trimLeft(\(\))?|trimRight(\(\))?|toUpper(\(\))?|toLower(\(\))?|cropLeft\((\d+)\)|cropRight\((\d+)\)|cropToDelimiter\(([^\(\),]+),(\d+)\)|cropFromDelimiter\(([^\(\),]+),(\d+)\)|substring\((\d+),(\d+)\)|replace\(([^\(\),]+),([^\(\),]+)\))\s?$/i;
13
+ _types;
14
+ _params;
15
+ /**
16
+ * Constructor of `OINOFormatter`
17
+ * @param types array of formatter types
18
+ * @param params array of formatter parameters according to type
19
+ */
20
+ constructor(types, params) {
21
+ this._types = types;
22
+ this._params = params;
23
+ }
24
+ /**
25
+ * Constructor for `OINOFormatter` as parser of http parameter.
26
+ *
27
+ * @param formatters string or array of strings of serialized representation of formatters with following functions
28
+ * - trim()
29
+ * - trimLeft()
30
+ * - trimRight()
31
+ * - toUpper()
32
+ * - toLower()
33
+ * - cropLeft(charsToCrop)
34
+ * - cropRight(charsToCrop)
35
+ * - cropToDelimiter(delimiter,offsetChars)
36
+ * - cropFromDelimiter(delimiter,offsetChars)
37
+ * - substring(start,end)
38
+ * - replace(search,replace)
39
+ */
40
+ static parse(formatters) {
41
+ if (typeof formatters === "string") {
42
+ formatters = [formatters];
43
+ }
44
+ if (!formatters || formatters.length === 0) {
45
+ return OINO_EMPTY_FORMATTER;
46
+ }
47
+ else {
48
+ const types = [];
49
+ const params = [];
50
+ for (let i = 0; i < formatters.length; i++) {
51
+ let match = formatters[i]?.match(this.OINO_FORMATTER_REGEXP);
52
+ if (!match) {
53
+ OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Invalid formatter string", { formatter: formatters[i] });
54
+ throw new Error(OINO_ERROR_PREFIX + "Invalid formatter: " + formatters[i]);
55
+ }
56
+ else {
57
+ const formatter_type = match[1].toLowerCase().substring(0, match[1].indexOf('('));
58
+ const formatter_params = [];
59
+ if ((formatter_type === "trim") || (formatter_type === "trimleft") || (formatter_type === "trimright") || (formatter_type === "toupper") || (formatter_type === "tolower")) {
60
+ // no parameters
61
+ }
62
+ else if (formatter_type === "cropleft") {
63
+ formatter_params.push(parseInt(match[7]));
64
+ }
65
+ else if (formatter_type === "cropright") {
66
+ formatter_params.push(parseInt(match[8]));
67
+ }
68
+ else if (formatter_type === "croptodelimiter") {
69
+ formatter_params.push(decodeURIComponent(match[9]), parseInt(match[10]));
70
+ }
71
+ else if (formatter_type === "cropfromdelimiter") {
72
+ formatter_params.push(decodeURIComponent(match[11]), parseInt(match[12]));
73
+ }
74
+ else if (formatter_type === "substring") {
75
+ formatter_params.push(parseInt(match[13]), parseInt(match[14]));
76
+ }
77
+ else if (formatter_type === "replace") {
78
+ formatter_params.push(decodeURIComponent(match[15]), decodeURIComponent(match[16]));
79
+ }
80
+ else {
81
+ OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Unknown formatter type", { formatter: formatters[i] });
82
+ throw new Error(OINO_ERROR_PREFIX + "Unsupported formatter: " + formatters[i]);
83
+ }
84
+ types.push(formatter_type);
85
+ params.push(formatter_params);
86
+ }
87
+ }
88
+ return new OINOFormatter(types, params);
89
+ }
90
+ }
91
+ /**
92
+ * Does formatter include any operations.
93
+ * @return true if formatter is empty
94
+ */
95
+ isEmpty() {
96
+ return this._types.length === 0;
97
+ }
98
+ /**
99
+ * Applies all formatters in order to given value.
100
+ *
101
+ * @param value string value to be formatted
102
+ * @returns formatted string value
103
+ */
104
+ format(value) {
105
+ let formatted = value;
106
+ for (let i = 0; i < this._types.length; i++) {
107
+ const formatter_type = this._types[i];
108
+ const formatter_params = this._params[i];
109
+ if (formatter_type === "trim") {
110
+ formatted = formatted.trim();
111
+ }
112
+ else if (formatter_type === "trimleft") {
113
+ formatted = formatted.trimStart();
114
+ }
115
+ else if (formatter_type === "trimright") {
116
+ formatted = formatted.trimEnd();
117
+ }
118
+ else if (formatter_type === "toupper") {
119
+ formatted = formatted.toUpperCase();
120
+ }
121
+ else if (formatter_type === "tolower") {
122
+ formatted = formatted.toLowerCase();
123
+ }
124
+ else if (formatter_type === "cropleft") {
125
+ formatted = formatted.slice(formatter_params[0]);
126
+ }
127
+ else if (formatter_type === "cropright") {
128
+ formatted = formatted.slice(0, formatted.length - formatter_params[0]);
129
+ }
130
+ else if (formatter_type === "croptodelimiter") {
131
+ const to_demilimiter_idx = formatted.indexOf(formatter_params[0]);
132
+ if (to_demilimiter_idx >= 0) {
133
+ formatted = formatted.slice(Math.max(to_demilimiter_idx + formatter_params[0].length + formatter_params[1], 0));
134
+ }
135
+ }
136
+ else if (formatter_type === "cropfromdelimiter") {
137
+ const from_demilimiter_idx = formatted.indexOf(formatter_params[0]);
138
+ if (from_demilimiter_idx >= 0) {
139
+ formatted = formatted.slice(0, Math.max(from_demilimiter_idx + formatter_params[1], 0));
140
+ }
141
+ }
142
+ else if (formatter_type === "substring") {
143
+ const start = formatter_params[0] ? parseInt(formatter_params[0]) : 0;
144
+ const end = formatter_params[1] ? parseInt(formatter_params[1]) : formatted.length;
145
+ formatted = formatted.substring(start, end);
146
+ }
147
+ else if (formatter_type === "replace") {
148
+ const search = formatter_params[0];
149
+ const replacement = formatter_params[1];
150
+ formatted = formatted.replaceAll(search, replacement);
151
+ }
152
+ // console.log("formatter:", formatter_type, "params:", formatter_params, "formatted:", formatted)
153
+ }
154
+ return formatted;
155
+ }
156
+ }
157
+ export const OINO_EMPTY_FORMATTER = new OINOFormatter([], []);
@@ -1,11 +1,17 @@
1
1
  import { OINOStr, OINOContentType, OINOHttpResult, OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PREFIX, OINOBenchmark } from ".";
2
+ import { OINO_EMPTY_FORMATTER, OINOFormatter } from "./OINOFormatter";
2
3
  /**
3
4
  * Class for rendering HTML from data.
4
5
  */
5
6
  export class OINOHtmlTemplate {
6
- _tag;
7
- _tagCleanRegex;
7
+ _tagOpen;
8
+ _tagClose;
8
9
  _variables = {};
10
+ _tagStart = [];
11
+ _tagEnd = [];
12
+ _tagVariable = [];
13
+ _tagFormatters = [];
14
+ _tagCount = 0;
9
15
  /** HTML template string */
10
16
  template;
11
17
  /** Cache modified value for template */
@@ -19,12 +25,13 @@ export class OINOHtmlTemplate {
19
25
  * @param tag tag to identify variables in template
20
26
  *
21
27
  */
22
- constructor(template, tag = "###") {
28
+ constructor(template, tagOpen = "{{{", tagClose = "}}}") {
23
29
  this.template = template;
24
30
  this.modified = 0;
25
31
  this.expires = 0;
26
- this._tag = tag;
27
- this._tagCleanRegex = new RegExp(tag + ".*" + tag, "g");
32
+ this._tagOpen = tagOpen;
33
+ this._tagClose = tagClose;
34
+ this._parseTemplate();
28
35
  }
29
36
  /**
30
37
  * @returns whether template is empty
@@ -32,10 +39,30 @@ export class OINOHtmlTemplate {
32
39
  isEmpty() {
33
40
  return this.template == "";
34
41
  }
35
- _createHttpResult(html, removeUnusedTags) {
36
- if (removeUnusedTags) {
37
- html = html.replace(this._tagCleanRegex, "");
42
+ _parseTemplate() {
43
+ const tag_open_length = this._tagOpen.length;
44
+ const tag_close_length = this._tagClose.length;
45
+ let tag_start_pos = this.template.indexOf(this._tagOpen, 0);
46
+ let tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length;
47
+ while ((tag_start_pos >= 0) && (tag_end_pos > tag_start_pos)) {
48
+ this._tagStart.push(tag_start_pos);
49
+ this._tagEnd.push(tag_end_pos);
50
+ let variable = this.template.slice(tag_start_pos + tag_open_length, tag_end_pos - tag_close_length);
51
+ const variable_parts = variable.split("|");
52
+ if (variable_parts.length > 1) {
53
+ const formatter = OINOFormatter.parse(variable_parts.slice(1));
54
+ this._tagFormatters.push(formatter);
55
+ }
56
+ else {
57
+ this._tagFormatters.push(OINO_EMPTY_FORMATTER);
58
+ }
59
+ this._tagVariable.push(variable_parts[0]);
60
+ this._tagCount = this._tagCount + 1;
61
+ tag_start_pos = this.template.indexOf(this._tagOpen, tag_end_pos);
62
+ tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length;
38
63
  }
64
+ }
65
+ _createHttpResult(html) {
39
66
  const result = new OINOHttpResult(html);
40
67
  if (this.expires >= 1) {
41
68
  result.expires = Math.round(this.expires);
@@ -46,11 +73,22 @@ export class OINOHtmlTemplate {
46
73
  return result;
47
74
  }
48
75
  _renderHtml() {
49
- let html = this.template;
50
- for (let key in this._variables) {
51
- const value = this._variables[key];
52
- html = html.replaceAll(this._tag + key + this._tag, value);
76
+ let html = "";
77
+ let start_pos = 0;
78
+ let end_pos = 0;
79
+ for (let i = 0; i < this._tagCount; i++) {
80
+ end_pos = this._tagStart[i];
81
+ const key = this._tagVariable[i];
82
+ const value = this._tagFormatters[i].format(this._variables[key] || "");
83
+ html += this.template.slice(start_pos, end_pos) + value;
84
+ start_pos = this._tagEnd[i];
53
85
  }
86
+ html += this.template.slice(start_pos);
87
+ // let html:string = this.template
88
+ // for (let key in this._variables) {
89
+ // const value = this._variables[key]
90
+ // html = html.replaceAll(this._tag + key + this._tag, value)
91
+ // }
54
92
  return html;
55
93
  }
56
94
  /**
@@ -96,26 +134,23 @@ export class OINOHtmlTemplate {
96
134
  /**
97
135
  * Creates HTML Response from set variables.
98
136
  *
99
- * @param removeUnusedTags whether to remove unused tags
100
- *
101
137
  */
102
- render(removeUnusedTags = true) {
138
+ render() {
103
139
  const html = this._renderHtml();
104
140
  this.clearVariables(); // clear variables after rendering
105
- return this._createHttpResult(html, removeUnusedTags);
141
+ return this._createHttpResult(html);
106
142
  }
107
143
  /**
108
144
  * Creates HTML Response from a key-value-pair.
109
145
  *
110
146
  * @param key key
111
147
  * @param value value
112
- * @param removeUnusedTags whether to remove unused tags
113
148
  *
114
149
  */
115
- renderFromKeyValue(key, value, removeUnusedTags = true) {
150
+ renderFromKeyValue(key, value) {
116
151
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromKeyValue");
117
152
  this.setVariableFromValue(key, value);
118
- const result = this.render(removeUnusedTags);
153
+ const result = this.render();
119
154
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromKeyValue");
120
155
  return result;
121
156
  }
@@ -123,13 +158,12 @@ export class OINOHtmlTemplate {
123
158
  * Creates HTML Response from object properties.
124
159
  *
125
160
  * @param object object
126
- * @param removeUnusedTags whether to remove unused tags
127
161
  *
128
162
  */
129
- renderFromObject(object, removeUnusedTags = true) {
163
+ renderFromObject(object = true) {
130
164
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromObject");
131
165
  this.setVariableFromProperties(object);
132
- const result = this.render(removeUnusedTags);
166
+ const result = this.render();
133
167
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromObject");
134
168
  return result;
135
169
  }
@@ -137,7 +171,6 @@ export class OINOHtmlTemplate {
137
171
  * Creates HTML Response from API result.
138
172
  *
139
173
  * @param result OINOResult-object
140
- * @param removeUnusedTags whether to remove unused tags
141
174
  * @param messageSeparator HTML separator for messages
142
175
  * @param includeErrorMessages include debug messages in result
143
176
  * @param includeWarningMessages include debug messages in result
@@ -145,7 +178,7 @@ export class OINOHtmlTemplate {
145
178
  * @param includeDebugMessages include debug messages in result
146
179
  *
147
180
  */
148
- renderFromResult(result, removeUnusedTags = true, messageSeparator = "", includeErrorMessages = false, includeWarningMessages = false, includeInfoMessages = false, includeDebugMessages = false) {
181
+ renderFromResult(result, messageSeparator = "", includeErrorMessages = false, includeWarningMessages = false, includeInfoMessages = false, includeDebugMessages = false) {
149
182
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromResult");
150
183
  this.setVariableFromValue("statusCode", result.statusCode.toString());
151
184
  this.setVariableFromValue("statusMessage", result.statusMessage.toString());
@@ -167,7 +200,7 @@ export class OINOHtmlTemplate {
167
200
  if (messageSeparator && (messages.length > 0)) {
168
201
  this.setVariableFromValue("messages", messages.join(messageSeparator), false); // messages have been escaped already
169
202
  }
170
- const http_result = this.render(removeUnusedTags);
203
+ const http_result = this.render();
171
204
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromResult");
172
205
  return http_result;
173
206
  }
package/dist/esm/index.js CHANGED
@@ -3,6 +3,7 @@ export { OINOLog, OINOLogLevel, OINOConsoleLog } from "./OINOLog.js";
3
3
  export { OINOResult, OINOHttpResult } from "./OINOResult.js";
4
4
  export { OINOStr } from "./OINOStr.js";
5
5
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js";
6
+ export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js";
6
7
  /** OINO error message prefix */
7
8
  export const OINO_ERROR_PREFIX = "OINO ERROR";
8
9
  /** OINO warning message prefix */
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Class for formatting strings and values.
3
+ *
4
+ */
5
+ export declare class OINOFormatter {
6
+ static OINO_FORMATTER_REGEXP: RegExp;
7
+ _types: string[];
8
+ _params: any[][];
9
+ /**
10
+ * Constructor of `OINOFormatter`
11
+ * @param types array of formatter types
12
+ * @param params array of formatter parameters according to type
13
+ */
14
+ constructor(types: string[], params: any[][]);
15
+ /**
16
+ * Constructor for `OINOFormatter` as parser of http parameter.
17
+ *
18
+ * @param formatters string or array of strings of serialized representation of formatters with following functions
19
+ * - trim()
20
+ * - trimLeft()
21
+ * - trimRight()
22
+ * - toUpper()
23
+ * - toLower()
24
+ * - cropLeft(charsToCrop)
25
+ * - cropRight(charsToCrop)
26
+ * - cropToDelimiter(delimiter,offsetChars)
27
+ * - cropFromDelimiter(delimiter,offsetChars)
28
+ * - substring(start,end)
29
+ * - replace(search,replace)
30
+ */
31
+ static parse(formatters: string | string[]): OINOFormatter;
32
+ /**
33
+ * Does formatter include any operations.
34
+ * @return true if formatter is empty
35
+ */
36
+ isEmpty(): boolean;
37
+ /**
38
+ * Applies all formatters in order to given value.
39
+ *
40
+ * @param value string value to be formatted
41
+ * @returns formatted string value
42
+ */
43
+ format(value: string): string;
44
+ }
45
+ export declare const OINO_EMPTY_FORMATTER: OINOFormatter;
@@ -3,9 +3,14 @@ import { OINOResult, OINOHttpResult } from ".";
3
3
  * Class for rendering HTML from data.
4
4
  */
5
5
  export declare class OINOHtmlTemplate {
6
- private _tag;
7
- private _tagCleanRegex;
6
+ private _tagOpen;
7
+ private _tagClose;
8
8
  private _variables;
9
+ private _tagStart;
10
+ private _tagEnd;
11
+ private _tagVariable;
12
+ private _tagFormatters;
13
+ private _tagCount;
9
14
  /** HTML template string */
10
15
  template: string;
11
16
  /** Cache modified value for template */
@@ -19,12 +24,13 @@ export declare class OINOHtmlTemplate {
19
24
  * @param tag tag to identify variables in template
20
25
  *
21
26
  */
22
- constructor(template: string, tag?: string);
27
+ constructor(template: string, tagOpen?: string, tagClose?: string);
23
28
  /**
24
29
  * @returns whether template is empty
25
30
  */
26
31
  isEmpty(): boolean;
27
- protected _createHttpResult(html: string, removeUnusedTags: boolean): OINOHttpResult;
32
+ protected _parseTemplate(): void;
33
+ protected _createHttpResult(html: string): OINOHttpResult;
28
34
  protected _renderHtml(): string;
29
35
  /**
30
36
  * Clear template variables.
@@ -51,32 +57,27 @@ export declare class OINOHtmlTemplate {
51
57
  /**
52
58
  * Creates HTML Response from set variables.
53
59
  *
54
- * @param removeUnusedTags whether to remove unused tags
55
- *
56
60
  */
57
- render(removeUnusedTags?: boolean): OINOHttpResult;
61
+ render(): OINOHttpResult;
58
62
  /**
59
63
  * Creates HTML Response from a key-value-pair.
60
64
  *
61
65
  * @param key key
62
66
  * @param value value
63
- * @param removeUnusedTags whether to remove unused tags
64
67
  *
65
68
  */
66
- renderFromKeyValue(key: string, value: string, removeUnusedTags?: boolean): OINOHttpResult;
69
+ renderFromKeyValue(key: string, value: string): OINOHttpResult;
67
70
  /**
68
71
  * Creates HTML Response from object properties.
69
72
  *
70
73
  * @param object object
71
- * @param removeUnusedTags whether to remove unused tags
72
74
  *
73
75
  */
74
- renderFromObject(object: any, removeUnusedTags?: boolean): OINOHttpResult;
76
+ renderFromObject(object?: any): OINOHttpResult;
75
77
  /**
76
78
  * Creates HTML Response from API result.
77
79
  *
78
80
  * @param result OINOResult-object
79
- * @param removeUnusedTags whether to remove unused tags
80
81
  * @param messageSeparator HTML separator for messages
81
82
  * @param includeErrorMessages include debug messages in result
82
83
  * @param includeWarningMessages include debug messages in result
@@ -84,5 +85,5 @@ export declare class OINOHtmlTemplate {
84
85
  * @param includeDebugMessages include debug messages in result
85
86
  *
86
87
  */
87
- renderFromResult(result: OINOResult, removeUnusedTags?: boolean, messageSeparator?: string, includeErrorMessages?: boolean, includeWarningMessages?: boolean, includeInfoMessages?: boolean, includeDebugMessages?: boolean): OINOHttpResult;
88
+ renderFromResult(result: OINOResult, messageSeparator?: string, includeErrorMessages?: boolean, includeWarningMessages?: boolean, includeInfoMessages?: boolean, includeDebugMessages?: boolean): OINOHttpResult;
88
89
  }
@@ -3,6 +3,7 @@ export { OINOLog, OINOLogLevel, OINOConsoleLog } from "./OINOLog.js";
3
3
  export { OINOResult, OINOHttpResult } from "./OINOResult.js";
4
4
  export { OINOStr } from "./OINOStr.js";
5
5
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js";
6
+ export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js";
6
7
  /** OINO error message prefix */
7
8
  export declare const OINO_ERROR_PREFIX = "OINO ERROR";
8
9
  /** OINO warning message prefix */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/common",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "OINO TS package for common classes.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  },
21
21
  "devDependencies": {
22
- "@oino-ts/types": "0.11.0",
22
+ "@oino-ts/types": "0.12.0",
23
23
  "@types/node": "^22.0.0",
24
24
  "typescript": "~5.9.0"
25
25
  },
@@ -0,0 +1,165 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { OINO_ERROR_PREFIX, OINOLog } from "./index.js"
8
+
9
+
10
+
11
+ /**
12
+ * Class for formatting strings and values.
13
+ *
14
+ */
15
+ export class OINOFormatter {
16
+ static OINO_FORMATTER_REGEXP = /\s?(trim(\(\))?|trimLeft(\(\))?|trimRight(\(\))?|toUpper(\(\))?|toLower(\(\))?|cropLeft\((\d+)\)|cropRight\((\d+)\)|cropToDelimiter\(([^\(\),]+),(\d+)\)|cropFromDelimiter\(([^\(\),]+),(\d+)\)|substring\((\d+),(\d+)\)|replace\(([^\(\),]+),([^\(\),]+)\))\s?$/i
17
+ _types: string[]
18
+ _params: any[][]
19
+
20
+ /**
21
+ * Constructor of `OINOFormatter`
22
+ * @param types array of formatter types
23
+ * @param params array of formatter parameters according to type
24
+ */
25
+ constructor(types: string[], params: any[][]) {
26
+ this._types = types
27
+ this._params = params
28
+ }
29
+
30
+ /**
31
+ * Constructor for `OINOFormatter` as parser of http parameter.
32
+ *
33
+ * @param formatters string or array of strings of serialized representation of formatters with following functions
34
+ * - trim()
35
+ * - trimLeft()
36
+ * - trimRight()
37
+ * - toUpper()
38
+ * - toLower()
39
+ * - cropLeft(charsToCrop)
40
+ * - cropRight(charsToCrop)
41
+ * - cropToDelimiter(delimiter,offsetChars)
42
+ * - cropFromDelimiter(delimiter,offsetChars)
43
+ * - substring(start,end)
44
+ * - replace(search,replace)
45
+ */
46
+ static parse(formatters: string|string[]): OINOFormatter {
47
+ if (typeof formatters === "string") {
48
+ formatters = [formatters]
49
+ }
50
+ if (!formatters || formatters.length === 0) {
51
+ return OINO_EMPTY_FORMATTER
52
+
53
+ } else {
54
+ const types:string[] = []
55
+ const params:any[][] = []
56
+ for (let i=0; i<formatters.length; i++) {
57
+ let match = formatters[i]?.match(this.OINO_FORMATTER_REGEXP)
58
+ if (!match) {
59
+ OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Invalid formatter string", {formatter:formatters[i]})
60
+ throw new Error(OINO_ERROR_PREFIX + "Invalid formatter: " + formatters[i])
61
+ } else {
62
+ const formatter_type = match[1].toLowerCase().substring(0, match[1].indexOf('('))
63
+ const formatter_params: any[] = []
64
+ if ((formatter_type === "trim") || (formatter_type === "trimleft") || (formatter_type === "trimright") || (formatter_type === "toupper") || (formatter_type === "tolower")) {
65
+ // no parameters
66
+
67
+ } else if (formatter_type=== "cropleft") {
68
+ formatter_params.push(parseInt(match[7]))
69
+
70
+ } else if (formatter_type === "cropright") {
71
+ formatter_params.push(parseInt(match[8]))
72
+
73
+ } else if (formatter_type === "croptodelimiter") {
74
+ formatter_params.push(decodeURIComponent(match[9]), parseInt(match[10]))
75
+
76
+ } else if (formatter_type === "cropfromdelimiter") {
77
+ formatter_params.push(decodeURIComponent(match[11]), parseInt(match[12]))
78
+
79
+ } else if (formatter_type === "substring") {
80
+ formatter_params.push(parseInt(match[13]), parseInt(match[14]))
81
+
82
+ } else if (formatter_type === "replace") {
83
+ formatter_params.push(decodeURIComponent(match[15]), decodeURIComponent(match[16]))
84
+ } else {
85
+ OINOLog.error("@oino-ts/common", "OINOFormatter", "parse", "Unknown formatter type", {formatter:formatters[i]})
86
+ throw new Error(OINO_ERROR_PREFIX + "Unsupported formatter: " + formatters[i])
87
+ }
88
+ types.push(formatter_type)
89
+ params.push(formatter_params)
90
+ }
91
+ }
92
+ return new OINOFormatter(types, params)
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Does formatter include any operations.
98
+ * @return true if formatter is empty
99
+ */
100
+ isEmpty():boolean {
101
+ return this._types.length === 0
102
+ }
103
+
104
+ /**
105
+ * Applies all formatters in order to given value.
106
+ *
107
+ * @param value string value to be formatted
108
+ * @returns formatted string value
109
+ */
110
+ format(value: string): string {
111
+ let formatted = value
112
+ for (let i = 0; i < this._types.length; i++) {
113
+ const formatter_type = this._types[i]
114
+ const formatter_params = this._params[i]
115
+ if (formatter_type === "trim") {
116
+ formatted = formatted.trim()
117
+
118
+ } else if (formatter_type === "trimleft") {
119
+ formatted = formatted.trimStart()
120
+
121
+ } else if (formatter_type === "trimright") {
122
+ formatted = formatted.trimEnd()
123
+
124
+ } else if (formatter_type === "toupper") {
125
+ formatted = formatted.toUpperCase()
126
+
127
+ } else if (formatter_type === "tolower") {
128
+ formatted = formatted.toLowerCase()
129
+
130
+ } else if (formatter_type === "cropleft") {
131
+ formatted = formatted.slice(formatter_params[0])
132
+
133
+ } else if (formatter_type === "cropright") {
134
+ formatted = formatted.slice(0, formatted.length-formatter_params[0])
135
+
136
+ } else if (formatter_type === "croptodelimiter") {
137
+ const to_demilimiter_idx = formatted.indexOf(formatter_params[0])
138
+ if (to_demilimiter_idx >= 0) {
139
+ formatted = formatted.slice(Math.max(to_demilimiter_idx + formatter_params[0].length + formatter_params[1], 0))
140
+ }
141
+
142
+ } else if (formatter_type === "cropfromdelimiter") {
143
+ const from_demilimiter_idx = formatted.indexOf(formatter_params[0])
144
+ if (from_demilimiter_idx >= 0) {
145
+ formatted = formatted.slice(0, Math.max(from_demilimiter_idx + formatter_params[1], 0))
146
+ }
147
+
148
+ } else if (formatter_type === "substring") {
149
+ const start = formatter_params[0] ? parseInt(formatter_params[0]) : 0
150
+ const end = formatter_params[1] ? parseInt(formatter_params[1]) : formatted.length
151
+ formatted = formatted.substring(start, end)
152
+
153
+ } else if (formatter_type === "replace") {
154
+ const search = formatter_params[0]
155
+ const replacement = formatter_params[1]
156
+ formatted = formatted.replaceAll(search, replacement)
157
+ }
158
+ // console.log("formatter:", formatter_type, "params:", formatter_params, "formatted:", formatted)
159
+ }
160
+ return formatted
161
+ }
162
+
163
+ }
164
+
165
+ export const OINO_EMPTY_FORMATTER = new OINOFormatter([], [])
@@ -0,0 +1,114 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { expect, test } from "bun:test";
8
+
9
+ import { OINOHtmlTemplate } from "./OINOHtmlTemplate"
10
+ import { OINOFormatter } from "./OINOFormatter";
11
+ import { OINOLog, OINOConsoleLog, OINOLogLevel } from "./OINOLog"
12
+
13
+ Math.random()
14
+
15
+ OINOLog.setInstance(new OINOConsoleLog(OINOLogLevel.error))
16
+
17
+ const VARIABLE_OPTIONS = [
18
+ "{{{var{i}}}}", // 0 nothing
19
+ "{{{var{i}|trim()}}}", // 1 trim
20
+ "{{{var{i}|trimLeft()}}}", // 2 trimLeft
21
+ "{{{var{i}|trimRight()}}}", // 3 trimRight
22
+ "{{{var{i}|toLower()}}}", // 4 toLower
23
+ "{{{var{i}|cropLeft(1)}}}", // 5 cropLeft
24
+ "{{{var{i}|cropRight(1)}}}", // 6 cropRight
25
+ "{{{var{i}|cropToDelimiter(¤,1)}}}", // 7 cropToDelimiter
26
+ "{{{var{i}|cropFromDelimiter(¤,0)}}}", // 8 cropFromDelimiter
27
+ "{{{var{i}|substring(2,10)}}}", // 9 substring
28
+ "{{{var{i}|replace(¤,a)}}}" // 10 replace
29
+ ]
30
+ const VARIABLE_VALUES = [
31
+ "value{i}", // 0 nothing
32
+ " value{i} ", // 1 trim
33
+ " value{i}", // 2 trimLeft
34
+ "value{i} ", // 3 trimRight
35
+ "VALUE{i}", // 4 toLower
36
+ "¤value{i}", // 5 cropLeft
37
+ "value{i}¤", // 6 cropRight
38
+ "¤¤value{i}", // 7 cropToDelimiter
39
+ "value{i}¤!", // 8 cropFromDelimiter
40
+ "!¤value{i}", // 9 substring
41
+ "v¤lue{i}" // 10 replace
42
+ ]
43
+
44
+
45
+ function _generateTemplateVariable(i:number): string {
46
+ return VARIABLE_OPTIONS[i % VARIABLE_OPTIONS.length].replace(/\{i\}/g, i.toString())
47
+ }
48
+
49
+ function _generateTemplateHtml(variableCount: number): string {
50
+ let template = "{{{header}}}\n<div>\n" // edge case: tag at the start of the template
51
+ for (let i = 1; i <= variableCount; i++) {
52
+ template += `<p>${_generateTemplateVariable(i)}</p>\n`
53
+ }
54
+ template += "</div>\n{{{footer}}}" // edge case: tag at the end of the template
55
+ return template
56
+ }
57
+
58
+ function _generateResultHtml(variableCount: number): string {
59
+ let template = "header\n<div>\n"
60
+ for (let i = 1; i <= variableCount; i++) {
61
+ template += `<p>value${i}</p>\n`
62
+ }
63
+ template += "</div>\nfooter"
64
+ return template
65
+ }
66
+
67
+
68
+ function _generateValue(i: number): string {
69
+ return VARIABLE_VALUES[i % VARIABLE_VALUES.length].replace(/\{i\}/g, i.toString())
70
+ }
71
+
72
+ function _generateValues(variableCount: number): any {
73
+ const values: any = { header: "header", footer: "footer" }
74
+ for (let i = 1; i <= variableCount; i++) {
75
+ values[`var${i}`] = _generateValue(i)
76
+ }
77
+ return values
78
+ }
79
+
80
+ function benchmarkOINOHtmlTemplate(variables:number, replacements: number = 1000): number {
81
+ const template = new OINOHtmlTemplate(_generateTemplateHtml(variables))
82
+ const values = _generateValues(variables)
83
+ const result = _generateResultHtml(variables)
84
+
85
+ const start = performance.now();
86
+
87
+ for (let i = 0; i < replacements; i+=variables) {
88
+ template.setVariableFromProperties(values)
89
+ const res = template.render()
90
+ expect(res.body).toBe(result)
91
+ }
92
+
93
+ const end = performance.now()
94
+ const duration = end - start
95
+ return Math.round(replacements / duration)
96
+ }
97
+
98
+ await test("OINOHtmlTemplate render and performance", async () => {
99
+
100
+ let hps_min = Number.MAX_VALUE
101
+ let hps_max = 0
102
+
103
+ for (let j=10; j<=100; j+=10) {
104
+ const rps = benchmarkOINOHtmlTemplate(j, 100000)
105
+ // console.log(`Template variable renders per second with ${j} variables: ${rps}krps`)
106
+ hps_min = Math.min(rps, hps_min)
107
+ hps_max = Math.max(rps, hps_max)
108
+ expect(hps_min).toBeGreaterThanOrEqual(2500)
109
+ expect(hps_max).toBeLessThanOrEqual(5000)
110
+ }
111
+ console.log("OINOHtmlTemplate performance: " + hps_min + "k - " + hps_max + "k renders per second")
112
+ })
113
+
114
+
@@ -1,12 +1,19 @@
1
1
  import { OINOStr, OINOContentType, OINOResult, OINOHttpResult, OINO_ERROR_PREFIX, OINO_WARNING_PREFIX, OINO_INFO_PREFIX, OINO_DEBUG_PREFIX, OINOBenchmark } from "."
2
+ import { OINO_EMPTY_FORMATTER, OINOFormatter } from "./OINOFormatter"
2
3
 
3
4
  /**
4
5
  * Class for rendering HTML from data.
5
6
  */
6
7
  export class OINOHtmlTemplate {
7
- private _tag:string
8
- private _tagCleanRegex:RegExp
8
+ private _tagOpen:string
9
+ private _tagClose:string
9
10
  private _variables:Record<string, string> = {}
11
+ private _tagStart:number[] = []
12
+ private _tagEnd:number[] = []
13
+ private _tagVariable:string[] = []
14
+ private _tagFormatters:OINOFormatter[] = []
15
+ private _tagCount:number = 0
16
+
10
17
  /** HTML template string */
11
18
  template: string;
12
19
 
@@ -23,12 +30,13 @@ export class OINOHtmlTemplate {
23
30
  * @param tag tag to identify variables in template
24
31
  *
25
32
  */
26
- constructor (template:string, tag:string = "###") {
33
+ constructor (template:string, tagOpen:string = "{{{", tagClose:string = "}}}") {
27
34
  this.template = template
28
35
  this.modified = 0
29
36
  this.expires = 0
30
- this._tag = tag
31
- this._tagCleanRegex = new RegExp(tag + ".*" + tag, "g")
37
+ this._tagOpen = tagOpen
38
+ this._tagClose = tagClose
39
+ this._parseTemplate()
32
40
  }
33
41
 
34
42
  /**
@@ -38,10 +46,30 @@ export class OINOHtmlTemplate {
38
46
  return this.template == ""
39
47
  }
40
48
 
41
- protected _createHttpResult(html:string, removeUnusedTags:boolean):OINOHttpResult {
42
- if (removeUnusedTags) {
43
- html = html.replace(this._tagCleanRegex, "")
49
+ protected _parseTemplate() {
50
+ const tag_open_length = this._tagOpen.length
51
+ const tag_close_length = this._tagClose.length
52
+ let tag_start_pos = this.template.indexOf(this._tagOpen, 0)
53
+ let tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length
54
+ while ((tag_start_pos >= 0) && (tag_end_pos > tag_start_pos)) {
55
+ this._tagStart.push(tag_start_pos)
56
+ this._tagEnd.push(tag_end_pos)
57
+ let variable = this.template.slice(tag_start_pos+tag_open_length, tag_end_pos - tag_close_length)
58
+ const variable_parts = variable.split("|")
59
+ if (variable_parts.length > 1) {
60
+ const formatter: OINOFormatter = OINOFormatter.parse(variable_parts.slice(1))
61
+ this._tagFormatters.push(formatter)
62
+ } else {
63
+ this._tagFormatters.push(OINO_EMPTY_FORMATTER)
64
+ }
65
+ this._tagVariable.push(variable_parts[0])
66
+ this._tagCount = this._tagCount + 1
67
+ tag_start_pos = this.template.indexOf(this._tagOpen, tag_end_pos)
68
+ tag_end_pos = this.template.indexOf(this._tagClose, tag_start_pos + tag_open_length) + tag_close_length
44
69
  }
70
+ }
71
+
72
+ protected _createHttpResult(html:string):OINOHttpResult {
45
73
  const result:OINOHttpResult = new OINOHttpResult(html)
46
74
  if (this.expires >= 1) {
47
75
  result.expires = Math.round(this.expires)
@@ -53,11 +81,22 @@ export class OINOHtmlTemplate {
53
81
  }
54
82
 
55
83
  protected _renderHtml():string {
56
- let html:string = this.template
57
- for (let key in this._variables) {
58
- const value = this._variables[key]
59
- html = html.replaceAll(this._tag + key + this._tag, value)
84
+ let html:string = ""
85
+ let start_pos = 0
86
+ let end_pos = 0
87
+ for (let i=0; i<this._tagCount; i++) {
88
+ end_pos = this._tagStart[i]
89
+ const key = this._tagVariable[i]
90
+ const value = this._tagFormatters[i].format(this._variables[key] || "")
91
+ html += this.template.slice(start_pos, end_pos) + value
92
+ start_pos = this._tagEnd[i]
60
93
  }
94
+ html += this.template.slice(start_pos)
95
+ // let html:string = this.template
96
+ // for (let key in this._variables) {
97
+ // const value = this._variables[key]
98
+ // html = html.replaceAll(this._tag + key + this._tag, value)
99
+ // }
61
100
  return html
62
101
  }
63
102
 
@@ -105,14 +144,12 @@ export class OINOHtmlTemplate {
105
144
 
106
145
  /**
107
146
  * Creates HTML Response from set variables.
108
- *
109
- * @param removeUnusedTags whether to remove unused tags
110
147
  *
111
148
  */
112
- render(removeUnusedTags:boolean = true):OINOHttpResult {
149
+ render():OINOHttpResult {
113
150
  const html:string = this._renderHtml()
114
151
  this.clearVariables() // clear variables after rendering
115
- return this._createHttpResult(html, removeUnusedTags)
152
+ return this._createHttpResult(html)
116
153
  }
117
154
 
118
155
  /**
@@ -120,13 +157,12 @@ export class OINOHtmlTemplate {
120
157
  *
121
158
  * @param key key
122
159
  * @param value value
123
- * @param removeUnusedTags whether to remove unused tags
124
160
  *
125
161
  */
126
- renderFromKeyValue(key:string, value:string, removeUnusedTags:boolean = true):OINOHttpResult {
162
+ renderFromKeyValue(key:string, value:string):OINOHttpResult {
127
163
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromKeyValue")
128
164
  this.setVariableFromValue(key, value)
129
- const result:OINOHttpResult = this.render(removeUnusedTags)
165
+ const result:OINOHttpResult = this.render()
130
166
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromKeyValue")
131
167
  return result
132
168
  }
@@ -135,13 +171,12 @@ export class OINOHtmlTemplate {
135
171
  * Creates HTML Response from object properties.
136
172
  *
137
173
  * @param object object
138
- * @param removeUnusedTags whether to remove unused tags
139
174
  *
140
175
  */
141
- renderFromObject(object:any, removeUnusedTags:boolean = true):OINOHttpResult {
176
+ renderFromObject(object:any = true):OINOHttpResult {
142
177
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromObject")
143
178
  this.setVariableFromProperties(object)
144
- const result:OINOHttpResult = this.render(removeUnusedTags)
179
+ const result:OINOHttpResult = this.render()
145
180
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromObject")
146
181
  return result
147
182
  }
@@ -150,7 +185,6 @@ export class OINOHtmlTemplate {
150
185
  * Creates HTML Response from API result.
151
186
  *
152
187
  * @param result OINOResult-object
153
- * @param removeUnusedTags whether to remove unused tags
154
188
  * @param messageSeparator HTML separator for messages
155
189
  * @param includeErrorMessages include debug messages in result
156
190
  * @param includeWarningMessages include debug messages in result
@@ -158,7 +192,7 @@ export class OINOHtmlTemplate {
158
192
  * @param includeDebugMessages include debug messages in result
159
193
  *
160
194
  */
161
- renderFromResult(result:OINOResult, removeUnusedTags:boolean=true, messageSeparator:string = "", includeErrorMessages:boolean=false, includeWarningMessages:boolean=false, includeInfoMessages:boolean=false, includeDebugMessages:boolean=false):OINOHttpResult {
195
+ renderFromResult(result:OINOResult, messageSeparator:string = "", includeErrorMessages:boolean=false, includeWarningMessages:boolean=false, includeInfoMessages:boolean=false, includeDebugMessages:boolean=false):OINOHttpResult {
162
196
  OINOBenchmark.startMetric("OINOHtmlTemplate", "renderFromResult")
163
197
  this.setVariableFromValue("statusCode", result.statusCode.toString())
164
198
  this.setVariableFromValue("statusMessage", result.statusMessage.toString())
@@ -181,7 +215,7 @@ export class OINOHtmlTemplate {
181
215
  if (messageSeparator && (messages.length > 0)) {
182
216
  this.setVariableFromValue("messages", messages.join(messageSeparator), false) // messages have been escaped already
183
217
  }
184
- const http_result:OINOHttpResult = this.render(removeUnusedTags)
218
+ const http_result:OINOHttpResult = this.render()
185
219
  OINOBenchmark.endMetric("OINOHtmlTemplate", "renderFromResult")
186
220
  return http_result
187
221
  }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export { OINOLog, OINOLogLevel, OINOConsoleLog } from "./OINOLog.js"
3
3
  export { OINOResult, OINOHttpResult } from "./OINOResult.js"
4
4
  export { OINOStr } from "./OINOStr.js"
5
5
  export { OINOHtmlTemplate } from "./OINOHtmlTemplate.js"
6
+ export { OINOFormatter, OINO_EMPTY_FORMATTER } from "./OINOFormatter.js"
6
7
 
7
8
  /** OINO error message prefix */
8
9
  export const OINO_ERROR_PREFIX = "OINO ERROR"