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.mjs CHANGED
@@ -6111,21 +6111,41 @@ 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 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;
6134
+ var ENTITY_RE2 = /&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);?/g;
6119
6135
  var PH_OPEN = "\uE000";
6120
6136
  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"]);
6137
+ var VOID_ELEMENTS = VOID_HTML_ELEMENTS;
6138
+ var SPACE_SEPARATED_ATTRIBUTES = /* @__PURE__ */ new Set(["class", "headers", "rel", "rev"]);
6123
6139
  var WHITE_SPACE = /* @__PURE__ */ new Set([
6124
6140
  9,
6125
6141
  10,
6126
6142
  11,
6127
6143
  12,
6128
6144
  13,
6145
+ 28,
6146
+ 29,
6147
+ 30,
6148
+ 31,
6129
6149
  32,
6130
6150
  133,
6131
6151
  160,
@@ -6145,40 +6165,104 @@ var require_wemlContentNormalizeV2 = __commonJS({
6145
6165
  8233,
6146
6166
  8239,
6147
6167
  8287,
6148
- 12288
6168
+ 12288,
6169
+ 65279
6149
6170
  ]);
6150
6171
  function formatWeml(input) {
6151
- const { html, entities: entities2 } = protectEntities(input);
6172
+ const { html, entities: entities2, marker } = protectEntities(input);
6152
6173
  const doc = parse2(normalizeVoidTagSyntax(html));
6174
+ const title = doc.querySelector("head title");
6175
+ if (title) processInlineContent(title, true);
6153
6176
  const body = doc.querySelector("body");
6154
6177
  if (body) processBlockElement(body);
6155
- return restoreEntities(serializeDocument(doc), entities2);
6178
+ return restoreEntities(serializeDocument(doc, entities2, marker), entities2, marker);
6156
6179
  }
6157
6180
  function protectEntities(input) {
6181
+ let markerLength = 1;
6182
+ let marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6183
+ while (input.includes(marker)) {
6184
+ markerLength += 1;
6185
+ marker = PH_OPEN.repeat(markerLength) + PH_CLOSE;
6186
+ }
6158
6187
  const entities2 = [];
6159
6188
  const html = input.replace(ENTITY_RE2, (match) => {
6160
6189
  const index = entities2.length;
6161
6190
  entities2.push(match);
6162
- return `${PH_OPEN}${index}${PH_CLOSE}`;
6191
+ return `${marker}${index}${marker}`;
6163
6192
  });
6164
- return { html, entities: entities2 };
6165
- }
6166
- function restoreEntities(text, entities2) {
6167
- return text.replace(PH_RE, (_, index) => entities2[Number(index)]);
6193
+ return { html, entities: entities2, marker };
6194
+ }
6195
+ function restoreEntities(text, entities2, marker) {
6196
+ const result = [];
6197
+ let position = 0;
6198
+ while (position < text.length) {
6199
+ const open = text.indexOf(marker, position);
6200
+ if (open < 0) {
6201
+ result.push(text.slice(position));
6202
+ break;
6203
+ }
6204
+ result.push(text.slice(position, open));
6205
+ const indexStart = open + marker.length;
6206
+ const close = text.indexOf(marker, indexStart);
6207
+ const indexText = close >= 0 ? text.slice(indexStart, close) : "";
6208
+ const entityIndex = Number(indexText);
6209
+ if (close < 0 || !/^[0-9]+$/.test(indexText) || !Number.isSafeInteger(entityIndex) || entityIndex >= entities2.length) {
6210
+ throw new Error("Invalid internal entity placeholder.");
6211
+ }
6212
+ result.push(entities2[entityIndex]);
6213
+ position = close + marker.length;
6214
+ }
6215
+ return result.join("");
6168
6216
  }
6169
6217
  function normalizeVoidTagSyntax(input) {
6170
- return input.replace(VOID_TAG_RE, (_, name, attrs) => `<${name.toLowerCase()}${attrs.trimEnd()} />`);
6218
+ function slashBelongsToUnquotedValue(attrs) {
6219
+ let index = 0;
6220
+ const whitespace = /* @__PURE__ */ new Set([" ", " ", "\n", "\f", "\r"]);
6221
+ while (index < attrs.length) {
6222
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6223
+ if (index >= attrs.length || attrs[index] === "/") return false;
6224
+ while (index < attrs.length && !whitespace.has(attrs[index]) && !["=", "/"].includes(attrs[index])) index += 1;
6225
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6226
+ if (index >= attrs.length || attrs[index] !== "=") continue;
6227
+ index += 1;
6228
+ while (index < attrs.length && whitespace.has(attrs[index])) index += 1;
6229
+ if (index >= attrs.length) return false;
6230
+ if (attrs[index] === '"' || attrs[index] === "'") {
6231
+ const quote = attrs[index];
6232
+ index += 1;
6233
+ while (index < attrs.length && attrs[index] !== quote) index += 1;
6234
+ if (index < attrs.length) index += 1;
6235
+ continue;
6236
+ }
6237
+ const valueStart = index;
6238
+ while (index < attrs.length && !whitespace.has(attrs[index])) index += 1;
6239
+ if (index === attrs.length) {
6240
+ return valueStart < attrs.length && attrs.endsWith("/");
6241
+ }
6242
+ }
6243
+ return false;
6244
+ }
6245
+ return input.replace(VOID_TAG_RE, (_, name, rawAttrs) => {
6246
+ let attrs = trimRawEndWhitespace(rawAttrs);
6247
+ if (attrs.endsWith("/") && !slashBelongsToUnquotedValue(attrs)) {
6248
+ attrs = trimRawEndWhitespace(attrs.slice(0, -1));
6249
+ }
6250
+ return `<${name.toLowerCase()}${attrs} />`;
6251
+ });
6171
6252
  }
6172
6253
  function processBlockElement(node) {
6173
6254
  for (const child of [...node.childNodes]) {
6174
- if (child.nodeType === 3 && child.rawText.trim() === "") child.remove();
6255
+ if (child.nodeType !== 3) continue;
6256
+ const normalized = trimWhitespace(child.rawText);
6257
+ if (normalized) child.rawText = normalized;
6258
+ else child.remove();
6175
6259
  }
6176
6260
  for (const child of [...node.childNodes]) {
6177
6261
  if (child.nodeType !== 1) continue;
6178
6262
  const name = child.rawTagName.toLowerCase();
6179
6263
  if (INLINE_CONTAINERS.has(name)) processInlineContainer(child);
6180
6264
  else if (BLOCK_ELEMENTS.has(name)) processBlockElement(child);
6181
- else if (INLINE_ELEMENTS.has(name)) processInlineContent(child, true);
6265
+ else if (isInlineTextElement(child)) processInlineContent(child, true);
6182
6266
  }
6183
6267
  }
6184
6268
  function processInlineContainer(node) {
@@ -6189,7 +6273,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6189
6273
  for (const child of [...node.childNodes]) {
6190
6274
  if (child.nodeType !== 1) continue;
6191
6275
  const name = child.rawTagName.toLowerCase();
6192
- if (INLINE_ELEMENTS.has(name)) {
6276
+ if (isInlineTextElement(child)) {
6193
6277
  if (hasDirectSentChildren(child)) processSentContainer(child);
6194
6278
  else processInlineContent(child, false);
6195
6279
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6199,19 +6283,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6199
6283
  }
6200
6284
  }
6201
6285
  for (const child of [...node.childNodes]) {
6202
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6286
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6203
6287
  bleedEdgeSpaces(child);
6204
6288
  }
6205
6289
  }
6206
6290
  for (const child of [...node.childNodes]) {
6207
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6291
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6208
6292
  }
6209
6293
  for (const child of [...node.childNodes]) {
6210
6294
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6211
6295
  const prev = child.previousSibling;
6212
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6296
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6213
6297
  const next = child.nextSibling;
6214
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6298
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6215
6299
  }
6216
6300
  }
6217
6301
  trimContainerEdges(node);
@@ -6225,17 +6309,17 @@ var require_wemlContentNormalizeV2 = __commonJS({
6225
6309
  const first = children[0];
6226
6310
  const last = children[children.length - 1];
6227
6311
  if (children.length === 1) {
6228
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6312
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6229
6313
  } else {
6230
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6231
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6314
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6315
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6232
6316
  }
6233
6317
  }
6234
6318
  function processInlineContent(node, trimEdges) {
6235
6319
  for (const child of [...node.childNodes]) {
6236
6320
  if (child.nodeType !== 1) continue;
6237
6321
  const name = child.rawTagName.toLowerCase();
6238
- if (INLINE_ELEMENTS.has(name)) {
6322
+ if (isInlineTextElement(child)) {
6239
6323
  if (hasDirectSentChildren(child)) processSentContainer(child);
6240
6324
  else processInlineContent(child, false);
6241
6325
  } else if (BLOCK_ELEMENTS.has(name)) {
@@ -6245,19 +6329,19 @@ var require_wemlContentNormalizeV2 = __commonJS({
6245
6329
  }
6246
6330
  }
6247
6331
  for (const child of [...node.childNodes]) {
6248
- if (child.nodeType === 1 && INLINE_ELEMENTS.has(child.rawTagName.toLowerCase())) {
6332
+ if (child.nodeType === 1 && isInlineTextElement(child)) {
6249
6333
  bleedEdgeSpaces(child);
6250
6334
  }
6251
6335
  }
6252
6336
  for (const child of [...node.childNodes]) {
6253
- if (child.nodeType === 3) child.rawText = child.rawText.replace(/[ \t\r\n]+/g, " ");
6337
+ if (child.nodeType === 3) child.rawText = collapseWhitespace(child.rawText);
6254
6338
  }
6255
6339
  for (const child of [...node.childNodes]) {
6256
6340
  if (child.nodeType === 1 && child.rawTagName.toLowerCase() === "br") {
6257
6341
  const prev = child.previousSibling;
6258
- if (prev && prev.nodeType === 3) prev.rawText = prev.rawText.replace(/\s+$/, "");
6342
+ if (prev && prev.nodeType === 3) prev.rawText = trimEndWhitespace(prev.rawText);
6259
6343
  const next = child.nextSibling;
6260
- if (next && next.nodeType === 3) next.rawText = next.rawText.replace(/^\s+/, "");
6344
+ if (next && next.nodeType === 3) next.rawText = trimStartWhitespace(next.rawText);
6261
6345
  }
6262
6346
  }
6263
6347
  if (trimEdges) trimEdgeTextNodes(node);
@@ -6295,7 +6379,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6295
6379
  const first = getFirstTextNode(node);
6296
6380
  if (!first) return "";
6297
6381
  const raw = first.rawText;
6298
- const trimmed = raw.replace(/^\s+/, "");
6382
+ const trimmed = trimStartWhitespace(raw);
6299
6383
  if (trimmed === raw) return "";
6300
6384
  first.rawText = trimmed;
6301
6385
  return " ";
@@ -6304,7 +6388,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6304
6388
  const last = getLastTextNode(node);
6305
6389
  if (!last) return "";
6306
6390
  const raw = last.rawText;
6307
- const trimmed = raw.replace(/\s+$/, "");
6391
+ const trimmed = trimEndWhitespace(raw);
6308
6392
  if (trimmed === raw) return "";
6309
6393
  last.rawText = trimmed;
6310
6394
  return " ";
@@ -6341,10 +6425,10 @@ var require_wemlContentNormalizeV2 = __commonJS({
6341
6425
  const first = children[0];
6342
6426
  const last = children[children.length - 1];
6343
6427
  if (first === last) {
6344
- if (first.nodeType === 3) first.rawText = first.rawText.trim();
6428
+ if (first.nodeType === 3) first.rawText = trimWhitespace(first.rawText);
6345
6429
  } else {
6346
- if (first.nodeType === 3) first.rawText = first.rawText.replace(/^\s+/, "");
6347
- if (last.nodeType === 3) last.rawText = last.rawText.replace(/\s+$/, "");
6430
+ if (first.nodeType === 3) first.rawText = trimStartWhitespace(first.rawText);
6431
+ if (last.nodeType === 3) last.rawText = trimEndWhitespace(last.rawText);
6348
6432
  }
6349
6433
  }
6350
6434
  function hasDirectSentChildren(node) {
@@ -6352,17 +6436,22 @@ var require_wemlContentNormalizeV2 = __commonJS({
6352
6436
  (child) => child.nodeType === 1 && child.rawTagName.toLowerCase() === "w-sent"
6353
6437
  );
6354
6438
  }
6355
- function serializeDocument(doc) {
6439
+ function isInlineTextElement(node) {
6440
+ if (node.nodeType !== 1) return false;
6441
+ const name = node.rawTagName.toLowerCase();
6442
+ return INLINE_ELEMENTS.has(name) || !BLOCK_ELEMENTS.has(name) && !INLINE_CONTAINERS.has(name) && !VOID_HTML_ELEMENTS.has(name);
6443
+ }
6444
+ function serializeDocument(doc, entities2, marker) {
6356
6445
  const parts = ["<!DOCTYPE html>\n"];
6357
6446
  const htmlNode = doc.querySelector("html");
6358
- parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.attrs : {})}>
6447
+ parts.push(`<html${serializeAttrs(htmlNode ? htmlNode.rawAttrs : "", entities2, marker)}>
6359
6448
 
6360
6449
  `);
6361
6450
  const head = doc.querySelector("head");
6362
6451
  parts.push("<head>\n");
6363
6452
  if (head) {
6364
6453
  for (const child of head.childNodes) {
6365
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6454
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6366
6455
  }
6367
6456
  }
6368
6457
  parts.push("</head>\n\n");
@@ -6370,28 +6459,81 @@ var require_wemlContentNormalizeV2 = __commonJS({
6370
6459
  parts.push("<body>\n");
6371
6460
  if (body) {
6372
6461
  for (const child of body.childNodes) {
6373
- if (child.nodeType === 1) parts.push(serializeNode(child), "\n");
6462
+ if (child.nodeType === 1) parts.push(serializeNode(child, entities2, marker), "\n");
6374
6463
  }
6375
6464
  }
6376
6465
  parts.push("</body>\n</html>");
6377
6466
  return parts.join("");
6378
6467
  }
6379
- function serializeNode(node) {
6468
+ function serializeNode(node, entities2, marker) {
6380
6469
  if (node.nodeType === 3) return node.rawText;
6381
6470
  if (node.nodeType === 8 || node.nodeType !== 1) return "";
6382
6471
  const name = node.rawTagName.toLowerCase();
6383
- const attrs = serializeAttrs(node.attrs);
6472
+ const attrs = serializeAttrs(node.rawAttrs, entities2, marker);
6384
6473
  if (VOID_HTML_ELEMENTS.has(name)) return `<${name}${attrs} />`;
6385
6474
  const isBlock = BLOCK_ELEMENTS.has(name);
6386
6475
  const inner = [];
6387
6476
  for (const child of node.childNodes) {
6388
- if (isBlock && child.nodeType === 3 && child.rawText.trim() === "") continue;
6389
- inner.push(serializeNode(child));
6477
+ if (isBlock && child.nodeType === 3 && isWhitespaceOnly(child.rawText)) continue;
6478
+ inner.push(serializeNode(child, entities2, marker));
6390
6479
  }
6391
6480
  return `<${name}${attrs}>${inner.join("")}</${name}>`;
6392
6481
  }
6393
- function serializeAttrs(attrs) {
6394
- return Object.entries(attrs).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6482
+ function serializeAttrs(rawAttrs, entities2, marker) {
6483
+ const canonical = /* @__PURE__ */ new Map();
6484
+ let index = 0;
6485
+ while (index < rawAttrs.length) {
6486
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6487
+ if (index >= rawAttrs.length || rawAttrs[index] === "/") break;
6488
+ const nameStart = index;
6489
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index]) && !"=/>".includes(rawAttrs[index])) index++;
6490
+ if (index === nameStart) {
6491
+ index++;
6492
+ continue;
6493
+ }
6494
+ const name = rawAttrs.slice(nameStart, index).toLowerCase();
6495
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6496
+ let value = "";
6497
+ if (rawAttrs[index] === "=") {
6498
+ index++;
6499
+ while (index < rawAttrs.length && isNormalizableWhitespace(rawAttrs[index])) index++;
6500
+ if (rawAttrs[index] === '"' || rawAttrs[index] === "'") {
6501
+ const quote = rawAttrs[index++];
6502
+ const valueStart = index;
6503
+ while (index < rawAttrs.length && rawAttrs[index] !== quote) index++;
6504
+ value = rawAttrs.slice(valueStart, index);
6505
+ if (index < rawAttrs.length) index++;
6506
+ } else {
6507
+ const valueStart = index;
6508
+ while (index < rawAttrs.length && !isNormalizableWhitespace(rawAttrs[index])) index++;
6509
+ value = rawAttrs.slice(valueStart, index);
6510
+ }
6511
+ }
6512
+ canonical.set(
6513
+ name,
6514
+ SPACE_SEPARATED_ATTRIBUTES.has(name) ? normalizeTokenList(value, entities2, marker) : value
6515
+ );
6516
+ }
6517
+ return [...canonical.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([name, value]) => ` ${name}="${encodeAttr(String(value))}"`).join("");
6518
+ }
6519
+ function normalizeTokenList(value, entities2, marker) {
6520
+ const normalized = trimWhitespace(value);
6521
+ if (!normalized) return normalized;
6522
+ return normalized.split(" ").sort((left, right) => compareUnicodeScalars(
6523
+ restoreEntities(left, entities2, marker),
6524
+ restoreEntities(right, entities2, marker)
6525
+ )).join(" ");
6526
+ }
6527
+ function compareUnicodeScalars(left, right) {
6528
+ const leftScalars = Array.from(left, (character) => character.codePointAt(0));
6529
+ const rightScalars = Array.from(right, (character) => character.codePointAt(0));
6530
+ const limit = Math.min(leftScalars.length, rightScalars.length);
6531
+ for (let index = 0; index < limit; index++) {
6532
+ if (leftScalars[index] !== rightScalars[index]) {
6533
+ return leftScalars[index] - rightScalars[index];
6534
+ }
6535
+ }
6536
+ return leftScalars.length - rightScalars.length;
6395
6537
  }
6396
6538
  function encodeAttr(value) {
6397
6539
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -6429,6 +6571,7 @@ var require_wemlContentNormalizeV2 = __commonJS({
6429
6571
  }
6430
6572
  };
6431
6573
  function wemlContentNormalize2(input, isWemlValid = false) {
6574
+ if (input.startsWith("\uFEFF")) input = input.slice(1);
6432
6575
  const inputText = withoutWhiteSpace(analyze(input, !isWemlValid).text);
6433
6576
  const result = formatWeml(input);
6434
6577
  const outputText = withoutWhiteSpace(analyze(result, true).text);
@@ -6443,6 +6586,9 @@ var require_wemlContentNormalizeV2 = __commonJS({
6443
6586
  if (source[index2] === "\n") lineStarts.push(index2 + 1);
6444
6587
  }
6445
6588
  const stack = [];
6589
+ const rootTagCounts = /* @__PURE__ */ new Map([["html", 0], ["head", 0], ["body", 0]]);
6590
+ let seenHead = false;
6591
+ let seenBody = false;
6446
6592
  const textParts = [];
6447
6593
  let textStart = 0;
6448
6594
  let index = 0;
@@ -6526,6 +6672,26 @@ var require_wemlContentNormalizeV2 = __commonJS({
6526
6672
  if (expected !== name) fail("MISMATCHED_CLOSING_TAG", index, expected, name);
6527
6673
  stack.pop();
6528
6674
  } else {
6675
+ if (rootTagCounts.has(name)) {
6676
+ if (rootTagCounts.get(name) > 0) {
6677
+ fail("DUPLICATE_ROOT_TAG", index, null, name);
6678
+ }
6679
+ const parent = stack.length > 0 ? stack.at(-1).name : null;
6680
+ if (name === "html") {
6681
+ if (parent !== null) fail("INVALID_ROOT_TAG_PARENT", index, null, name);
6682
+ } else if (parent !== "html") {
6683
+ fail("INVALID_ROOT_TAG_PARENT", index, "html", name);
6684
+ }
6685
+ if (name === "body" && !seenHead) {
6686
+ fail("BODY_BEFORE_HEAD", index, "head", "body");
6687
+ }
6688
+ if (name === "head" && seenBody) {
6689
+ fail("HEAD_AFTER_BODY", index, "body", "head");
6690
+ }
6691
+ rootTagCounts.set(name, rootTagCounts.get(name) + 1);
6692
+ seenHead || (seenHead = name === "head");
6693
+ seenBody || (seenBody = name === "body");
6694
+ }
6529
6695
  let last = found.end - 1;
6530
6696
  while (last >= cursor && isSourceSpace(source[last])) last--;
6531
6697
  const selfClosing = last >= cursor && source[last] === "/";
@@ -6547,6 +6713,11 @@ var require_wemlContentNormalizeV2 = __commonJS({
6547
6713
  null
6548
6714
  );
6549
6715
  }
6716
+ if (validateStructure) {
6717
+ for (const name of ["html", "head", "body"]) {
6718
+ if (rootTagCounts.get(name) === 0) fail("MISSING_ROOT_TAG", 0, name, null);
6719
+ }
6720
+ }
6550
6721
  return { text: textParts.join("") };
6551
6722
  }
6552
6723
  function findTagEnd(source, start) {
@@ -6575,6 +6746,42 @@ var require_wemlContentNormalizeV2 = __commonJS({
6575
6746
  function withoutWhiteSpace(text) {
6576
6747
  return Array.from(text, (character) => WHITE_SPACE.has(character.codePointAt(0)) ? "" : character).join("");
6577
6748
  }
6749
+ function isNormalizableWhitespace(character) {
6750
+ return character !== void 0 && WHITE_SPACE.has(character.codePointAt(0));
6751
+ }
6752
+ function collapseWhitespace(text) {
6753
+ const result = [];
6754
+ let inWhitespace = false;
6755
+ for (const character of text) {
6756
+ if (isNormalizableWhitespace(character)) {
6757
+ if (!inWhitespace) result.push(" ");
6758
+ inWhitespace = true;
6759
+ } else {
6760
+ result.push(character);
6761
+ inWhitespace = false;
6762
+ }
6763
+ }
6764
+ return result.join("");
6765
+ }
6766
+ function trimStartWhitespace(text) {
6767
+ return collapseWhitespace(text).replace(/^ +/, "");
6768
+ }
6769
+ function trimEndWhitespace(text) {
6770
+ return collapseWhitespace(text).replace(/ +$/, "");
6771
+ }
6772
+ function trimWhitespace(text) {
6773
+ return collapseWhitespace(text).replace(/^ +| +$/g, "");
6774
+ }
6775
+ function trimRawEndWhitespace(text) {
6776
+ const characters = Array.from(text);
6777
+ while (characters.length > 0 && isNormalizableWhitespace(characters.at(-1))) {
6778
+ characters.pop();
6779
+ }
6780
+ return characters.join("");
6781
+ }
6782
+ function isWhitespaceOnly(text) {
6783
+ return Array.from(text).every(isNormalizableWhitespace);
6784
+ }
6578
6785
  function sha256(text) {
6579
6786
  const crypto = __require("crypto");
6580
6787
  return crypto.createHash("sha256").update(text, "utf8").digest("hex");
@@ -6594,6 +6801,16 @@ var require_wemlContentNormalizeV2 = __commonJS({
6594
6801
  detail = `unexpected closing tag </${actualTag}>.`;
6595
6802
  } else if (code === "UNCLOSED_TAG") {
6596
6803
  detail = `tag <${expectedTag}> is not closed.`;
6804
+ } else if (code === "DUPLICATE_ROOT_TAG") {
6805
+ detail = `document contains more than one <${actualTag}> tag.`;
6806
+ } else if (code === "INVALID_ROOT_TAG_PARENT") {
6807
+ detail = expectedTag ? `tag <${actualTag}> must be a direct child of <${expectedTag}>.` : `root tag <${actualTag}> must not be nested.`;
6808
+ } else if (code === "BODY_BEFORE_HEAD") {
6809
+ detail = "tag <body> appears before <head>.";
6810
+ } else if (code === "HEAD_AFTER_BODY") {
6811
+ detail = "tag <head> appears after <body>.";
6812
+ } else if (code === "MISSING_ROOT_TAG") {
6813
+ detail = `required root tag <${expectedTag}> is missing.`;
6597
6814
  } else if (code === "UNTERMINATED_COMMENT") {
6598
6815
  detail = "comment is not terminated.";
6599
6816
  } else if (code === "UNTERMINATED_DECLARATION") {
@@ -29245,6 +29462,13 @@ var TAGS = {
29245
29462
  "default": "paragraph",
29246
29463
  "description": "Type of the paragraph. See corresponding page for values",
29247
29464
  "enumRef": "paragraph-type"
29465
+ },
29466
+ {
29467
+ "name": "align",
29468
+ "required": false,
29469
+ "default": "`left` for LTR, `right` for RTL",
29470
+ "description": "Horizontal alignment. See corresponding page for allowed values",
29471
+ "enumRef": "horizontal-alignment"
29248
29472
  }
29249
29473
  ],
29250
29474
  "notes": "",
@@ -29257,6 +29481,24 @@ var TAGS = {
29257
29481
  "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
29482
  "source": "inlines/br.md"
29259
29483
  },
29484
+ "w-color": {
29485
+ "tag": "w-color",
29486
+ "description": "Represents an inline span with a foreground color, a background color, or both.",
29487
+ "attributes": [
29488
+ {
29489
+ "name": "background",
29490
+ "required": false,
29491
+ "description": "Background color. See Colors."
29492
+ },
29493
+ {
29494
+ "name": "foreground",
29495
+ "required": false,
29496
+ "description": "Foreground color. See Colors."
29497
+ }
29498
+ ],
29499
+ "notes": "Any inline elements.",
29500
+ "source": "inlines/w-color.md"
29501
+ },
29260
29502
  "w-entity": {
29261
29503
  "tag": "w-entity",
29262
29504
  "description": "An entity is a reference to a person, place, or thing. Entities are used to",
@@ -29389,6 +29631,16 @@ var TAGS = {
29389
29631
  "name": "override-chapter-number",
29390
29632
  "required": false,
29391
29633
  "description": "Chapter number for manual set"
29634
+ },
29635
+ {
29636
+ "name": "background",
29637
+ "required": false,
29638
+ "description": "Background color. See Colors"
29639
+ },
29640
+ {
29641
+ "name": "foreground",
29642
+ "required": false,
29643
+ "description": "Foreground color. See Colors"
29392
29644
  }
29393
29645
  ],
29394
29646
  "notes": "Must contain a single text block element.",
@@ -29419,6 +29671,16 @@ var TAGS = {
29419
29671
  "required": false,
29420
29672
  "description": "Horizontal alignment. See corresponding page for allowed values. Defaults to `left` for left-to-right languages and `right` to right-to-left",
29421
29673
  "enumRef": "horizontal-alignment"
29674
+ },
29675
+ {
29676
+ "name": "background",
29677
+ "required": false,
29678
+ "description": "Background color. See Colors"
29679
+ },
29680
+ {
29681
+ "name": "foreground",
29682
+ "required": false,
29683
+ "description": "Foreground color. See Colors"
29422
29684
  }
29423
29685
  ],
29424
29686
  "notes": "Must contain a single block element.",
@@ -29775,8 +30037,12 @@ var META_FIELDS = [
29775
30037
  "description": "Publication description."
29776
30038
  },
29777
30039
  {
29778
- "name": "parent_id",
29779
- "description": "Parent publication version GUID."
30040
+ "name": "purchase-link",
30041
+ "description": "Purchase link."
30042
+ },
30043
+ {
30044
+ "name": "version-id",
30045
+ "description": "Publication version GUID."
29780
30046
  }
29781
30047
  ];
29782
30048
 
@@ -30033,7 +30299,7 @@ Unicode: \`U+${resolved.codePoint.toString(16).toUpperCase().padStart(4, "0")}\`
30033
30299
  // src/schema/structureRules.ts
30034
30300
  var CONTAINER_TAGS = ["w-para", "w-heading", "w-para-group", "w-toc"];
30035
30301
  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"];
30302
+ var INLINE_TAGS = ["a", "br", "w-color", "w-entity", "w-format", "w-lang", "w-non-egw", "w-note", "w-page", "w-sent"];
30037
30303
  var STRUCTURE_RULES = {
30038
30304
  html: {
30039
30305
  children: [
@@ -30180,6 +30446,13 @@ var STRUCTURE_RULES = {
30180
30446
  allowText: true,
30181
30447
  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
30448
  },
30449
+ "w-color": {
30450
+ children: [
30451
+ { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
30452
+ ],
30453
+ allowText: true,
30454
+ description: "May contain inline elements and text; requires a foreground or background color."
30455
+ },
30183
30456
  "w-entity": {
30184
30457
  children: [
30185
30458
  { tags: [...INLINE_TAGS], min: 0, max: null, description: "inline elements" }
@@ -30253,11 +30526,18 @@ var STRUCTURE_RULES = {
30253
30526
  }
30254
30527
  };
30255
30528
  var ARABIC_OR_ROMAN = /^(?:\d+|[ivxlcdm]+)$/i;
30529
+ var COLOR_VALUE = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
30256
30530
  var ATTRIBUTE_CONSTRAINTS = [
30257
30531
  { tag: "w-heading", attribute: "level", kind: "integer-range", min: 1, max: 6, message: "`level` must be an integer between 1 and 6." },
30258
30532
  { tag: "w-heading", attribute: "override-chapter-number", kind: "integer-min", min: 0, message: "`override-chapter-number` must be a non-negative integer." },
30533
+ { 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." },
30534
+ { 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
30535
  { tag: "w-page", attribute: "number", kind: "pattern", pattern: ARABIC_OR_ROMAN, message: "`number` must be an arabic or roman number." },
30260
30536
  { tag: "w-para", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30537
+ { 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." },
30538
+ { 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." },
30539
+ { 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." },
30540
+ { 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
30541
  { tag: "w-note-para", attribute: "indent", kind: "integer-min", min: 0, message: "`indent` must be an integer >= 0." },
30262
30542
  { tag: "td", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
30263
30543
  { tag: "th", attribute: "indent", kind: "integer-min", min: -4, message: "`indent` must be an integer >= -4." },
@@ -30398,6 +30678,9 @@ function indent(text) {
30398
30678
  function buildElement(tag, counter, depth) {
30399
30679
  const schema = TAGS[tag];
30400
30680
  let attrs = "";
30681
+ if (tag === "w-color") {
30682
+ attrs = ` foreground="${placeholder(counter)}"`;
30683
+ }
30401
30684
  for (const attr of schema?.attributes ?? []) {
30402
30685
  if (attr.required) {
30403
30686
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30433,6 +30716,9 @@ function buildElementSnippet(tag) {
30433
30716
  function buildOpeningTag(tag) {
30434
30717
  const counter = { n: 1 };
30435
30718
  let attrs = "";
30719
+ if (tag === "w-color") {
30720
+ attrs = ` foreground="${placeholder(counter)}"`;
30721
+ }
30436
30722
  for (const attr of TAGS[tag]?.attributes ?? []) {
30437
30723
  if (attr.required) {
30438
30724
  attrs += ` ${attr.name}="${placeholder(counter)}"`;
@@ -30636,6 +30922,7 @@ function dedupe(items) {
30636
30922
  function hasValueSuggestions(tag, attribute, enumRef) {
30637
30923
  if (enumRef && ENUMS[enumRef]) return true;
30638
30924
  if (tag === "w-heading" && attribute === "level") return true;
30925
+ if (tag === "meta" && attribute === "name") return true;
30639
30926
  if (tag === "meta" && attribute === "content") return true;
30640
30927
  if (tag === "a" && attribute === "href") return true;
30641
30928
  return false;
@@ -30805,6 +31092,18 @@ var WemlCompletionProvider = class {
30805
31092
  });
30806
31093
  }
30807
31094
  }
31095
+ if (ctx.tag === "meta" && ctx.attribute === "name") {
31096
+ for (const field of META_FIELDS) {
31097
+ items.push({
31098
+ label: field.name,
31099
+ kind: this.m.languages.CompletionItemKind.Property,
31100
+ detail: "WEML metadata field",
31101
+ documentation: metaFieldMarkdown(field.name),
31102
+ insertText: wrap(field.name),
31103
+ range
31104
+ });
31105
+ }
31106
+ }
30808
31107
  if (ctx.tag === "meta" && ctx.attribute === "content" && stripQuotes2(node.attributes?.["name"]) === "type") {
30809
31108
  const codeEnum = ENUMS["publication-type"];
30810
31109
  if (codeEnum) {
@@ -30962,6 +31261,9 @@ function validateWemlModel(m, model) {
30962
31261
  push(href.valueRange, `Unrecognized href format "${href.value}". Expected formats like: ${types}.`, "warning");
30963
31262
  }
30964
31263
  }
31264
+ if (tag === "w-color" && !attrs.has("background") && !attrs.has("foreground")) {
31265
+ push(tagRange, '<w-color> must have either "background" or "foreground".', "error");
31266
+ }
30965
31267
  if (tag === "div") {
30966
31268
  const id = stripQuotes2(node.attributes?.["id"]);
30967
31269
  const range = attrs.get("id")?.valueRange ?? tagRange;
@@ -30975,7 +31277,8 @@ function validateWemlModel(m, model) {
30975
31277
  function validateConstraint(constraint, info) {
30976
31278
  if (info.value === void 0 || !info.valueRange) return;
30977
31279
  if (constraint.kind === "pattern") {
30978
- if (info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
31280
+ const emptyIsInvalid = constraint.allowEmpty === false && info.value === "";
31281
+ if (emptyIsInvalid || info.value !== "" && constraint.pattern && !constraint.pattern.test(info.value)) {
30979
31282
  push(info.valueRange, constraint.message, "error");
30980
31283
  }
30981
31284
  return;
@@ -31354,7 +31657,7 @@ function escapeSnippet(text) {
31354
31657
  return text.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/}/g, "\\}");
31355
31658
  }
31356
31659
  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("");
31660
+ 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
31661
  const inner = selection.length > 0 ? escapeSnippet(selection) : attrs.includes("$1") ? "$2" : "$1";
31359
31662
  return `<${tag}${attrs}>${inner}</${tag}>`;
31360
31663
  }
@@ -31507,7 +31810,7 @@ var WRAP_OPTIONS = [
31507
31810
  tag: "w-format",
31508
31811
  type: v.value
31509
31812
  })),
31510
- ...["w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31813
+ ...["w-color", "w-lang", "w-non-egw", "w-entity", "a"].map((t2) => ({ value: t2, label: t2, tag: t2 }))
31511
31814
  ];
31512
31815
  var SNIPPET_BUTTONS = [
31513
31816
  { label: "w-para", id: "w-para" },