sommark 3.3.3 → 4.0.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.
Files changed (61) hide show
  1. package/README.md +98 -82
  2. package/assets/logo.json +28 -0
  3. package/assets/smark.logo.png +0 -0
  4. package/assets/smark.logo.svg +21 -0
  5. package/cli/cli.mjs +8 -16
  6. package/cli/commands/build.js +24 -4
  7. package/cli/commands/color.js +22 -26
  8. package/cli/commands/help.js +10 -10
  9. package/cli/commands/init.js +19 -42
  10. package/cli/commands/print.js +20 -12
  11. package/cli/commands/show.js +4 -0
  12. package/cli/commands/version.js +6 -0
  13. package/cli/constants.js +9 -5
  14. package/cli/helpers/config.js +11 -0
  15. package/cli/helpers/file.js +17 -6
  16. package/cli/helpers/transpile.js +7 -8
  17. package/core/errors.js +49 -25
  18. package/core/formats.js +7 -3
  19. package/core/formatter.js +215 -0
  20. package/core/helpers/config-loader.js +37 -56
  21. package/core/labels.js +21 -9
  22. package/core/lexer.js +491 -212
  23. package/core/modules.js +164 -0
  24. package/core/parser.js +516 -389
  25. package/core/tokenTypes.js +36 -1
  26. package/core/transpiler.js +237 -151
  27. package/core/validator.js +79 -0
  28. package/formatter/mark.js +203 -43
  29. package/formatter/tag.js +202 -32
  30. package/grammar.ebnf +57 -50
  31. package/helpers/colorize.js +26 -13
  32. package/helpers/escapeHTML.js +13 -6
  33. package/helpers/kebabize.js +6 -0
  34. package/helpers/peek.js +9 -0
  35. package/helpers/removeChar.js +26 -13
  36. package/helpers/safeDataParser.js +114 -0
  37. package/helpers/utils.js +140 -158
  38. package/index.js +198 -188
  39. package/mappers/languages/html.js +105 -213
  40. package/mappers/languages/json.js +122 -171
  41. package/mappers/languages/markdown.js +355 -108
  42. package/mappers/languages/mdx.js +76 -114
  43. package/mappers/languages/xml.js +114 -0
  44. package/mappers/mapper.js +152 -123
  45. package/mappers/shared/index.js +22 -0
  46. package/package.json +26 -6
  47. package/SOMMARK-SPEC.md +0 -481
  48. package/cli/commands/list.js +0 -124
  49. package/constants/html_tags.js +0 -146
  50. package/core/pluginManager.js +0 -149
  51. package/core/plugins/comment-remover.js +0 -47
  52. package/core/plugins/module-system.js +0 -176
  53. package/core/plugins/raw-content-plugin.js +0 -78
  54. package/core/plugins/rules-validation-plugin.js +0 -231
  55. package/core/plugins/sommark-format.js +0 -244
  56. package/coverage_test.js +0 -21
  57. package/debug.js +0 -15
  58. package/helpers/camelize.js +0 -2
  59. package/helpers/defaultTheme.js +0 -3
  60. package/test_format_fix.js +0 -42
  61. package/v3-todo.smark +0 -73
@@ -1,133 +1,95 @@
1
1
  import Mapper from "../mapper.js";
2
- import MARKDOWN from "./markdown.js";
3
- import { HTML_TAGS } from "../../constants/html_tags.js";
4
- import { HTML_PROPS } from "../../constants/html_props.js";
2
+ import MARKDOWN, { renderHeading } from "./markdown.js";
5
3
  import { VOID_ELEMENTS } from "../../constants/void_elements.js";
6
- import kebabize from "../../helpers/kebabize.js";
7
4
 
8
- class MdxMapper extends Mapper {
9
- constructor() {
10
- super();
11
- }
5
+ /**
6
+ * The MDX Mapper used for generating Markdown with JSX.
7
+ */
8
+ const MDX = Mapper.define({
9
+ /**
10
+ * Renders a JSX-style comment in MDX output.
11
+ * @param {string} text - The raw comment text.
12
+ * @returns {string} - Formatted JSX comment string.
13
+ */
12
14
  comment(text) {
13
- return `{/*${text.replace("#", "")} */}\n`;
14
- }
15
+ return `{/* ${text} */}`;
16
+ },
17
+
18
+ /**
19
+ * Provides high-fidelity fallback for unknown tags by rendering them as JSX components.
20
+ * @param {Object} node - The unknown AST node.
21
+ * @returns {Object} - A virtual tag registration for JSX rendering.
22
+ */
15
23
  getUnknownTag(node) {
16
24
  const tagName = node.id;
25
+ const lowerId = tagName.toLowerCase();
26
+ const isVoid = VOID_ELEMENTS.has(lowerId);
27
+ const isCodeStyleOrScript = ["code", "style", "script"].includes(lowerId);
28
+
17
29
  return {
18
- render: ({ args, content }) => {
19
- const element = this.tag(tagName);
20
- element.props(this.jsxProps(args, tagName));
21
- return element.body(content);
30
+ render: (ctx) => {
31
+ const { args, content } = ctx;
32
+ const element = this.tag(tagName).jsxProps(args);
33
+ return isVoid ? element.selfClose() : element.body(content);
34
+ },
35
+ options: {
36
+ type: isVoid ? "Block" : (isCodeStyleOrScript ? ["Block", "AtBlock"] : ["Block", "Inline", "AtBlock"]),
37
+ escape: !isCodeStyleOrScript,
38
+ rules: { is_self_closing: isVoid }
22
39
  }
23
40
  };
24
- }
25
-
26
- jsxProps(args, tagName = "div") {
27
- const jsxProps = [];
28
- const styleObj = {};
29
- const isHtmlTag = HTML_TAGS.has(tagName.toLowerCase());
30
-
31
- const keys = Object.keys(args).filter(arg => isNaN(arg));
32
- keys.forEach(key => {
33
- let val = args[key];
34
- const isEvent = key.toLowerCase().startsWith("on");
35
-
36
- let k = key;
37
- if (k === "class") k = "className";
38
-
39
- // Quote stripping
40
- if (typeof val === "string" && ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'")))) {
41
- val = val.slice(1, -1);
42
- }
43
-
44
- if (k === "style") {
45
- if (typeof val === "string") {
46
- const pairs = val.includes(";") ? val.split(";") : val.split(",");
47
- pairs.forEach(pair => {
48
- let [prop, value] = pair.split(":").map(s => s.trim());
49
- if (prop && value) {
50
- const camelProp = prop.replace(/-([a-z])/g, g => g[1].toUpperCase());
51
- styleObj[camelProp] = value;
52
- }
53
- });
54
- } else if (typeof val === "object") {
55
- Object.assign(styleObj, val);
56
- }
57
- } else {
58
- // Detection for expressions
59
- const isBoolean = val === "true" || val === "false" || typeof val === "boolean";
60
- const isNumeric = val !== "" && !isNaN(val) && typeof val !== "boolean";
61
- const looksLikeExpression = typeof val === "string" &&
62
- (/[0-9]/.test(val) && /[+\-*/%()]/.test(val)); // Math expression detection
63
-
64
- const shouldBeJSXExpression = isEvent || isBoolean || isNumeric || looksLikeExpression;
65
-
66
- let finalVal = val;
67
- if (val === "true") finalVal = true;
68
- if (val === "false") finalVal = false;
69
- if (isNumeric && typeof val === "string") finalVal = Number(val);
70
-
71
- jsxProps.push({
72
- __type__: shouldBeJSXExpression ? "other" : "string",
73
- [k]: finalVal
74
- });
75
- }
76
- });
77
-
78
- if (Object.keys(styleObj).length > 0) {
79
- const styleStr = JSON.stringify(styleObj).replace(/"/g, "'").replace(/'([^']+)':/g, '$1:');
80
- jsxProps.push({ __type__: "other", style: styleStr });
41
+ },
42
+
43
+ options: {
44
+ trimAndWrapBlocks: true
45
+ },
46
+
47
+ /**
48
+ * Formats a plain text node with Markdown escaping.
49
+ */
50
+ text(text, options) {
51
+ return MARKDOWN.text.call(this, text, options);
52
+ },
53
+
54
+ /**
55
+ * Formats inline content before rendering, respecting explicit escape flags.
56
+ */
57
+ inlineText(text, options) {
58
+ return MARKDOWN.inlineText.call(this, text, options);
59
+ },
60
+
61
+ /**
62
+ * Formats the literal content inside AtBlocks.
63
+ */
64
+ atBlockBody(text, options) {
65
+ let out = text;
66
+ if (options?.escape !== false) {
67
+ out = this.escapeHTML(out);
81
68
  }
82
-
83
- return jsxProps;
69
+ if (out.includes('\n')) {
70
+ out = '\n' + out + '\n';
71
+ }
72
+ return out;
84
73
  }
85
- }
74
+ });
86
75
 
87
- const MDX = new MdxMapper();
88
76
  const { tag } = MDX;
89
77
 
90
78
  MDX.inherit(MARKDOWN);
79
+ MDX.md = MARKDOWN.md; // Provide the Markdown escaping tool
91
80
 
92
- // Block for raw MDX content (ESM, etc.)
93
- MDX.register("mdx", ({ content }) => content, { escape: false, type: ["AtBlock", "Block"] });
94
-
95
- // Re-register HTML tags to use jsxProps
96
- HTML_TAGS.forEach(tagName => {
97
- const capitalized = tagName.charAt(0).toUpperCase() + tagName.slice(1);
98
-
99
- // Register even if it exists in MARKDOWN to override it with JSX version
100
- const idsToRegister = [tagName, capitalized];
101
-
102
- MDX.register(
103
- idsToRegister,
104
- ({ args, content }) => {
105
- const element = tag(tagName);
106
-
107
- // Auto-ID for Headings
108
- if (/^h[1-6]$/i.test(tagName) && !args.id && content && /^[A-Za-z0-9]/.test(content)) {
109
- const id = content
110
- .toString()
111
- .toLowerCase()
112
- .replace(/[^\w\s-]/g, "")
113
- .replace(/\s+/g, "-");
114
- args.id = id;
115
- }
116
-
117
- element.props(MDX.jsxProps(args, tagName));
118
-
119
- if (VOID_ELEMENTS.has(tagName)) {
120
- return element.selfClose();
121
- }
122
- return element.body(content);
123
- },
124
- {
125
- type: VOID_ELEMENTS.has(tagName) ? "Block" : ["Block", "Inline"],
126
- rules: {
127
- is_self_closing: VOID_ELEMENTS.has(tagName)
128
- }
129
- }
130
- );
81
+ // MDX defaults to HTML tags for headings to ensure high-fidelity JSX output
82
+ ["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
83
+ MDX.register(heading, function (ctx) {
84
+ return renderHeading.call(this, ctx, "html");
85
+ }, { type: "Block" });
131
86
  });
132
87
 
88
+ /**
89
+ * mdx AtBlock - Renders raw MDX content (ESM imports, exports, or complex JSX).
90
+ */
91
+ MDX.register("mdx", ({ content }) => {
92
+ return content;
93
+ }, { escape: false, type: "AtBlock" });
94
+
133
95
  export default MDX;
@@ -0,0 +1,114 @@
1
+ import Mapper from "../mapper.js";
2
+ import { registerSharedOutputs } from "../shared/index.js";
3
+
4
+ /**
5
+ * Renders a standard XML tag based on the provided identifier and arguments.
6
+ * Ensures strict attribute quoting and handles self-closing tags for empty bodies.
7
+ *
8
+ * @param {string} id - The XML tag identifier (case-sensitive).
9
+ * @param {Object} args - Key-value pairs to be rendered as XML attributes.
10
+ * @param {string} content - The rendered inner content of the tag.
11
+ * @returns {string} The fully rendered XML tag string.
12
+ */
13
+ const renderXmlTag = function (id, args, content) {
14
+ // XML is case-sensitive, so we use the exact id provided
15
+ const element = this.tag(id);
16
+
17
+ // Filter out positional indices (numeric keys) for XML attributes
18
+ const namedArgs = {};
19
+ Object.keys(args).forEach(key => {
20
+ if (isNaN(parseInt(key))) {
21
+ namedArgs[key] = args[key];
22
+ }
23
+ });
24
+
25
+ // In XML, attributes must always have values (strict = true)
26
+ element.attributes(namedArgs, true);
27
+
28
+ const hasBody = typeof content === "string" && content.trim().length > 0;
29
+
30
+ if (!hasBody) {
31
+ return element.selfClose();
32
+ }
33
+
34
+ return element.body(content);
35
+ };
36
+
37
+ /**
38
+ * The XML Mapper used for creating XML pages.
39
+ */
40
+ const XML = Mapper.define({
41
+ /**
42
+ * Renders a comment in XML format.
43
+ * @param {string} text - The comment content.
44
+ * @returns {string}
45
+ */
46
+ comment(text) {
47
+ return `<!-- ${text} -->`;
48
+ },
49
+
50
+ /**
51
+ * Resolves unknown tags by preserving their original case and applying XML rules.
52
+ * @param {Object} node - The AST node representing the unknown tag.
53
+ * @returns {Object} Renderer definition for the tag.
54
+ */
55
+ getUnknownTag(node) {
56
+ const id = node.id;
57
+ return {
58
+ render: ({ args, content }) => renderXmlTag.call(this, id, args, content),
59
+ options: {
60
+ type: "any"
61
+ }
62
+ };
63
+ }
64
+ });
65
+
66
+ /**
67
+ * Registers the XML declaration as a self-closing block.
68
+ * Usage: [xml = version: "1.0", encoding: "UTF-8"]
69
+ */
70
+ XML.register("xml", ({ args }) => {
71
+ const version = args.version || "1.0";
72
+ const encoding = args.encoding || "UTF-8";
73
+ return `<?xml version="${version}" encoding="${encoding}"?>`;
74
+ }, { type: "Block", rules: { is_self_closing: true } });
75
+
76
+ /**
77
+ * Registers the DOCTYPE declaration.
78
+ * Usage: [doctype = root: "note", system: "note.dtd"]
79
+ */
80
+ XML.register("doctype", ({ args }) => {
81
+ const root = args.root || "root";
82
+ const system = args.system;
83
+ const pub = args.public || args.fpi;
84
+
85
+ if (pub && system) {
86
+ return `<!DOCTYPE ${root} PUBLIC "${pub}" "${system}">`;
87
+ } else if (system) {
88
+ return `<!DOCTYPE ${root} SYSTEM "${system}">`;
89
+ }
90
+ return `<!DOCTYPE ${root}>`;
91
+ }, { type: "Block", rules: { is_self_closing: true } });
92
+
93
+ /**
94
+ * Registers the XML stylesheet processing instruction.
95
+ * Usage: [xml-stylesheet = href: "style.xsl"]
96
+ */
97
+ XML.register("xml-stylesheet", ({ args }) => {
98
+ const type = args.type || "text/xsl";
99
+ const href = args.href;
100
+ if (!href) return "";
101
+ return `<?xml-stylesheet type="${type}" href="${href}"?>`;
102
+ }, { type: "Block", rules: { is_self_closing: true } });
103
+
104
+ /**
105
+ * Registers CDATA sections.
106
+ * Usage: @_cdata_@: ; raw content @_end_@
107
+ */
108
+ XML.register("cdata", ({ content }) => {
109
+ return `<![CDATA[${content}]]>`;
110
+ }, { type: "AtBlock" });
111
+
112
+ registerSharedOutputs(XML);
113
+
114
+ export default XML;
package/mappers/mapper.js CHANGED
@@ -2,92 +2,40 @@ import TagBuilder from "../formatter/tag.js";
2
2
  import MarkdownBuilder from "../formatter/mark.js";
3
3
  import escapeHTML from "../helpers/escapeHTML.js";
4
4
  import { sommarkError } from "../core/errors.js";
5
- import { matchedValue, safeArg, htmlTable, list, todo } from "../helpers/utils.js";
5
+ import { matchedValue, safeArg } from "../helpers/utils.js";
6
6
 
7
7
 
8
- // ========================================================================== //
9
- // Main Mapper Class //
10
- // ========================================================================== //
8
+ /**
9
+ * The base class for all mappers. It manages how tags and blocks are turned into final text.
10
+ * This is used to build HTML, MDX, and other output formats.
11
+ */
11
12
  class Mapper {
12
- #customHeaderContent;
13
- // ========================================================================== //
14
- // Constructor //
15
- // ========================================================================== //
13
+ /**
14
+ * Sets up a new mapper with empty lists for rules and tags.
15
+ */
16
16
  constructor() {
17
+ /** @type {Array<Object>} List of rules for formatting tags. */
17
18
  this.outputs = [];
19
+ /** @type {MarkdownBuilder} Specialized builder for Markdown-related formatting. */
18
20
  this.md = new MarkdownBuilder();
19
-
20
- this.extraProps = new Set(); // For plugins to add recognized arguments
21
-
22
- this.pageProps = {
23
- pageTitle: "SomMark Page",
24
- tabIcon: {
25
- type: "image/x-icon",
26
- src: ""
27
- },
28
- charset: "UTF-8",
29
- viewport: "width=device-width, initial-scale=1.0",
30
- httpEquiv: { "X-UA-Compatible": "IE=edge" }
31
- };
32
-
33
- this.#customHeaderContent = "";
34
-
21
+ /** @type {Set<string>} A list of extra property names this mapper understands. */
22
+ this.customProps = new Set();
23
+ /** @type {Function} Helper that makes text safe for HTML. */
35
24
  this.escapeHTML = escapeHTML;
36
- this.htmlTable = htmlTable;
37
- this.list = list;
38
- this.todo = todo;
39
- this.styles = [];
40
- }
41
-
42
- // ========================================================================== //
43
- // Style Management //
44
- // ========================================================================== //
45
-
46
- addStyle(css) {
47
- if (typeof css === "object" && css !== null) {
48
- let styleString = "";
49
- for (const [selector, rules] of Object.entries(css)) {
50
- let rulesString = "";
51
- for (const [prop, value] of Object.entries(rules)) {
52
- rulesString += `${prop}:${value};`;
53
- }
54
- styleString += `${selector}{${rulesString}}`;
55
- }
56
- css = styleString;
57
- }
58
-
59
- if (typeof css === "string" && css.trim() && !this.styles.includes(css.trim())) {
60
- this.styles.push(css.trim());
61
- }
25
+ /** @type {Object} Settings that change how this mapper works. */
26
+ this.options = {};
62
27
  }
63
28
 
29
+ // -- Tag Registration ---------------------------------------------------- //
64
30
 
65
- // ========================================================================== //
66
- // Header Generation //
67
- // ========================================================================== //
68
- get header() {
69
- const { pageTitle, tabIcon, charset, viewport, httpEquiv } = this.pageProps;
70
-
71
- let metas = "";
72
- metas += `${this.tag("meta").attributes({ charset }).selfClose()}\n`;
73
- metas += `${this.tag("meta").attributes({ name: "viewport", content: viewport }).selfClose()}\n`;
74
-
75
- for (const [key, value] of Object.entries(httpEquiv)) {
76
- metas += `${this.tag("meta").attributes({ "http-equiv": key, content: value }).selfClose()}\n`;
77
- }
78
-
79
- metas += `${this.tag("title").body(pageTitle)}\n`;
80
-
81
- if (tabIcon && tabIcon.src) {
82
- metas += `${this.tag("link").attributes({ rel: "icon", type: tabIcon.type, href: tabIcon.src }).selfClose()}\n`;
83
- }
84
-
85
- return metas + this.#customHeaderContent;
86
- }
87
-
88
- // ========================================================================== //
89
- // Mappings //
90
- // ========================================================================== //
31
+ /**
32
+ * Registers a new tag rule. It needs a name and a function that says how to format it.
33
+ *
34
+ * @param {string|Array<string>} id - The name of the tag (like 'Person' or ['p', 'para']).
35
+ * @param {Function} renderOutput - The function that formats this tag.
36
+ * @param {Object} [options={ escape: true }] - Settings for this tag.
37
+ * @param {boolean} [options.escape=true] - If true, the content will be made safe for HTML automatically.
38
+ */
91
39
  register(id, renderOutput, options = { escape: true }) {
92
40
  if (!id || !renderOutput) {
93
41
  throw new Error("Expected arguments are not defined");
@@ -100,17 +48,8 @@ class Mapper {
100
48
  if (typeof renderOutput !== "function") {
101
49
  throw new TypeError("argument 'renderOutput' expected to be a function");
102
50
  }
103
- const render = function (data) {
104
- if (
105
- typeof data !== "object" ||
106
- data === null ||
107
- !Object.prototype.hasOwnProperty.call(data, "args") ||
108
- !Object.prototype.hasOwnProperty.call(data, "content")
109
- ) {
110
- throw new TypeError("render expects an object with properties { args, content }");
111
- }
112
- return renderOutput.call(this, data);
113
- };
51
+
52
+ const render = renderOutput;
114
53
 
115
54
  // Prevent duplicate IDs by removing any existing overlap before registering
116
55
  const ids = Array.isArray(id) ? id : [id];
@@ -121,6 +60,12 @@ class Mapper {
121
60
  this.outputs.push({ id, render, options });
122
61
  }
123
62
 
63
+ /**
64
+ * Inherits all registered outputs from one or more other mappers.
65
+ * Last-match-wins logic: If an output exists in multiple mappers, the one from the last mapper in the list is used.
66
+ *
67
+ * @param {...Mapper} mappers - The mapper instances to inherit from.
68
+ */
124
69
  inherit(...mappers) {
125
70
  for (const mapper of mappers) {
126
71
  if (mapper && Array.isArray(mapper.outputs)) {
@@ -135,46 +80,100 @@ class Mapper {
135
80
  }
136
81
  }
137
82
 
83
+ /**
84
+ * Removes a specific output registration by its ID.
85
+ * @param {string} id - The output identifier to remove.
86
+ */
138
87
  removeOutput(id) {
139
- this.outputs = this.outputs.filter(output => {
140
- if (Array.isArray(output.id)) {
141
- return !output.id.some(singleId => singleId === id);
142
- } else {
143
- return output.id !== id;
144
- }
145
- });
88
+ this.outputs = this.outputs
89
+ .map(output => {
90
+ if (Array.isArray(output.id)) {
91
+ // Only remove the specific ID from the array
92
+ const newIds = output.id.filter(singleId => singleId !== id);
93
+ if (newIds.length === 0) return null; // Remove entire entry if no IDs left
94
+ if (newIds.length === output.id.length) return output; // No change
95
+ return { ...output, id: newIds }; // Return updated entry
96
+ } else {
97
+ return output.id === id ? null : output;
98
+ }
99
+ })
100
+ .filter(Boolean); // Clean up nulls
146
101
  }
147
102
 
103
+ /**
104
+ * Retrieves a registered output entry (render function and options) by ID.
105
+ * @param {string} id - The output identifier.
106
+ * @returns {Object|null} - The output entry or null if not found.
107
+ */
148
108
  get(id) {
149
109
  return matchedValue(this.outputs, id) || null;
150
110
  }
151
111
 
152
- // ========================================================================== //
153
- // Helpers //
154
- // ========================================================================== //
112
+ // -- Utility Helpers ------------------------------------------------------ //
113
+
114
+ /**
115
+ * Placeholder for comment rendering. Should be overridden by specific mappers.
116
+ * @param {string} text - The raw comment text.
117
+ * @returns {string} - The formatted comment string.
118
+ */
155
119
  comment(text) {
156
120
  return "";
157
121
  }
122
+
123
+ /**
124
+ * Formats a plain text node.
125
+ * @param {string} text - The raw text content.
126
+ * @param {Object} [options] - Target options like { escape: false }.
127
+ * @returns {string} - The formatted text string.
128
+ */
129
+ text(text, options) {
130
+ return text;
131
+ }
132
+
133
+ /**
134
+ * Formats the content of an inline statement.
135
+ * @param {string} text - The raw inline content.
136
+ * @param {Object} options - The target output options.
137
+ * @returns {string} - The formatted inline string.
138
+ */
139
+ inlineText(text, options) {
140
+ return text;
141
+ }
142
+
143
+ /**
144
+ * Formats the raw body of an At-Block.
145
+ * @param {string} text - The raw atblock body.
146
+ * @param {Object} options - The target output options.
147
+ * @returns {string} - The formatted atblock string.
148
+ */
149
+ atBlockBody(text, options) {
150
+ return text;
151
+ }
152
+
153
+
154
+ /**
155
+ * Handles unknown tags. Should be overridden by specific mappers to provide fallback behavior.
156
+ * @param {Object} node - The AST node for the unknown id.
157
+ * @returns {Object|null} - A tag-like entry for fallback rendering.
158
+ */
158
159
  getUnknownTag(node) {
159
160
  return null;
160
161
  }
162
+
163
+ /**
164
+ * Creates a new TagBuilder instance for programmatic HTML-like tag creation.
165
+ * @param {string} tagName - The name of the tag (e.g., 'div', 'p').
166
+ * @returns {TagBuilder}
167
+ */
161
168
  tag(tagName) {
162
169
  return new TagBuilder(tagName);
163
170
  }
164
171
 
165
- getCustomHeaderContent() {
166
- return this.#customHeaderContent;
167
- }
168
-
169
- setHeader(rawData) {
170
- if (Array.isArray(rawData)) {
171
- for (const data of rawData) {
172
- if (typeof data === "string") {
173
- this.#customHeaderContent += data + "\n";
174
- }
175
- }
176
- }
177
- }
172
+ /**
173
+ * Checks if this mapper has any of the specified IDs registered.
174
+ * @param {Array<string>} ids - List of IDs to check for.
175
+ * @returns {boolean} - True if at least one ID exists.
176
+ */
178
177
  includesId(ids) {
179
178
  try {
180
179
  if (!Array.isArray(ids) || ids.length === 0) {
@@ -209,35 +208,65 @@ class Mapper {
209
208
  }
210
209
  }
211
210
 
212
- safeArg(args, index, key, type = null, setType = null, fallBack = null) {
213
- return safeArg(args, index, key, type, setType, fallBack);
211
+ /**
212
+ * Safely retrieves an argument value, handling both named and positional access.
213
+ *
214
+ * @param {Object} options - Resolution options.
215
+ * @returns {any} - The resolved argument value.
216
+ */
217
+ safeArg(options) {
218
+ return safeArg(options);
214
219
  }
215
220
 
221
+ /**
222
+ * Creates a deep clone of the mapper instance, isolating output registrations.
223
+ * Inherits all properties and binds methods to the new instance.
224
+ *
225
+ * @returns {Mapper} - The cloned mapper instance.
226
+ */
216
227
  clone() {
217
- const newMapper = new this.constructor();
228
+ const newMapper = new Mapper();
229
+ for (const [key, val] of Object.entries(this)) {
230
+ if (key === "outputs" || key === "customProps" || key === "md") continue;
218
231
 
219
- // Map-clone outputs to ensure options are isolated but render remains bound
232
+ if (typeof val === "object" && val !== null && !Array.isArray(val)) {
233
+ newMapper[key] = { ...val };
234
+ } else {
235
+ newMapper[key] = val;
236
+ }
237
+ }
238
+
239
+ newMapper.options = { ...this.options };
240
+
241
+ // Deep-clone specific structural properties
220
242
  newMapper.outputs = this.outputs.map(out => ({
221
243
  ...out,
222
244
  options: out.options ? { ...out.options } : { escape: true }
223
245
  }));
224
246
 
225
- newMapper.styles = [...this.styles];
226
-
227
- // deep clone pageProps
228
- newMapper.pageProps = JSON.parse(JSON.stringify(this.pageProps));
229
-
230
- newMapper.extraProps = new Set(this.extraProps);
231
- newMapper.setHeader([this.getCustomHeaderContent()]);
247
+ newMapper.customProps = new Set(this.customProps);
248
+
232
249
  return newMapper;
233
250
  }
234
251
 
252
+ /**
253
+ * Clears all registered outputs from the mapper.
254
+ */
235
255
  clear() {
236
256
  this.outputs = [];
237
257
  }
238
258
 
239
- formatOutput(output, includeDocument) {
240
- return output;
259
+ /**
260
+ * Static factory method to create a new Mapper instance with pre-defined properties.
261
+ * @param {Object} [options={}] - Properties and methods to add to the mapper.
262
+ * @returns {Mapper}
263
+ */
264
+ static define(options = {}) {
265
+ const mapper = new Mapper();
266
+ for (const [key, val] of Object.entries(options)) {
267
+ mapper[key] = val;
268
+ }
269
+ return mapper;
241
270
  }
242
271
  }
243
272
  export default Mapper;