@dgck81lnn/satori-element-input 0.0.0-stage → 0.1.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.
package/README.md CHANGED
@@ -1,3 +1,46 @@
1
- # Temporary Holding Version
1
+ # @dgck81lnn/satori-element-input
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@dgck81lnn/satori-element-input?style=flat-square)](https://www.npmjs.com/package/@dgck81lnn/satori-element-input)
4
+
5
+ Utilities for parsing and processing Satori element markup without discarding whitespace around tags.
6
+
7
+ ## Why
8
+
9
+ Whitespace around an element can be meaningful when markup represents a user's message, not source code written for readability. [koishijs/koishi#1561](https://github.com/koishijs/koishi/issues/1561) documents how trimming newlines around tags can erroneously join command tokens or remove line breaks around inline elements when user content is serialized and parsed again.
10
+
11
+ This package parses text and tags into the same `Element` class from Satori, while keeping the surrounding text intact. For example, the newline before and after an emoji remains part of the neighboring text nodes:
12
+
13
+ ```js
14
+ import { parse } from '@dgck81lnn/satori-element-input'
15
+
16
+ const elements = parse('First line <emoji id="297"/>\nSecond line')
17
+ /*
18
+ [
19
+ Element {
20
+ type: 'text',
21
+ attrs: { content: 'First line ' },
22
+ children: []
23
+ },
24
+ Element { type: 'emoji', attrs: { id: '297' }, children: [] },
25
+ Element {
26
+ type: 'text',
27
+ attrs: { content: '\nSecond line' }, // <- '\n' preserved!
28
+ children: []
29
+ }
30
+ ]
31
+ */
32
+ ```
33
+
34
+ The parsed array contains text nodes ending in `\n` before the image and beginning with `\n` after it. Use this when whitespace from user input must survive parsing; use the standard Satori parser when its whitespace normalization is desired.
35
+
36
+ ## API
37
+
38
+ - `parse(source)` parses markup into `@satorijs/element` elements and preserves text whitespace around tags.
39
+ - `normalize(source)` converts a string by parsing it, or converts an existing fragment to an element array.
40
+ - `stringify(source)` serializes a fragment to a string. An omitted or empty source produces an empty string.
41
+ - `escape(source, inline?)` and `unescape(source)` escape and unescape Satori text.
42
+ - `select(source, query)` selects elements from either a markup string or an element array using CSS-like selectors.
43
+ - `transform(source, rules, session?)` transforms a string or element array. It returns a string for string input and an element array for array input.
44
+ - `transformAsync(source, rules, session?)` is the asynchronous equivalent of `transform` and returns a promise of the corresponding result.
45
+
46
+ The transform functions accept the same rule and session shapes as `@satorijs/element`'s `transform` and `transformAsync` APIs.
package/lib/index.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import Element from '@satorijs/element';
2
+ import { Awaitable, type Dict } from 'cosmokit';
3
+ export declare function parse(source: string): any[];
4
+ export declare function parseSelector(input: string): Element.Selector[][];
5
+ export declare function normalize(source: Element.Fragment): any[];
6
+ export declare function stringify(source?: Element.Fragment): string;
7
+ export declare function escape(source: string, inline?: boolean): string;
8
+ export declare function unescape(source: string): string;
9
+ export declare function select(source: string | Element[], query: string | Element.Selector[][]): Element[];
10
+ type SyncVisitor<S> = Dict<Element.SyncTransformer<S>> | Element.Visit<boolean | Element.Fragment, S>;
11
+ type Visitor<S> = Dict<Element.Transformer<S>> | Element.Visit<Awaitable<boolean | Element.Fragment>, S>;
12
+ type Rest<T> = [T] extends [never] ? [session?: T] : [session: T];
13
+ export declare function transform<S = never>(source: string, rules: SyncVisitor<S>, ...rest: Rest<S>): string;
14
+ export declare function transform<S = never>(source: Element[], rules: SyncVisitor<S>, ...rest: Rest<S>): Element[];
15
+ export declare function transformAsync<S = never>(source: string, rules: Visitor<S>, ...rest: Rest<S>): Promise<string>;
16
+ export declare function transformAsync<S = never>(source: Element[], rules: Visitor<S>, ...rest: Rest<S>): Promise<Element[]>;
17
+ export {};
package/lib/index.js ADDED
@@ -0,0 +1,200 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ escape: () => escape,
34
+ normalize: () => normalize,
35
+ parse: () => parse,
36
+ parseSelector: () => parseSelector,
37
+ select: () => select,
38
+ stringify: () => stringify,
39
+ transform: () => transform,
40
+ transformAsync: () => transformAsync,
41
+ unescape: () => unescape
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+ var import_element = __toESM(require("@satorijs/element"));
45
+ var import_cosmokit = require("cosmokit");
46
+ var tagRegExp1 = /(?<comment><!--[\s\S]*?-->)|(?<tag><(\/?)([^!\s>/]*)([^>]*?)\s*(\/?)>)/;
47
+ var attrRegExp1 = /([^\s=]+)(?:="(?<value1>[^"]*)"|='(?<value2>[^']*)')?/g;
48
+ function parse(source) {
49
+ const tokens = [];
50
+ function pushText(content) {
51
+ if (content) tokens.push(content);
52
+ }
53
+ __name(pushText, "pushText");
54
+ let tagCap;
55
+ while (tagCap = tagRegExp1.exec(source)) {
56
+ parseContent(source.slice(0, tagCap.index));
57
+ source = source.slice(tagCap.index + tagCap[0].length);
58
+ const [_, , , close, type, extra, empty] = tagCap;
59
+ if (tagCap.groups.comment) continue;
60
+ tokens.push({
61
+ type: "angle",
62
+ source: _,
63
+ name: type || "",
64
+ position: close ? 1 /* CLOSE */ : empty ? 2 /* EMPTY */ : 0 /* OPEN */,
65
+ extra
66
+ });
67
+ }
68
+ parseContent(source);
69
+ return parseTokens(foldTokens(tokens));
70
+ function parseContent(source2) {
71
+ source2 = unescape(source2);
72
+ pushText(source2);
73
+ }
74
+ __name(parseContent, "parseContent");
75
+ }
76
+ __name(parse, "parse");
77
+ function foldTokens(tokens) {
78
+ const stack = [[{
79
+ type: "angle",
80
+ name: import_element.default.Fragment,
81
+ position: 0 /* OPEN */,
82
+ source: "",
83
+ extra: "",
84
+ children: { default: [] }
85
+ }, "default"]];
86
+ function pushToken(...items) {
87
+ const [token, slot] = stack[0];
88
+ token.children[slot].push(...items);
89
+ }
90
+ __name(pushToken, "pushToken");
91
+ for (const token of tokens) {
92
+ if (typeof token === "string") {
93
+ pushToken(token);
94
+ continue;
95
+ }
96
+ const { name, position } = token;
97
+ if (position === 1 /* CLOSE */) {
98
+ if (stack[0][0].name === name) {
99
+ stack.shift();
100
+ }
101
+ } else if (position === 0 /* OPEN */) {
102
+ pushToken(token);
103
+ token.children = { default: [] };
104
+ stack.unshift([token, "default"]);
105
+ } else {
106
+ pushToken(token);
107
+ }
108
+ }
109
+ return stack[stack.length - 1][0].children.default;
110
+ }
111
+ __name(foldTokens, "foldTokens");
112
+ function parseTokens(tokens) {
113
+ const result = [];
114
+ for (const token of tokens) {
115
+ if (typeof token === "string") {
116
+ result.push((0, import_element.default)("text", { content: token }));
117
+ } else {
118
+ const attrs = {};
119
+ let attrCap;
120
+ attrRegExp1.lastIndex = 0;
121
+ while (attrCap = attrRegExp1.exec(token.extra)) {
122
+ const [, key, v1, v2 = v1] = attrCap;
123
+ if (!(0, import_cosmokit.isNullable)(v2)) {
124
+ attrs[key] = unescape(v2);
125
+ } else if (key.startsWith("no-")) {
126
+ attrs[key.slice(3)] = false;
127
+ } else {
128
+ attrs[key] = true;
129
+ }
130
+ }
131
+ result.push((0, import_element.default)(token.name, attrs, token.children && parseTokens(token.children.default)));
132
+ }
133
+ }
134
+ return result;
135
+ }
136
+ __name(parseTokens, "parseTokens");
137
+ var combRegExp = / *([ >+~]) */;
138
+ function parseSelector(input) {
139
+ return input.split(",").map((query) => {
140
+ const selectors = [];
141
+ query = query.trim();
142
+ let combCap, combinator = " ";
143
+ while (combCap = combRegExp.exec(query)) {
144
+ selectors.push({ type: query.slice(0, combCap.index), combinator });
145
+ combinator = combCap[1];
146
+ query = query.slice(combCap.index + combCap[0].length);
147
+ }
148
+ selectors.push({ type: query, combinator });
149
+ return selectors;
150
+ });
151
+ }
152
+ __name(parseSelector, "parseSelector");
153
+ function normalize(source) {
154
+ return typeof source === "string" ? parse(source) : import_element.default.toElementArray(source);
155
+ }
156
+ __name(normalize, "normalize");
157
+ function stringify(source) {
158
+ if (Array.isArray(source)) return source.filter(import_cosmokit.isNonNullable).join("");
159
+ if (source) return source.toString();
160
+ return "";
161
+ }
162
+ __name(stringify, "stringify");
163
+ function escape(source, inline = false) {
164
+ return import_element.default.escape(source, inline);
165
+ }
166
+ __name(escape, "escape");
167
+ function unescape(source) {
168
+ return source.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#0*([1-9]\d*|0);/g, (_, code) => code === "38" ? _ : String.fromCharCode(+code)).replace(/&#x0*([1-9a-f][0-9a-f]*|0);/gi, (_, code) => code === "26" ? _ : String.fromCharCode(parseInt(code, 16))).replace(/&(amp|#0*38|#x0*26);/g, "&");
169
+ }
170
+ __name(unescape, "unescape");
171
+ function select(source, query) {
172
+ if (typeof source === "string") source = parse(source);
173
+ if (typeof query === "string") query = parseSelector(query);
174
+ return import_element.default.select(source, query);
175
+ }
176
+ __name(select, "select");
177
+ function transform(source, rules, session) {
178
+ const elements = typeof source === "string" ? parse(source) : source;
179
+ const output = import_element.default.transform(elements, rules, session);
180
+ return typeof source === "string" ? output.join("") : output;
181
+ }
182
+ __name(transform, "transform");
183
+ async function transformAsync(source, rules, session) {
184
+ const elements = typeof source === "string" ? parse(source) : source;
185
+ const output = await import_element.default.transformAsync(elements, rules, session);
186
+ return typeof source === "string" ? output.join("") : output;
187
+ }
188
+ __name(transformAsync, "transformAsync");
189
+ // Annotate the CommonJS export names for ESM import in node:
190
+ 0 && (module.exports = {
191
+ escape,
192
+ normalize,
193
+ parse,
194
+ parseSelector,
195
+ select,
196
+ stringify,
197
+ transform,
198
+ transformAsync,
199
+ unescape
200
+ });
package/package.json CHANGED
@@ -1,6 +1,24 @@
1
1
  {
2
2
  "name": "@dgck81lnn/satori-element-input",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Whitespace-preserving parsing and processing utilities for Satori element input.",
5
+ "keywords": [
6
+ "koishi"
7
+ ],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/DGCK81LNN/koishi-better-parse.git",
11
+ "directory": "packages/satori-element-input"
12
+ },
13
+ "homepage": "https://github.com/DGCK81LNN/koishi-better-parse",
14
+ "license": "MIT",
15
+ "main": "lib/index.js",
16
+ "typings": "lib/index.d.ts",
17
+ "files": [
18
+ "lib"
19
+ ],
20
+ "dependencies": {
21
+ "@satorijs/element": "^3.2.0",
22
+ "cosmokit": "^1.8.1"
23
+ }
24
+ }