bytefun
Version:
一个打通了原型设计、UI设计与代码转换、跨平台原生代码开发等的平台
295 lines (215 loc) • 9.73 kB
text/typescript
/**
* HTML零宽高修复器
* 参考vsFixZeroWH.ts算法,通过子节点位置计算父节点的合适宽高
*/
export class HtmlZeroWHFixer {
/**
* 修复HTML中的零宽高节点
* @param htmlContent HTML内容字符串
* @returns 修复后的HTML内容
*/
public static fixZeroWidthAndHeight(htmlContent: string): string {
try {
let modifiedHtml = htmlContent;
let hasChanges = false;
// 先修正零宽度节点
const widthChanges = this.fixZeroWidthNodes(modifiedHtml);
if (widthChanges.hasChanges) {
modifiedHtml = widthChanges.modifiedHtml;
hasChanges = true;
}
// 再修正零高度节点
const heightChanges = this.fixZeroHeightNodes(modifiedHtml);
if (heightChanges.hasChanges) {
modifiedHtml = heightChanges.modifiedHtml;
hasChanges = true;
}
if (hasChanges) {
} else {
}
return modifiedHtml;
} catch (error) {
console.error('❌ [HtmlZeroWHFixer] 修正零宽高节点失败:', error);
return htmlContent; // 出错时返回原始内容
}
}
/**
* 修正零宽度节点
* @param htmlContent HTML内容
* @returns 修正结果
*/
private static fixZeroWidthNodes(htmlContent: string): { hasChanges: boolean, modifiedHtml: string } {
try {
let modifiedHtml = htmlContent;
let hasChanges = false;
// 查找所有w="0"的节点
const zeroWidthRegex = /<([^>]+)\sw="0"([^>]*)>/g;
let match;
const zeroWidthNodes: Array<{ fullTag: string, tagName: string, attributes: string }> = [];
while ((match = zeroWidthRegex.exec(htmlContent)) !== null) {
const fullTag = match[0];
const beforeW = match[1];
const afterW = match[2];
// 提取标签名
const tagNameMatch = beforeW.match(/^\s*(\w+)/);
const tagName = tagNameMatch ? tagNameMatch[1] : 'div';
zeroWidthNodes.push({
fullTag: fullTag,
tagName: tagName,
attributes: beforeW + afterW
});
}
for (let i = 0; i < zeroWidthNodes.length; i++) {
const node = zeroWidthNodes[i];
// 查找该节点的子节点
const nodePattern = this.escapeRegExp(node.fullTag);
const nodeRegex = new RegExp(nodePattern + '([\\s\\S]*?)<\\/' + node.tagName + '>', 'i');
const nodeMatch = modifiedHtml.match(nodeRegex);
if (!nodeMatch) {
continue;
}
const nodeContent = nodeMatch[1];
// 提取子节点的位置和宽度信息
const childNodes = this.extractChildNodesInfo(nodeContent);
if (childNodes.length === 0) {
continue;
}
// 过滤出有效宽度的子节点
const validChildren = childNodes.filter(child => child.w > 0);
if (validChildren.length === 0) {
continue;
}
// 计算子节点的最左边和最右边位置
let minLeft = Number.MAX_SAFE_INTEGER;
let maxRight = Number.MIN_SAFE_INTEGER;
validChildren.forEach(child => {
const childRight = child.x + child.w;
minLeft = Math.min(minLeft, child.x);
maxRight = Math.max(maxRight, childRight);
});
// 提取父节点的x坐标
const parentXMatch = node.fullTag.match(/\sx="(\d+)"/);
const parentX = parentXMatch ? parseInt(parentXMatch[1]) : 0;
// 计算父节点应该的宽度
const newW = maxRight - Math.min(parentX, minLeft);
// 替换w="0"为新计算的宽度
const newTag = node.fullTag.replace(/\sw="0"/, ` w="${newW}"`);
// 如果需要调整x坐标
const adjustedX = Math.min(parentX, minLeft);
let finalTag = newTag;
if (adjustedX !== parentX) {
finalTag = newTag.replace(/\sx="(\d+)"/, ` x="${adjustedX}"`);
}
modifiedHtml = modifiedHtml.replace(node.fullTag, finalTag);
hasChanges = true;
}
return { hasChanges, modifiedHtml };
} catch (error) {
console.error('❌ [HtmlZeroWHFixer] 修正零宽度节点失败:', error);
return { hasChanges: false, modifiedHtml: htmlContent };
}
}
/**
* 修正零高度节点
* @param htmlContent HTML内容
* @returns 修正结果
*/
private static fixZeroHeightNodes(htmlContent: string): { hasChanges: boolean, modifiedHtml: string } {
try {
let modifiedHtml = htmlContent;
let hasChanges = false;
// 查找所有h="0"的节点
const zeroHeightRegex = /<([^>]+)\sh="0"([^>]*)>/g;
let match;
const zeroHeightNodes: Array<{ fullTag: string, tagName: string, attributes: string }> = [];
while ((match = zeroHeightRegex.exec(htmlContent)) !== null) {
const fullTag = match[0];
const beforeH = match[1];
const afterH = match[2];
// 提取标签名
const tagNameMatch = beforeH.match(/^\s*(\w+)/);
const tagName = tagNameMatch ? tagNameMatch[1] : 'div';
zeroHeightNodes.push({
fullTag: fullTag,
tagName: tagName,
attributes: beforeH + afterH
});
}
for (let i = 0; i < zeroHeightNodes.length; i++) {
const node = zeroHeightNodes[i];
// 查找该节点的子节点
const nodePattern = this.escapeRegExp(node.fullTag);
const nodeRegex = new RegExp(nodePattern + '([\\s\\S]*?)<\\/' + node.tagName + '>', 'i');
const nodeMatch = modifiedHtml.match(nodeRegex);
if (!nodeMatch) {
continue;
}
const nodeContent = nodeMatch[1];
// 提取子节点的位置和高度信息
const childNodes = this.extractChildNodesInfo(nodeContent);
if (childNodes.length === 0) {
continue;
}
// 过滤出有效高度的子节点
const validChildren = childNodes.filter(child => child.h > 0);
if (validChildren.length === 0) {
continue;
}
// 计算子节点的最上边和最下边位置
let minTop = Number.MAX_SAFE_INTEGER;
let maxBottom = Number.MIN_SAFE_INTEGER;
validChildren.forEach(child => {
const childBottom = child.y + child.h;
minTop = Math.min(minTop, child.y);
maxBottom = Math.max(maxBottom, childBottom);
});
// 提取父节点的y坐标
const parentYMatch = node.fullTag.match(/\sy="(\d+)"/);
const parentY = parentYMatch ? parseInt(parentYMatch[1]) : 0;
// 计算父节点应该的高度
const newH = maxBottom - Math.min(parentY, minTop);
// 替换h="0"为新计算的高度
const newTag = node.fullTag.replace(/\sh="0"/, ` h="${newH}"`);
// 如果需要调整y坐标
const adjustedY = Math.min(parentY, minTop);
let finalTag = newTag;
if (adjustedY !== parentY) {
finalTag = newTag.replace(/\sy="(\d+)"/, ` y="${adjustedY}"`);
}
modifiedHtml = modifiedHtml.replace(node.fullTag, finalTag);
hasChanges = true;
}
return { hasChanges, modifiedHtml };
} catch (error) {
console.error('❌ [HtmlZeroWHFixer] 修正零高度节点失败:', error);
return { hasChanges: false, modifiedHtml: htmlContent };
}
}
/**
* 提取子节点的位置和尺寸信息
* @param content 节点内容
* @returns 子节点信息数组
*/
private static extractChildNodesInfo(content: string): Array<{ x: number, y: number, w: number, h: number }> {
const childNodes: Array<{ x: number, y: number, w: number, h: number }> = [];
// 查找所有带有x,y,w,h属性的标签
const nodeRegex = /<[^>]+\sx="(\d+)"[^>]+\sy="(\d+)"[^>]+\sw="(\d+)"[^>]+\sh="(\d+)"[^>]*>/g;
let match;
while ((match = nodeRegex.exec(content)) !== null) {
const x = parseInt(match[1]);
const y = parseInt(match[2]);
const w = parseInt(match[3]);
const h = parseInt(match[4]);
childNodes.push({ x, y, w, h });
}
return childNodes;
}
/**
* 转义正则表达式特殊字符
* @param string 要转义的字符串
* @returns 转义后的字符串
*/
private static escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
}