cparse
Version:
一个基于 Cheerio 的 HTML 解析和数据提取工具库
152 lines (126 loc) • 4.34 kB
JavaScript
/**
* @fileoverview 查询语法解析器 - 简化版,专注于语法糖功能
* @module query-parser
*/
/**
* 选择器和属性匹配正则表达式
* 匹配格式: selector@attribute
* 例如: "h1@text", "a@href", ".title"
*/
const rselector = /^([^@]*)(?:@\s*([\w-_:]+))?$/;
/**
* 过滤器分割正则表达式
* 匹配管道符 | 但不匹配 |=
*/
const rfilters = /\s*\|(?!=)\s*/;
const filter_parser = require('./format-parser');
const { QueryParseError, Validator } = require('./errors');
/**
* 查询解析结果缓存
* 避免重复解析相同的查询字符串
*/
const parseCache = new Map();
/**
* 解析自定义语法糖
* @param {string} selectorPart - 选择器部分
* @returns {string} 转换后的选择器
*/
function parseCustomSyntax(selectorPart) {
// 检查自定义伪选择器: selector:not-empty -> selector:not(:empty)
// 这是 Cheerio 不支持的语法糖,有实际价值
const result = selectorPart.replace(/([^:]+):not-empty/g, '$1:not(:empty)');
return result;
}
/**
* 解析选择器和属性
* @param {string} selectorPart - 选择器部分
* @returns {Object} 解析结果 {selector, attribute}
*/
function parseSelector(selectorPart) {
// 首先处理自定义语法糖
const processedSelector = parseCustomSyntax(selectorPart);
// 解析属性提取语法: selector@attribute
const match = processedSelector.match(rselector) || [];
const selector = match[1] ? match[1].trim() : match[1];
const attribute = match[2];
return { selector, attribute };
}
/**
* 解析查询字符串 - 简化版,专注于语法糖功能
*
* @param {string} str - 查询字符串
* @returns {Object} 解析结果
* @returns {string|null} returns.selector - CSS 选择器
* @returns {string|undefined} returns.attribute - 属性名
* @returns {Array} returns.filters - 过滤器数组
* @returns {boolean} returns.getAll - 是否获取所有匹配元素
*
* @example
* queryParser('h1') // { selector: 'h1', attribute: undefined, filters: [], getAll: false }
* queryParser('[h1]') // { selector: 'h1', attribute: undefined, filters: [], getAll: true }
* queryParser('a@href') // { selector: 'a', attribute: 'href', filters: [], getAll: false }
* queryParser('span | trim | int') // { selector: 'span', attribute: undefined, filters: [...], getAll: false }
* queryParser('div.active') // { selector: 'div.active', attribute: undefined, filters: [], getAll: false }
* queryParser('p:not-empty') // { selector: 'p:not(:empty)', attribute: undefined, filters: [], getAll: false }
*/
function queryParser(str) {
try {
// 参数验证
const validatedQuery = Validator.validateQuery(str);
// 检查缓存
if (parseCache.has(validatedQuery)) {
return parseCache.get(validatedQuery);
}
const originalStr = validatedQuery;
str = validatedQuery;
// 检查是否需要获取所有匹配元素 (用方括号包围)
let getAll = false;
if (str.startsWith('[') && str.endsWith(']')) {
getAll = true;
str = str.slice(1, -1).trim();
if (str === '') {
throw new QueryParseError('Empty selector inside brackets', originalStr);
}
}
// 分割过滤器
const filters = str.split(rfilters);
const selectorPart = filters.shift();
// 解析选择器和属性
const { selector, attribute } = parseSelector(selectorPart);
// 解析过滤器
const parsedFilters = filters.length ? filter_parser(filters.join('|')) : [];
const result = {
selector: selector || null,
attribute,
filters: parsedFilters,
getAll
};
// 缓存结果(限制缓存大小避免内存泄漏)
if (parseCache.size < 1000) {
parseCache.set(originalStr, result);
}
return result;
} catch (error) {
if (error instanceof QueryParseError) {
throw error;
}
throw new QueryParseError(`Failed to parse query: ${error.message}`, str, { originalError: error });
}
}
/**
* 清理查询解析缓存
* 用于测试或内存管理
*/
queryParser.clearCache = function() {
parseCache.clear();
};
/**
* 获取缓存统计信息
*/
queryParser.getCacheStats = function() {
return {
size: parseCache.size,
maxSize: 1000
};
};
module.exports = queryParser;