weml-monaco 0.4.0 → 0.4.2

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,41 @@ 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 VOID_TAG_RE = /<[ \t\n\f\r]*(area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr|param)(?=[ \t\n\f\r/>])((?:[^<>"']|"[^"]*"|'[^']*')*?)>/gi;
6129
+ var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
6114
6130
  var PH_OPEN = "\uE000";
6115
6131
  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"]);
6132
+ var VOID_ELEMENTS = VOID_HTML_ELEMENTS;
6133
+ var SPACE_SEPARATED_ATTRIBUTES = /* @__PURE__ */ new Set(["class", "headers", "rel", "rev"]);
6118
6134
  var WHITE_SPACE = /* @__PURE__ */ new Set([
6119
6135
  9,
6120
6136
  10,
6121
6137
  11,
6122
6138
  12,
6123
6139
  13,
6140
+ 28,
6141
+ 29,
6142
+ 30,
6143
+ 31,
6124
6144
  32,
6125
6145
  133,
6126
6146
  160,
@@ -6140,40 +6160,104 @@ var require_wemlContentNormalizeV2 = __commonJS({
6140
6160
  8233,
6141
6161
  8239,
6142
6162
  8287,
6143
- 12288
6163
+ 12288,
6164
+ 65279
6144
6165
  ]);
6145
6166
  function formatWeml(input) {
6146
- const { html, entities: entities2 } = protectEntities(input);
6167
+ const { html, entities: entities2, marker } = protectEntities(input);
6147
6168
  const doc = parse2(normalizeVoidTagSyntax(html));
6169
+ const title = doc.querySelector("head title");
6170
+ if (title) processInlineContent(title, true);
6148
6171
  const body = doc.querySelector("body");
6149
6172
  if (body) processBlockElement(body);
6150
- return restoreEntities(serializeDocument(doc), entities2);
6173
+ return restoreEntities(serializeDocument(doc, entities2, marker), entities2, marker);
6151
6174
  }
6152
6175
  function protectEntities(input) {
6176
+ let markerLength = 1;
6177
+ let marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6178
+ while (input.includes(marker)) {
6179
+ markerLength += 1;
6180
+ marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6181
+ }
6153
6182
  const entities2 = [];
6154
6183
  const html = input.replace(ENTITY_RE2, (match) => {
6155
6184
  const index = entities2.length;
6156
6185
  entities2.push(match);
6157
- return `${PH_OPEN}${index}${PH_CLOSE}`;
6186
+ return `${marker}${index}${marker}`;
6158
6187
  });
6159
- return { html, entities: entities2 };
6160
- }
6161
- function restoreEntities(text, entities2) {
6162
- return text.replace(PH_RE, (_, index) => entities2[Number(index)]);
6188
+ return { html, entities: entities2, marker };
6189
+ }
6190
+ function restoreEntities(text, entities2, marker) {
6191
+ const result = [];
6192
+ let position = 0;
6193
+ while (position < text.length) {
6194
+ const open = text.indexOf(marker, position);
6195
+ if (open < 0) {
6196
+ result.push(text.slice(position));
6197
+ break;
6198
+ }
6199
+ result.push(text.slice(position, open));
6200
+ const indexStart = open + marker.length;
6201
+ const close = text.indexOf(marker, indexStart);
6202
+ const indexText = close >= 0 ? text.slice(indexStart, close) : "";
6203
+ const entityIndex = Number(indexText);
6204
+ if (close < 0 || !/^[0-9]+$/.test(indexText) || !Number.isSafeInteger(entityIndex) || entityIndex >= entities2.length) {
6205
+ throw new Error("Invalid internal entity placeholder.");
6206
+ }
6207
+ result.push(entities2[entityIndex]);
6208
+ position = close + marker.length;
6209
+ }
6210
+ return result.join("");
6163
6211
  }
6164
6212
  function normalizeVoidTagSyntax(input) {
6165
- return input.replace(VOID_TAG_RE, (_, name, attrs) => `<${name.toLowerCase()}${attrs.trimEnd()} />`);
6213
+ function slashBelongsToUnquotedValue(attrs) {
6214
+ let index = 0;
6215
+ const whitespace = /* @__PURE__ */ new Set([" ", " ", "\n", "\f", "\r"]);
6216
+ while (index < attrs.length) {
6217
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6218
+ if (index >= attrs.length || attrs[index] === "/") return false;
6219
+ while (index < attrs.length && !whitespace.has(attrs[index]) && !["=", "/"].includes(attrs[index])) index += 1;
6220
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6221
+ if (index >= attrs.length || attrs[index] !== "=") continue;
6222
+ index += 1;
6223
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6224
+ if (index >= attrs.length) return false;
6225
+ if (attrs[index] === '"' || attrs[index] === "'") {
6226
+ const quote = attrs[index];
6227
+ index += 1;
6228
+ while (index < attrs.length && attrs[index] !== quote) index += 1;
6229
+ if (index < attrs.length) index += 1;
6230
+ continue;
6231
+ }
6232
+ const valueStart = index;
6233
+ while (index < attrs.length && !whitespace.has(attrs[index])) index += 1;
6234
+ if (index === attrs.length) {
6235
+ return valueStart < attrs.length && attrs.endsWith("/");
6236
+ }
6237
+ }
6238
+ return false;
6239
+ }
6240
+ return input.replace(VOID_TAG_RE, (_, name, rawAttrs) => {
6241
+ let attrs = trimRawEndWhitespace(rawAttrs);
6242
+ if (attrs.endsWith("/") && !slashBelongsToUnquotedValue(attrs)) {
6243
+ attrs = trimRawEndWhitespace(attrs.slice(0, -1));
6244
+ }
6245
+ return `<${name.toLowerCase()}${attrs} />`;
6246
+ });
6166
6247
  }
6167
6248
  function processBlockElement(node) {
6168
6249
  for (const child of [...node.childNodes]) {
6169
- if (child.nodeType === 3 && child.rawText.trim() === "") child.remove();
6250
+ if (child.nodeType !== 3) continue;
6251
+ const normalized = trimWhitespace(child.rawText);
6252
+ if (normalized) child.rawText = normalized;
6253
+ else child.remove();
6170
6254
  }
6171
6255
  for (const child of [...node.childNodes]) {
6172
6256
  if (child.nodeType !== 1) continue;
6173
6257
  const name = child.rawTagName.toLowerCase();
6174
6258
  if (INLINE_CONTAINERS.has(name)) processInlineContainer(child);
6175
6259
  else if (BLOCK_ELEMENTS.has(name)) processBlockElement(child);
6176
- else if (INLINE_ELEMENTS.has(name)) processInlineContent(child, true);
6260
+ else if (isInlineTextElement(child)) processInlineContent(child, true);
6177
6261
  }
6178
6262
  }
6179
6263
  function processInlineContainer(node) {
@@ -6184,7 +6268,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6184
6268
  for (const child of [...node.childNodes]) {
6185
6269
  if (child.nodeType !== 1) continue;
6186
6270
  const name = child.rawTagName.toLowerCase();
6187
- if (INLINE_ELEMENTS.has(name)) {
6271
+ if (isInlineTextElement(child)) {
6188
6272
  if (hasDirectSentChildren(child)) processSentContainer(child);
6189
6273
  else processInlineContent(child, false);
6190
6274
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6194,19 +6278,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6194
6278
  }
6195
6279
  }
6196
6280
  for (const child of [...node.childNodes]) {
6197
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6281
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6198
6282
  bleedEdgeSpaces(child);
6199
6283
  }
6200
6284
  }
6201
6285
  for (const child of [...node.childNodes]) {
6202
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6286
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6203
6287
  }
6204
6288
  for (const child of [...node.childNodes]) {
6205
6289
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6206
6290
  const prev = child.previousSibling;
6207
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6291
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6208
6292
  const next = child.nextSibling;
6209
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6293
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6210
6294
  }
6211
6295
  }
6212
6296
  trimContainerEdges(node);
@@ -6220,17 +6304,17 @@ var require_wemlContentNormalizeV2 = __commonJS({
6220
6304
  const first = children[0];
6221
6305
  const last = children[children.length - 1];
6222
6306
  if (children.length === 1) {
6223
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6307
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6224
6308
  } else {
6225
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6226
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6309
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6310
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6227
6311
  }
6228
6312
  }
6229
6313
  function processInlineContent(node, trimEdges) {
6230
6314
  for (const child of [...node.childNodes]) {
6231
6315
  if (child.nodeType !== 1) continue;
6232
6316
  const name = child.rawTagName.toLowerCase();
6233
- if (INLINE_ELEMENTS.has(name)) {
6317
+ if (isInlineTextElement(child)) {
6234
6318
  if (hasDirectSentChildren(child)) processSentContainer(child);
6235
6319
  else processInlineContent(child, false);
6236
6320
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6240,19 +6324,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6240
6324
  }
6241
6325
  }
6242
6326
  for (const child of [...node.childNodes]) {
6243
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6327
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6244
6328
  bleedEdgeSpaces(child);
6245
6329
  }
6246
6330
  }
6247
6331
  for (const child of [...node.childNodes]) {
6248
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6332
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6249
6333
  }
6250
6334
  for (const child of [...node.childNodes]) {
6251
6335
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6252
6336
  const prev = child.previousSibling;
6253
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6337
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6254
6338
  const next = child.nextSibling;
6255
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6339
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6256
6340
  }
6257
6341
  }
6258
6342
  if (trimEdges) trimEdgeTextNodes(node);
@@ -6290,7 +6374,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6290
6374
  const first = getFirstTextNode(node);
6291
6375
  if (!first) return "";
6292
6376
  const raw = first.rawText;
6293
- const trimmed = raw.replace(/^\s+/, "");
6377
+ const trimmed = trimStartWhitespace(raw);
6294
6378
  if (trimmed === raw) return "";
6295
6379
  first.rawText = trimmed;
6296
6380
  return " ";
@@ -6299,7 +6383,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6299
6383
  const last = getLastTextNode(node);
6300
6384
  if (!last) return "";
6301
6385
  const raw = last.rawText;
6302
- const trimmed = raw.replace(/\s+$/, "");
6386
+ const trimmed = trimEndWhitespace(raw);
6303
6387
  if (trimmed === raw) return "";
6304
6388
  last.rawText = trimmed;
6305
6389
  return " ";
@@ -6336,10 +6420,10 @@ var require_wemlContentNormalizeV2 = __commonJS({
6336
6420
  const first = children[0];
6337
6421
  const last = children[children.length - 1];
6338
6422
  if (first === last) {
6339
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6423
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6340
6424
  } else {
6341
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6342
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6425
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6426
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6343
6427
  }
6344
6428
  }
6345
6429
  function hasDirectSentChildren(node) {
@@ -6347,17 +6431,22 @@ var require_wemlContentNormalizeV2 = __commonJS({
6347
6431
  (child) => child.nodeType === 1 && child.rawTagName.toLowerCase() === "w-sent"
6348
6432
  );
6349
6433
  }
6350
- function serializeDocument(doc) {
6434
+ function isInlineTextElement(node) {
6435
+ if (node.nodeType !== 1) return false;
6436
+ const name = node.rawTagName.toLowerCase();
6437
+ return INLINE_ELEMENTS.has(name) || !BLOCK_ELEMENTS.has(name) && !INLINE_CONTAINERS.has(name) && !VOID_HTML_ELEMENTS.has(name);
6438
+ }
6439
+ function serializeDocument(doc, entities2, marker) {
6351
6440
  const parts = ["<!DOCTYPE html>\n"];
6352
6441
  const htmlNode = doc.querySelector("html");
6353
- parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.attrs : {})}>
6442
+ parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.rawAttrs : "", entities2, marker)}>
6354
6443
 
6355
6444
  `);
6356
6445
  const head = doc.querySelector("head");
6357
6446
  parts.push("<head>\n");
6358
6447
  if (head) {
6359
6448
  for (const child of head.childNodes) {
6360
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6449
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6361
6450
  }
6362
6451
  }
6363
6452
  parts.push("</head>\n\n");
@@ -6365,28 +6454,81 @@ var require_wemlContentNormalizeV2 = __commonJS({
6365
6454
  parts.push("<body>\n");
6366
6455
  if (body) {
6367
6456
  for (const child of body.childNodes) {
6368
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6457
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6369
6458
  }
6370
6459
  }
6371
6460
  parts.push("</body>\n</html>");
6372
6461
  return parts.join("");
6373
6462
  }
6374
- function serializeNode(node) {
6463
+ function serializeNode(node, entities2, marker) {
6375
6464
  if (node.nodeType === 3) return node.rawText;
6376
6465
  if (node.nodeType === 8 || node.nodeType !== 1) return "";
6377
6466
  const name = node.rawTagName.toLowerCase();
6378
- const attrs = serializeAttrs(node.attrs);
6467
+ const attrs = serializeAttrs(node.rawAttrs, entities2, marker);
6379
6468
  if (VOID_HTML_ELEMENTS.has(name)) return `<${name}${attrs} />`;
6380
6469
  const isBlock = BLOCK_ELEMENTS.has(name);
6381
6470
  const inner = [];
6382
6471
  for (const child of node.childNodes) {
6383
- if (isBlock && child.nodeType === 3 && child.rawText.trim() === "") continue;
6384
- inner.push(serializeNode(child));
6472
+ if (isBlock && child.nodeType === 3 && isWhitespaceOnly(child.rawText)) continue;
6473
+ inner.push(serializeNode(child, entities2, marker));
6385
6474
  }
6386
6475
  return `<${name}${attrs}>${inner.join("")}</${name}>`;
6387
6476
  }
6388
- function serializeAttrs(attrs) {
6389
- return Object.entries(attrs).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6477
+ function serializeAttrs(rawAttrs, entities2, marker) {
6478
+ const canonical = /* @__PURE__ */ new Map();
6479
+ let index = 0;
6480
+ while (index < rawAttrs.length) {
6481
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6482
+ if (index >= rawAttrs.length || rawAttrs[index] === "/") break;
6483
+ const nameStart = index;
6484
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index]) && !"=/>".includes(rawAttrs[index])) index++;
6485
+ if (index === nameStart) {
6486
+ index++;
6487
+ continue;
6488
+ }
6489
+ const name = rawAttrs.slice(nameStart, index).toLowerCase();
6490
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6491
+ let value = "";
6492
+ if (rawAttrs[index] === "=") {
6493
+ index++;
6494
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6495
+ if (rawAttrs[index] === '"' || rawAttrs[index] === "'") {
6496
+ const quote = rawAttrs[index++];
6497
+ const valueStart = index;
6498
+ while (index < rawAttrs.length && rawAttrs[index] !== quote) index++;
6499
+ value = rawAttrs.slice(valueStart, index);
6500
+ if (index < rawAttrs.length) index++;
6501
+ } else {
6502
+ const valueStart = index;
6503
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index])) index++;
6504
+ value = rawAttrs.slice(valueStart, index);
6505
+ }
6506
+ }
6507
+ canonical.set(
6508
+ name,
6509
+ SPACE_SEPARATED_ATTRIBUTES.has(name) ? normalizeTokenList(value, entities2, marker) : value
6510
+ );
6511
+ }
6512
+ return [...canonical.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6513
+ }
6514
+ function normalizeTokenList(value, entities2, marker) {
6515
+ const normalized = trimWhitespace(value);
6516
+ if (!normalized) return normalized;
6517
+ return normalized.split(" ").sort((left, right) => compareUnicodeScalars(
6518
+ restoreEntities(left, entities2, marker),
6519
+ restoreEntities(right, entities2, marker)
6520
+ )).join(" ");
6521
+ }
6522
+ function compareUnicodeScalars(left, right) {
6523
+ const leftScalars = Array.from(left, (character) => character.codePointAt(0));
6524
+ const rightScalars = Array.from(right, (character) => character.codePointAt(0));
6525
+ const limit = Math.min(leftScalars.length, rightScalars.length);
6526
+ for (let index = 0; index < limit; index++) {
6527
+ if (leftScalars[index] !== rightScalars[index]) {
6528
+ return leftScalars[index] - rightScalars[index];
6529
+ }
6530
+ }
6531
+ return leftScalars.length - rightScalars.length;
6390
6532
  }
6391
6533
  function encodeAttr(value) {
6392
6534
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -6424,6 +6566,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6424
6566
  }
6425
6567
  };
6426
6568
  function wemlContentNormalize2(input, isWemlValid = false) {
6569
+ if (input.startsWith("\uFEFF")) input = input.slice(1);
6427
6570
  const inputText = withoutWhiteSpace(analyze(input, !isWemlValid).text);
6428
6571
  const result = formatWeml(input);
6429
6572
  const outputText = withoutWhiteSpace(analyze(result, true).text);
@@ -6438,6 +6581,9 @@ var require_wemlContentNormalizeV2 = __commonJS({
6438
6581
  if (source[index2] === "\n") lineStarts.push(index2 + 1);
6439
6582
  }
6440
6583
  const stack = [];
6584
+ const rootTagCounts = /* @__PURE__ */ new Map([["html", 0], ["head", 0], ["body", 0]]);
6585
+ let seenHead = false;
6586
+ let seenBody = false;
6441
6587
  const textParts = [];
6442
6588
  let textStart = 0;
6443
6589
  let index = 0;
@@ -6521,6 +6667,26 @@ var require_wemlContentNormalizeV2 = __commonJS({
6521
6667
  if (expected !== name) fail("MISMATCHED_CLOSING_TAG", index, expected, name);
6522
6668
  stack.pop();
6523
6669
  } else {
6670
+ if (rootTagCounts.has(name)) {
6671
+ if (rootTagCounts.get(name) > 0) {
6672
+ fail("DUPLICATE_ROOT_TAG", index, null, name);
6673
+ }
6674
+ const parent = stack.length > 0 ? stack.at(-1).name : null;
6675
+ if (name === "html") {
6676
+ if (parent !== null) fail("INVALID_ROOT_TAG_PARENT", index, null, name);
6677
+ } else if (parent !== "html") {
6678
+ fail("INVALID_ROOT_TAG_PARENT", index, "html", name);
6679
+ }
6680
+ if (name === "body" && !seenHead) {
6681
+ fail("BODY_BEFORE_HEAD", index, "head", "body");
6682
+ }
6683
+ if (name === "head" && seenBody) {
6684
+ fail("HEAD_AFTER_BODY", index, "body", "head");
6685
+ }
6686
+ rootTagCounts.set(name, rootTagCounts.get(name) + 1);
6687
+ seenHead || (seenHead = name === "head");
6688
+ seenBody || (seenBody = name === "body");
6689
+ }
6524
6690
  let last = found.end - 1;
6525
6691
  while (last >= cursor && isSourceSpace(source[last])) last--;
6526
6692
  const selfClosing = last >= cursor && source[last] === "/";
@@ -6542,6 +6708,11 @@ var require_wemlContentNormalizeV2 = __commonJS({
6542
6708
  null
6543
6709
  );
6544
6710
  }
6711
+ if (validateStructure) {
6712
+ for (const name of ["html", "head", "body"]) {
6713
+ if (rootTagCounts.get(name) === 0) fail("MISSING_ROOT_TAG", 0, name, null);
6714
+ }
6715
+ }
6545
6716
  return { text: textParts.join("") };
6546
6717
  }
6547
6718
  function findTagEnd(source, start) {
@@ -6570,6 +6741,42 @@ var require_wemlContentNormalizeV2 = __commonJS({
6570
6741
  function withoutWhiteSpace(text) {
6571
6742
  return Array.from(text, (character) => WHITE_SPACE.has(character.codePointAt(0)) ? "" : character).join("");
6572
6743
  }
6744
+ function isNormalizableWhitespace(character) {
6745
+ return character !== void 0 && WHITE_SPACE.has(character.codePointAt(0));
6746
+ }
6747
+ function collapseWhitespace(text) {
6748
+ const result = [];
6749
+ let inWhitespace = false;
6750
+ for (const character of text) {
6751
+ if (isNormalizableWhitespace(character)) {
6752
+ if (!inWhitespace) result.push(" ");
6753
+ inWhitespace = true;
6754
+ } else {
6755
+ result.push(character);
6756
+ inWhitespace = false;
6757
+ }
6758
+ }
6759
+ return result.join("");
6760
+ }
6761
+ function trimStartWhitespace(text) {
6762
+ return collapseWhitespace(text).replace(/^ +/, "");
6763
+ }
6764
+ function trimEndWhitespace(text) {
6765
+ return collapseWhitespace(text).replace(/ +$/, "");
6766
+ }
6767
+ function trimWhitespace(text) {
6768
+ return collapseWhitespace(text).replace(/^ +| +$/g, "");
6769
+ }
6770
+ function trimRawEndWhitespace(text) {
6771
+ const characters = Array.from(text);
6772
+ while (characters.length > 0 && isNormalizableWhitespace(characters.at(-1))) {
6773
+ characters.pop();
6774
+ }
6775
+ return characters.join("");
6776
+ }
6777
+ function isWhitespaceOnly(text) {
6778
+ return Array.from(text).every(isNormalizableWhitespace);
6779
+ }
6573
6780
  function sha256(text) {
6574
6781
  const crypto = require("crypto");
6575
6782
  return crypto.createHash("sha256").update(text, "utf8").digest("hex");
@@ -6589,6 +6796,16 @@ var require_wemlContentNormalizeV2 = __commonJS({
6589
6796
  detail = `unexpected closing tag </${actualTag}>.`;
6590
6797
  } else if (code === "UNCLOSED_TAG") {
6591
6798
  detail = `tag <${expectedTag}> is not closed.`;
6799
+ } else if (code === "DUPLICATE_ROOT_TAG") {
6800
+ detail = `document contains more than one <${actualTag}> tag.`;
6801
+ } else if (code === "INVALID_ROOT_TAG_PARENT") {
6802
+ detail = expectedTag ? `tag <${actualTag}> must be a direct child of <${expectedTag}>.` : `root tag <${actualTag}> must not be nested.`;
6803
+ } else if (code === "BODY_BEFORE_HEAD") {
6804
+ detail = "tag <body> appears before <head>.";
6805
+ } else if (code === "HEAD_AFTER_BODY") {
6806
+ detail = "tag <head> appears after <body>.";
6807
+ } else if (code === "MISSING_ROOT_TAG") {
6808
+ detail = `required root tag <${expectedTag}> is missing.`;
6592
6809
  } else if (code === "UNTERMINATED_COMMENT") {
6593
6810
  detail = "comment is not terminated.";
6594
6811
  } else if (code === "UNTERMINATED_DECLARATION") {
@@ -29261,6 +29478,13 @@ var TAGS = {
29261
29478
  "default": "paragraph",
29262
29479
  "description": "Type of the paragraph. See corresponding page for values",
29263
29480
  "enumRef": "paragraph-type"
29481
+ },
29482
+ {
29483
+ "name": "align",
29484
+ "required": false,
29485
+ "default": "`left` for LTR, `right` for RTL",
29486
+ "description": "Horizontal alignment. See corresponding page for allowed values",
29487
+ "enumRef": "horizontal-alignment"
29264
29488
  }
29265
29489
  ],
29266
29490
  "notes": "",
@@ -29273,6 +29497,24 @@ var TAGS = {
29273
29497
  "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
29498
  "source": "inlines/br.md"
29275
29499
  },
29500
+ "w-color": {
29501
+ "tag": "w-color",
29502
+ "description": "Represents an inline span with a foreground color, a background color, or both.",
29503
+ "attributes": [
29504
+ {
29505
+ "name": "background",
29506
+ "required": false,
29507
+ "description": "Background color. See Colors."
29508
+ },
29509
+ {
29510
+ "name": "foreground",
29511
+ "required": false,
29512
+ "description": "Foreground color. See Colors."
29513
+ }
29514
+ ],
29515
+ "notes": "Any inline elements.",
29516
+ "source": "inlines/w-color.md"
29517
+ },
29276
29518
  "w-entity": {
29277
29519
  "tag": "w-entity",
29278
29520
  "description": "An entity is a reference to a person, place, or thing. Entities are used to",
@@ -29405,6 +29647,16 @@ var TAGS = {
29405
29647
  "name": "override-chapter-number",
29406
29648
  "required": false,
29407
29649
  "description": "Chapter number for manual set"
29650
+ },
29651
+ {
29652
+ "name": "background",
29653
+ "required": false,
29654
+ "description": "Background color. See Colors"
29655
+ },
29656
+ {
29657
+ "name": "foreground",
29658
+ "required": false,
29659
+ "description": "Foreground color. See Colors"
29408
29660
  }
29409
29661
  ],
29410
29662
  "notes": "Must contain a single text block element.",
@@ -29435,6 +29687,16 @@ var TAGS = {
29435
29687
  "required": false,
29436
29688
  "description": "Horizontal alignment. See corresponding page for allowed values. Defaults to `left` for left-to-right languages and `right` to right-to-left",
29437
29689
  "enumRef": "horizontal-alignment"
29690
+ },
29691
+ {
29692
+ "name": "background",
29693
+ "required": false,
29694
+ "description": "Background color. See Colors"
29695
+ },
29696
+ {
29697
+ "name": "foreground",
29698
+ "required": false,
29699
+ "description": "Foreground color. See Colors"
29438
29700
  }
29439
29701
  ],
29440
29702
  "notes": "Must contain a single block element.",
@@ -29791,8 +30053,12 @@ var META_FIELDS = [
29791
30053
  "description": "Publication description."
29792
30054
  },
29793
30055
  {
29794
- "name": "parent_id",
29795
- "description": "Parent publication version GUID."
30056
+ "name": "purchase-link",
30057
+ "description": "Purchase link."
30058
+ },
30059
+ {
30060
+ "name": "version-id",
30061
+ "description": "Publication version GUID."
29796
30062
  }
29797
30063
  ];
29798
30064
 
@@ -30049,7 +30315,7 @@ Unicode: \`U+${resolved.codePoint.toString(16).toUpperCase().padStart(4, "0")}\`
30049
30315
  // src/schema/structureRules.ts
30050
30316
  var CONTAINER_TAGS = ["w-para", "w-heading", "w-para-group", "w-toc"];
30051
30317
  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"];
30318
+ var INLINE_TAGS = ["a", "br", "w-color", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30053
30319
  var STRUCTURE_RULES = {
30054
30320
  html: {
30055
30321
  children: [
@@ -30196,6 +30462,13 @@ var STRUCTURE_RULES = {
30196
30462
  allowText: true,
30197
30463
  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
30464
  },
30465
+ "w-color": {
30466
+ children: [
30467
+ { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
30468
+ ],
30469
+ allowText: true,
30470
+ description: "May contain inline elements and text; requires a foreground or background color."
30471
+ },
30199
30472
  "w-entity": {
30200
30473
  children: [
30201
30474
  { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
@@ -30269,11 +30542,18 @@ var STRUCTURE_RULES = {
30269
30542
  }
30270
30543
  };
30271
30544
  var ARABIC_OR_ROMAN = /^(?:\d+|[ivxlcdm]+)$/i;
30545
+ var COLOR_VALUE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
30272
30546
  var ATTRIBUTE_CONSTRAINTS = [
30273
30547
  { tag: "w-heading", attribute: "level", kind: "integer-range", min: 1, max: 6, message: "`level` must be an integer between 1 and 6." },
30274
30548
  { tag: "w-heading", attribute: "override-chapter-number", kind: "integer-min", min: 0, message: "`override-chapter-number` must be a non-negative integer." },
30549
+ { 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." },
30550
+ { 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
30551
  { tag: "w-page", attribute: "number", kind: "pattern", pattern: ARABIC_OR_ROMAN, message: "`number` must be an arabic or roman number." },
30276
30552
  { tag: "w-para", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30553
+ { 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." },
30554
+ { 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." },
30555
+ { 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." },
30556
+ { 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
30557
  { tag: "w-note-para", attribute: "indent", kind: "integer-min", min: 0, message: "`indent` must be an integer >= 0." },
30278
30558
  { tag: "td", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30279
30559
  { tag: "th", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
@@ -30414,6 +30694,9 @@ function indent(text) {
30414
30694
  function buildElement(tag, counter, depth) {
30415
30695
  const schema = TAGS[tag];
30416
30696
  let attrs = "";
30697
+ if (tag === "w-color") {
30698
+ attrs = ` foreground="${placeholder(counter)}"`;
30699
+ }
30417
30700
  for (const attr of schema?.attributes ?? []) {
30418
30701
  if (attr.required) {
30419
30702
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30449,6 +30732,9 @@ function buildElementSnippet(tag) {
30449
30732
  function buildOpeningTag(tag) {
30450
30733
  const counter = { n: 1 };
30451
30734
  let attrs = "";
30735
+ if (tag === "w-color") {
30736
+ attrs = ` foreground="${placeholder(counter)}"`;
30737
+ }
30452
30738
  for (const attr of TAGS[tag]?.attributes ?? []) {
30453
30739
  if (attr.required) {
30454
30740
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30652,6 +30938,7 @@ function dedupe(items) {
30652
30938
  function hasValueSuggestions(tag, attribute, enumRef) {
30653
30939
  if (enumRef && ENUMS[enumRef]) return true;
30654
30940
  if (tag === "w-heading" && attribute === "level") return true;
30941
+ if (tag === "meta" && attribute === "name") return true;
30655
30942
  if (tag === "meta" && attribute === "content") return true;
30656
30943
  if (tag === "a" && attribute === "href") return true;
30657
30944
  return false;
@@ -30821,6 +31108,18 @@ var WemlCompletionProvider = class {
30821
31108
  });
30822
31109
  }
30823
31110
  }
31111
+ if (ctx.tag === "meta" && ctx.attribute === "name") {
31112
+ for (const field of META_FIELDS) {
31113
+ items.push({
31114
+ label: field.name,
31115
+ kind: this.m.languages.CompletionItemKind.Property,
31116
+ detail: "WEML metadata field",
31117
+ documentation: metaFieldMarkdown(field.name),
31118
+ insertText: wrap(field.name),
31119
+ range
31120
+ });
31121
+ }
31122
+ }
30824
31123
  if (ctx.tag === "meta" && ctx.attribute === "content" && stripQuotes2(node.attributes?.["name"]) === "type") {
30825
31124
  const codeEnum = ENUMS["publication-type"];
30826
31125
  if (codeEnum) {
@@ -30978,6 +31277,9 @@ function validateWemlModel(m, model) {
30978
31277
  push(href.valueRange, `Unrecognized href format "${href.value}". Expected formats like: ${types}.`, "warning");
30979
31278
  }
30980
31279
  }
31280
+ if (tag === "w-color" && !attrs.has("background") && !attrs.has("foreground")) {
31281
+ push(tagRange, '<w-color> must have either "background" or "foreground".', "error");
31282
+ }
30981
31283
  if (tag === "div") {
30982
31284
  const id = stripQuotes2(node.attributes?.["id"]);
30983
31285
  const range = attrs.get("id")?.valueRange ?? tagRange;
@@ -30991,7 +31293,8 @@ function validateWemlModel(m, model) {
30991
31293
  function validateConstraint(constraint, info) {
30992
31294
  if (info.value === void 0 || !info.valueRange) return;
30993
31295
  if (constraint.kind === "pattern") {
30994
- if (info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
31296
+ const emptyIsInvalid = constraint.allowEmpty === false && info.value === "";
31297
+ if (emptyIsInvalid || info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
30995
31298
  push(info.valueRange, constraint.message, "error");
30996
31299
  }
30997
31300
  return;
@@ -31370,7 +31673,7 @@ function escapeSnippet(text) {
31370
31673
  return text.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/}/g, "\\}");
31371
31674
  }
31372
31675
  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("");
31676
+ 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
31677
  const inner = selection.length > 0 ? escapeSnippet(selection) : attrs.includes("$1") ? "$2" : "$1";
31375
31678
  return `<${tag}${attrs}>${inner}</${tag}>`;
31376
31679
  }
@@ -31523,7 +31826,7 @@ var WRAP_OPTIONS = [
31523
31826
  tag: "w-format",
31524
31827
  type: v.value
31525
31828
  })),
31526
- ...["w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31829
+ ...["w-color", "w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31527
31830
  ];
31528
31831
  var SNIPPET_BUTTONS = [
31529
31832
  { label: "w-para", id: "w-para" },