@xuda.io/runtime-bundle 1.0.1360 → 1.0.1361

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 @@
1
+ export const childlessTags=["style","script","template"];export const closingTags=["html","head","body","p","dt","dd","li","option","thead","th","tbody","tr","td","tfoot","colgroup"];export const closingTagAncestorBreakers={li:["ul","ol","menu"],dt:["dl"],dd:["dl"],tbody:["table"],thead:["table"],tfoot:["table"],tr:["table"],td:["table"]};export const voidTags=["!doctype","area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"];export function uuidv4(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16))}export function isObject(val){return val instanceof Object}export function formatAttributes(attributes,options={}){return Object.keys(attributes).reduce((attrs,attrKey)=>{const key=attrKey;var value=attributes[attrKey];if(value===null||typeof value!=="boolean"&&!value){return`${attrs} ${key}`}if(isObject(value)){const jsonString=JSON.stringify(value);return`${attrs} ${key}='${jsonString}'`}value=value.toString();return`${attrs} ${key}="${value}"`},"")}export function toHTML(tree,options){return tree.map(node=>{if(!node.type)return;if(node.type==="text"){return node.content}if(node.type==="comment"){return`<!--${node.content}-->`}const escapeHtml=unsafe=>{return unsafe.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#039;")};var text=node.attributes?.["xu-text"]&&escapeHtml(node.attributes?.["xu-text"])||node.attributes?.["xu-html"]||node?.content&&escapeHtml(node.content)||"";var{tagName,attributes,children}=node;delete attributes.internal_tree_id;delete attributes.internal_path;attributes=JSON.parse(JSON.stringify(attributes));if(!attributes.internal_tree_id&&!options.remove_tree_id)attributes.internal_tree_id=node.id;if(options.remove_tree_id){delete attributes.internal_tree_id}if(options.add_studio_meta){attributes.internal_path=node?.path?.toString?.();attributes.xuda_hide=node.$hide}const isSelfClosing=arrayIncludes(options.voidTags,tagName.toLowerCase());return isSelfClosing?`<${tagName}${formatAttributes(attributes)}>`:`<${tagName}${formatAttributes(attributes)}>${text}${toHTML(children,options)}</${tagName}>`}).join("")}export function parser(tokens,options){const root={tagName:null,children:[]};const state={tokens:tokens,options:options,cursor:0,stack:[root]};parserParse(state);return root.children}export function hasTerminalParent(tagName,stack,terminals){const tagParents=terminals[tagName];if(tagParents){let currentIndex=stack.length-1;while(currentIndex>=0){const parentTagName=stack[currentIndex].tagName;if(parentTagName===tagName){break}if(arrayIncludes(tagParents,parentTagName)){return true}currentIndex--}}return false}export function rewindStack(stack,newLength,childrenEndPosition,endPosition){stack[newLength].position.end=endPosition;for(let i=newLength+1,len=stack.length;i<len;i++){stack[i].position.end=childrenEndPosition}stack.splice(newLength)}export function parserParse(state){const{tokens,options}=state;let{stack}=state;let nodes=stack[stack.length-1].children;const len=tokens.length;let{cursor}=state;while(cursor<len){const token=tokens[cursor];if(token.type!=="tag-start"){nodes.push(token);cursor++;continue}const tagToken=tokens[++cursor];cursor++;const tagName=tagToken.content.toLowerCase();if(token.close){let index=stack.length;let shouldRewind=false;while(--index>-1){if(stack[index].tagName===tagName){shouldRewind=true;break}}while(cursor<len){const endToken=tokens[cursor];if(endToken.type!=="tag-end")break;cursor++}if(shouldRewind){rewindStack(stack,index,token.position.start,tokens[cursor-1].position.end);break}else{continue}}const isClosingTag=arrayIncludes(options.closingTags,tagName);let shouldRewindToAutoClose=isClosingTag;if(shouldRewindToAutoClose){const{closingTagAncestorBreakers:terminals}=options;shouldRewindToAutoClose=!hasTerminalParent(tagName,stack,terminals)}if(shouldRewindToAutoClose){let currentIndex=stack.length-1;while(currentIndex>0){if(tagName===stack[currentIndex].tagName){rewindStack(stack,currentIndex,token.position.start,token.position.start);const previousIndex=currentIndex-1;nodes=stack[previousIndex].children;break}currentIndex=currentIndex-1}}let attributes={};let attrToken;while(cursor<len){attrToken=tokens[cursor];if(attrToken.type==="tag-end")break;attributes[attrToken.content]="";cursor++}cursor++;const children=[];const position={start:token.position.start,end:attrToken.position.end};const elementNode={type:"element",tagName:tagToken.content,attributes:attributes,children:children,position:position};nodes.push(elementNode);const hasChildren=!(attrToken.close||arrayIncludes(options.voidTags,tagName));if(hasChildren){const size=stack.push({tagName:tagName,children:children,position:position});const innerState={tokens:tokens,options:options,cursor:cursor,stack:stack};parserParse(innerState);cursor=innerState.cursor;const rewoundInElement=stack.length===size;if(rewoundInElement){elementNode.position.end=tokens[cursor-1].position.end}}}state.cursor=cursor}export function feedPosition(position,str,len){const start=position.index;const end=position.index=start+len;for(let i=start;i<end;i++){const char=str.charAt(i);if(char==="\n"){position.line++;position.column=0}else{position.column++}}}export function jumpPosition(position,str,end){const len=end-position.index;return feedPosition(position,str,len)}export function makeInitialPosition(){return{index:0,column:0,line:0}}export function copyPosition(position){return{index:position.index,line:position.line,column:position.column}}export function lexer(str,options){const state={str:str,options:options,position:makeInitialPosition(),tokens:[]};lex(state);return state.tokens}export function lex(state){const{str,options:{childlessTags}}=state;const len=str.length;while(state.position.index<len){const start=state.position.index;lexText(state);if(state.position.index===start){const isComment=startsWith(str,"!--",start+1);if(isComment){lexComment(state)}else{const tagName=lexTag(state);const safeTag=tagName.toLowerCase();if(arrayIncludes(childlessTags,safeTag)){lexSkipTag(tagName,state)}}}}}const alphanumeric=/[A-Za-z0-9]/;export function findTextEnd(str,index){while(true){const textEnd=str.indexOf("<",index);if(textEnd===-1){return textEnd}const char=str.charAt(textEnd+1);if(char==="/"||char==="!"||alphanumeric.test(char)){return textEnd}index=textEnd+1}}export function lexText(state){const type="text";const{str,position}=state;let textEnd=findTextEnd(str,position.index);if(textEnd===position.index)return;if(textEnd===-1){textEnd=str.length}const start=copyPosition(position);const content=str.slice(position.index,textEnd)?.trim();jumpPosition(position,str,textEnd);const end=copyPosition(position);if(content)state.tokens.push({type:type,content:content,position:{start:start,end:end}})}export function lexComment(state){const{str,position}=state;const start=copyPosition(position);feedPosition(position,str,4);let contentEnd=str.indexOf("--\x3e",position.index);let commentEnd=contentEnd+3;if(contentEnd===-1){contentEnd=commentEnd=str.length}const content=str.slice(position.index,contentEnd);jumpPosition(position,str,commentEnd);state.tokens.push({type:"comment",content:content,position:{start:start,end:copyPosition(position)}})}export function lexTag(state){const{str,position}=state;{const secondChar=str.charAt(position.index+1);const close=secondChar==="/";const start=copyPosition(position);feedPosition(position,str,close?2:1);state.tokens.push({type:"tag-start",close:close,position:{start:start}})}const tagName=lexTagName(state);lexTagAttributes(state);{const firstChar=str.charAt(position.index);const close=firstChar==="/";feedPosition(position,str,close?2:1);const end=copyPosition(position);state.tokens.push({type:"tag-end",close:close,position:{end:end}})}return tagName}const whitespace=/\s/;export function isWhitespaceChar(char){return whitespace.test(char)}export function lexTagName(state){const{str,position}=state;const len=str.length;let start=position.index;while(start<len){const char=str.charAt(start);const isTagChar=!(isWhitespaceChar(char)||char==="/"||char===">");if(isTagChar)break;start++}let end=start+1;while(end<len){const char=str.charAt(end);const isTagChar=!(isWhitespaceChar(char)||char==="/"||char===">");if(!isTagChar)break;end++}jumpPosition(position,str,end);const tagName=str.slice(start,end);state.tokens.push({type:"tag",content:tagName});return tagName}export function lexTagAttributes(state){const{str,position,tokens}=state;let cursor=position.index;let quote=null;let wordBegin=cursor;const words=[];const len=str.length;while(cursor<len){const char=str.charAt(cursor);if(quote){const isQuoteEnd=char===quote;if(isQuoteEnd){quote=null}cursor++;continue}const isTagEnd=char==="/"||char===">";if(isTagEnd){if(cursor!==wordBegin){words.push(str.slice(wordBegin,cursor))}break}const isWordEnd=isWhitespaceChar(char);if(isWordEnd){if(cursor!==wordBegin){words.push(str.slice(wordBegin,cursor))}wordBegin=cursor+1;cursor++;continue}const isQuoteStart=char==="'"||char==='"';if(isQuoteStart){quote=char;cursor++;continue}cursor++}jumpPosition(position,str,cursor);const wLen=words.length;const type="attribute";for(let i=0;i<wLen;i++){const word=words[i];const isNotPair=word.indexOf("=")===-1;if(isNotPair){const secondWord=words[i+1];if(secondWord&&startsWith(secondWord,"=")){if(secondWord.length>1){const newWord=word+secondWord;tokens.push({type:type,content:newWord});i+=1;continue}const thirdWord=words[i+2];i+=1;if(thirdWord){const newWord=word+"="+thirdWord;tokens.push({type:type,content:newWord});i+=1;continue}}}if(endsWith(word,"=")){const secondWord=words[i+1];if(secondWord&&!stringIncludes(secondWord,"=")){const newWord=word+secondWord;tokens.push({type:type,content:newWord});i+=1;continue}const newWord=word.slice(0,-1);tokens.push({type:type,content:newWord});continue}tokens.push({type:type,content:word})}}const push=[].push;export function lexSkipTag(tagName,state){const{str,position,tokens}=state;const safeTagName=tagName.toLowerCase();const len=str.length;let index=position.index;while(index<len){const nextTag=str.indexOf("</",index);if(nextTag===-1){lexText(state);break}const tagStartPosition=copyPosition(position);jumpPosition(tagStartPosition,str,nextTag);const tagState={str:str,position:tagStartPosition,tokens:[]};const name=lexTag(tagState);if(safeTagName!==name.toLowerCase()){index=tagState.position.index;continue}if(nextTag!==position.index){const textStart=copyPosition(position);jumpPosition(position,str,nextTag);tokens.push({type:"text",content:str.slice(textStart.index,nextTag),position:{start:textStart,end:copyPosition(position)}})}push.apply(tokens,tagState.tokens);jumpPosition(position,str,tagState.position.index);break}}export function startsWith(str,searchString,position){return str.substr(position||0,searchString.length)===searchString}export function endsWith(str,searchString,position){const index=(position||str.length)-searchString.length;const lastIndex=str.lastIndexOf(searchString,index);return lastIndex!==-1&&lastIndex===index}export function stringIncludes(str,searchString,position){return str.indexOf(searchString,position||0)!==-1}export function isRealNaN(x){return typeof x==="number"&&isNaN(x)}export function arrayIncludes(array,searchElement,position){const len=array.length;if(len===0)return false;const lookupIndex=position|0;const isNaNElement=isRealNaN(searchElement);let searchIndex=lookupIndex>=0?lookupIndex:len+lookupIndex;while(searchIndex<len){const element=array[searchIndex++];if(element===searchElement)return true;if(isNaNElement&&isRealNaN(element))return true}return false}export function splitHead(str,sep){const idx=str.indexOf(sep);if(idx===-1)return[str];return[str.slice(0,idx),str.slice(idx+sep.length)]}export function unquote(str){const car=str.charAt(0);const end=str.length-1;const isQuoteStart=car==='"'||car==="'";if(isQuoteStart&&car===str.charAt(end)){return str.slice(1,end)}return str}export function format(nodes,options){return nodes.map(node=>{var outputNode={};const type=node.type;if(node.children){var textIndex=node.children?.findIndex(e=>{return e.type==="text"});if(textIndex!==-1){outputNode.content=node.children[textIndex]?.content?.trim();node.children.splice(textIndex,1)}}switch(type){case"element":var ATTRS=renderFormatAttributes(node.attributes);delete ATTRS.internal_tree_id;outputNode={...outputNode,type:type,tagName:node.tagName.toLowerCase(),attributes:ATTRS,children:format(node.children,options),id:node.id||generateTreeId()};break;default:outputNode={type:type,content:node.content?.trim()};break}if(options.includePositions){outputNode.position=node.position}return outputNode})}export function renderFormatAttributes(attributes){var ret={};Object.entries(attributes).forEach(([attribute,val])=>{const parts=splitHead(attribute.trim(),"=");const key=parts[0];const value=typeof parts[1]==="string"?unquote(parts[1]):null;var getValue=value=>{if(!value)return value;const trimmed=value.trim();const looksLikeJSON=trimmed.startsWith("[")||trimmed.startsWith("{")||trimmed.startsWith('"')&&trimmed.endsWith('"');if(looksLikeJSON){try{return JSON.parse(value)}catch(error){console.warn(`Failed to parse JSON attribute ${key}:`,value,error);return value}}return value};ret[key]=getValue(value)});return ret}export const parseDefaults={voidTags:voidTags,closingTags:closingTags,childlessTags:childlessTags,closingTagAncestorBreakers:closingTagAncestorBreakers,includePositions:false};export function generateTreeId(){return"node-"+uuidv4()}export function xudaPrase(str,options=parseDefaults){const tokens=lexer(str,options);const nodes=parser(tokens,options);return format(nodes,{...parseDefaults,...options})}export function xudaStringify(ast,options){return toHTML(ast,{...parseDefaults,...options})}
@@ -0,0 +1,703 @@
1
+ /*
2
+ Tags which contain arbitary non-parsed content
3
+ For example: <script> JavaScript should not be parsed
4
+ */
5
+ export const childlessTags = ['style', 'script', 'template'];
6
+
7
+ /*
8
+ Tags which auto-close because they cannot be nested
9
+ For example: <p>Outer<p>Inner is <p>Outer</p><p>Inner</p>
10
+ */
11
+ export const closingTags = ['html', 'head', 'body', 'p', 'dt', 'dd', 'li', 'option', 'thead', 'th', 'tbody', 'tr', 'td', 'tfoot', 'colgroup'];
12
+
13
+ /*
14
+ Closing tags which have ancestor tags which
15
+ may exist within them which prevent the
16
+ closing tag from auto-closing.
17
+ For example: in <li><ul><li></ul></li>,
18
+ the top-level <li> should not auto-close.
19
+ */
20
+ export const closingTagAncestorBreakers = {
21
+ li: ['ul', 'ol', 'menu'],
22
+ dt: ['dl'],
23
+ dd: ['dl'],
24
+ tbody: ['table'],
25
+ thead: ['table'],
26
+ tfoot: ['table'],
27
+ tr: ['table'],
28
+ td: ['table'],
29
+ };
30
+
31
+ /*
32
+ Tags which do not need the closing tag
33
+ For example: <img> does not need </img>
34
+ */
35
+ export const voidTags = ['!doctype', 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
36
+
37
+ export function uuidv4() {
38
+ return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
39
+ }
40
+
41
+ export function isObject(val) {
42
+ return val instanceof Object;
43
+ }
44
+
45
+ export function formatAttributes(attributes, options = {}) {
46
+ return Object.keys(attributes).reduce((attrs, attrKey) => {
47
+ const key = attrKey;
48
+ var value = attributes[attrKey];
49
+
50
+ if (value === null || (typeof value !== 'boolean' && !value)) {
51
+ return `${attrs} ${key}`;
52
+ }
53
+
54
+ if (isObject(value)) {
55
+ // For editor mode, just put raw JSON with single quotes around the attribute
56
+ const jsonString = JSON.stringify(value);
57
+ return `${attrs} ${key}='${jsonString}'`;
58
+ }
59
+
60
+ value = value.toString();
61
+ // For simple strings, use double quotes
62
+ return `${attrs} ${key}="${value}"`;
63
+ }, '');
64
+ }
65
+
66
+ export function toHTML(tree, options) {
67
+ return tree
68
+ .map((node) => {
69
+ if (!node.type) return;
70
+
71
+ if (node.type === 'text') {
72
+ return node.content;
73
+ }
74
+ if (node.type === 'comment') {
75
+ return `<!--${node.content}-->`;
76
+ }
77
+
78
+ const escapeHtml = (unsafe) => {
79
+ return unsafe.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&#039;'); // or &apos;
80
+ };
81
+
82
+ var text = (node.attributes?.['xu-text'] && escapeHtml(node.attributes?.['xu-text'])) || node.attributes?.['xu-html'] || (node?.content && escapeHtml(node.content)) || '';
83
+ // if (node.content) {
84
+ // text = node.content;
85
+ // }
86
+
87
+ var { tagName, attributes, children } = node;
88
+ delete attributes.internal_tree_id;
89
+ delete attributes.internal_path;
90
+ attributes = JSON.parse(JSON.stringify(attributes));
91
+
92
+ if (!attributes.internal_tree_id && !options.remove_tree_id) attributes.internal_tree_id = node.id;
93
+ if (options.remove_tree_id) {
94
+ delete attributes.internal_tree_id;
95
+ }
96
+
97
+ if (options.add_studio_meta) {
98
+ attributes.internal_path = node?.path?.toString?.();
99
+ attributes.xuda_hide = node.$hide;
100
+ }
101
+
102
+ const isSelfClosing = arrayIncludes(options.voidTags, tagName.toLowerCase());
103
+ return isSelfClosing ? `<${tagName}${formatAttributes(attributes)}>` : `<${tagName}${formatAttributes(attributes)}>${text}${toHTML(children, options)}</${tagName}>`;
104
+ })
105
+ .join('');
106
+ }
107
+
108
+ export function parser(tokens, options) {
109
+ const root = { tagName: null, children: [] };
110
+ const state = { tokens, options, cursor: 0, stack: [root] };
111
+ parserParse(state);
112
+ return root.children;
113
+ }
114
+
115
+ export function hasTerminalParent(tagName, stack, terminals) {
116
+ const tagParents = terminals[tagName];
117
+ if (tagParents) {
118
+ let currentIndex = stack.length - 1;
119
+ while (currentIndex >= 0) {
120
+ const parentTagName = stack[currentIndex].tagName;
121
+ if (parentTagName === tagName) {
122
+ break;
123
+ }
124
+ if (arrayIncludes(tagParents, parentTagName)) {
125
+ return true;
126
+ }
127
+ currentIndex--;
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+
133
+ export function rewindStack(stack, newLength, childrenEndPosition, endPosition) {
134
+ stack[newLength].position.end = endPosition;
135
+ for (let i = newLength + 1, len = stack.length; i < len; i++) {
136
+ stack[i].position.end = childrenEndPosition;
137
+ }
138
+ stack.splice(newLength);
139
+ }
140
+
141
+ export function parserParse(state) {
142
+ const { tokens, options } = state;
143
+ let { stack } = state;
144
+ let nodes = stack[stack.length - 1].children;
145
+ const len = tokens.length;
146
+ let { cursor } = state;
147
+ while (cursor < len) {
148
+ const token = tokens[cursor];
149
+ if (token.type !== 'tag-start') {
150
+ nodes.push(token);
151
+ cursor++;
152
+ continue;
153
+ }
154
+
155
+ const tagToken = tokens[++cursor];
156
+ cursor++;
157
+ const tagName = tagToken.content.toLowerCase();
158
+ if (token.close) {
159
+ let index = stack.length;
160
+ let shouldRewind = false;
161
+ while (--index > -1) {
162
+ if (stack[index].tagName === tagName) {
163
+ shouldRewind = true;
164
+ break;
165
+ }
166
+ }
167
+ while (cursor < len) {
168
+ const endToken = tokens[cursor];
169
+ if (endToken.type !== 'tag-end') break;
170
+ cursor++;
171
+ }
172
+ if (shouldRewind) {
173
+ rewindStack(stack, index, token.position.start, tokens[cursor - 1].position.end);
174
+ break;
175
+ } else {
176
+ continue;
177
+ }
178
+ }
179
+
180
+ const isClosingTag = arrayIncludes(options.closingTags, tagName);
181
+ let shouldRewindToAutoClose = isClosingTag;
182
+ if (shouldRewindToAutoClose) {
183
+ const { closingTagAncestorBreakers: terminals } = options;
184
+ shouldRewindToAutoClose = !hasTerminalParent(tagName, stack, terminals);
185
+ }
186
+
187
+ if (shouldRewindToAutoClose) {
188
+ // rewind the stack to just above the previous
189
+ // closing tag of the same name
190
+ let currentIndex = stack.length - 1;
191
+ while (currentIndex > 0) {
192
+ if (tagName === stack[currentIndex].tagName) {
193
+ rewindStack(stack, currentIndex, token.position.start, token.position.start);
194
+ const previousIndex = currentIndex - 1;
195
+ nodes = stack[previousIndex].children;
196
+ break;
197
+ }
198
+ currentIndex = currentIndex - 1;
199
+ }
200
+ }
201
+
202
+ // let attributes = [];
203
+ let attributes = {};
204
+ let attrToken;
205
+ while (cursor < len) {
206
+ attrToken = tokens[cursor];
207
+ if (attrToken.type === 'tag-end') break;
208
+ // debugger;
209
+ // attributes.push(attrToken.content);
210
+ attributes[attrToken.content] = '';
211
+ cursor++;
212
+ }
213
+
214
+ cursor++;
215
+ const children = [];
216
+ const position = {
217
+ start: token.position.start,
218
+ end: attrToken.position.end,
219
+ };
220
+ const elementNode = {
221
+ type: 'element',
222
+ tagName: tagToken.content,
223
+ attributes,
224
+ children,
225
+ position,
226
+ };
227
+ nodes.push(elementNode);
228
+
229
+ const hasChildren = !(attrToken.close || arrayIncludes(options.voidTags, tagName));
230
+ if (hasChildren) {
231
+ const size = stack.push({ tagName, children, position });
232
+ const innerState = { tokens, options, cursor, stack };
233
+ parserParse(innerState);
234
+ cursor = innerState.cursor;
235
+ const rewoundInElement = stack.length === size;
236
+ if (rewoundInElement) {
237
+ elementNode.position.end = tokens[cursor - 1].position.end;
238
+ }
239
+ }
240
+ }
241
+ state.cursor = cursor;
242
+ }
243
+
244
+ export function feedPosition(position, str, len) {
245
+ const start = position.index;
246
+ const end = (position.index = start + len);
247
+ for (let i = start; i < end; i++) {
248
+ const char = str.charAt(i);
249
+ if (char === '\n') {
250
+ position.line++;
251
+ position.column = 0;
252
+ } else {
253
+ position.column++;
254
+ }
255
+ }
256
+ }
257
+
258
+ export function jumpPosition(position, str, end) {
259
+ const len = end - position.index;
260
+ return feedPosition(position, str, len);
261
+ }
262
+
263
+ export function makeInitialPosition() {
264
+ return {
265
+ index: 0,
266
+ column: 0,
267
+ line: 0,
268
+ };
269
+ }
270
+
271
+ export function copyPosition(position) {
272
+ return {
273
+ index: position.index,
274
+ line: position.line,
275
+ column: position.column,
276
+ };
277
+ }
278
+
279
+ export function lexer(str, options) {
280
+ const state = {
281
+ str,
282
+ options,
283
+ position: makeInitialPosition(),
284
+ tokens: [],
285
+ };
286
+ lex(state);
287
+ return state.tokens;
288
+ }
289
+
290
+ export function lex(state) {
291
+ const {
292
+ str,
293
+ options: { childlessTags },
294
+ } = state;
295
+ const len = str.length;
296
+ while (state.position.index < len) {
297
+ const start = state.position.index;
298
+ lexText(state);
299
+ if (state.position.index === start) {
300
+ const isComment = startsWith(str, '!--', start + 1);
301
+ if (isComment) {
302
+ lexComment(state);
303
+ } else {
304
+ const tagName = lexTag(state);
305
+ const safeTag = tagName.toLowerCase();
306
+ if (arrayIncludes(childlessTags, safeTag)) {
307
+ lexSkipTag(tagName, state);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ }
313
+
314
+ const alphanumeric = /[A-Za-z0-9]/;
315
+ export function findTextEnd(str, index) {
316
+ while (true) {
317
+ const textEnd = str.indexOf('<', index);
318
+ if (textEnd === -1) {
319
+ return textEnd;
320
+ }
321
+ const char = str.charAt(textEnd + 1);
322
+ if (char === '/' || char === '!' || alphanumeric.test(char)) {
323
+ return textEnd;
324
+ }
325
+ index = textEnd + 1;
326
+ }
327
+ }
328
+
329
+ export function lexText(state) {
330
+ const type = 'text';
331
+ const { str, position } = state;
332
+
333
+ let textEnd = findTextEnd(str, position.index);
334
+ if (textEnd === position.index) return;
335
+ if (textEnd === -1) {
336
+ textEnd = str.length;
337
+ }
338
+
339
+ const start = copyPosition(position);
340
+ const content = str.slice(position.index, textEnd)?.trim();
341
+ jumpPosition(position, str, textEnd);
342
+ const end = copyPosition(position);
343
+ if (content) state.tokens.push({ type, content, position: { start, end } });
344
+ }
345
+
346
+ export function lexComment(state) {
347
+ const { str, position } = state;
348
+ const start = copyPosition(position);
349
+ feedPosition(position, str, 4); // "<!--".length
350
+ let contentEnd = str.indexOf('-->', position.index);
351
+ let commentEnd = contentEnd + 3; // "-->".length
352
+ if (contentEnd === -1) {
353
+ contentEnd = commentEnd = str.length;
354
+ }
355
+
356
+ const content = str.slice(position.index, contentEnd);
357
+ jumpPosition(position, str, commentEnd);
358
+ state.tokens.push({
359
+ type: 'comment',
360
+ content,
361
+ position: {
362
+ start,
363
+ end: copyPosition(position),
364
+ },
365
+ });
366
+ }
367
+
368
+ export function lexTag(state) {
369
+ const { str, position } = state;
370
+ {
371
+ const secondChar = str.charAt(position.index + 1);
372
+ const close = secondChar === '/';
373
+ const start = copyPosition(position);
374
+ feedPosition(position, str, close ? 2 : 1);
375
+ state.tokens.push({ type: 'tag-start', close, position: { start } });
376
+ }
377
+ const tagName = lexTagName(state);
378
+ lexTagAttributes(state);
379
+ {
380
+ const firstChar = str.charAt(position.index);
381
+ const close = firstChar === '/';
382
+ feedPosition(position, str, close ? 2 : 1);
383
+ const end = copyPosition(position);
384
+ state.tokens.push({ type: 'tag-end', close, position: { end } });
385
+ }
386
+ return tagName;
387
+ }
388
+
389
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#special-white-space
390
+ const whitespace = /\s/;
391
+ export function isWhitespaceChar(char) {
392
+ return whitespace.test(char);
393
+ }
394
+
395
+ export function lexTagName(state) {
396
+ const { str, position } = state;
397
+ const len = str.length;
398
+ let start = position.index;
399
+ while (start < len) {
400
+ const char = str.charAt(start);
401
+ const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>');
402
+ if (isTagChar) break;
403
+ start++;
404
+ }
405
+
406
+ let end = start + 1;
407
+ while (end < len) {
408
+ const char = str.charAt(end);
409
+ const isTagChar = !(isWhitespaceChar(char) || char === '/' || char === '>');
410
+ if (!isTagChar) break;
411
+ end++;
412
+ }
413
+
414
+ jumpPosition(position, str, end);
415
+ const tagName = str.slice(start, end);
416
+ state.tokens.push({
417
+ type: 'tag',
418
+ content: tagName,
419
+ });
420
+ return tagName;
421
+ }
422
+
423
+ export function lexTagAttributes(state) {
424
+ const { str, position, tokens } = state;
425
+ let cursor = position.index;
426
+ let quote = null; // null, single-, or double-quote
427
+ let wordBegin = cursor; // index of word start
428
+ const words = []; // "key", "key=value", "key='value'", etc
429
+ const len = str.length;
430
+ while (cursor < len) {
431
+ const char = str.charAt(cursor);
432
+ if (quote) {
433
+ const isQuoteEnd = char === quote;
434
+ if (isQuoteEnd) {
435
+ quote = null;
436
+ }
437
+ cursor++;
438
+ continue;
439
+ }
440
+
441
+ const isTagEnd = char === '/' || char === '>';
442
+ if (isTagEnd) {
443
+ if (cursor !== wordBegin) {
444
+ words.push(str.slice(wordBegin, cursor));
445
+ }
446
+ break;
447
+ }
448
+
449
+ const isWordEnd = isWhitespaceChar(char);
450
+ if (isWordEnd) {
451
+ if (cursor !== wordBegin) {
452
+ words.push(str.slice(wordBegin, cursor));
453
+ }
454
+ wordBegin = cursor + 1;
455
+ cursor++;
456
+ continue;
457
+ }
458
+
459
+ const isQuoteStart = char === "'" || char === '"';
460
+ if (isQuoteStart) {
461
+ quote = char;
462
+ cursor++;
463
+ continue;
464
+ }
465
+
466
+ cursor++;
467
+ }
468
+ jumpPosition(position, str, cursor);
469
+
470
+ const wLen = words.length;
471
+ const type = 'attribute';
472
+ for (let i = 0; i < wLen; i++) {
473
+ const word = words[i];
474
+ const isNotPair = word.indexOf('=') === -1;
475
+ if (isNotPair) {
476
+ const secondWord = words[i + 1];
477
+ if (secondWord && startsWith(secondWord, '=')) {
478
+ if (secondWord.length > 1) {
479
+ const newWord = word + secondWord;
480
+ tokens.push({ type, content: newWord });
481
+ i += 1;
482
+ continue;
483
+ }
484
+ const thirdWord = words[i + 2];
485
+ i += 1;
486
+ if (thirdWord) {
487
+ const newWord = word + '=' + thirdWord;
488
+ tokens.push({ type, content: newWord });
489
+ i += 1;
490
+ continue;
491
+ }
492
+ }
493
+ }
494
+ if (endsWith(word, '=')) {
495
+ const secondWord = words[i + 1];
496
+ if (secondWord && !stringIncludes(secondWord, '=')) {
497
+ const newWord = word + secondWord;
498
+ tokens.push({ type, content: newWord });
499
+ i += 1;
500
+ continue;
501
+ }
502
+
503
+ const newWord = word.slice(0, -1);
504
+ tokens.push({ type, content: newWord });
505
+ continue;
506
+ }
507
+
508
+ tokens.push({ type, content: word });
509
+ }
510
+ }
511
+
512
+ const push = [].push;
513
+
514
+ export function lexSkipTag(tagName, state) {
515
+ const { str, position, tokens } = state;
516
+ const safeTagName = tagName.toLowerCase();
517
+ const len = str.length;
518
+ let index = position.index;
519
+ while (index < len) {
520
+ const nextTag = str.indexOf('</', index);
521
+ if (nextTag === -1) {
522
+ lexText(state);
523
+ break;
524
+ }
525
+
526
+ const tagStartPosition = copyPosition(position);
527
+ jumpPosition(tagStartPosition, str, nextTag);
528
+ const tagState = { str, position: tagStartPosition, tokens: [] };
529
+ const name = lexTag(tagState);
530
+ if (safeTagName !== name.toLowerCase()) {
531
+ index = tagState.position.index;
532
+ continue;
533
+ }
534
+
535
+ if (nextTag !== position.index) {
536
+ const textStart = copyPosition(position);
537
+ jumpPosition(position, str, nextTag);
538
+
539
+ tokens.push({
540
+ type: 'text',
541
+ content: str.slice(textStart.index, nextTag),
542
+ position: {
543
+ start: textStart,
544
+ end: copyPosition(position),
545
+ },
546
+ });
547
+ }
548
+
549
+ push.apply(tokens, tagState.tokens);
550
+ jumpPosition(position, str, tagState.position.index);
551
+ break;
552
+ }
553
+ }
554
+
555
+ export function startsWith(str, searchString, position) {
556
+ return str.substr(position || 0, searchString.length) === searchString;
557
+ }
558
+
559
+ export function endsWith(str, searchString, position) {
560
+ const index = (position || str.length) - searchString.length;
561
+ const lastIndex = str.lastIndexOf(searchString, index);
562
+ return lastIndex !== -1 && lastIndex === index;
563
+ }
564
+
565
+ export function stringIncludes(str, searchString, position) {
566
+ return str.indexOf(searchString, position || 0) !== -1;
567
+ }
568
+
569
+ export function isRealNaN(x) {
570
+ return typeof x === 'number' && isNaN(x);
571
+ }
572
+
573
+ export function arrayIncludes(array, searchElement, position) {
574
+ const len = array.length;
575
+ if (len === 0) return false;
576
+
577
+ const lookupIndex = position | 0;
578
+ const isNaNElement = isRealNaN(searchElement);
579
+ let searchIndex = lookupIndex >= 0 ? lookupIndex : len + lookupIndex;
580
+ while (searchIndex < len) {
581
+ const element = array[searchIndex++];
582
+ if (element === searchElement) return true;
583
+ if (isNaNElement && isRealNaN(element)) return true;
584
+ }
585
+
586
+ return false;
587
+ }
588
+
589
+ export function splitHead(str, sep) {
590
+ const idx = str.indexOf(sep);
591
+ if (idx === -1) return [str];
592
+ return [str.slice(0, idx), str.slice(idx + sep.length)];
593
+ }
594
+
595
+ export function unquote(str) {
596
+ const car = str.charAt(0);
597
+ const end = str.length - 1;
598
+ const isQuoteStart = car === '"' || car === "'";
599
+ if (isQuoteStart && car === str.charAt(end)) {
600
+ return str.slice(1, end);
601
+ }
602
+ return str;
603
+ }
604
+
605
+ export function format(nodes, options) {
606
+ return nodes.map((node) => {
607
+ var outputNode = {};
608
+ const type = node.type;
609
+
610
+ if (node.children) {
611
+ var textIndex = node.children?.findIndex((e) => {
612
+ return e.type === 'text';
613
+ });
614
+ if (textIndex !== -1) {
615
+ outputNode.content = node.children[textIndex]?.content?.trim();
616
+ node.children.splice(textIndex, 1);
617
+ }
618
+ }
619
+
620
+ switch (type) {
621
+ case 'element':
622
+ var ATTRS = renderFormatAttributes(node.attributes);
623
+ delete ATTRS.internal_tree_id;
624
+
625
+ outputNode = {
626
+ ...outputNode,
627
+ type,
628
+ tagName: node.tagName.toLowerCase(),
629
+ attributes: ATTRS,
630
+ children: format(node.children, options),
631
+ id: node.id || generateTreeId(),
632
+ };
633
+ break;
634
+
635
+ default:
636
+ outputNode = { type, content: node.content?.trim() };
637
+ break;
638
+ }
639
+
640
+ if (options.includePositions) {
641
+ outputNode.position = node.position;
642
+ }
643
+
644
+ return outputNode;
645
+ });
646
+ }
647
+
648
+ export function renderFormatAttributes(attributes) {
649
+ var ret = {};
650
+
651
+ Object.entries(attributes).forEach(([attribute, val]) => {
652
+ const parts = splitHead(attribute.trim(), '=');
653
+ const key = parts[0];
654
+ const value = typeof parts[1] === 'string' ? unquote(parts[1]) : null;
655
+
656
+ var getValue = (value) => {
657
+ if (!value) return value;
658
+
659
+ // Check if it looks like JSON (starts with [, {, or ")
660
+ const trimmed = value.trim();
661
+ const looksLikeJSON = trimmed.startsWith('[') || trimmed.startsWith('{') || (trimmed.startsWith('"') && trimmed.endsWith('"'));
662
+
663
+ if (looksLikeJSON) {
664
+ try {
665
+ return JSON.parse(value);
666
+ } catch (error) {
667
+ console.warn(`Failed to parse JSON attribute ${key}:`, value, error);
668
+ return value; // Return as string if parsing fails
669
+ }
670
+ }
671
+
672
+ // For non-JSON values, return as-is
673
+ return value;
674
+ };
675
+
676
+ ret[key] = getValue(value);
677
+ });
678
+
679
+ return ret;
680
+ }
681
+
682
+ export const parseDefaults = {
683
+ voidTags,
684
+ closingTags,
685
+ childlessTags,
686
+ closingTagAncestorBreakers,
687
+ includePositions: false,
688
+ };
689
+
690
+ export function generateTreeId() {
691
+ return 'node-' + uuidv4();
692
+ }
693
+
694
+ export function xudaPrase(str, options = parseDefaults) {
695
+ const tokens = lexer(str, options);
696
+ const nodes = parser(tokens, options);
697
+
698
+ return format(nodes, { ...parseDefaults, ...options });
699
+ }
700
+
701
+ export function xudaStringify(ast, options) {
702
+ return toHTML(ast, { ...parseDefaults, ...options });
703
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/runtime-bundle",
3
- "version": "1.0.1360",
3
+ "version": "1.0.1361",
4
4
  "description": "The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform. ",
5
5
  "scripts": {
6
6
  "pub": "npm version patch --force && npm publish --access public"