]/g) || []).length;
- const closes = (line.match(/<\/div\s*>/g) || []).length;
- depth += opens - closes;
-
- if (depth <= 0) break; // closing
of the variant wrapper
- content.push(line);
- }
- }
-
- return content.length > 0 ? content : null;
+ const text = stripStyleAndJoin(lines, block);
+ const inner = extractInnerByAttr(text, 'data-impeccable-variant="' + variantNum + '"');
+ if (inner === null) return null;
+ const result = inner.split('\n');
+ // Collapse a lone empty leading/trailing line (common after string splice).
+ while (result.length > 1 && result[0].trim() === '') result.shift();
+ while (result.length > 1 && result[result.length - 1].trim() === '') result.pop();
+ return result.length > 0 ? result : null;
}
/**
* Extract the colocated ` — return the inner content.
+ * 3. Multi-line: `` on a later line — return
+ * the lines between them.
*/
function extractCss(lines, block, id) {
const styleAttr = 'data-impeccable-css="' + id + '"';
@@ -287,6 +313,14 @@ function extractCss(lines, block, id) {
const line = lines[i];
if (!inStyle && line.includes(styleAttr)) {
+ // Self-closing: nothing to carbonize.
+ if (/` to close on)
+ * - Same-line `` blocks
+ * - Multi-line `` blocks
*/
-function extractOriginal(lines, block) {
- let inOriginal = false;
+function stripStyleAndJoin(lines, block) {
+ const out = [];
let inStyle = false;
- let depth = 0;
- const content = [];
-
for (let i = block.start; i <= block.end; i++) {
- const line = lines[i];
+ let line = lines[i];
- if (!inStyle && /')) inStyle = false;
- continue;
- }
+ if (!inStyle) {
+ // Strip any complete .
+ const closeIdx = line.search(/<\/style\s*>/);
+ if (closeIdx !== -1) {
+ inStyle = false;
+ out.push(line.slice(closeIdx).replace(/<\/style\s*>/, ''));
+ }
+ // else: skip line entirely
}
}
+ return out.join('\n');
+}
- return content;
+/**
+ * Find the inner content of `
` inside `text`,
+ * handling nested same-tag elements via depth counting. `attrMatch` is a
+ * regex source fragment that must appear inside the opener tag.
+ * Returns the inner string (may be empty), or null if not found.
+ */
+function extractInnerByAttr(text, attrMatch) {
+ const openerRe = new RegExp('<([A-Za-z][A-Za-z0-9]*)\\b[^>]*' + attrMatch + '[^>]*>');
+ const openMatch = text.match(openerRe);
+ if (!openMatch) return null;
+
+ const tagName = openMatch[1];
+ const innerStart = openMatch.index + openMatch[0].length;
+
+ // Match any opener or closer of this tag name after innerStart.
+ // (Does not match self-closing
, which doesn't contribute to depth.)
+ const tagRe = new RegExp('<(?:/)?' + tagName + '\\b[^>]*>', 'g');
+ tagRe.lastIndex = innerStart;
+
+ let depth = 1;
+ let m;
+ while ((m = tagRe.exec(text))) {
+ const isClose = m[0].startsWith('');
+ const isSelfClose = !isClose && /\/\s*>$/.test(m[0]);
+ if (isClose) {
+ depth--;
+ if (depth === 0) return text.slice(innerStart, m.index);
+ } else if (!isSelfClose) {
+ depth++;
+ }
+ }
+ return null;
+}
+
+/**
+ * Extract the original element content from within the variant wrapper.
+ * Returns an array of lines.
+ */
+function extractOriginal(lines, block) {
+ const text = stripStyleAndJoin(lines, block);
+ const inner = extractInnerByAttr(text, 'data-impeccable-variant="original"');
+ if (inner === null) return [];
+ return inner.split('\n');
}
/**
* Extract a specific variant's inner content (stripping the wrapper div).
* Returns an array of lines, or null if not found.
- *
- * Skip ')) inStyle = false;
- continue;
- }
-
- if (!inVariant && line.includes('data-impeccable-variant="' + variantNum + '"')) {
- inVariant = true;
- depth = 1;
- continue; // skip the opening
- }
-
- if (inVariant) {
- const opens = (line.match(/
]/g) || []).length;
- const closes = (line.match(/<\/div\s*>/g) || []).length;
- depth += opens - closes;
-
- if (depth <= 0) break; // closing
of the variant wrapper
- content.push(line);
- }
- }
-
- return content.length > 0 ? content : null;
+ const text = stripStyleAndJoin(lines, block);
+ const inner = extractInnerByAttr(text, 'data-impeccable-variant="' + variantNum + '"');
+ if (inner === null) return null;
+ const result = inner.split('\n');
+ // Collapse a lone empty leading/trailing line (common after string splice).
+ while (result.length > 1 && result[0].trim() === '') result.shift();
+ while (result.length > 1 && result[result.length - 1].trim() === '') result.pop();
+ return result.length > 0 ? result : null;
}
/**
* Extract the colocated ` — return the inner content.
+ * 3. Multi-line: `` on a later line — return
+ * the lines between them.
*/
function extractCss(lines, block, id) {
const styleAttr = 'data-impeccable-css="' + id + '"';
@@ -287,6 +313,14 @@ function extractCss(lines, block, id) {
const line = lines[i];
if (!inStyle && line.includes(styleAttr)) {
+ // Self-closing: nothing to carbonize.
+ if (/ line-by-line. JSX self-closing has no
+ // separate closer, so it got stuck forever and missed data-impeccable-variant
+ // divs that came after.
+ it('finds the accepted variant after a JSX self-closing block', () => {
+ const html = `
+
+
+
+`;
+ writeFileSync(join(tmp, 'page.html'), html);
+
+ const result = runAccept(tmp, ['--id', 'SELFC', '--variant', '2']);
+ assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
+
+ const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
+ // Self-closing style has no extractable CSS body, so there's nothing to carbonize —
+ // no carbonize block, no data-impeccable-variant wrapper (it would serve no purpose).
+ assert.ok(!after.includes('impeccable-carbonize-start'), 'no carbonize block (self-closing style has no body)');
+ assert.ok(!after.includes('impeccable-variants-start'), 'variant markers removed');
+ assert.ok(after.includes('variant two'), 'variant 2 content kept');
+ assert.ok(!after.includes('variant three'), 'other variant content dropped');
+ assert.ok(!after.includes('variant one'), 'other variant content dropped');
+ assert.ok(!after.includes('original text'), 'original content dropped');
+ });
+
+ // Variant: same-line block should also be treated as a
+ // single skipped unit; the line has both open and close tags.
+ it('finds the accepted variant after a single-line block', () => {
+ const html = `
+
+
+
+`;
+ writeFileSync(join(tmp, 'page.html'), html);
+
+ const result = runAccept(tmp, ['--id', 'ONELINE', '--variant', '3']);
+ assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
+
+ const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
+ assert.ok(after.includes('data-impeccable-variant="3"'), 'accepted wrapper for variant 3 present');
+ assert.ok(after.includes('variant three'), 'variant 3 content kept');
+ assert.ok(!after.includes('variant two'), 'other variant content dropped');
+ });
+
+ // Baseline: the standard multi-line case must keep working.
+ it('finds the accepted variant after a multi-line block (regression baseline)', () => {
+ const html = `
+
+
+
+`;
+ writeFileSync(join(tmp, 'page.html'), html);
+
+ const result = runAccept(tmp, ['--id', 'MULTI', '--variant', '1']);
+ assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
+
+ const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
+ assert.ok(after.includes('data-impeccable-variant="1"'), 'accepted wrapper for variant 1 present');
+ assert.ok(after.includes('variant one'), 'variant 1 content kept');
+ });
+
+ // Discard must restore the original element after a self-closing ,
+ // proving extractOriginal also survives the style pattern.
+ it('discard restores the original element after a JSX self-closing ', () => {
+ const html = `
+
+
+
+`;
+ writeFileSync(join(tmp, 'page.html'), html);
+
+ const result = runAccept(tmp, ['--id', 'DISC', '--discard']);
+ assert.equal(result.handled, true, `discard should succeed: ${JSON.stringify(result)}`);
+
+ const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
+ assert.ok(after.includes('ORIGINAL CONTENT'), 'original restored');
+ assert.ok(!after.includes('impeccable-variants-start'), 'wrapper markers gone');
+ assert.ok(!after.includes('variant one'), 'variants dropped');
+ });
+});