UNPKG

@ry-krystal/kicad-converter

Version:

专业的KiCad符号文件与JSON互转工具

357 lines (356 loc) 13 kB
/** * 数据验证器 - 核心版本 * 负责验证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); } }