cparse
Version:
一个基于 Cheerio 的 HTML 解析和数据提取工具库
310 lines (278 loc) • 7.29 kB
JavaScript
/**
* 自定义错误类和错误处理工具
*/
/**
* 基础解析错误类
*/
class ParseError extends Error {
constructor(message, details = {}) {
super(message);
this.name = 'ParseError';
this.details = details;
// 保持堆栈跟踪
if (Error.captureStackTrace) {
Error.captureStackTrace(this, ParseError);
}
}
}
/**
* 查询解析错误
*/
class QueryParseError extends ParseError {
constructor(message, query, details = {}) {
super(message, { query, ...details });
this.name = 'QueryParseError';
this.query = query;
}
}
/**
* 过滤器错误
*/
class FilterError extends ParseError {
constructor(message, filterName, value, details = {}) {
super(message, { filterName, value, ...details });
this.name = 'FilterError';
this.filterName = filterName;
this.value = value;
}
}
/**
* 选择器错误
*/
class SelectorError extends ParseError {
constructor(message, selector, details = {}) {
super(message, { selector, ...details });
this.name = 'SelectorError';
this.selector = selector;
}
}
/**
* 验证错误
*/
class ValidationError extends ParseError {
constructor(message, field, value, details = {}) {
super(message, { field, value, ...details });
this.name = 'ValidationError';
this.field = field;
this.value = value;
}
}
/**
* 输入验证器
*/
class Validator {
/**
* 验证查询字符串
* @param {*} query - 查询输入
* @returns {string} 验证后的查询字符串
* @throws {ValidationError} 验证失败时抛出
*/
static validateQuery(query) {
if (typeof query !== 'string') {
throw new ValidationError(
`Query must be a string, got ${typeof query}`,
'query',
query,
{ expectedType: 'string', actualType: typeof query }
);
}
const trimmed = query.trim();
if (trimmed === '') {
throw new ValidationError(
'Query string cannot be empty',
'query',
query,
{ reason: 'empty_string' }
);
}
// 检查查询长度
if (trimmed.length > 1000) {
throw new ValidationError(
'Query string is too long (max 1000 characters)',
'query',
query,
{ maxLength: 1000, actualLength: trimmed.length }
);
}
return trimmed;
}
/**
* 验证选择器
* @param {*} selector - 选择器输入
* @returns {string} 验证后的选择器
* @throws {ValidationError} 验证失败时抛出
*/
static validateSelector(selector) {
if (selector === null || selector === undefined) {
return selector;
}
if (typeof selector !== 'string') {
throw new ValidationError(
`Selector must be a string or null, got ${typeof selector}`,
'selector',
selector,
{ expectedType: 'string|null', actualType: typeof selector }
);
}
// 检查危险的选择器模式
const dangerousPatterns = [
/javascript:/i,
/data:/i,
/vbscript:/i,
/<script/i,
/on\w+\s*=/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(selector)) {
throw new ValidationError(
'Selector contains potentially dangerous content',
'selector',
selector,
{ reason: 'dangerous_pattern', pattern: pattern.toString() }
);
}
}
return selector;
}
/**
* 验证过滤器名称
* @param {*} filterName - 过滤器名称
* @param {Object} availableFilters - 可用过滤器对象
* @returns {string} 验证后的过滤器名称
* @throws {ValidationError} 验证失败时抛出
*/
static validateFilterName(filterName, availableFilters = {}) {
if (typeof filterName !== 'string') {
throw new ValidationError(
`Filter name must be a string, got ${typeof filterName}`,
'filterName',
filterName,
{ expectedType: 'string', actualType: typeof filterName }
);
}
if (filterName.trim() === '') {
throw new ValidationError(
'Filter name cannot be empty',
'filterName',
filterName,
{ reason: 'empty_string' }
);
}
// 检查过滤器是否存在
if (Object.keys(availableFilters).length > 0 && !availableFilters[filterName]) {
throw new ValidationError(
`Unknown filter: ${filterName}`,
'filterName',
filterName,
{
reason: 'unknown_filter',
availableFilters: Object.keys(availableFilters)
}
);
}
return filterName;
}
/**
* 验证HTML内容
* @param {*} html - HTML内容
* @returns {string} 验证后的HTML
* @throws {ValidationError} 验证失败时抛出
*/
static validateHtml(html) {
if (html === null || html === undefined) {
return html;
}
if (typeof html !== 'string') {
throw new ValidationError(
`HTML must be a string, got ${typeof html}`,
'html',
html,
{ expectedType: 'string', actualType: typeof html }
);
}
// 检查HTML长度
if (html.length > 10 * 1024 * 1024) { // 10MB
throw new ValidationError(
'HTML content is too large (max 10MB)',
'html',
html,
{ maxSize: '10MB', actualSize: `${Math.round(html.length / 1024 / 1024)}MB` }
);
}
return html;
}
}
/**
* 错误处理工具
*/
class ErrorHandler {
/**
* 包装函数以提供统一的错误处理
* @param {Function} fn - 要包装的函数
* @param {string} context - 错误上下文
* @returns {Function} 包装后的函数
*/
static wrap(fn, context = 'unknown') {
return function(...args) {
try {
return fn.apply(this, args);
} catch (error) {
if (error instanceof ParseError) {
// 重新抛出已知的解析错误
throw error;
}
// 包装未知错误
throw new ParseError(
`${context}: ${error.message}`,
{
originalError: error,
context,
args: args.map(arg => typeof arg === 'object' ? '[Object]' : String(arg))
}
);
}
};
}
/**
* 安全执行函数,返回结果或错误信息
* @param {Function} fn - 要执行的函数
* @param {*} defaultValue - 默认返回值
* @returns {*} 执行结果或默认值
*/
static safeExecute(fn, defaultValue = null) {
try {
return fn();
} catch (error) {
console.warn('Safe execution failed:', error.message);
return defaultValue;
}
}
/**
* 格式化错误信息
* @param {Error} error - 错误对象
* @returns {Object} 格式化的错误信息
*/
static formatError(error) {
const formatted = {
name: error.name || 'Error',
message: error.message,
timestamp: new Date().toISOString()
};
if (error instanceof ParseError) {
formatted.details = error.details;
}
if (error.stack) {
formatted.stack = error.stack.split('\n').slice(0, 5); // 只保留前5行堆栈
}
return formatted;
}
}
module.exports = {
ParseError,
QueryParseError,
FilterError,
SelectorError,
ValidationError,
Validator,
ErrorHandler
};