Skip to content

Instantly share code, notes, and snippets.

@vcanales
Created August 28, 2023 19:10
Show Gist options
  • Save vcanales/1df0cd9fe27c5823ca987536e68ecce0 to your computer and use it in GitHub Desktop.
Save vcanales/1df0cd9fe27c5823ca987536e68ecce0 to your computer and use it in GitHub Desktop.
Escape patterns
import { RewritingStream } from 'parse5-html-rewriting-stream';
import fs from 'fs';
import path from 'path';
import { table } from 'table';
function getPatternTable(themeSlug, patterns) {
const tableConfig = {
columnDefault: {
width: 40,
},
header: {
alignment: 'center',
content: `THEME: ${themeSlug}\n\n Following patterns may get updated with escaped strings and/or image paths`,
}
};
return table(patterns.map(p => [p]), tableConfig);
}
function escapeImagePath(src) {
if (!src || src.trim().startsWith('<?php')) return src;
const assetsDir = 'assets';
const parts = src.split('/');
const resultSrc = parts.slice(parts.indexOf(assetsDir)).join('/');
return `<?php echo esc_url( get_template_directory_uri() ); ?>/${resultSrc}`;
}
async function escapePatterns() {
// get all file paths on patterns/
const patternsDir = path.join('./patterns');
const patterns = fs.readdirSync(patternsDir);
// arrange patterns by theme
const themePatterns = patterns.reduce((acc, file) => {
const themeSlug = file.split('/').shift();
return {
...acc,
[themeSlug]: (acc[themeSlug] || []).concat(file)
};
}, {});
Object.entries(themePatterns).forEach(async ([themeSlug, patterns]) => {
console.log(getPatternTable(themeSlug, patterns));
const themeslug = 'twentytwentyfour';
patterns.forEach(file => {
// get file path
const filePath = path.join(patternsDir, file);
const rewriter = getReWriter(themeslug);
const tmpFile = `${file}-tmp`;
const readStream = fs.createReadStream( filePath, { encoding: 'UTF-8' } );
const writeStream = fs.createWriteStream( tmpFile, { encoding: 'UTF-8' } );
writeStream.on('finish', () => {
fs.renameSync(tmpFile, filePath);
});
readStream.pipe(rewriter).pipe(writeStream);
});
});
// Helper functions
function getReWriter(themeSlug) {
const rewriter = new RewritingStream();
rewriter.on('text', (_, raw) => {
rewriter.emitRaw(escapeText(raw, themeSlug));
});
rewriter.on('startTag', (startTag, rawHtml) => {
if (startTag.tagName === 'img') {
const attrs = startTag.attrs.filter(attr => ['src', 'alt'].includes(attr.name));
attrs.forEach(attr => {
if (attr.name === 'src') {
attr.value = escapeImagePath(attr.value);
} else if (attr.name === 'alt') {
attr.value = escapeText(attr.value, themeSlug, true);
}
});
}
rewriter.emitStartTag(startTag);
});
rewriter.on('comment', (comment, rawHtml) => {
if (comment.text.startsWith('?php')) {
rewriter.emitRaw(rawHtml);
return;
}
// escape the strings in block config (blocks that are represented as comments)
// ex: <!-- wp:search {label: "Search"} /-->
const block = escapeBlockAttrs(comment.text, themeSlug)
rewriter.emitComment({...comment, text: block})
});
return rewriter;
}
}
function escapeBlockAttrs(block, themeSlug) {
// Set isAttr to true if it is an attribute in the result HTML
// If set to true, it generates esc_attr_, otherwise it generates esc_html_
const allowedAttrs = [
{ name: 'label' },
{ name: 'placeholder', isAttr: true },
{ name: 'buttonText' },
{ name: 'content' },
];
const start = block.indexOf('{');
const end = block.lastIndexOf('}');
const configPrefix = block.slice(0, start);
const config = block.slice(start, end + 1);
const configSuffix = block.slice(end + 1);
try {
const configJson = JSON.parse(config);
allowedAttrs.forEach((attr) => {
if (!configJson[attr.name]) return;
configJson[attr.name] = escapeText(
configJson[attr.name],
themeSlug,
attr.isAttr
);
});
return configPrefix + JSON.stringify(configJson) + configSuffix;
} catch (error) {
// do nothing
return block;
}
}
function escapeText(text, themeSlug, isAttr = false) {
const trimmedText = text && text.trim();
if (!themeSlug || !trimmedText || trimmedText.startsWith(`<?php`))
return text;
const escFunction = isAttr ? 'esc_attr__' : 'esc_html__';
const spaceChar = text.startsWith(' ') ? '&nbsp;' : '';
const resultText = text.replace("'", "\\'").trim();
return `${spaceChar}<?php echo ${escFunction}( '${resultText}', '${themeSlug}' ); ?>`;
}
escapePatterns();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment