weml-monaco 0.4.0 → 0.4.3

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/dist/index.cjs CHANGED
@@ -6106,21 +6106,47 @@ var require_wemlContentNormalizeV2 = __commonJS({
6106
6106
  "a",
6107
6107
  "w-anchor",
6108
6108
  "w-sent",
6109
- "w-page"
6109
+ "w-page",
6110
+ "w-color"
6110
6111
  ]);
6111
- var VOID_HTML_ELEMENTS = /* @__PURE__ */ new Set(["meta", "br", "hr", "img", "link"]);
6112
- var VOID_TAG_RE = /<\s*(meta|br|hr|img|link)\b([^<>]*?)(?:\/\s*)?>/gi;
6113
- var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g;
6112
+ var VOID_HTML_ELEMENTS = /* @__PURE__ */ new Set([
6113
+ "area",
6114
+ "base",
6115
+ "br",
6116
+ "col",
6117
+ "embed",
6118
+ "hr",
6119
+ "img",
6120
+ "input",
6121
+ "link",
6122
+ "meta",
6123
+ "source",
6124
+ "track",
6125
+ "wbr",
6126
+ "param"
6127
+ ]);
6128
+ var EMPTY_TEXT_WRAPPERS = /* @__PURE__ */ new Set([
6129
+ "w-format",
6130
+ "w-lang",
6131
+ "w-color",
6132
+ "w-non-egw",
6133
+ "w-sent"
6134
+ ]);
6135
+ var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
6114
6136
  var PH_OPEN = "\uE000";
6115
6137
  var PH_CLOSE = "\uE001";
6116
- var PH_RE = /\uE000([0-9]+)\uE001/g;
6117
- var VOID_ELEMENTS = /* @__PURE__ */ new Set(["meta", "br", "hr", "img", "link"]);
6138
+ var VOID_ELEMENTS = VOID_HTML_ELEMENTS;
6139
+ var SPACE_SEPARATED_ATTRIBUTES = /* @__PURE__ */ new Set(["class", "headers", "rel", "rev"]);
6118
6140
  var WHITE_SPACE = /* @__PURE__ */ new Set([
6119
6141
  9,
6120
6142
  10,
6121
6143
  11,
6122
6144
  12,
6123
6145
  13,
6146
+ 28,
6147
+ 29,
6148
+ 30,
6149
+ 31,
6124
6150
  32,
6125
6151
  133,
6126
6152
  160,
@@ -6140,40 +6166,193 @@ var require_wemlContentNormalizeV2 = __commonJS({
6140
6166
  8233,
6141
6167
  8239,
6142
6168
  8287,
6143
- 12288
6169
+ 12288,
6170
+ 65279
6144
6171
  ]);
6145
6172
  function formatWeml(input) {
6146
- const { html, entities: entities2 } = protectEntities(input);
6173
+ const { html, entities: entities2, marker } = protectEntities(input);
6147
6174
  const doc = parse2(normalizeVoidTagSyntax(html));
6175
+ removeEmptyWrappers(doc);
6176
+ const title = doc.querySelector("head title");
6177
+ if (title) processInlineContent(title, true);
6148
6178
  const body = doc.querySelector("body");
6149
6179
  if (body) processBlockElement(body);
6150
- return restoreEntities(serializeDocument(doc), entities2);
6180
+ return restoreEntities(serializeDocument(doc, entities2, marker), entities2, marker);
6151
6181
  }
6152
6182
  function protectEntities(input) {
6183
+ let markerLength = 1;
6184
+ let marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6185
+ while (input.includes(marker)) {
6186
+ markerLength += 1;
6187
+ marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6188
+ }
6153
6189
  const entities2 = [];
6154
6190
  const html = input.replace(ENTITY_RE2, (match) => {
6155
6191
  const index = entities2.length;
6156
6192
  entities2.push(match);
6157
- return `${PH_OPEN}${index}${PH_CLOSE}`;
6193
+ return `${marker}${index}${marker}`;
6158
6194
  });
6159
- return { html, entities: entities2 };
6160
- }
6161
- function restoreEntities(text, entities2) {
6162
- return text.replace(PH_RE, (_, index) => entities2[Number(index)]);
6195
+ return { html, entities: entities2, marker };
6196
+ }
6197
+ function restoreEntities(text, entities2, marker) {
6198
+ const result = [];
6199
+ let position = 0;
6200
+ while (position < text.length) {
6201
+ const open = text.indexOf(marker, position);
6202
+ if (open < 0) {
6203
+ result.push(text.slice(position));
6204
+ break;
6205
+ }
6206
+ result.push(text.slice(position, open));
6207
+ const indexStart = open + marker.length;
6208
+ const close = text.indexOf(marker, indexStart);
6209
+ const indexText = close >= 0 ? text.slice(indexStart, close) : "";
6210
+ const entityIndex = Number(indexText);
6211
+ if (close < 0 || !/^[0-9]+$/.test(indexText) || !Number.isSafeInteger(entityIndex) || entityIndex >= entities2.length) {
6212
+ throw new Error("Invalid internal entity placeholder.");
6213
+ }
6214
+ result.push(entities2[entityIndex]);
6215
+ position = close + marker.length;
6216
+ }
6217
+ return result.join("");
6163
6218
  }
6164
6219
  function normalizeVoidTagSyntax(input) {
6165
- return input.replace(VOID_TAG_RE, (_, name, attrs) => `<${name.toLowerCase()}${attrs.trimEnd()} />`);
6220
+ function slashBelongsToUnquotedValue(attrs) {
6221
+ let index2 = 0;
6222
+ const whitespace = /* @__PURE__ */ new Set([" ", " ", "\n", "\f", "\r", "\v"]);
6223
+ while (index2 < attrs.length) {
6224
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6225
+ if (index2 >= attrs.length || attrs[index2] === "/") return false;
6226
+ while (index2 < attrs.length && !whitespace.has(attrs[index2]) && !["=", "/"].includes(attrs[index2])) index2 += 1;
6227
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6228
+ if (index2 >= attrs.length || attrs[index2] !== "=") continue;
6229
+ index2 += 1;
6230
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6231
+ if (index2 >= attrs.length) return false;
6232
+ if (attrs[index2] === '"' || attrs[index2] === "'") {
6233
+ const quote = attrs[index2];
6234
+ index2 += 1;
6235
+ while (index2 < attrs.length && attrs[index2] !== quote) index2 += 1;
6236
+ if (index2 < attrs.length) index2 += 1;
6237
+ continue;
6238
+ }
6239
+ const valueStart = index2;
6240
+ while (index2 < attrs.length && !whitespace.has(attrs[index2])) index2 += 1;
6241
+ if (index2 === attrs.length) {
6242
+ return valueStart < attrs.length && attrs.endsWith("/");
6243
+ }
6244
+ }
6245
+ return false;
6246
+ }
6247
+ const result = [];
6248
+ let index = 0;
6249
+ while (index < input.length) {
6250
+ if (input[index] !== "<") {
6251
+ result.push(input[index++]);
6252
+ continue;
6253
+ }
6254
+ const start = index;
6255
+ if (input.startsWith("<!--", index) || input.startsWith("<?", index)) {
6256
+ const delimiter = input.startsWith("<!--", index) ? "-->" : "?>";
6257
+ let end2 = input.indexOf(delimiter, index + 2);
6258
+ end2 = end2 < 0 ? input.length : end2 + delimiter.length;
6259
+ result.push(input.slice(start, end2));
6260
+ index = end2;
6261
+ continue;
6262
+ }
6263
+ const { end } = findTagEnd(input, index + 1);
6264
+ if (end < 0) {
6265
+ result.push(input.slice(index));
6266
+ break;
6267
+ }
6268
+ let cursor = index + 1;
6269
+ if (input[cursor] === "!") {
6270
+ result.push(input.slice(start, end + 1));
6271
+ index = end + 1;
6272
+ continue;
6273
+ }
6274
+ const closing = input[cursor] === "/";
6275
+ if (closing) cursor++;
6276
+ if (cursor >= end || !isNameStart(input[cursor])) {
6277
+ result.push(input[index++]);
6278
+ continue;
6279
+ }
6280
+ const nameStart = cursor++;
6281
+ while (cursor < end && isNameChar(input[cursor])) cursor++;
6282
+ const name = input.slice(nameStart, cursor);
6283
+ const attributes = [];
6284
+ let quote = null;
6285
+ let inSpace = false;
6286
+ for (let offset = cursor; offset < end; offset++) {
6287
+ const character = input[offset];
6288
+ if (quote !== null) {
6289
+ attributes.push(character);
6290
+ if (character === quote) quote = null;
6291
+ continue;
6292
+ }
6293
+ if (isSourceSpace(character)) {
6294
+ if (!inSpace) attributes.push(" ");
6295
+ inSpace = true;
6296
+ } else {
6297
+ inSpace = false;
6298
+ attributes.push(character);
6299
+ if (character === '"' || character === "'") quote = character;
6300
+ }
6301
+ }
6302
+ let attrs = attributes.join("").replace(/ +$/, "");
6303
+ if (!closing && VOID_HTML_ELEMENTS.has(name.toLowerCase())) {
6304
+ if (attrs.endsWith("/") && !slashBelongsToUnquotedValue(attrs)) {
6305
+ attrs = attrs.slice(0, -1).replace(/ +$/, "");
6306
+ }
6307
+ result.push(`<${name.toLowerCase()}${attrs} />`);
6308
+ } else {
6309
+ result.push(input.slice(start, cursor), attrs, ">");
6310
+ }
6311
+ index = end + 1;
6312
+ }
6313
+ return result.join("");
6166
6314
  }
6167
- function processBlockElement(node) {
6315
+ function removeEmptyWrappers(node) {
6316
+ for (const child of [...node.childNodes]) {
6317
+ if (child.nodeType === 8) {
6318
+ node.removeChild(child);
6319
+ continue;
6320
+ }
6321
+ if (child.nodeType !== 1) continue;
6322
+ removeEmptyWrappers(child);
6323
+ const hasId = Object.keys(child.rawAttributes).some((name) => name.toLowerCase() === "id");
6324
+ if (EMPTY_TEXT_WRAPPERS.has(child.rawTagName.toLowerCase()) && !hasId && child.childNodes.every((item) => item.nodeType === 3 && isWhitespaceOnly(item.rawText))) {
6325
+ const replacement = new TextNode(child.childNodes.some((item) => item.rawText.length > 0) ? " " : "", node);
6326
+ node.exchangeChild(child, replacement);
6327
+ }
6328
+ }
6329
+ let previous = null;
6168
6330
  for (const child of [...node.childNodes]) {
6169
- if (child.nodeType === 3 && child.rawText.trim() === "") child.remove();
6331
+ if (child.nodeType === 3) {
6332
+ if (previous !== null) {
6333
+ previous.rawText += child.rawText;
6334
+ node.removeChild(child);
6335
+ } else previous = child;
6336
+ } else previous = null;
6170
6337
  }
6338
+ }
6339
+ function processBlockElement(node) {
6340
+ node.childNodes = node.childNodes.filter((child) => {
6341
+ if (child.nodeType !== 3) return true;
6342
+ const normalized = trimWhitespace(child.rawText);
6343
+ if (normalized) {
6344
+ child.rawText = normalized;
6345
+ return true;
6346
+ }
6347
+ child.parentNode = null;
6348
+ return false;
6349
+ });
6171
6350
  for (const child of [...node.childNodes]) {
6172
6351
  if (child.nodeType !== 1) continue;
6173
6352
  const name = child.rawTagName.toLowerCase();
6174
6353
  if (INLINE_CONTAINERS.has(name)) processInlineContainer(child);
6175
6354
  else if (BLOCK_ELEMENTS.has(name)) processBlockElement(child);
6176
- else if (INLINE_ELEMENTS.has(name)) processInlineContent(child, true);
6355
+ else if (isInlineTextElement(child)) processInlineContent(child, true);
6177
6356
  }
6178
6357
  }
6179
6358
  function processInlineContainer(node) {
@@ -6184,7 +6363,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6184
6363
  for (const child of [...node.childNodes]) {
6185
6364
  if (child.nodeType !== 1) continue;
6186
6365
  const name = child.rawTagName.toLowerCase();
6187
- if (INLINE_ELEMENTS.has(name)) {
6366
+ if (isInlineTextElement(child)) {
6188
6367
  if (hasDirectSentChildren(child)) processSentContainer(child);
6189
6368
  else processInlineContent(child, false);
6190
6369
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6194,19 +6373,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6194
6373
  }
6195
6374
  }
6196
6375
  for (const child of [...node.childNodes]) {
6197
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6376
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6198
6377
  bleedEdgeSpaces(child);
6199
6378
  }
6200
6379
  }
6201
6380
  for (const child of [...node.childNodes]) {
6202
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6381
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6203
6382
  }
6204
6383
  for (const child of [...node.childNodes]) {
6205
6384
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6206
6385
  const prev = child.previousSibling;
6207
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6386
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6208
6387
  const next = child.nextSibling;
6209
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6388
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6210
6389
  }
6211
6390
  }
6212
6391
  trimContainerEdges(node);
@@ -6220,17 +6399,17 @@ var require_wemlContentNormalizeV2 = __commonJS({
6220
6399
  const first = children[0];
6221
6400
  const last = children[children.length - 1];
6222
6401
  if (children.length === 1) {
6223
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6402
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6224
6403
  } else {
6225
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6226
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6404
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6405
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6227
6406
  }
6228
6407
  }
6229
6408
  function processInlineContent(node, trimEdges) {
6230
6409
  for (const child of [...node.childNodes]) {
6231
6410
  if (child.nodeType !== 1) continue;
6232
6411
  const name = child.rawTagName.toLowerCase();
6233
- if (INLINE_ELEMENTS.has(name)) {
6412
+ if (isInlineTextElement(child)) {
6234
6413
  if (hasDirectSentChildren(child)) processSentContainer(child);
6235
6414
  else processInlineContent(child, false);
6236
6415
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6240,19 +6419,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6240
6419
  }
6241
6420
  }
6242
6421
  for (const child of [...node.childNodes]) {
6243
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6422
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6244
6423
  bleedEdgeSpaces(child);
6245
6424
  }
6246
6425
  }
6247
6426
  for (const child of [...node.childNodes]) {
6248
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6427
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6249
6428
  }
6250
6429
  for (const child of [...node.childNodes]) {
6251
6430
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6252
6431
  const prev = child.previousSibling;
6253
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6432
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6254
6433
  const next = child.nextSibling;
6255
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6434
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6256
6435
  }
6257
6436
  }
6258
6437
  if (trimEdges) trimEdgeTextNodes(node);
@@ -6290,7 +6469,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6290
6469
  const first = getFirstTextNode(node);
6291
6470
  if (!first) return "";
6292
6471
  const raw = first.rawText;
6293
- const trimmed = raw.replace(/^\s+/, "");
6472
+ const trimmed = trimStartWhitespace(raw);
6294
6473
  if (trimmed === raw) return "";
6295
6474
  first.rawText = trimmed;
6296
6475
  return " ";
@@ -6299,7 +6478,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6299
6478
  const last = getLastTextNode(node);
6300
6479
  if (!last) return "";
6301
6480
  const raw = last.rawText;
6302
- const trimmed = raw.replace(/\s+$/, "");
6481
+ const trimmed = trimEndWhitespace(raw);
6303
6482
  if (trimmed === raw) return "";
6304
6483
  last.rawText = trimmed;
6305
6484
  return " ";
@@ -6336,10 +6515,10 @@ var require_wemlContentNormalizeV2 = __commonJS({
6336
6515
  const first = children[0];
6337
6516
  const last = children[children.length - 1];
6338
6517
  if (first === last) {
6339
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6518
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6340
6519
  } else {
6341
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6342
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6520
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6521
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6343
6522
  }
6344
6523
  }
6345
6524
  function hasDirectSentChildren(node) {
@@ -6347,17 +6526,22 @@ var require_wemlContentNormalizeV2 = __commonJS({
6347
6526
  (child) => child.nodeType === 1 && child.rawTagName.toLowerCase() === "w-sent"
6348
6527
  );
6349
6528
  }
6350
- function serializeDocument(doc) {
6529
+ function isInlineTextElement(node) {
6530
+ if (node.nodeType !== 1) return false;
6531
+ const name = node.rawTagName.toLowerCase();
6532
+ return INLINE_ELEMENTS.has(name) || !BLOCK_ELEMENTS.has(name) && !INLINE_CONTAINERS.has(name) && !VOID_HTML_ELEMENTS.has(name);
6533
+ }
6534
+ function serializeDocument(doc, entities2, marker) {
6351
6535
  const parts = ["<!DOCTYPE html>\n"];
6352
6536
  const htmlNode = doc.querySelector("html");
6353
- parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.attrs : {})}>
6537
+ parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.rawAttrs : "", entities2, marker)}>
6354
6538
 
6355
6539
  `);
6356
6540
  const head = doc.querySelector("head");
6357
6541
  parts.push("<head>\n");
6358
6542
  if (head) {
6359
6543
  for (const child of head.childNodes) {
6360
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6544
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6361
6545
  }
6362
6546
  }
6363
6547
  parts.push("</head>\n\n");
@@ -6365,28 +6549,81 @@ var require_wemlContentNormalizeV2 = __commonJS({
6365
6549
  parts.push("<body>\n");
6366
6550
  if (body) {
6367
6551
  for (const child of body.childNodes) {
6368
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6552
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6369
6553
  }
6370
6554
  }
6371
6555
  parts.push("</body>\n</html>");
6372
6556
  return parts.join("");
6373
6557
  }
6374
- function serializeNode(node) {
6558
+ function serializeNode(node, entities2, marker) {
6375
6559
  if (node.nodeType === 3) return node.rawText;
6376
6560
  if (node.nodeType === 8 || node.nodeType !== 1) return "";
6377
6561
  const name = node.rawTagName.toLowerCase();
6378
- const attrs = serializeAttrs(node.attrs);
6562
+ const attrs = serializeAttrs(node.rawAttrs, entities2, marker);
6379
6563
  if (VOID_HTML_ELEMENTS.has(name)) return `<${name}${attrs} />`;
6380
6564
  const isBlock = BLOCK_ELEMENTS.has(name);
6381
6565
  const inner = [];
6382
6566
  for (const child of node.childNodes) {
6383
- if (isBlock && child.nodeType === 3 && child.rawText.trim() === "") continue;
6384
- inner.push(serializeNode(child));
6567
+ if (isBlock && child.nodeType === 3 && isWhitespaceOnly(child.rawText)) continue;
6568
+ inner.push(serializeNode(child, entities2, marker));
6385
6569
  }
6386
6570
  return `<${name}${attrs}>${inner.join("")}</${name}>`;
6387
6571
  }
6388
- function serializeAttrs(attrs) {
6389
- return Object.entries(attrs).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6572
+ function serializeAttrs(rawAttrs, entities2, marker) {
6573
+ const canonical = /* @__PURE__ */ new Map();
6574
+ let index = 0;
6575
+ while (index < rawAttrs.length) {
6576
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6577
+ if (index >= rawAttrs.length || rawAttrs[index] === "/") break;
6578
+ const nameStart = index;
6579
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index]) && !"=/>".includes(rawAttrs[index])) index++;
6580
+ if (index === nameStart) {
6581
+ index++;
6582
+ continue;
6583
+ }
6584
+ const name = rawAttrs.slice(nameStart, index).toLowerCase();
6585
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6586
+ let value = "";
6587
+ if (rawAttrs[index] === "=") {
6588
+ index++;
6589
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6590
+ if (rawAttrs[index] === '"' || rawAttrs[index] === "'") {
6591
+ const quote = rawAttrs[index++];
6592
+ const valueStart = index;
6593
+ while (index < rawAttrs.length && rawAttrs[index] !== quote) index++;
6594
+ value = rawAttrs.slice(valueStart, index);
6595
+ if (index < rawAttrs.length) index++;
6596
+ } else {
6597
+ const valueStart = index;
6598
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index])) index++;
6599
+ value = rawAttrs.slice(valueStart, index);
6600
+ }
6601
+ }
6602
+ canonical.set(
6603
+ name,
6604
+ SPACE_SEPARATED_ATTRIBUTES.has(name) ? normalizeTokenList(value, entities2, marker) : trimWhitespace(value)
6605
+ );
6606
+ }
6607
+ return [...canonical.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6608
+ }
6609
+ function normalizeTokenList(value, entities2, marker) {
6610
+ const normalized = trimWhitespace(value);
6611
+ if (!normalized) return normalized;
6612
+ return normalized.split(" ").sort((left, right) => compareUnicodeScalars(
6613
+ restoreEntities(left, entities2, marker),
6614
+ restoreEntities(right, entities2, marker)
6615
+ )).join(" ");
6616
+ }
6617
+ function compareUnicodeScalars(left, right) {
6618
+ const leftScalars = Array.from(left, (character) => character.codePointAt(0));
6619
+ const rightScalars = Array.from(right, (character) => character.codePointAt(0));
6620
+ const limit = Math.min(leftScalars.length, rightScalars.length);
6621
+ for (let index = 0; index < limit; index++) {
6622
+ if (leftScalars[index] !== rightScalars[index]) {
6623
+ return leftScalars[index] - rightScalars[index];
6624
+ }
6625
+ }
6626
+ return leftScalars.length - rightScalars.length;
6390
6627
  }
6391
6628
  function encodeAttr(value) {
6392
6629
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -6424,6 +6661,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6424
6661
  }
6425
6662
  };
6426
6663
  function wemlContentNormalize2(input, isWemlValid = false) {
6664
+ if (input.startsWith("\uFEFF")) input = input.slice(1);
6427
6665
  const inputText = withoutWhiteSpace(analyze(input, !isWemlValid).text);
6428
6666
  const result = formatWeml(input);
6429
6667
  const outputText = withoutWhiteSpace(analyze(result, true).text);
@@ -6438,6 +6676,9 @@ var require_wemlContentNormalizeV2 = __commonJS({
6438
6676
  if (source[index2] === "\n") lineStarts.push(index2 + 1);
6439
6677
  }
6440
6678
  const stack = [];
6679
+ const rootTagCounts = /* @__PURE__ */ new Map([["html", 0], ["head", 0], ["body", 0]]);
6680
+ let seenHead = false;
6681
+ let seenBody = false;
6441
6682
  const textParts = [];
6442
6683
  let textStart = 0;
6443
6684
  let index = 0;
@@ -6521,6 +6762,26 @@ var require_wemlContentNormalizeV2 = __commonJS({
6521
6762
  if (expected !== name) fail("MISMATCHED_CLOSING_TAG", index, expected, name);
6522
6763
  stack.pop();
6523
6764
  } else {
6765
+ if (rootTagCounts.has(name)) {
6766
+ if (rootTagCounts.get(name) > 0) {
6767
+ fail("DUPLICATE_ROOT_TAG", index, null, name);
6768
+ }
6769
+ const parent = stack.length > 0 ? stack.at(-1).name : null;
6770
+ if (name === "html") {
6771
+ if (parent !== null) fail("INVALID_ROOT_TAG_PARENT", index, null, name);
6772
+ } else if (parent !== "html") {
6773
+ fail("INVALID_ROOT_TAG_PARENT", index, "html", name);
6774
+ }
6775
+ if (name === "body" && !seenHead) {
6776
+ fail("BODY_BEFORE_HEAD", index, "head", "body");
6777
+ }
6778
+ if (name === "head" && seenBody) {
6779
+ fail("HEAD_AFTER_BODY", index, "body", "head");
6780
+ }
6781
+ rootTagCounts.set(name, rootTagCounts.get(name) + 1);
6782
+ seenHead || (seenHead = name === "head");
6783
+ seenBody || (seenBody = name === "body");
6784
+ }
6524
6785
  let last = found.end - 1;
6525
6786
  while (last >= cursor && isSourceSpace(source[last])) last--;
6526
6787
  const selfClosing = last >= cursor && source[last] === "/";
@@ -6542,6 +6803,11 @@ var require_wemlContentNormalizeV2 = __commonJS({
6542
6803
  null
6543
6804
  );
6544
6805
  }
6806
+ if (validateStructure) {
6807
+ for (const name of ["html", "head", "body"]) {
6808
+ if (rootTagCounts.get(name) === 0) fail("MISSING_ROOT_TAG", 0, name, null);
6809
+ }
6810
+ }
6545
6811
  return { text: textParts.join("") };
6546
6812
  }
6547
6813
  function findTagEnd(source, start) {
@@ -6565,11 +6831,40 @@ var require_wemlContentNormalizeV2 = __commonJS({
6565
6831
  return isNameStart(character) || character >= "0" && character <= "9" || character === "-" || character === "_" || character === ":" || character === ".";
6566
6832
  }
6567
6833
  function isSourceSpace(character) {
6568
- return character === " " || character === " " || character === "\n" || character === "\r" || character === "\f" || character === "\v";
6834
+ return isNormalizableWhitespace(character);
6569
6835
  }
6570
6836
  function withoutWhiteSpace(text) {
6571
6837
  return Array.from(text, (character) => WHITE_SPACE.has(character.codePointAt(0)) ? "" : character).join("");
6572
6838
  }
6839
+ function isNormalizableWhitespace(character) {
6840
+ return character !== void 0 && WHITE_SPACE.has(character.codePointAt(0));
6841
+ }
6842
+ function collapseWhitespace(text) {
6843
+ const result = [];
6844
+ let inWhitespace = false;
6845
+ for (const character of text) {
6846
+ if (isNormalizableWhitespace(character)) {
6847
+ if (!inWhitespace) result.push(" ");
6848
+ inWhitespace = true;
6849
+ } else {
6850
+ result.push(character);
6851
+ inWhitespace = false;
6852
+ }
6853
+ }
6854
+ return result.join("");
6855
+ }
6856
+ function trimStartWhitespace(text) {
6857
+ return collapseWhitespace(text).replace(/^ +/, "");
6858
+ }
6859
+ function trimEndWhitespace(text) {
6860
+ return collapseWhitespace(text).replace(/ +$/, "");
6861
+ }
6862
+ function trimWhitespace(text) {
6863
+ return collapseWhitespace(text).replace(/^ +| +$/g, "");
6864
+ }
6865
+ function isWhitespaceOnly(text) {
6866
+ return Array.from(text).every(isNormalizableWhitespace);
6867
+ }
6573
6868
  function sha256(text) {
6574
6869
  const crypto = require("crypto");
6575
6870
  return crypto.createHash("sha256").update(text, "utf8").digest("hex");
@@ -6589,6 +6884,16 @@ var require_wemlContentNormalizeV2 = __commonJS({
6589
6884
  detail = `unexpected closing tag </${actualTag}>.`;
6590
6885
  } else if (code === "UNCLOSED_TAG") {
6591
6886
  detail = `tag <${expectedTag}> is not closed.`;
6887
+ } else if (code === "DUPLICATE_ROOT_TAG") {
6888
+ detail = `document contains more than one <${actualTag}> tag.`;
6889
+ } else if (code === "INVALID_ROOT_TAG_PARENT") {
6890
+ detail = expectedTag ? `tag <${actualTag}> must be a direct child of <${expectedTag}>.` : `root tag <${actualTag}> must not be nested.`;
6891
+ } else if (code === "BODY_BEFORE_HEAD") {
6892
+ detail = "tag <body> appears before <head>.";
6893
+ } else if (code === "HEAD_AFTER_BODY") {
6894
+ detail = "tag <head> appears after <body>.";
6895
+ } else if (code === "MISSING_ROOT_TAG") {
6896
+ detail = `required root tag <${expectedTag}> is missing.`;
6592
6897
  } else if (code === "UNTERMINATED_COMMENT") {
6593
6898
  detail = "comment is not terminated.";
6594
6899
  } else if (code === "UNTERMINATED_DECLARATION") {
@@ -29261,6 +29566,13 @@ var TAGS = {
29261
29566
  "default": "paragraph",
29262
29567
  "description": "Type of the paragraph. See corresponding page for values",
29263
29568
  "enumRef": "paragraph-type"
29569
+ },
29570
+ {
29571
+ "name": "align",
29572
+ "required": false,
29573
+ "default": "`left` for LTR, `right` for RTL",
29574
+ "description": "Horizontal alignment. See corresponding page for allowed values",
29575
+ "enumRef": "horizontal-alignment"
29264
29576
  }
29265
29577
  ],
29266
29578
  "notes": "",
@@ -29273,6 +29585,24 @@ var TAGS = {
29273
29585
  "notes": "Current line is broken and the next line is started. Next line has the same alignment\nas the current line, and the same indentation level as any line other than the first.",
29274
29586
  "source": "inlines/br.md"
29275
29587
  },
29588
+ "w-color": {
29589
+ "tag": "w-color",
29590
+ "description": "Represents an inline span with a foreground color, a background color, or both.",
29591
+ "attributes": [
29592
+ {
29593
+ "name": "background",
29594
+ "required": false,
29595
+ "description": "Background color. See Colors."
29596
+ },
29597
+ {
29598
+ "name": "foreground",
29599
+ "required": false,
29600
+ "description": "Foreground color. See Colors."
29601
+ }
29602
+ ],
29603
+ "notes": "Any inline elements.",
29604
+ "source": "inlines/w-color.md"
29605
+ },
29276
29606
  "w-entity": {
29277
29607
  "tag": "w-entity",
29278
29608
  "description": "An entity is a reference to a person, place, or thing. Entities are used to",
@@ -29405,6 +29735,16 @@ var TAGS = {
29405
29735
  "name": "override-chapter-number",
29406
29736
  "required": false,
29407
29737
  "description": "Chapter number for manual set"
29738
+ },
29739
+ {
29740
+ "name": "background",
29741
+ "required": false,
29742
+ "description": "Background color. See Colors"
29743
+ },
29744
+ {
29745
+ "name": "foreground",
29746
+ "required": false,
29747
+ "description": "Foreground color. See Colors"
29408
29748
  }
29409
29749
  ],
29410
29750
  "notes": "Must contain a single text block element.",
@@ -29435,6 +29775,16 @@ var TAGS = {
29435
29775
  "required": false,
29436
29776
  "description": "Horizontal alignment. See corresponding page for allowed values. Defaults to `left` for left-to-right languages and `right` to right-to-left",
29437
29777
  "enumRef": "horizontal-alignment"
29778
+ },
29779
+ {
29780
+ "name": "background",
29781
+ "required": false,
29782
+ "description": "Background color. See Colors"
29783
+ },
29784
+ {
29785
+ "name": "foreground",
29786
+ "required": false,
29787
+ "description": "Foreground color. See Colors"
29438
29788
  }
29439
29789
  ],
29440
29790
  "notes": "Must contain a single block element.",
@@ -29791,8 +30141,12 @@ var META_FIELDS = [
29791
30141
  "description": "Publication description."
29792
30142
  },
29793
30143
  {
29794
- "name": "parent_id",
29795
- "description": "Parent publication version GUID."
30144
+ "name": "purchase-link",
30145
+ "description": "Purchase link."
30146
+ },
30147
+ {
30148
+ "name": "version-id",
30149
+ "description": "Publication version GUID."
29796
30150
  }
29797
30151
  ];
29798
30152
 
@@ -30049,7 +30403,7 @@ Unicode: \`U+${resolved.codePoint.toString(16).toUpperCase().padStart(4, "0")}\`
30049
30403
  // src/schema/structureRules.ts
30050
30404
  var CONTAINER_TAGS = ["w-para", "w-heading", "w-para-group", "w-toc"];
30051
30405
  var BLOCK_TAGS = ["w-text-block", "figure", "w-list", "table", "hr"];
30052
- var INLINE_TAGS = ["a", "br", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30406
+ var INLINE_TAGS = ["a", "br", "w-color", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30053
30407
  var STRUCTURE_RULES = {
30054
30408
  html: {
30055
30409
  children: [
@@ -30196,6 +30550,13 @@ var STRUCTURE_RULES = {
30196
30550
  allowText: true,
30197
30551
  description: "With href: a link that may contain inline content. With id: a named anchor that must be empty. Exactly one of href/id is required, not both."
30198
30552
  },
30553
+ "w-color": {
30554
+ children: [
30555
+ { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
30556
+ ],
30557
+ allowText: true,
30558
+ description: "May contain inline elements and text; requires a foreground or background color."
30559
+ },
30199
30560
  "w-entity": {
30200
30561
  children: [
30201
30562
  { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
@@ -30269,11 +30630,18 @@ var STRUCTURE_RULES = {
30269
30630
  }
30270
30631
  };
30271
30632
  var ARABIC_OR_ROMAN = /^(?:\d+|[ivxlcdm]+)$/i;
30633
+ var COLOR_VALUE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
30272
30634
  var ATTRIBUTE_CONSTRAINTS = [
30273
30635
  { tag: "w-heading", attribute: "level", kind: "integer-range", min: 1, max: 6, message: "`level` must be an integer between 1 and 6." },
30274
30636
  { tag: "w-heading", attribute: "override-chapter-number", kind: "integer-min", min: 0, message: "`override-chapter-number` must be a non-negative integer." },
30637
+ { tag: "w-heading", attribute: "background", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`background` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30638
+ { tag: "w-heading", attribute: "foreground", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`foreground` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30275
30639
  { tag: "w-page", attribute: "number", kind: "pattern", pattern: ARABIC_OR_ROMAN, message: "`number` must be an arabic or roman number." },
30276
30640
  { tag: "w-para", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30641
+ { tag: "w-para", attribute: "background", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`background` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30642
+ { tag: "w-para", attribute: "foreground", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`foreground` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30643
+ { tag: "w-color", attribute: "background", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`background` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30644
+ { tag: "w-color", attribute: "foreground", kind: "pattern", pattern: COLOR_VALUE, allowEmpty: false, message: "`foreground` must be a color in #RGB, #RGBA, #RRGGBB or #RRGGBBAA format." },
30277
30645
  { tag: "w-note-para", attribute: "indent", kind: "integer-min", min: 0, message: "`indent` must be an integer >= 0." },
30278
30646
  { tag: "td", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30279
30647
  { tag: "th", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
@@ -30414,6 +30782,9 @@ function indent(text) {
30414
30782
  function buildElement(tag, counter, depth) {
30415
30783
  const schema = TAGS[tag];
30416
30784
  let attrs = "";
30785
+ if (tag === "w-color") {
30786
+ attrs = ` foreground="${placeholder(counter)}"`;
30787
+ }
30417
30788
  for (const attr of schema?.attributes ?? []) {
30418
30789
  if (attr.required) {
30419
30790
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30449,6 +30820,9 @@ function buildElementSnippet(tag) {
30449
30820
  function buildOpeningTag(tag) {
30450
30821
  const counter = { n: 1 };
30451
30822
  let attrs = "";
30823
+ if (tag === "w-color") {
30824
+ attrs = ` foreground="${placeholder(counter)}"`;
30825
+ }
30452
30826
  for (const attr of TAGS[tag]?.attributes ?? []) {
30453
30827
  if (attr.required) {
30454
30828
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30556,24 +30930,22 @@ function getCompletionContext(text, offset, nodeStart) {
30556
30930
  switch (token) {
30557
30931
  case TokenType.StartTagOpen:
30558
30932
  if (scanner.getTokenEnd() === offset) {
30559
- const end = scanNextEnd(scanner, offset, TokenType.StartTag);
30560
- return { kind: "tag", start: offset - 1, end, includesBracket: true };
30933
+ return { kind: "tag", start: offset - 1, end: offset, includesBracket: true };
30561
30934
  }
30562
30935
  break;
30563
30936
  case TokenType.EndTagOpen:
30564
30937
  if (scanner.getTokenEnd() === offset) {
30565
- const end = scanNextEnd(scanner, offset, TokenType.EndTag);
30566
- return { kind: "end-tag", start: offset, end };
30938
+ return { kind: "end-tag", start: offset, end: offset };
30567
30939
  }
30568
30940
  break;
30569
30941
  case TokenType.EndTag:
30570
30942
  if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
30571
- return { kind: "end-tag", start: scanner.getTokenOffset(), end: scanner.getTokenEnd() };
30943
+ return { kind: "end-tag", start: scanner.getTokenOffset(), end: offset };
30572
30944
  }
30573
30945
  break;
30574
30946
  case TokenType.StartTag:
30575
30947
  if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
30576
- return { kind: "tag", start: scanner.getTokenOffset(), end: scanner.getTokenEnd(), includesBracket: false };
30948
+ return { kind: "tag", start: scanner.getTokenOffset(), end: offset, includesBracket: false };
30577
30949
  }
30578
30950
  currentTag = scanner.getTokenText();
30579
30951
  break;
@@ -30611,8 +30983,7 @@ function getCompletionContext(text, offset, nodeStart) {
30611
30983
  switch (scanner.getScannerState()) {
30612
30984
  case ScannerState.AfterOpeningStartTag: {
30613
30985
  const start = scanner.getTokenOffset();
30614
- const end = scanNextEnd(scanner, offset, TokenType.StartTag);
30615
- return { kind: "tag", start, end, includesBracket: false };
30986
+ return { kind: "tag", start, end: offset, includesBracket: false };
30616
30987
  }
30617
30988
  case ScannerState.WithinTag:
30618
30989
  case ScannerState.AfterAttributeName:
@@ -30652,6 +31023,7 @@ function dedupe(items) {
30652
31023
  function hasValueSuggestions(tag, attribute, enumRef) {
30653
31024
  if (enumRef && ENUMS[enumRef]) return true;
30654
31025
  if (tag === "w-heading" && attribute === "level") return true;
31026
+ if (tag === "meta" && attribute === "name") return true;
30655
31027
  if (tag === "meta" && attribute === "content") return true;
30656
31028
  if (tag === "a" && attribute === "href") return true;
30657
31029
  return false;
@@ -30821,6 +31193,18 @@ var WemlCompletionProvider = class {
30821
31193
  });
30822
31194
  }
30823
31195
  }
31196
+ if (ctx.tag === "meta" && ctx.attribute === "name") {
31197
+ for (const field of META_FIELDS) {
31198
+ items.push({
31199
+ label: field.name,
31200
+ kind: this.m.languages.CompletionItemKind.Property,
31201
+ detail: "WEML metadata field",
31202
+ documentation: metaFieldMarkdown(field.name),
31203
+ insertText: wrap(field.name),
31204
+ range
31205
+ });
31206
+ }
31207
+ }
30824
31208
  if (ctx.tag === "meta" && ctx.attribute === "content" && stripQuotes2(node.attributes?.["name"]) === "type") {
30825
31209
  const codeEnum = ENUMS["publication-type"];
30826
31210
  if (codeEnum) {
@@ -30978,6 +31362,9 @@ function validateWemlModel(m, model) {
30978
31362
  push(href.valueRange, `Unrecognized href format "${href.value}". Expected formats like: ${types}.`, "warning");
30979
31363
  }
30980
31364
  }
31365
+ if (tag === "w-color" && !attrs.has("background") && !attrs.has("foreground")) {
31366
+ push(tagRange, '<w-color> must have either "background" or "foreground".', "error");
31367
+ }
30981
31368
  if (tag === "div") {
30982
31369
  const id = stripQuotes2(node.attributes?.["id"]);
30983
31370
  const range = attrs.get("id")?.valueRange ?? tagRange;
@@ -30991,7 +31378,8 @@ function validateWemlModel(m, model) {
30991
31378
  function validateConstraint(constraint, info) {
30992
31379
  if (info.value === void 0 || !info.valueRange) return;
30993
31380
  if (constraint.kind === "pattern") {
30994
- if (info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
31381
+ const emptyIsInvalid = constraint.allowEmpty === false && info.value === "";
31382
+ if (emptyIsInvalid || info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
30995
31383
  push(info.valueRange, constraint.message, "error");
30996
31384
  }
30997
31385
  return;
@@ -31370,7 +31758,7 @@ function escapeSnippet(text) {
31370
31758
  return text.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/}/g, "\\}");
31371
31759
  }
31372
31760
  function buildWrapSnippet(tag, type, selection) {
31373
- const attrs = type ? ` type="${type}"` : tag === "a" ? ' href="$1"' : (TAGS[tag]?.attributes ?? []).filter((a) => a.required).map((a) => ` ${a.name}="$1"`).join("");
31761
+ const attrs = type ? ` type="${type}"` : tag === "a" ? ' href="$1"' : tag === "w-color" ? ' foreground="$1"' : (TAGS[tag]?.attributes ?? []).filter((a) => a.required).map((a) => ` ${a.name}="$1"`).join("");
31374
31762
  const inner = selection.length > 0 ? escapeSnippet(selection) : attrs.includes("$1") ? "$2" : "$1";
31375
31763
  return `<${tag}${attrs}>${inner}</${tag}>`;
31376
31764
  }
@@ -31523,7 +31911,7 @@ var WRAP_OPTIONS = [
31523
31911
  tag: "w-format",
31524
31912
  type: v.value
31525
31913
  })),
31526
- ...["w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31914
+ ...["w-color", "w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31527
31915
  ];
31528
31916
  var SNIPPET_BUTTONS = [
31529
31917
  { label: "w-para", id: "w-para" },