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.mjs CHANGED
@@ -6111,21 +6111,47 @@ var require_wemlContentNormalizeV2 = __commonJS({
6111
6111
  "a",
6112
6112
  "w-anchor",
6113
6113
  "w-sent",
6114
- "w-page"
6114
+ "w-page",
6115
+ "w-color"
6115
6116
  ]);
6116
- var VOID_HTML_ELEMENTS = /* @__PURE__ */ new Set(["meta", "br", "hr", "img", "link"]);
6117
- var VOID_TAG_RE = /<\s*(meta|br|hr|img|link)\b([^<>]*?)(?:\/\s*)?>/gi;
6118
- var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g;
6117
+ var VOID_HTML_ELEMENTS = /* @__PURE__ */ new Set([
6118
+ "area",
6119
+ "base",
6120
+ "br",
6121
+ "col",
6122
+ "embed",
6123
+ "hr",
6124
+ "img",
6125
+ "input",
6126
+ "link",
6127
+ "meta",
6128
+ "source",
6129
+ "track",
6130
+ "wbr",
6131
+ "param"
6132
+ ]);
6133
+ var EMPTY_TEXT_WRAPPERS = /* @__PURE__ */ new Set([
6134
+ "w-format",
6135
+ "w-lang",
6136
+ "w-color",
6137
+ "w-non-egw",
6138
+ "w-sent"
6139
+ ]);
6140
+ var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
6119
6141
  var PH_OPEN = "\uE000";
6120
6142
  var PH_CLOSE = "\uE001";
6121
- var PH_RE = /\uE000([0-9]+)\uE001/g;
6122
- var VOID_ELEMENTS = /* @__PURE__ */ new Set(["meta", "br", "hr", "img", "link"]);
6143
+ var VOID_ELEMENTS = VOID_HTML_ELEMENTS;
6144
+ var SPACE_SEPARATED_ATTRIBUTES = /* @__PURE__ */ new Set(["class", "headers", "rel", "rev"]);
6123
6145
  var WHITE_SPACE = /* @__PURE__ */ new Set([
6124
6146
  9,
6125
6147
  10,
6126
6148
  11,
6127
6149
  12,
6128
6150
  13,
6151
+ 28,
6152
+ 29,
6153
+ 30,
6154
+ 31,
6129
6155
  32,
6130
6156
  133,
6131
6157
  160,
@@ -6145,40 +6171,193 @@ var require_wemlContentNormalizeV2 = __commonJS({
6145
6171
  8233,
6146
6172
  8239,
6147
6173
  8287,
6148
- 12288
6174
+ 12288,
6175
+ 65279
6149
6176
  ]);
6150
6177
  function formatWeml(input) {
6151
- const { html, entities: entities2 } = protectEntities(input);
6178
+ const { html, entities: entities2, marker } = protectEntities(input);
6152
6179
  const doc = parse2(normalizeVoidTagSyntax(html));
6180
+ removeEmptyWrappers(doc);
6181
+ const title = doc.querySelector("head title");
6182
+ if (title) processInlineContent(title, true);
6153
6183
  const body = doc.querySelector("body");
6154
6184
  if (body) processBlockElement(body);
6155
- return restoreEntities(serializeDocument(doc), entities2);
6185
+ return restoreEntities(serializeDocument(doc, entities2, marker), entities2, marker);
6156
6186
  }
6157
6187
  function protectEntities(input) {
6188
+ let markerLength = 1;
6189
+ let marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6190
+ while (input.includes(marker)) {
6191
+ markerLength += 1;
6192
+ marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6193
+ }
6158
6194
  const entities2 = [];
6159
6195
  const html = input.replace(ENTITY_RE2, (match) => {
6160
6196
  const index = entities2.length;
6161
6197
  entities2.push(match);
6162
- return `${PH_OPEN}${index}${PH_CLOSE}`;
6198
+ return `${marker}${index}${marker}`;
6163
6199
  });
6164
- return { html, entities: entities2 };
6165
- }
6166
- function restoreEntities(text, entities2) {
6167
- return text.replace(PH_RE, (_, index) => entities2[Number(index)]);
6200
+ return { html, entities: entities2, marker };
6201
+ }
6202
+ function restoreEntities(text, entities2, marker) {
6203
+ const result = [];
6204
+ let position = 0;
6205
+ while (position < text.length) {
6206
+ const open = text.indexOf(marker, position);
6207
+ if (open < 0) {
6208
+ result.push(text.slice(position));
6209
+ break;
6210
+ }
6211
+ result.push(text.slice(position, open));
6212
+ const indexStart = open + marker.length;
6213
+ const close = text.indexOf(marker, indexStart);
6214
+ const indexText = close >= 0 ? text.slice(indexStart, close) : "";
6215
+ const entityIndex = Number(indexText);
6216
+ if (close < 0 || !/^[0-9]+$/.test(indexText) || !Number.isSafeInteger(entityIndex) || entityIndex >= entities2.length) {
6217
+ throw new Error("Invalid internal entity placeholder.");
6218
+ }
6219
+ result.push(entities2[entityIndex]);
6220
+ position = close + marker.length;
6221
+ }
6222
+ return result.join("");
6168
6223
  }
6169
6224
  function normalizeVoidTagSyntax(input) {
6170
- return input.replace(VOID_TAG_RE, (_, name, attrs) => `<${name.toLowerCase()}${attrs.trimEnd()} />`);
6225
+ function slashBelongsToUnquotedValue(attrs) {
6226
+ let index2 = 0;
6227
+ const whitespace = /* @__PURE__ */ new Set([" ", " ", "\n", "\f", "\r", "\v"]);
6228
+ while (index2 < attrs.length) {
6229
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6230
+ if (index2 >= attrs.length || attrs[index2] === "/") return false;
6231
+ while (index2 < attrs.length && !whitespace.has(attrs[index2]) && !["=", "/"].includes(attrs[index2])) index2 += 1;
6232
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6233
+ if (index2 >= attrs.length || attrs[index2] !== "=") continue;
6234
+ index2 += 1;
6235
+ while (index2 < attrs.length && whitespace.has(attrs[index2])) index2 += 1;
6236
+ if (index2 >= attrs.length) return false;
6237
+ if (attrs[index2] === '"' || attrs[index2] === "'") {
6238
+ const quote = attrs[index2];
6239
+ index2 += 1;
6240
+ while (index2 < attrs.length && attrs[index2] !== quote) index2 += 1;
6241
+ if (index2 < attrs.length) index2 += 1;
6242
+ continue;
6243
+ }
6244
+ const valueStart = index2;
6245
+ while (index2 < attrs.length && !whitespace.has(attrs[index2])) index2 += 1;
6246
+ if (index2 === attrs.length) {
6247
+ return valueStart < attrs.length && attrs.endsWith("/");
6248
+ }
6249
+ }
6250
+ return false;
6251
+ }
6252
+ const result = [];
6253
+ let index = 0;
6254
+ while (index < input.length) {
6255
+ if (input[index] !== "<") {
6256
+ result.push(input[index++]);
6257
+ continue;
6258
+ }
6259
+ const start = index;
6260
+ if (input.startsWith("<!--", index) || input.startsWith("<?", index)) {
6261
+ const delimiter = input.startsWith("<!--", index) ? "-->" : "?>";
6262
+ let end2 = input.indexOf(delimiter, index + 2);
6263
+ end2 = end2 < 0 ? input.length : end2 + delimiter.length;
6264
+ result.push(input.slice(start, end2));
6265
+ index = end2;
6266
+ continue;
6267
+ }
6268
+ const { end } = findTagEnd(input, index + 1);
6269
+ if (end < 0) {
6270
+ result.push(input.slice(index));
6271
+ break;
6272
+ }
6273
+ let cursor = index + 1;
6274
+ if (input[cursor] === "!") {
6275
+ result.push(input.slice(start, end + 1));
6276
+ index = end + 1;
6277
+ continue;
6278
+ }
6279
+ const closing = input[cursor] === "/";
6280
+ if (closing) cursor++;
6281
+ if (cursor >= end || !isNameStart(input[cursor])) {
6282
+ result.push(input[index++]);
6283
+ continue;
6284
+ }
6285
+ const nameStart = cursor++;
6286
+ while (cursor < end && isNameChar(input[cursor])) cursor++;
6287
+ const name = input.slice(nameStart, cursor);
6288
+ const attributes = [];
6289
+ let quote = null;
6290
+ let inSpace = false;
6291
+ for (let offset = cursor; offset < end; offset++) {
6292
+ const character = input[offset];
6293
+ if (quote !== null) {
6294
+ attributes.push(character);
6295
+ if (character === quote) quote = null;
6296
+ continue;
6297
+ }
6298
+ if (isSourceSpace(character)) {
6299
+ if (!inSpace) attributes.push(" ");
6300
+ inSpace = true;
6301
+ } else {
6302
+ inSpace = false;
6303
+ attributes.push(character);
6304
+ if (character === '"' || character === "'") quote = character;
6305
+ }
6306
+ }
6307
+ let attrs = attributes.join("").replace(/ +$/, "");
6308
+ if (!closing && VOID_HTML_ELEMENTS.has(name.toLowerCase())) {
6309
+ if (attrs.endsWith("/") && !slashBelongsToUnquotedValue(attrs)) {
6310
+ attrs = attrs.slice(0, -1).replace(/ +$/, "");
6311
+ }
6312
+ result.push(`<${name.toLowerCase()}${attrs} />`);
6313
+ } else {
6314
+ result.push(input.slice(start, cursor), attrs, ">");
6315
+ }
6316
+ index = end + 1;
6317
+ }
6318
+ return result.join("");
6171
6319
  }
6172
- function processBlockElement(node) {
6320
+ function removeEmptyWrappers(node) {
6321
+ for (const child of [...node.childNodes]) {
6322
+ if (child.nodeType === 8) {
6323
+ node.removeChild(child);
6324
+ continue;
6325
+ }
6326
+ if (child.nodeType !== 1) continue;
6327
+ removeEmptyWrappers(child);
6328
+ const hasId = Object.keys(child.rawAttributes).some((name) => name.toLowerCase() === "id");
6329
+ if (EMPTY_TEXT_WRAPPERS.has(child.rawTagName.toLowerCase()) && !hasId && child.childNodes.every((item) => item.nodeType === 3 && isWhitespaceOnly(item.rawText))) {
6330
+ const replacement = new TextNode(child.childNodes.some((item) => item.rawText.length > 0) ? " " : "", node);
6331
+ node.exchangeChild(child, replacement);
6332
+ }
6333
+ }
6334
+ let previous = null;
6173
6335
  for (const child of [...node.childNodes]) {
6174
- if (child.nodeType === 3 && child.rawText.trim() === "") child.remove();
6336
+ if (child.nodeType === 3) {
6337
+ if (previous !== null) {
6338
+ previous.rawText += child.rawText;
6339
+ node.removeChild(child);
6340
+ } else previous = child;
6341
+ } else previous = null;
6175
6342
  }
6343
+ }
6344
+ function processBlockElement(node) {
6345
+ node.childNodes = node.childNodes.filter((child) => {
6346
+ if (child.nodeType !== 3) return true;
6347
+ const normalized = trimWhitespace(child.rawText);
6348
+ if (normalized) {
6349
+ child.rawText = normalized;
6350
+ return true;
6351
+ }
6352
+ child.parentNode = null;
6353
+ return false;
6354
+ });
6176
6355
  for (const child of [...node.childNodes]) {
6177
6356
  if (child.nodeType !== 1) continue;
6178
6357
  const name = child.rawTagName.toLowerCase();
6179
6358
  if (INLINE_CONTAINERS.has(name)) processInlineContainer(child);
6180
6359
  else if (BLOCK_ELEMENTS.has(name)) processBlockElement(child);
6181
- else if (INLINE_ELEMENTS.has(name)) processInlineContent(child, true);
6360
+ else if (isInlineTextElement(child)) processInlineContent(child, true);
6182
6361
  }
6183
6362
  }
6184
6363
  function processInlineContainer(node) {
@@ -6189,7 +6368,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6189
6368
  for (const child of [...node.childNodes]) {
6190
6369
  if (child.nodeType !== 1) continue;
6191
6370
  const name = child.rawTagName.toLowerCase();
6192
- if (INLINE_ELEMENTS.has(name)) {
6371
+ if (isInlineTextElement(child)) {
6193
6372
  if (hasDirectSentChildren(child)) processSentContainer(child);
6194
6373
  else processInlineContent(child, false);
6195
6374
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6199,19 +6378,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6199
6378
  }
6200
6379
  }
6201
6380
  for (const child of [...node.childNodes]) {
6202
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6381
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6203
6382
  bleedEdgeSpaces(child);
6204
6383
  }
6205
6384
  }
6206
6385
  for (const child of [...node.childNodes]) {
6207
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6386
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6208
6387
  }
6209
6388
  for (const child of [...node.childNodes]) {
6210
6389
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6211
6390
  const prev = child.previousSibling;
6212
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6391
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6213
6392
  const next = child.nextSibling;
6214
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6393
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6215
6394
  }
6216
6395
  }
6217
6396
  trimContainerEdges(node);
@@ -6225,17 +6404,17 @@ var require_wemlContentNormalizeV2 = __commonJS({
6225
6404
  const first = children[0];
6226
6405
  const last = children[children.length - 1];
6227
6406
  if (children.length === 1) {
6228
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6407
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6229
6408
  } else {
6230
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6231
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6409
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6410
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6232
6411
  }
6233
6412
  }
6234
6413
  function processInlineContent(node, trimEdges) {
6235
6414
  for (const child of [...node.childNodes]) {
6236
6415
  if (child.nodeType !== 1) continue;
6237
6416
  const name = child.rawTagName.toLowerCase();
6238
- if (INLINE_ELEMENTS.has(name)) {
6417
+ if (isInlineTextElement(child)) {
6239
6418
  if (hasDirectSentChildren(child)) processSentContainer(child);
6240
6419
  else processInlineContent(child, false);
6241
6420
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6245,19 +6424,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6245
6424
  }
6246
6425
  }
6247
6426
  for (const child of [...node.childNodes]) {
6248
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6427
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6249
6428
  bleedEdgeSpaces(child);
6250
6429
  }
6251
6430
  }
6252
6431
  for (const child of [...node.childNodes]) {
6253
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6432
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6254
6433
  }
6255
6434
  for (const child of [...node.childNodes]) {
6256
6435
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6257
6436
  const prev = child.previousSibling;
6258
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6437
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6259
6438
  const next = child.nextSibling;
6260
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6439
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6261
6440
  }
6262
6441
  }
6263
6442
  if (trimEdges) trimEdgeTextNodes(node);
@@ -6295,7 +6474,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6295
6474
  const first = getFirstTextNode(node);
6296
6475
  if (!first) return "";
6297
6476
  const raw = first.rawText;
6298
- const trimmed = raw.replace(/^\s+/, "");
6477
+ const trimmed = trimStartWhitespace(raw);
6299
6478
  if (trimmed === raw) return "";
6300
6479
  first.rawText = trimmed;
6301
6480
  return " ";
@@ -6304,7 +6483,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6304
6483
  const last = getLastTextNode(node);
6305
6484
  if (!last) return "";
6306
6485
  const raw = last.rawText;
6307
- const trimmed = raw.replace(/\s+$/, "");
6486
+ const trimmed = trimEndWhitespace(raw);
6308
6487
  if (trimmed === raw) return "";
6309
6488
  last.rawText = trimmed;
6310
6489
  return " ";
@@ -6341,10 +6520,10 @@ var require_wemlContentNormalizeV2 = __commonJS({
6341
6520
  const first = children[0];
6342
6521
  const last = children[children.length - 1];
6343
6522
  if (first === last) {
6344
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6523
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6345
6524
  } else {
6346
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6347
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6525
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6526
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6348
6527
  }
6349
6528
  }
6350
6529
  function hasDirectSentChildren(node) {
@@ -6352,17 +6531,22 @@ var require_wemlContentNormalizeV2 = __commonJS({
6352
6531
  (child) => child.nodeType === 1 && child.rawTagName.toLowerCase() === "w-sent"
6353
6532
  );
6354
6533
  }
6355
- function serializeDocument(doc) {
6534
+ function isInlineTextElement(node) {
6535
+ if (node.nodeType !== 1) return false;
6536
+ const name = node.rawTagName.toLowerCase();
6537
+ return INLINE_ELEMENTS.has(name) || !BLOCK_ELEMENTS.has(name) && !INLINE_CONTAINERS.has(name) && !VOID_HTML_ELEMENTS.has(name);
6538
+ }
6539
+ function serializeDocument(doc, entities2, marker) {
6356
6540
  const parts = ["<!DOCTYPE html>\n"];
6357
6541
  const htmlNode = doc.querySelector("html");
6358
- parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.attrs : {})}>
6542
+ parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.rawAttrs : "", entities2, marker)}>
6359
6543
 
6360
6544
  `);
6361
6545
  const head = doc.querySelector("head");
6362
6546
  parts.push("<head>\n");
6363
6547
  if (head) {
6364
6548
  for (const child of head.childNodes) {
6365
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6549
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6366
6550
  }
6367
6551
  }
6368
6552
  parts.push("</head>\n\n");
@@ -6370,28 +6554,81 @@ var require_wemlContentNormalizeV2 = __commonJS({
6370
6554
  parts.push("<body>\n");
6371
6555
  if (body) {
6372
6556
  for (const child of body.childNodes) {
6373
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6557
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6374
6558
  }
6375
6559
  }
6376
6560
  parts.push("</body>\n</html>");
6377
6561
  return parts.join("");
6378
6562
  }
6379
- function serializeNode(node) {
6563
+ function serializeNode(node, entities2, marker) {
6380
6564
  if (node.nodeType === 3) return node.rawText;
6381
6565
  if (node.nodeType === 8 || node.nodeType !== 1) return "";
6382
6566
  const name = node.rawTagName.toLowerCase();
6383
- const attrs = serializeAttrs(node.attrs);
6567
+ const attrs = serializeAttrs(node.rawAttrs, entities2, marker);
6384
6568
  if (VOID_HTML_ELEMENTS.has(name)) return `<${name}${attrs} />`;
6385
6569
  const isBlock = BLOCK_ELEMENTS.has(name);
6386
6570
  const inner = [];
6387
6571
  for (const child of node.childNodes) {
6388
- if (isBlock && child.nodeType === 3 && child.rawText.trim() === "") continue;
6389
- inner.push(serializeNode(child));
6572
+ if (isBlock && child.nodeType === 3 && isWhitespaceOnly(child.rawText)) continue;
6573
+ inner.push(serializeNode(child, entities2, marker));
6390
6574
  }
6391
6575
  return `<${name}${attrs}>${inner.join("")}</${name}>`;
6392
6576
  }
6393
- function serializeAttrs(attrs) {
6394
- return Object.entries(attrs).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6577
+ function serializeAttrs(rawAttrs, entities2, marker) {
6578
+ const canonical = /* @__PURE__ */ new Map();
6579
+ let index = 0;
6580
+ while (index < rawAttrs.length) {
6581
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6582
+ if (index >= rawAttrs.length || rawAttrs[index] === "/") break;
6583
+ const nameStart = index;
6584
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index]) && !"=/>".includes(rawAttrs[index])) index++;
6585
+ if (index === nameStart) {
6586
+ index++;
6587
+ continue;
6588
+ }
6589
+ const name = rawAttrs.slice(nameStart, index).toLowerCase();
6590
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6591
+ let value = "";
6592
+ if (rawAttrs[index] === "=") {
6593
+ index++;
6594
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6595
+ if (rawAttrs[index] === '"' || rawAttrs[index] === "'") {
6596
+ const quote = rawAttrs[index++];
6597
+ const valueStart = index;
6598
+ while (index < rawAttrs.length && rawAttrs[index] !== quote) index++;
6599
+ value = rawAttrs.slice(valueStart, index);
6600
+ if (index < rawAttrs.length) index++;
6601
+ } else {
6602
+ const valueStart = index;
6603
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index])) index++;
6604
+ value = rawAttrs.slice(valueStart, index);
6605
+ }
6606
+ }
6607
+ canonical.set(
6608
+ name,
6609
+ SPACE_SEPARATED_ATTRIBUTES.has(name) ? normalizeTokenList(value, entities2, marker) : trimWhitespace(value)
6610
+ );
6611
+ }
6612
+ return [...canonical.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6613
+ }
6614
+ function normalizeTokenList(value, entities2, marker) {
6615
+ const normalized = trimWhitespace(value);
6616
+ if (!normalized) return normalized;
6617
+ return normalized.split(" ").sort((left, right) => compareUnicodeScalars(
6618
+ restoreEntities(left, entities2, marker),
6619
+ restoreEntities(right, entities2, marker)
6620
+ )).join(" ");
6621
+ }
6622
+ function compareUnicodeScalars(left, right) {
6623
+ const leftScalars = Array.from(left, (character) => character.codePointAt(0));
6624
+ const rightScalars = Array.from(right, (character) => character.codePointAt(0));
6625
+ const limit = Math.min(leftScalars.length, rightScalars.length);
6626
+ for (let index = 0; index < limit; index++) {
6627
+ if (leftScalars[index] !== rightScalars[index]) {
6628
+ return leftScalars[index] - rightScalars[index];
6629
+ }
6630
+ }
6631
+ return leftScalars.length - rightScalars.length;
6395
6632
  }
6396
6633
  function encodeAttr(value) {
6397
6634
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -6429,6 +6666,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6429
6666
  }
6430
6667
  };
6431
6668
  function wemlContentNormalize2(input, isWemlValid = false) {
6669
+ if (input.startsWith("\uFEFF")) input = input.slice(1);
6432
6670
  const inputText = withoutWhiteSpace(analyze(input, !isWemlValid).text);
6433
6671
  const result = formatWeml(input);
6434
6672
  const outputText = withoutWhiteSpace(analyze(result, true).text);
@@ -6443,6 +6681,9 @@ var require_wemlContentNormalizeV2 = __commonJS({
6443
6681
  if (source[index2] === "\n") lineStarts.push(index2 + 1);
6444
6682
  }
6445
6683
  const stack = [];
6684
+ const rootTagCounts = /* @__PURE__ */ new Map([["html", 0], ["head", 0], ["body", 0]]);
6685
+ let seenHead = false;
6686
+ let seenBody = false;
6446
6687
  const textParts = [];
6447
6688
  let textStart = 0;
6448
6689
  let index = 0;
@@ -6526,6 +6767,26 @@ var require_wemlContentNormalizeV2 = __commonJS({
6526
6767
  if (expected !== name) fail("MISMATCHED_CLOSING_TAG", index, expected, name);
6527
6768
  stack.pop();
6528
6769
  } else {
6770
+ if (rootTagCounts.has(name)) {
6771
+ if (rootTagCounts.get(name) > 0) {
6772
+ fail("DUPLICATE_ROOT_TAG", index, null, name);
6773
+ }
6774
+ const parent = stack.length > 0 ? stack.at(-1).name : null;
6775
+ if (name === "html") {
6776
+ if (parent !== null) fail("INVALID_ROOT_TAG_PARENT", index, null, name);
6777
+ } else if (parent !== "html") {
6778
+ fail("INVALID_ROOT_TAG_PARENT", index, "html", name);
6779
+ }
6780
+ if (name === "body" && !seenHead) {
6781
+ fail("BODY_BEFORE_HEAD", index, "head", "body");
6782
+ }
6783
+ if (name === "head" && seenBody) {
6784
+ fail("HEAD_AFTER_BODY", index, "body", "head");
6785
+ }
6786
+ rootTagCounts.set(name, rootTagCounts.get(name) + 1);
6787
+ seenHead || (seenHead = name === "head");
6788
+ seenBody || (seenBody = name === "body");
6789
+ }
6529
6790
  let last = found.end - 1;
6530
6791
  while (last >= cursor && isSourceSpace(source[last])) last--;
6531
6792
  const selfClosing = last >= cursor && source[last] === "/";
@@ -6547,6 +6808,11 @@ var require_wemlContentNormalizeV2 = __commonJS({
6547
6808
  null
6548
6809
  );
6549
6810
  }
6811
+ if (validateStructure) {
6812
+ for (const name of ["html", "head", "body"]) {
6813
+ if (rootTagCounts.get(name) === 0) fail("MISSING_ROOT_TAG", 0, name, null);
6814
+ }
6815
+ }
6550
6816
  return { text: textParts.join("") };
6551
6817
  }
6552
6818
  function findTagEnd(source, start) {
@@ -6570,11 +6836,40 @@ var require_wemlContentNormalizeV2 = __commonJS({
6570
6836
  return isNameStart(character) || character >= "0" && character <= "9" || character === "-" || character === "_" || character === ":" || character === ".";
6571
6837
  }
6572
6838
  function isSourceSpace(character) {
6573
- return character === " " || character === " " || character === "\n" || character === "\r" || character === "\f" || character === "\v";
6839
+ return isNormalizableWhitespace(character);
6574
6840
  }
6575
6841
  function withoutWhiteSpace(text) {
6576
6842
  return Array.from(text, (character) => WHITE_SPACE.has(character.codePointAt(0)) ? "" : character).join("");
6577
6843
  }
6844
+ function isNormalizableWhitespace(character) {
6845
+ return character !== void 0 && WHITE_SPACE.has(character.codePointAt(0));
6846
+ }
6847
+ function collapseWhitespace(text) {
6848
+ const result = [];
6849
+ let inWhitespace = false;
6850
+ for (const character of text) {
6851
+ if (isNormalizableWhitespace(character)) {
6852
+ if (!inWhitespace) result.push(" ");
6853
+ inWhitespace = true;
6854
+ } else {
6855
+ result.push(character);
6856
+ inWhitespace = false;
6857
+ }
6858
+ }
6859
+ return result.join("");
6860
+ }
6861
+ function trimStartWhitespace(text) {
6862
+ return collapseWhitespace(text).replace(/^ +/, "");
6863
+ }
6864
+ function trimEndWhitespace(text) {
6865
+ return collapseWhitespace(text).replace(/ +$/, "");
6866
+ }
6867
+ function trimWhitespace(text) {
6868
+ return collapseWhitespace(text).replace(/^ +| +$/g, "");
6869
+ }
6870
+ function isWhitespaceOnly(text) {
6871
+ return Array.from(text).every(isNormalizableWhitespace);
6872
+ }
6578
6873
  function sha256(text) {
6579
6874
  const crypto = __require("crypto");
6580
6875
  return crypto.createHash("sha256").update(text, "utf8").digest("hex");
@@ -6594,6 +6889,16 @@ var require_wemlContentNormalizeV2 = __commonJS({
6594
6889
  detail = `unexpected closing tag </${actualTag}>.`;
6595
6890
  } else if (code === "UNCLOSED_TAG") {
6596
6891
  detail = `tag <${expectedTag}> is not closed.`;
6892
+ } else if (code === "DUPLICATE_ROOT_TAG") {
6893
+ detail = `document contains more than one <${actualTag}> tag.`;
6894
+ } else if (code === "INVALID_ROOT_TAG_PARENT") {
6895
+ detail = expectedTag ? `tag <${actualTag}> must be a direct child of <${expectedTag}>.` : `root tag <${actualTag}> must not be nested.`;
6896
+ } else if (code === "BODY_BEFORE_HEAD") {
6897
+ detail = "tag <body> appears before <head>.";
6898
+ } else if (code === "HEAD_AFTER_BODY") {
6899
+ detail = "tag <head> appears after <body>.";
6900
+ } else if (code === "MISSING_ROOT_TAG") {
6901
+ detail = `required root tag <${expectedTag}> is missing.`;
6597
6902
  } else if (code === "UNTERMINATED_COMMENT") {
6598
6903
  detail = "comment is not terminated.";
6599
6904
  } else if (code === "UNTERMINATED_DECLARATION") {
@@ -29245,6 +29550,13 @@ var TAGS = {
29245
29550
  "default": "paragraph",
29246
29551
  "description": "Type of the paragraph. See corresponding page for values",
29247
29552
  "enumRef": "paragraph-type"
29553
+ },
29554
+ {
29555
+ "name": "align",
29556
+ "required": false,
29557
+ "default": "`left` for LTR, `right` for RTL",
29558
+ "description": "Horizontal alignment. See corresponding page for allowed values",
29559
+ "enumRef": "horizontal-alignment"
29248
29560
  }
29249
29561
  ],
29250
29562
  "notes": "",
@@ -29257,6 +29569,24 @@ var TAGS = {
29257
29569
  "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.",
29258
29570
  "source": "inlines/br.md"
29259
29571
  },
29572
+ "w-color": {
29573
+ "tag": "w-color",
29574
+ "description": "Represents an inline span with a foreground color, a background color, or both.",
29575
+ "attributes": [
29576
+ {
29577
+ "name": "background",
29578
+ "required": false,
29579
+ "description": "Background color. See Colors."
29580
+ },
29581
+ {
29582
+ "name": "foreground",
29583
+ "required": false,
29584
+ "description": "Foreground color. See Colors."
29585
+ }
29586
+ ],
29587
+ "notes": "Any inline elements.",
29588
+ "source": "inlines/w-color.md"
29589
+ },
29260
29590
  "w-entity": {
29261
29591
  "tag": "w-entity",
29262
29592
  "description": "An entity is a reference to a person, place, or thing. Entities are used to",
@@ -29389,6 +29719,16 @@ var TAGS = {
29389
29719
  "name": "override-chapter-number",
29390
29720
  "required": false,
29391
29721
  "description": "Chapter number for manual set"
29722
+ },
29723
+ {
29724
+ "name": "background",
29725
+ "required": false,
29726
+ "description": "Background color. See Colors"
29727
+ },
29728
+ {
29729
+ "name": "foreground",
29730
+ "required": false,
29731
+ "description": "Foreground color. See Colors"
29392
29732
  }
29393
29733
  ],
29394
29734
  "notes": "Must contain a single text block element.",
@@ -29419,6 +29759,16 @@ var TAGS = {
29419
29759
  "required": false,
29420
29760
  "description": "Horizontal alignment. See corresponding page for allowed values. Defaults to `left` for left-to-right languages and `right` to right-to-left",
29421
29761
  "enumRef": "horizontal-alignment"
29762
+ },
29763
+ {
29764
+ "name": "background",
29765
+ "required": false,
29766
+ "description": "Background color. See Colors"
29767
+ },
29768
+ {
29769
+ "name": "foreground",
29770
+ "required": false,
29771
+ "description": "Foreground color. See Colors"
29422
29772
  }
29423
29773
  ],
29424
29774
  "notes": "Must contain a single block element.",
@@ -29775,8 +30125,12 @@ var META_FIELDS = [
29775
30125
  "description": "Publication description."
29776
30126
  },
29777
30127
  {
29778
- "name": "parent_id",
29779
- "description": "Parent publication version GUID."
30128
+ "name": "purchase-link",
30129
+ "description": "Purchase link."
30130
+ },
30131
+ {
30132
+ "name": "version-id",
30133
+ "description": "Publication version GUID."
29780
30134
  }
29781
30135
  ];
29782
30136
 
@@ -30033,7 +30387,7 @@ Unicode: \`U+${resolved.codePoint.toString(16).toUpperCase().padStart(4, "0")}\`
30033
30387
  // src/schema/structureRules.ts
30034
30388
  var CONTAINER_TAGS = ["w-para", "w-heading", "w-para-group", "w-toc"];
30035
30389
  var BLOCK_TAGS = ["w-text-block", "figure", "w-list", "table", "hr"];
30036
- var INLINE_TAGS = ["a", "br", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30390
+ var INLINE_TAGS = ["a", "br", "w-color", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30037
30391
  var STRUCTURE_RULES = {
30038
30392
  html: {
30039
30393
  children: [
@@ -30180,6 +30534,13 @@ var STRUCTURE_RULES = {
30180
30534
  allowText: true,
30181
30535
  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."
30182
30536
  },
30537
+ "w-color": {
30538
+ children: [
30539
+ { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
30540
+ ],
30541
+ allowText: true,
30542
+ description: "May contain inline elements and text; requires a foreground or background color."
30543
+ },
30183
30544
  "w-entity": {
30184
30545
  children: [
30185
30546
  { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
@@ -30253,11 +30614,18 @@ var STRUCTURE_RULES = {
30253
30614
  }
30254
30615
  };
30255
30616
  var ARABIC_OR_ROMAN = /^(?:\d+|[ivxlcdm]+)$/i;
30617
+ var COLOR_VALUE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
30256
30618
  var ATTRIBUTE_CONSTRAINTS = [
30257
30619
  { tag: "w-heading", attribute: "level", kind: "integer-range", min: 1, max: 6, message: "`level` must be an integer between 1 and 6." },
30258
30620
  { tag: "w-heading", attribute: "override-chapter-number", kind: "integer-min", min: 0, message: "`override-chapter-number` must be a non-negative integer." },
30621
+ { 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." },
30622
+ { 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." },
30259
30623
  { tag: "w-page", attribute: "number", kind: "pattern", pattern: ARABIC_OR_ROMAN, message: "`number` must be an arabic or roman number." },
30260
30624
  { tag: "w-para", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30625
+ { 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." },
30626
+ { 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." },
30627
+ { 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." },
30628
+ { 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." },
30261
30629
  { tag: "w-note-para", attribute: "indent", kind: "integer-min", min: 0, message: "`indent` must be an integer >= 0." },
30262
30630
  { tag: "td", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30263
30631
  { tag: "th", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
@@ -30398,6 +30766,9 @@ function indent(text) {
30398
30766
  function buildElement(tag, counter, depth) {
30399
30767
  const schema = TAGS[tag];
30400
30768
  let attrs = "";
30769
+ if (tag === "w-color") {
30770
+ attrs = ` foreground="${placeholder(counter)}"`;
30771
+ }
30401
30772
  for (const attr of schema?.attributes ?? []) {
30402
30773
  if (attr.required) {
30403
30774
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30433,6 +30804,9 @@ function buildElementSnippet(tag) {
30433
30804
  function buildOpeningTag(tag) {
30434
30805
  const counter = { n: 1 };
30435
30806
  let attrs = "";
30807
+ if (tag === "w-color") {
30808
+ attrs = ` foreground="${placeholder(counter)}"`;
30809
+ }
30436
30810
  for (const attr of TAGS[tag]?.attributes ?? []) {
30437
30811
  if (attr.required) {
30438
30812
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30540,24 +30914,22 @@ function getCompletionContext(text, offset, nodeStart) {
30540
30914
  switch (token) {
30541
30915
  case TokenType.StartTagOpen:
30542
30916
  if (scanner.getTokenEnd() === offset) {
30543
- const end = scanNextEnd(scanner, offset, TokenType.StartTag);
30544
- return { kind: "tag", start: offset - 1, end, includesBracket: true };
30917
+ return { kind: "tag", start: offset - 1, end: offset, includesBracket: true };
30545
30918
  }
30546
30919
  break;
30547
30920
  case TokenType.EndTagOpen:
30548
30921
  if (scanner.getTokenEnd() === offset) {
30549
- const end = scanNextEnd(scanner, offset, TokenType.EndTag);
30550
- return { kind: "end-tag", start: offset, end };
30922
+ return { kind: "end-tag", start: offset, end: offset };
30551
30923
  }
30552
30924
  break;
30553
30925
  case TokenType.EndTag:
30554
30926
  if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
30555
- return { kind: "end-tag", start: scanner.getTokenOffset(), end: scanner.getTokenEnd() };
30927
+ return { kind: "end-tag", start: scanner.getTokenOffset(), end: offset };
30556
30928
  }
30557
30929
  break;
30558
30930
  case TokenType.StartTag:
30559
30931
  if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
30560
- return { kind: "tag", start: scanner.getTokenOffset(), end: scanner.getTokenEnd(), includesBracket: false };
30932
+ return { kind: "tag", start: scanner.getTokenOffset(), end: offset, includesBracket: false };
30561
30933
  }
30562
30934
  currentTag = scanner.getTokenText();
30563
30935
  break;
@@ -30595,8 +30967,7 @@ function getCompletionContext(text, offset, nodeStart) {
30595
30967
  switch (scanner.getScannerState()) {
30596
30968
  case ScannerState.AfterOpeningStartTag: {
30597
30969
  const start = scanner.getTokenOffset();
30598
- const end = scanNextEnd(scanner, offset, TokenType.StartTag);
30599
- return { kind: "tag", start, end, includesBracket: false };
30970
+ return { kind: "tag", start, end: offset, includesBracket: false };
30600
30971
  }
30601
30972
  case ScannerState.WithinTag:
30602
30973
  case ScannerState.AfterAttributeName:
@@ -30636,6 +31007,7 @@ function dedupe(items) {
30636
31007
  function hasValueSuggestions(tag, attribute, enumRef) {
30637
31008
  if (enumRef && ENUMS[enumRef]) return true;
30638
31009
  if (tag === "w-heading" && attribute === "level") return true;
31010
+ if (tag === "meta" && attribute === "name") return true;
30639
31011
  if (tag === "meta" && attribute === "content") return true;
30640
31012
  if (tag === "a" && attribute === "href") return true;
30641
31013
  return false;
@@ -30805,6 +31177,18 @@ var WemlCompletionProvider = class {
30805
31177
  });
30806
31178
  }
30807
31179
  }
31180
+ if (ctx.tag === "meta" && ctx.attribute === "name") {
31181
+ for (const field of META_FIELDS) {
31182
+ items.push({
31183
+ label: field.name,
31184
+ kind: this.m.languages.CompletionItemKind.Property,
31185
+ detail: "WEML metadata field",
31186
+ documentation: metaFieldMarkdown(field.name),
31187
+ insertText: wrap(field.name),
31188
+ range
31189
+ });
31190
+ }
31191
+ }
30808
31192
  if (ctx.tag === "meta" && ctx.attribute === "content" && stripQuotes2(node.attributes?.["name"]) === "type") {
30809
31193
  const codeEnum = ENUMS["publication-type"];
30810
31194
  if (codeEnum) {
@@ -30962,6 +31346,9 @@ function validateWemlModel(m, model) {
30962
31346
  push(href.valueRange, `Unrecognized href format "${href.value}". Expected formats like: ${types}.`, "warning");
30963
31347
  }
30964
31348
  }
31349
+ if (tag === "w-color" && !attrs.has("background") && !attrs.has("foreground")) {
31350
+ push(tagRange, '<w-color> must have either "background" or "foreground".', "error");
31351
+ }
30965
31352
  if (tag === "div") {
30966
31353
  const id = stripQuotes2(node.attributes?.["id"]);
30967
31354
  const range = attrs.get("id")?.valueRange ?? tagRange;
@@ -30975,7 +31362,8 @@ function validateWemlModel(m, model) {
30975
31362
  function validateConstraint(constraint, info) {
30976
31363
  if (info.value === void 0 || !info.valueRange) return;
30977
31364
  if (constraint.kind === "pattern") {
30978
- if (info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
31365
+ const emptyIsInvalid = constraint.allowEmpty === false && info.value === "";
31366
+ if (emptyIsInvalid || info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
30979
31367
  push(info.valueRange, constraint.message, "error");
30980
31368
  }
30981
31369
  return;
@@ -31354,7 +31742,7 @@ function escapeSnippet(text) {
31354
31742
  return text.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/}/g, "\\}");
31355
31743
  }
31356
31744
  function buildWrapSnippet(tag, type, selection) {
31357
- const attrs = type ? ` type="${type}"` : tag === "a" ? ' href="$1"' : (TAGS[tag]?.attributes ?? []).filter((a) => a.required).map((a) => ` ${a.name}="$1"`).join("");
31745
+ 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("");
31358
31746
  const inner = selection.length > 0 ? escapeSnippet(selection) : attrs.includes("$1") ? "$2" : "$1";
31359
31747
  return `<${tag}${attrs}>${inner}</${tag}>`;
31360
31748
  }
@@ -31507,7 +31895,7 @@ var WRAP_OPTIONS = [
31507
31895
  tag: "w-format",
31508
31896
  type: v.value
31509
31897
  })),
31510
- ...["w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31898
+ ...["w-color", "w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31511
31899
  ];
31512
31900
  var SNIPPET_BUTTONS = [
31513
31901
  { label: "w-para", id: "w-para" },