html-to-word-js
Version:
一个将HTML转换为DOCX文档的TypeScript库,支持标题、段落、文本格式化、列表等常见HTML元素
1,164 lines (1,155 loc) • 115 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('docx')) :
typeof define === 'function' && define.amd ? define(['exports', 'docx'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HtmlToDocxConverter = {}, global.docx));
})(this, (function (exports, docx) { 'use strict';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* 解析 style 标签中的 CSS 规则
* @param cssText - CSS 文本内容
* @returns CSSRuleSet - 解析后的 CSS 规则集合
*/
function parseCSSRules(cssText) {
var rules = {};
// 移除 CSS 注释
var cleanCSS = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
// 分割 CSS 规则
var ruleMatches = cleanCSS.match(/([^{]+)\{([^}]+)\}/g) || [];
ruleMatches.forEach(function (rule) {
// 提取选择器和属性
var match = rule.match(/([^{]+)\{([^}]+)\}/);
if (!match)
return;
var selector = match[1], propertiesText = match[2];
var cleanSelector = selector.trim();
// 提取属性键值对
var properties = {};
var propertyMatches = propertiesText.split(';').map(function (p) { return p.trim(); }).filter(function (p) { return p; });
propertyMatches.forEach(function (property) {
var _a = property.split(':').map(function (p) { return p.trim(); }), key = _a[0], value = _a[1];
if (key && value) {
properties[key] = value;
}
});
if (cleanSelector && Object.keys(properties).length > 0) {
rules[cleanSelector] = properties;
}
});
return rules;
}
/**
* 检测当前环境是否为浏览器
*/
function isBrowser() {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
/**
* 从 HTML 中提取所有 style 标签的 CSS 规则
* @param doc - DOM 文档对象
* @returns CSSRuleSet - 合并后的 CSS 规则集合
*/
function extractCSSRules(doc) {
var allRules = {};
// 获取所有 style 标签
var styleElements = doc.querySelectorAll('style');
styleElements.forEach(function (styleElement) {
var cssText = styleElement.textContent || '';
var rules = parseCSSRules(cssText);
// 合并 CSS 规则
Object.keys(rules).forEach(function (selector) {
if (!allRules[selector]) {
allRules[selector] = {};
}
// 合并属性,如果有冲突则后面的会覆盖前面的
Object.assign(allRules[selector], rules[selector]);
});
});
return allRules;
}
/**
* 解析颜色值,支持 hex、rgb、rgba、颜色名称
*/
function parseColor(colorValue) {
if (!colorValue)
return undefined;
var normalizedColor = colorValue.trim().toLowerCase();
// 移除 # 号并转换 hex 颜色
if (normalizedColor.startsWith('#')) {
var hex = normalizedColor.substring(1);
// 3位转6位
if (hex.length === 3) {
hex = hex.split('').map(function (c) { return c + c; }).join('');
}
if (hex.length === 6 && /^[0-9a-f]{6}$/i.test(hex)) {
return hex.toUpperCase();
}
}
// 解析 rgb/rgba
var rgbMatch = normalizedColor.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);
if (rgbMatch) {
var r = parseInt(rgbMatch[1], 10).toString(16).padStart(2, '0');
var g = parseInt(rgbMatch[2], 10).toString(16).padStart(2, '0');
var b = parseInt(rgbMatch[3], 10).toString(16).padStart(2, '0');
return (r + g + b).toUpperCase();
}
// 常用颜色名称映射
var colorNames = {
black: '000000',
white: 'FFFFFF',
red: 'FF0000',
green: '008000',
blue: '0000FF',
yellow: 'FFFF00',
cyan: '00FFFF',
magenta: 'FF00FF',
orange: 'FFA500',
purple: '800080',
gray: '808080',
grey: '808080',
brown: 'A52A2A',
pink: 'FFC0CB',
navy: '000080',
lime: '00FF00',
maroon: '800000',
teal: '008080',
olive: '808000',
silver: 'C0C0C0'
};
return colorNames[normalizedColor];
}
/**
* 解析字体大小,支持 px、pt、em 等单位
*/
function parseFontSize(sizeValue) {
if (!sizeValue)
return undefined;
var normalizedSize = sizeValue.trim().toLowerCase();
// 移除单位并获取数值
var match = normalizedSize.match(/^(\d*\.?\d+)(px|pt|em|rem|%)?$/);
if (!match)
return undefined;
var value = parseFloat(match[1]);
var unit = match[2] || 'px';
// 转换为半点(DOCX 使用半点作为单位)
switch (unit) {
case 'pt':
return Math.round(value * 2); // 1pt = 2 半点
case 'px':
return Math.round(value * 1.5); // 1px ≈ 0.75pt ≈ 1.5 半点
case 'em':
case 'rem':
return Math.round(value * 16 * 1.5); // 假设 1em = 16px
case '%':
return Math.round((value / 100) * 16 * 1.5); // 相对于 16px 基准
default:
return Math.round(value * 1.5);
}
}
/**
* 将像素高度转换为 DOCX 行高(twips)
*/
function convertHeightToLineHeight(heightPx) {
// 将像素转换为 twips(1px ≈ 15 twips)
return Math.round(heightPx * 15);
}
/**
* 解析 margin 值,支持 px、pt、em 等单位
*/
function parseMargin(marginValue) {
if (!marginValue)
return undefined;
var normalizedMargin = marginValue.trim().toLowerCase();
var match = normalizedMargin.match(/^(\d*\.?\d+)(px|pt|em|rem|%)?$/);
if (!match)
return undefined;
var value = parseFloat(match[1]);
var unit = match[2] || 'px';
// 转换为 twips(DOCX 使用 twips 作为间距单位,1pt = 20 twips)
switch (unit) {
case 'pt':
return Math.round(value * 20); // 1pt = 20 twips
case 'px':
return Math.round(value * 15); // 1px ≈ 0.75pt ≈ 15 twips
case 'em':
case 'rem':
return Math.round(value * 16 * 15); // 假设 1em = 16px
default:
return Math.round(value * 15); // 默认按 px 处理
}
}
/**
* 解析 margin 简写属性
* 支持以下格式:
* - margin: 10pt (所有方向)
* - margin: 10pt 20pt (上下 左右)
* - margin: 10pt 20pt 15pt (上 左右 下)
* - margin: 10pt 20pt 15pt 25pt (上 右 下 左)
*/
function parseMarginShorthand(marginValue) {
if (!marginValue)
return {};
var parts = marginValue.trim().split(/\s+/);
var result = {};
if (parts.length === 1) {
// margin: 10pt
var value = parseMargin(parts[0]);
if (value !== undefined) {
result.marginTop = value;
result.marginRight = value;
result.marginBottom = value;
result.marginLeft = value;
}
}
else if (parts.length === 2) {
// margin: 10pt 20pt (上下 左右)
var topBottom = parseMargin(parts[0]);
var leftRight = parseMargin(parts[1]);
if (topBottom !== undefined) {
result.marginTop = topBottom;
result.marginBottom = topBottom;
}
if (leftRight !== undefined) {
result.marginLeft = leftRight;
result.marginRight = leftRight;
}
}
else if (parts.length === 3) {
// margin: 10pt 20pt 15pt (上 左右 下)
var top_1 = parseMargin(parts[0]);
var leftRight = parseMargin(parts[1]);
var bottom = parseMargin(parts[2]);
if (top_1 !== undefined)
result.marginTop = top_1;
if (leftRight !== undefined) {
result.marginLeft = leftRight;
result.marginRight = leftRight;
}
if (bottom !== undefined)
result.marginBottom = bottom;
}
else if (parts.length === 4) {
// margin: 10pt 20pt 15pt 25pt (上 右 下 左)
var top_2 = parseMargin(parts[0]);
var right = parseMargin(parts[1]);
var bottom = parseMargin(parts[2]);
var left = parseMargin(parts[3]);
if (top_2 !== undefined)
result.marginTop = top_2;
if (right !== undefined)
result.marginRight = right;
if (bottom !== undefined)
result.marginBottom = bottom;
if (left !== undefined)
result.marginLeft = left;
}
return result;
}
/**
* 解析缩进值,支持 px、pt、em 等单位
*/
function parseIndent(indentValue) {
if (!indentValue)
return undefined;
var normalizedIndent = indentValue.trim().toLowerCase();
var match = normalizedIndent.match(/^(\d*\.?\d+)(px|pt|em|rem|%)?$/);
if (!match)
return undefined;
var value = parseFloat(match[1]);
var unit = match[2] || 'px';
// 转换为 twips(DOCX 使用 twips 作为缩进单位,1pt = 20 twips)
switch (unit) {
case 'pt':
return Math.round(value * 20);
case 'px':
return Math.round(value * 15); // 1px ≈ 0.75pt ≈ 15 twips
case 'em':
case 'rem':
return Math.round(value * 16 * 15); // 假设 1em = 16px
default:
return Math.round(value * 15);
}
}
/**
* 解析边框宽度,支持 px、pt 等单位,转换为 twips
*/
function parseBorderWidth(widthValue) {
if (!widthValue)
return undefined;
var normalizedWidth = widthValue.trim().toLowerCase();
// 处理常见关键字
switch (normalizedWidth) {
case 'thin':
return 15; // 1px ≈ 15 twips
case 'medium':
return 30; // 2px ≈ 30 twips
case 'thick':
return 45; // 3px ≈ 45 twips
case 'none':
case '0':
return 0;
}
var match = normalizedWidth.match(/^(\d*\.?\d+)(px|pt|em|rem)?$/);
if (!match)
return undefined;
var value = parseFloat(match[1]);
var unit = match[2] || 'px';
if (Number.isNaN(value) || value < 0)
return undefined;
// 转换为 twips(1pt = 20 twips)
switch (unit) {
case 'pt':
return Math.round(value * 20);
case 'px':
return Math.round(value * 15); // 1px ≈ 0.75pt ≈ 15 twips
case 'em':
case 'rem':
return Math.round(value * 16 * 15); // 假设 1em = 16px
default:
return Math.round(value * 15); // 默认按px处理
}
}
/**
* 解析边框样式
*/
function parseBorderStyle(styleValue) {
if (!styleValue)
return undefined;
var normalizedStyle = styleValue.trim().toLowerCase();
switch (normalizedStyle) {
case 'solid':
return 'single';
case 'double':
return 'double';
case 'dotted':
return 'dotted';
case 'dashed':
return 'dashed';
case 'none':
case 'hidden':
return 'none';
default:
return 'single'; // 默认为实线
}
}
/**
* 解析边框简写属性,如 "1px solid red" 或 "2pt dashed #333"
* 特殊处理rgb颜色值,如 "1pt solid rgb(0, 0, 1)"
*/
function parseBorder(borderValue) {
if (!borderValue || borderValue.trim().toLowerCase() === 'none') {
return { style: 'none', width: 0 };
}
var border = {};
var remainingValue = borderValue.trim();
// 尝试提取宽度
var widthMatch = remainingValue.match(/^\s*(\d*\.?\d+\s*(?:px|pt|em|rem|thin|medium|thick))/i);
if (widthMatch) {
border.width = parseBorderWidth(widthMatch[1]);
remainingValue = remainingValue.replace(widthMatch[0], '').trim();
}
// 尝试提取样式
var styleMatch = remainingValue.match(/^\s*(solid|double|dotted|dashed|none)/i);
if (styleMatch) {
border.style = parseBorderStyle(styleMatch[1]);
remainingValue = remainingValue.replace(styleMatch[0], '').trim();
}
// 尝试提取颜色
// 处理rgb/rgba颜色值(可能包含逗号)
var colorMatch = remainingValue.match(/^\s*(rgba?\(.*?\)|#[0-9a-f]{3,6}|[a-zA-Z]+)/i);
if (colorMatch) {
border.color = parseColor(colorMatch[1]);
}
// 设置默认值
if (!border.width)
border.width = 15; // 默认1px
if (!border.style)
border.style = 'single'; // 默认实线
if (!border.color)
border.color = '000000'; // 默认黑色
return border;
}
/**
* 解析缩放值,支持百分比 - 用于字符间距缩放
*/
function parseScale(scaleValue) {
if (!scaleValue)
return undefined;
var normalizedScale = scaleValue.trim();
// 移除 % 号并获取数值
var value = parseFloat(normalizedScale.replace('%', ''));
if (Number.isNaN(value) || value <= 0)
return undefined;
// 返回缩放比例 (例如:55% 返回 55)
return value;
}
/**
* 解析文本对齐值
*/
function parseTextAlign(alignValue) {
if (!alignValue)
return undefined;
var normalizedAlign = alignValue.trim().toLowerCase();
switch (normalizedAlign) {
case 'left':
return 'left';
case 'center':
return 'center';
case 'right':
return 'right';
case 'justify':
return 'justify';
default:
return undefined;
}
}
/**
* 将文本对齐值转换为 DOCX AlignmentType
*/
function convertToAlignmentType(align) {
if (!align)
return undefined;
switch (align) {
case 'left':
return docx.AlignmentType.LEFT;
case 'center':
return docx.AlignmentType.CENTER;
case 'right':
return docx.AlignmentType.RIGHT;
case 'justify':
return docx.AlignmentType.JUSTIFIED;
default:
return undefined;
}
}
/**
* 将ParsedBorderStyle转换为DOCX BorderStyle
*/
function convertToDOCXBorderStyle(border) {
// 确保即使是默认边框也有样式
if (!border) {
return undefined;
}
// 只有明确指定为none的边框才返回无边框样式
if (border.style === 'none' || border.width === 0) {
return undefined;
}
var docxStyle = docx.BorderStyle.SINGLE;
var styleType = border.style;
if (styleType) {
switch (styleType) {
case 'single':
docxStyle = docx.BorderStyle.SINGLE;
break;
case 'double':
docxStyle = docx.BorderStyle.DOUBLE;
break;
case 'dotted':
docxStyle = docx.BorderStyle.DOTTED;
break;
case 'dashed':
docxStyle = docx.BorderStyle.DASHED;
break;
case 'none':
return undefined;
default:
docxStyle = docx.BorderStyle.SINGLE;
break;
}
}
return {
style: docxStyle,
size: Math.max(1, Math.round((border.width || 15) / 8)),
color: border.color || '000000'
};
}
/**
* 转换行高设置为DOCX spacing属性
*/
function convertToSpacing(lineHeight) {
if (!lineHeight)
return undefined;
if (lineHeight.lineRule === 'auto') {
// 倍数行距或自动行距
return { line: lineHeight.line, lineRule: 'auto' };
}
if (lineHeight.lineRule === 'atLeast') {
// 最小行距:确保行高不小于指定值
return { line: lineHeight.line, lineRule: 'atLeast' };
}
// 固定行距
return { line: lineHeight.line, lineRule: 'exact' };
}
/**
* 解析行高值,返回包含值和规则的对象
* @param lineHeightValue 行高值字符串
* @param baseFontSize 基础字体大小(半点单位,来自 parseFontSize)
*/
function parseLineHeight(lineHeightValue, baseFontSize) {
if (baseFontSize === void 0) { baseFontSize = 24; }
if (!lineHeightValue)
return undefined;
var normalizedLineHeight = lineHeightValue.trim().toLowerCase();
// 将基础字体大小从半点转换为点
var baseFontSizeInPt = baseFontSize / 2;
// 处理 normal 关键字 - 使用自动行距
if (normalizedLineHeight === 'normal') {
return {
line: Math.round(baseFontSizeInPt * 1.2 * 20),
lineRule: 'auto'
};
}
// 纯数字(倍数行距)
var numberMatch = normalizedLineHeight.match(/^(\d*\.?\d+)$/);
if (numberMatch) {
var multiplier = parseFloat(numberMatch[1]);
if (!Number.isNaN(multiplier) && multiplier > 0) {
// 倍数行距:使用相对于字体大小的倍数
return {
line: Math.round(240 * multiplier),
lineRule: 'auto'
};
}
}
// 带单位的值(固定行距)
var unitMatch = normalizedLineHeight.match(/^(\d*\.?\d+)(px|pt|em|rem|%)?$/);
if (!unitMatch)
return undefined;
var value = parseFloat(unitMatch[1]);
var unit = unitMatch[2] || 'px';
if (Number.isNaN(value) || value <= 0)
return undefined;
// 固定行距:转换为 twips(1pt = 20 twips)
var lineValue;
switch (unit) {
case 'pt':
lineValue = Math.round(value * 20); // 1pt = 20 twips
break;
case 'px':
lineValue = Math.round(value * 15); // 1px ≈ 0.75pt ≈ 15 twips
break;
case 'em':
case 'rem':
// 基于基础字体大小
lineValue = Math.round(baseFontSizeInPt * value * 20);
break;
case '%': {
// 百分比转换为倍数行距
var multiplier = value / 100;
return {
line: Math.round(240 * multiplier),
lineRule: 'auto'
};
}
default:
lineValue = Math.round(value * 15); // 默认按px处理
break;
}
return {
line: lineValue,
lineRule: 'exact' // 固定行距
};
}
/**
* 使用JSDOM获取元素的计算样式
*/
/**
* 解析元素的内联样式和自定义属性,同时应用CSS规则和计算样式
*/
function parseInlineStyle(element, cssRules, parentElement) {
var _a, _b;
var style = {};
var styleAttr = element.getAttribute('style');
// 0. 如果有父元素,先继承父元素的样式
if (parentElement) {
var parentStyle = parseInlineStyle(parentElement, cssRules);
// 继承可继承的属性
if (parentStyle.color)
style.color = parentStyle.color;
if (parentStyle.fontSize)
style.fontSize = parentStyle.fontSize;
if (parentStyle.fontFamily)
style.fontFamily = parentStyle.fontFamily;
if (parentStyle.bold !== undefined)
style.bold = parentStyle.bold;
if (parentStyle.italic !== undefined)
style.italic = parentStyle.italic;
if (parentStyle.underline !== undefined)
style.underline = parentStyle.underline;
if (parentStyle.textAlign)
style.textAlign = parentStyle.textAlign;
if (parentStyle.lineHeight)
style.lineHeight = parentStyle.lineHeight;
if (parentStyle.characterSpacing !== undefined) {
style.characterSpacing = parentStyle.characterSpacing;
}
}
// 解析 data-scale 自定义属性 - 用于字符间距缩放
var dataScale = element.getAttribute('data-scale');
if (dataScale) {
console.log('找到 data-scale:', dataScale, '在元素:', element.tagName);
style.characterSpacing = parseScale(dataScale);
}
// 1. 应用来自style标签的CSS规则(如果有)
if (cssRules) {
// 获取元素的选择器路径
var tagName_1 = ((_a = element.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
var id_1 = element.getAttribute('id');
var classList_1 = ((_b = element.getAttribute('class')) === null || _b === void 0 ? void 0 : _b.split(/\s+/).filter(function (cls) { return cls.trim(); })) || [];
// 检查并应用匹配的CSS规则
Object.keys(cssRules).forEach(function (selector) {
var _a, _b;
var isMatch = false;
// 标签选择器匹配
if (selector === tagName_1) {
isMatch = true;
}
else if (selector.startsWith('#') && id_1 && selector === "#".concat(id_1)) {
// ID选择器匹配
isMatch = true;
}
else if (selector.startsWith('.') && classList_1.some(function (cls) { return selector === ".".concat(cls); })) {
// 类选择器匹配
isMatch = true;
}
else if (selector.includes('#')) {
// 组合选择器匹配(简单版本)
var selectorId = (_a = selector.match(/#([^.\s]+)/)) === null || _a === void 0 ? void 0 : _a[1];
if (selectorId && id_1 === selectorId) {
isMatch = true;
}
}
else if (selector.includes('.')) {
var selectorClass = (_b = selector.match(/\.([^#\s]+)/)) === null || _b === void 0 ? void 0 : _b[1];
if (selectorClass && classList_1.includes(selectorClass)) {
isMatch = true;
}
}
if (isMatch) {
var ruleProperties_1 = cssRules[selector];
Object.keys(ruleProperties_1).forEach(function (property) {
var value = ruleProperties_1[property];
// 应用CSS规则属性
switch (property.toLowerCase()) {
case 'color':
if (!style.color) {
style.color = parseColor(value);
}
break;
case 'font-size':
if (!style.fontSize) {
style.fontSize = parseFontSize(value);
}
break;
case 'font-family':
if (!style.fontFamily) {
style.fontFamily = value.replace(/['"]/g, '').split(',')[0].trim();
}
break;
case 'font-weight':
if (style.bold === undefined) {
style.bold = value === 'bold' || value === 'bolder' || parseInt(value, 10) >= 600;
}
break;
case 'font-style':
if (style.italic === undefined) {
style.italic = value === 'italic' || value === 'oblique';
}
break;
case 'text-decoration':
if (style.underline === undefined) {
style.underline = value.includes('underline');
}
if (style.strikethrough === undefined) {
style.strikethrough = value.includes('line-through');
}
break;
case 'text-indent':
case 'padding-left':
if (!style.indent) { // 只取第一个有效的缩进值
style.indent = parseIndent(value);
}
break;
case 'text-align':
if (!style.textAlign) {
style.textAlign = parseTextAlign(value);
}
break;
case 'line-height':
// 需要传入当前字体大小作为基准,如果没有则使用默认值
if (!style.lineHeight) {
style.lineHeight = parseLineHeight(value, style.fontSize || 24);
}
break;
case 'background-color':
if (!style.backgroundColor) {
style.backgroundColor = parseColor(value);
}
break;
case 'border':
if (!style.border) {
style.border = parseBorder(value);
}
break;
case 'border-top':
if (!style.borderTop) {
style.borderTop = parseBorder(value);
}
break;
case 'border-right':
if (!style.borderRight) {
style.borderRight = parseBorder(value);
}
break;
case 'border-bottom':
if (!style.borderBottom) {
style.borderBottom = parseBorder(value);
}
break;
case 'border-left':
if (!style.borderLeft) {
style.borderLeft = parseBorder(value);
}
break;
case 'margin-bottom':
if (!style.marginBottom) {
style.marginBottom = parseMargin(value);
}
break;
case 'margin-top':
if (!style.marginTop) {
style.marginTop = parseMargin(value);
}
break;
case 'margin-right':
if (!style.marginRight) {
style.marginRight = parseMargin(value);
}
break;
case 'margin-left':
if (!style.marginLeft) {
style.marginLeft = parseMargin(value);
}
break;
}
});
}
});
}
// 2. 解析内联样式(优先级高于CSS规则)
if (styleAttr) {
var inlineRules = styleAttr.split(';').map(function (rule) { return rule.trim(); }).filter(function (rule) { return rule; });
inlineRules.forEach(function (rule) {
var _a = rule.split(':').map(function (s) { return s.trim(); }), property = _a[0], value = _a[1];
if (!property || !value)
return;
switch (property.toLowerCase()) {
case 'color':
style.color = parseColor(value);
break;
case 'font-size':
style.fontSize = parseFontSize(value);
break;
case 'font-family':
style.fontFamily = value.replace(/['"]/g, '').split(',')[0].trim();
break;
case 'font-weight':
style.bold = value === 'bold' || value === 'bolder' || parseInt(value, 10) >= 600;
break;
case 'font-style':
style.italic = value === 'italic' || value === 'oblique';
break;
case 'text-decoration':
style.underline = value.includes('underline');
style.strikethrough = value.includes('line-through');
break;
case 'text-indent':
case 'padding-left':
if (!style.indent) { // 只取第一个有效的缩进值
style.indent = parseIndent(value);
}
break;
case 'margin-top':
style.marginTop = parseMargin(value);
break;
case 'margin-right':
style.marginRight = parseMargin(value);
break;
case 'margin-bottom':
style.marginBottom = parseMargin(value);
break;
case 'margin-left':
style.marginLeft = parseMargin(value);
break;
case 'margin': {
// 处理 margin 简写属性
var marginShorthand = parseMarginShorthand(value);
if (marginShorthand.marginTop !== undefined) {
style.marginTop = marginShorthand.marginTop;
}
if (marginShorthand.marginRight !== undefined) {
style.marginRight = marginShorthand.marginRight;
}
if (marginShorthand.marginBottom !== undefined) {
style.marginBottom = marginShorthand.marginBottom;
}
if (marginShorthand.marginLeft !== undefined) {
style.marginLeft = marginShorthand.marginLeft;
}
break;
}
case 'text-align':
style.textAlign = parseTextAlign(value);
break;
case 'line-height':
// 需要传入当前字体大小作为基准,如果没有则使用默认值
style.lineHeight = parseLineHeight(value, style.fontSize || 24);
break;
case 'background-color':
style.backgroundColor = parseColor(value);
break;
case 'border':
style.border = parseBorder(value);
break;
case 'border-top':
style.borderTop = parseBorder(value);
break;
case 'border-right':
style.borderRight = parseBorder(value);
break;
case 'border-bottom':
style.borderBottom = parseBorder(value);
break;
case 'border-left':
style.borderLeft = parseBorder(value);
break;
case 'border-width':
// 处理单独的宽度设置
if (!style.border)
style.border = {};
style.border.width = parseBorderWidth(value);
break;
case 'border-style':
// 处理单独的样式设置
if (!style.border)
style.border = {};
style.border.style = parseBorderStyle(value);
break;
case 'border-color':
// 处理单独的颜色设置
if (!style.border)
style.border = {};
style.border.color = parseColor(value);
break;
}
});
}
// 3. 在没有获取到关键样式属性时,尝试使用浏览器原生的getComputedStyle获取计算样式(样式继承)
// 只在浏览器环境中并且缺少某些关键样式属性时才使用
if (typeof window !== 'undefined' && (!style.color || !style.fontSize || !style.fontFamily)) {
try {
// 注意:在实际的浏览器环境中,我们可以直接使用DOM元素的getComputedStyle
// 这里我们只是返回一个空对象,因为在实际使用时,cheerio元素没有直接对应的DOM元素
// 在实际集成到浏览器环境的代码中,应该传入真实的DOM元素而不是cheerio元素
return style;
}
catch (error) {
console.warn('获取计算样式失败:', error);
}
}
return style;
}
/**
* 合并多个样式对象
*/
function mergeStyles() {
var styles = [];
for (var _i = 0; _i < arguments.length; _i++) {
styles[_i] = arguments[_i];
}
var merged = {};
styles.forEach(function (style) {
if (style.color)
merged.color = style.color;
if (style.fontSize)
merged.fontSize = style.fontSize;
if (style.fontFamily)
merged.fontFamily = style.fontFamily;
if (style.bold !== undefined)
merged.bold = style.bold;
if (style.italic !== undefined)
merged.italic = style.italic;
if (style.underline !== undefined)
merged.underline = style.underline;
if (style.strikethrough !== undefined)
merged.strikethrough = style.strikethrough;
if (style.indent !== undefined)
merged.indent = style.indent;
if (style.characterSpacing !== undefined)
merged.characterSpacing = style.characterSpacing;
if (style.textAlign)
merged.textAlign = style.textAlign;
if (style.lineHeight)
merged.lineHeight = style.lineHeight;
if (style.backgroundColor)
merged.backgroundColor = style.backgroundColor;
if (style.border)
merged.border = style.border;
if (style.borderTop)
merged.borderTop = style.borderTop;
if (style.borderRight)
merged.borderRight = style.borderRight;
if (style.borderBottom)
merged.borderBottom = style.borderBottom;
if (style.borderLeft)
merged.borderLeft = style.borderLeft;
if (style.marginTop !== undefined)
merged.marginTop = style.marginTop;
if (style.marginRight !== undefined)
merged.marginRight = style.marginRight;
if (style.marginBottom !== undefined)
merged.marginBottom = style.marginBottom;
if (style.marginLeft !== undefined)
merged.marginLeft = style.marginLeft;
if (style.href)
merged.href = style.href;
});
return merged;
}
/**
* 计算最终的字体大小 - 简化版本,不再处理缩放
*/
function calculateFinalFontSize(currentFontSize, parentFontSize) {
return currentFontSize || parentFontSize;
}
/**
* 计算字符缩放值,用于 DOCX 的 scale 属性
*/
function calculateCharacterScale(scale) {
if (scale === undefined)
return undefined;
// DOCX 中 scale 属性的单位是百分比
// 直接返回百分比值,例如 55% -> 55
return Math.round(scale);
}
/**
* 从 URL 或 base64 字符串获取图片数据
*/
function getImageData(src) {
return __awaiter(this, void 0, void 0, function () {
var base64Data, mimeType_1, response, blob, arrayBuffer, data_1, data, width, height, i, response, blob_1, arrayBuffer, data_2, response, blob_2, arrayBuffer, data_3, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 16, , 17]);
if (!src.startsWith('data:')) return [3 /*break*/, 5];
base64Data = src.split(',')[1];
mimeType_1 = src.split(',')[0].split(':')[1].split(';')[0];
if (!isBrowser()) return [3 /*break*/, 4];
return [4 /*yield*/, fetch(src)];
case 1:
response = _a.sent();
return [4 /*yield*/, response.blob()];
case 2:
blob = _a.sent();
return [4 /*yield*/, blob.arrayBuffer()];
case 3:
arrayBuffer = _a.sent();
data_1 = new Uint8Array(arrayBuffer);
// 创建临时 Image 元素获取尺寸
return [2 /*return*/, new Promise(function (resolve) {
var img = new Image();
img.onload = function () {
resolve({
data: data_1,
width: img.width,
height: img.height,
type: mimeType_1
});
};
img.onerror = function () { return resolve(null); };
img.src = src;
})];
case 4:
data = Buffer.from(base64Data, 'base64');
width = 400;
height = 300;
try {
if (mimeType_1 === 'image/png') {
// PNG 文件头包含尺寸信息
if (data.length >= 24) {
width = data.readUInt32BE(16);
height = data.readUInt32BE(20);
}
}
else if (mimeType_1 === 'image/jpeg') {
i = 0;
while (i < data.length - 2) {
if (data[i] === 0xFF && data[i + 1] === 0xC0) {
// SOF0 标记
if (i + 9 < data.length) {
height = data.readUInt16BE(i + 5);
width = data.readUInt16BE(i + 7);
break;
}
}
i += 1;
}
}
else if (mimeType_1 === 'image/gif') {
// GIF 文件头包含尺寸信息
if (data.length >= 10) {
width = data.readUInt16LE(6);
height = data.readUInt16LE(8);
}
}
}
catch (error) {
console.warn('解析图片尺寸失败,使用默认尺寸:', error);
}
return [2 /*return*/, {
data: data,
width: width,
height: height,
type: mimeType_1
}];
case 5:
if (!src.startsWith('blob:')) return [3 /*break*/, 10];
if (!isBrowser()) return [3 /*break*/, 9];
return [4 /*yield*/, fetch(src)];
case 6:
response = _a.sent();
if (!response.ok)
throw new Error("\u65E0\u6CD5\u83B7\u53D6 blob \u6570\u636E: HTTP ".concat(response.status));
return [4 /*yield*/, response.blob()];
case 7:
blob_1 = _a.sent();
return [4 /*yield*/, blob_1.arrayBuffer()];
case 8:
arrayBuffer = _a.sent();
data_2 = new Uint8Array(arrayBuffer);
// 创建临时 Image 元素获取尺寸
return [2 /*return*/, new Promise(function (resolve) {
var img = new Image();
img.onload = function () {
resolve({
data: data_2,
width: img.width,
height: img.height,
type: blob_1.type || 'image/jpeg'
});
};
img.onerror = function () { return resolve(null); };
img.src = src;
})];
case 9:
// Node.js 环境不支持 blob URL
throw new Error('Node.js 环境下不支持 blob: URL,请将图片转换为 base64 格式');
case 10:
if (!(src.startsWith('http://') || src.startsWith('https://'))) return [3 /*break*/, 15];
if (!isBrowser()) return [3 /*break*/, 14];
return [4 /*yield*/, fetch(src)];
case 11:
response = _a.sent();
if (!response.ok)
throw new Error("HTTP ".concat(response.status));
return [4 /*yield*/, response.blob()];
case 12:
blob_2 = _a.sent();
return [4 /*yield*/, blob_2.arrayBuffer()];
case 13:
arrayBuffer = _a.sent();
data_3 = new Uint8Array(arrayBuffer);
// 创建临时 Image 元素获取尺寸
return [2 /*return*/, new Promise(function (resolve) {
var img = new Image();
img.onload = function () {
resolve({
data: data_3,
width: img.width,
height: img.height,
type: blob_2.type || 'image/jpeg'
});
};
img.onerror = function () { return resolve(null); };
img.src = src;
})];
case 14:
// Node.js 环境:仅支持 base64,远程图片需要用户预处理
throw new Error('Node.js 环境下仅支持 base64 图片,远程图片请先转换为 base64');
case 15:
// 其他格式的 URL 或路径
throw new Error("\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F\u6216\u8DEF\u5F84: ".concat(src));
case 16:
error_1 = _a.sent();
console.warn('Failed to load image:', src, error_1);
return [2 /*return*/, null];
case 17: return [2 /*return*/];
}
});
});
}
/**
* 计算 Word 文档的书写区域宽度(以像素为单位)
* A4 纸宽