@ry-krystal/kicad-converter
Version:
专业的KiCad符号文件与JSON互转工具
357 lines (356 loc) • 13 kB
JavaScript
/**
* 数据验证器 - 核心版本
* 负责验证KiCad数据结构的完整性和正确性
*/
/**
* 验证器类
*/
export class KiCadValidator {
config;
constructor(config = {}) {
this.config = {
checkRequiredFields: true,
checkDataTypes: true,
checkValueRanges: true,
checkReferenceIntegrity: true,
strictMode: false,
...config
};
}
/**
* 验证完整的符号库
* @param symbolLib 符号库数据
* @returns 验证结果
*/
validateSymbolLib(symbolLib) {
const errors = [];
const warnings = [];
const suggestions = [];
try {
// 验证根级别字段
this.validateRootFields(symbolLib, errors, warnings);
// 验证每个符号
symbolLib.symbols.forEach((symbol, index) => {
const symbolResult = this.validateSymbol(symbol, `symbols[${index}]`);
errors.push(...symbolResult.errors);
warnings.push(...symbolResult.warnings);
suggestions.push(...symbolResult.suggestions);
});
// 检查符号名称重复
this.checkDuplicateSymbolNames(symbolLib.symbols, warnings);
// 生成改进建议
this.generateSuggestions(symbolLib, suggestions);
}
catch (error) {
errors.push(`验证过程出错: ${error instanceof Error ? error.message : String(error)}`);
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions
};
}
/**
* 验证单个符号
* @param symbol 符号数据
* @param path 路径前缀
* @returns 验证结果
*/
validateSymbol(symbol, path = '') {
const errors = [];
const warnings = [];
const suggestions = [];
// 验证符号基本字段
this.validateSymbolFields(symbol, path, errors, warnings);
// 验证属性
symbol.properties.forEach((property, index) => {
const propResult = this.validateProperty(property, `${path}.properties[${index}]`);
errors.push(...propResult.errors);
warnings.push(...propResult.warnings);
});
// 验证引脚
symbol.pins.forEach((pin, index) => {
const pinResult = this.validatePin(pin, `${path}.pins[${index}]`);
errors.push(...pinResult.errors);
warnings.push(...pinResult.warnings);
});
// 验证图形元素
symbol.graphics.forEach((graphic, index) => {
const graphicResult = this.validateGraphic(graphic, `${path}.graphics[${index}]`);
errors.push(...graphicResult.errors);
warnings.push(...graphicResult.warnings);
});
// 检查符号的完整性
this.checkSymbolIntegrity(symbol, path, warnings, suggestions);
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions
};
}
/**
* 验证根级别字段
*/
validateRootFields(symbolLib, errors, warnings) {
if (this.config.checkRequiredFields) {
if (!symbolLib.version) {
errors.push('缺少必需字段: version');
}
if (!symbolLib.generator) {
warnings.push('建议添加generator字段');
}
if (!Array.isArray(symbolLib.symbols)) {
errors.push('symbols字段必须是数组');
}
}
if (this.config.checkDataTypes) {
if (symbolLib.version && typeof symbolLib.version !== 'string') {
errors.push('version字段必须是字符串');
}
if (symbolLib.generator && typeof symbolLib.generator !== 'string') {
errors.push('generator字段必须是字符串');
}
}
if (this.config.checkValueRanges) {
if (symbolLib.version && !this.isValidVersion(symbolLib.version)) {
warnings.push(`版本格式可能不正确: ${symbolLib.version}`);
}
}
}
/**
* 验证符号字段
*/
validateSymbolFields(symbol, path, errors, _warnings) {
if (this.config.checkRequiredFields) {
if (!symbol.name) {
errors.push(`${path}: 缺少必需字段name`);
}
if (!Array.isArray(symbol.properties)) {
errors.push(`${path}: properties字段必须是数组`);
}
if (!Array.isArray(symbol.pins)) {
errors.push(`${path}: pins字段必须是数组`);
}
if (!Array.isArray(symbol.graphics)) {
errors.push(`${path}: graphics字段必须是数组`);
}
}
if (this.config.checkDataTypes) {
if (symbol.name && typeof symbol.name !== 'string') {
errors.push(`${path}: name字段必须是字符串`);
}
if (symbol.excludeFromSim !== undefined && typeof symbol.excludeFromSim !== 'boolean') {
errors.push(`${path}: excludeFromSim字段必须是布尔值`);
}
if (symbol.inBom !== undefined && typeof symbol.inBom !== 'boolean') {
errors.push(`${path}: inBom字段必须是布尔值`);
}
if (symbol.onBoard !== undefined && typeof symbol.onBoard !== 'boolean') {
errors.push(`${path}: onBoard字段必须是布尔值`);
}
}
}
/**
* 验证属性
*/
validateProperty(property, path) {
const errors = [];
const warnings = [];
const suggestions = [];
if (this.config.checkRequiredFields) {
if (!property.name) {
errors.push(`${path}: 缺少必需字段name`);
}
if (property.value === undefined || property.value === null) {
errors.push(`${path}: 缺少必需字段value`);
}
if (!property.position) {
errors.push(`${path}: 缺少必需字段position`);
}
}
if (this.config.checkDataTypes) {
if (property.name && typeof property.name !== 'string') {
errors.push(`${path}: name字段必须是字符串`);
}
if (property.value && typeof property.value !== 'string') {
errors.push(`${path}: value字段必须是字符串`);
}
if (property.position) {
if (typeof property.position.x !== 'number' || typeof property.position.y !== 'number') {
errors.push(`${path}: position.x和position.y必须是数字`);
}
}
}
// 检查常见属性名称
if (property.name && this.isCommonProperty(property.name)) {
if (!property.value.trim()) {
warnings.push(`${path}: 重要属性${property.name}的值为空`);
}
}
return { isValid: errors.length === 0, errors, warnings, suggestions };
}
/**
* 验证引脚
*/
validatePin(pin, path) {
const errors = [];
const warnings = [];
const suggestions = [];
if (this.config.checkRequiredFields) {
if (!pin.type) {
errors.push(`${path}: 缺少必需字段type`);
}
if (!pin.shape) {
errors.push(`${path}: 缺少必需字段shape`);
}
if (!pin.position) {
errors.push(`${path}: 缺少必需字段position`);
}
if (pin.length === undefined || pin.length === null) {
errors.push(`${path}: 缺少必需字段length`);
}
}
if (this.config.checkValueRanges) {
if (pin.type && !this.isValidPinType(pin.type)) {
errors.push(`${path}: 无效的引脚类型: ${pin.type}`);
}
if (pin.shape && !this.isValidPinShape(pin.shape)) {
errors.push(`${path}: 无效的引脚形状: ${pin.shape}`);
}
if (pin.length !== undefined && (pin.length < 0 || pin.length > 50)) {
warnings.push(`${path}: 引脚长度可能不合理: ${pin.length}`);
}
}
// 检查引脚编号重复
if (!pin.number.text || !pin.number.text.trim()) {
warnings.push(`${path}: 引脚编号为空`);
}
return { isValid: errors.length === 0, errors, warnings, suggestions };
}
/**
* 验证图形元素
*/
validateGraphic(graphic, path) {
const errors = [];
const warnings = [];
const suggestions = [];
if (this.config.checkRequiredFields) {
if (!graphic.type) {
errors.push(`${path}: 缺少必需字段type`);
}
}
if (graphic.type && !this.isValidGraphicType(graphic.type)) {
warnings.push(`${path}: 未知的图形类型: ${graphic.type}`);
}
return { isValid: errors.length === 0, errors, warnings, suggestions };
}
/**
* 检查符号完整性
*/
checkSymbolIntegrity(symbol, path, warnings, suggestions) {
// 检查必需属性
const requiredProps = ['Reference', 'Value'];
const propNames = symbol.properties.map(p => p.name);
requiredProps.forEach(propName => {
if (!propNames.includes(propName)) {
warnings.push(`${path}: 缺少重要属性: ${propName}`);
}
});
// 检查引脚编号重复
const pinNumbers = symbol.pins.map(p => p.number.text).filter(n => n);
const duplicates = this.findDuplicates(pinNumbers);
if (duplicates.length > 0) {
warnings.push(`${path}: 发现重复的引脚编号: ${duplicates.join(', ')}`);
}
// 检查符号是否有图形表示
if (symbol.graphics.length === 0 && symbol.pins.length > 0) {
suggestions.push(`${path}: 符号有引脚但没有图形元素,建议添加外框`);
}
}
/**
* 检查重复的符号名称
*/
checkDuplicateSymbolNames(symbols, warnings) {
const names = symbols.map(s => s.name);
const duplicates = this.findDuplicates(names);
if (duplicates.length > 0) {
warnings.push(`发现重复的符号名称: ${duplicates.join(', ')}`);
}
}
/**
* 生成改进建议
*/
generateSuggestions(symbolLib, suggestions) {
// 符号数量建议
if (symbolLib.symbols.length === 0) {
suggestions.push('符号库为空,建议添加符号');
}
else if (symbolLib.symbols.length > 1000) {
suggestions.push('符号库包含大量符号,建议考虑拆分为多个文件');
}
// 版本建议
if (!symbolLib.generatorVersion) {
suggestions.push('建议添加generatorVersion字段以便版本追踪');
}
}
/**
* 辅助方法:检查版本格式
*/
isValidVersion(version) {
return /^\d{8}$/.test(version) || /^\d+\.\d+(\.\d+)?$/.test(version);
}
/**
* 辅助方法:检查是否为常见属性
*/
isCommonProperty(name) {
const commonProps = ['Reference', 'Value', 'Footprint', 'Datasheet', 'Description'];
return commonProps.includes(name);
}
/**
* 辅助方法:检查引脚类型有效性
*/
isValidPinType(type) {
const validTypes = [
'input', 'output', 'bidirectional', 'tri_state', 'passive',
'free', 'unspecified', 'power_in', 'power_out',
'open_collector', 'open_emitter', 'no_connect'
];
return validTypes.includes(type);
}
/**
* 辅助方法:检查引脚形状有效性
*/
isValidPinShape(shape) {
const validShapes = [
'line', 'inverted', 'clock', 'inverted_clock',
'input_low', 'clock_low', 'output_low',
'edge_clock_high', 'non_logic'
];
return validShapes.includes(shape);
}
/**
* 辅助方法:检查图形类型有效性
*/
isValidGraphicType(type) {
const validTypes = ['rectangle', 'circle', 'polyline', 'text', 'arc'];
return validTypes.includes(type);
}
/**
* 辅助方法:查找数组中的重复项
*/
findDuplicates(array) {
const seen = new Set();
const duplicates = new Set();
array.forEach(item => {
if (seen.has(item)) {
duplicates.add(item);
}
else {
seen.add(item);
}
});
return Array.from(duplicates);
}
}