UNPKG

@ry-krystal/kicad-converter

Version:

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

779 lines (778 loc) 27.8 kB
/** * KiCad文件解析器 - 核心版本 * 负责将KiCad S表达式格式解析为结构化数据 */ /** * KiCad解析器类 */ export class KiCadParser { /** * 解析KiCad符号库文件内容 * @param content KiCad文件内容 * @returns 解析结果 */ parse(content) { try { // 预处理:移除注释和多余空白 const cleaned = this.preprocess(content); // 词法分析:分词 const tokens = this.tokenize(cleaned); // 语法分析:构建AST const ast = this.parseTokens(tokens); // 语义分析:转换为数据结构 return this.convertToSymbolLib(ast); } catch (error) { throw new Error(`KiCad解析失败: ${error instanceof Error ? error.message : String(error)}`); } } /** * 预处理文本内容 */ preprocess(content) { return content // 移除行注释 .replace(/;;.*$/gm, '') // 标准化换行符 .replace(/\r\n/g, '\n') // 移除多余空白 .replace(/\s+/g, ' ') .trim(); } /** * 词法分析 - 将文本分解为token */ tokenize(content) { const tokens = []; let current = ''; let inString = false; let escaped = false; for (let i = 0; i < content.length; i++) { const char = content[i]; if (escaped) { current += char; escaped = false; continue; } if (char === '\\') { escaped = true; current += char; continue; } if (char === '"') { inString = !inString; current += char; continue; } if (inString) { current += char; continue; } if (char === '(' || char === ')') { if (current.trim()) { tokens.push(current.trim()); current = ''; } tokens.push(char); } else if (/\s/.test(char)) { if (current.trim()) { tokens.push(current.trim()); current = ''; } } else { current += char; } } if (current.trim()) { tokens.push(current.trim()); } return tokens; } /** * 语法分析 - 构建S表达式AST */ parseTokens(tokens) { let index = 0; const parseNode = () => { const token = tokens[index++]; if (token === '(') { const children = []; while (index < tokens.length && tokens[index] !== ')') { children.push(parseNode()); } if (index >= tokens.length) { throw new Error('未闭合的括号'); } index++; // 跳过 ')' return { type: 'list', children }; } else if (token === ')') { throw new Error('意外的闭合括号'); } else { // 尝试解析为数字 const numValue = this.parseNumber(token); return { type: 'atom', value: numValue !== null ? numValue : this.parseString(token) }; } }; return parseNode(); } /** * 尝试解析数字 */ parseNumber(token) { if (/^-?\d+(\.\d+)?$/.test(token)) { return parseFloat(token); } return null; } /** * 解析字符串(移除引号) */ parseString(token) { if (token.startsWith('"') && token.endsWith('"')) { return token.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); } return token; } /** * 将AST转换为符号库数据结构 */ convertToSymbolLib(ast) { if (ast.type !== 'list' || !ast.children) { throw new Error('无效的根节点'); } const rootType = this.getNodeValue(ast.children[0]); if (rootType !== 'kicad_symbol_lib') { throw new Error(`期望根节点为kicad_symbol_lib,实际为: ${rootType}`); } const result = { version: '', generator: '', generatorVersion: '', symbols: [] }; // 收集所有符号节点,包括嵌套的符号单元 const allSymbolNodes = []; this.collectSymbolNodes(ast, allSymbolNodes); // 按符号名称分组,合并主符号和符号单元 const symbolGroups = new Map(); for (const symbolNode of allSymbolNodes) { if (symbolNode.children && symbolNode.children.length > 1) { const symbolName = this.getNodeValue(symbolNode.children[1]); const baseName = symbolName.replace(/_\d+$/, ''); // 移除_1后缀获取基础名称 if (!symbolGroups.has(baseName)) { symbolGroups.set(baseName, []); } symbolGroups.get(baseName).push(symbolNode); } } // 解析每个符号组 for (const [baseName, nodes] of symbolGroups) { const symbol = this.parseSymbolGroup(baseName, nodes); if (symbol) { result.symbols.push(symbol); } } // 处理其他节点 for (let i = 1; i < ast.children.length; i++) { const child = ast.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'version': result.version = this.getNodeValue(child.children[1]); break; case 'generator': result.generator = this.getNodeValue(child.children[1]); break; case 'generator_version': result.generatorVersion = this.getNodeValue(child.children[1]); break; } } } return result; } /** * 递归收集所有符号节点(包括嵌套的) */ collectSymbolNodes(node, result) { if (node.type === 'list' && node.children) { for (const child of node.children) { if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); if (childType === 'symbol') { result.push(child); // 继续递归查找嵌套的符号单元 this.collectSymbolNodes(child, result); } else { this.collectSymbolNodes(child, result); } } } } } /** * 解析符号组(主符号+符号单元) */ parseSymbolGroup(baseName, nodes) { if (nodes.length === 0) return null; // 查找主符号节点(没有_数字后缀的) let mainSymbolNode = null; const unitNodes = []; for (const node of nodes) { if (node.children && node.children.length > 1) { const symbolName = this.getNodeValue(node.children[1]); if (symbolName === baseName) { mainSymbolNode = node; } else if (symbolName.startsWith(baseName + '_')) { unitNodes.push(node); } } } // 如果没有主符号节点,使用第一个节点作为主符号 if (!mainSymbolNode && nodes.length > 0) { mainSymbolNode = nodes[0]; } if (!mainSymbolNode) return null; // 解析主符号 const symbol = this.parseSymbol(mainSymbolNode); symbol.name = baseName; // 确保使用基础名称 // 合并所有符号单元的引脚和图形 for (const unitNode of unitNodes) { const unitSymbol = this.parseSymbol(unitNode); symbol.pins.push(...unitSymbol.pins); symbol.graphics.push(...unitSymbol.graphics); // 合并属性(避免重复) for (const prop of unitSymbol.properties) { const existingProp = symbol.properties.find(p => p.name === prop.name); if (!existingProp) { symbol.properties.push(prop); } } } return symbol; } /** * 解析符号 */ parseSymbol(node) { if (node.type !== 'list' || !node.children) { throw new Error('无效的符号节点'); } const symbolName = this.getNodeValue(node.children[1]); const symbol = { name: symbolName, excludeFromSim: false, inBom: true, onBoard: true, properties: [], pins: [], graphics: [], embeddedFonts: false }; for (let i = 2; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'exclude_from_sim': symbol.excludeFromSim = this.getNodeValue(child.children[1]) === 'yes'; break; case 'in_bom': symbol.inBom = this.getNodeValue(child.children[1]) === 'yes'; break; case 'on_board': symbol.onBoard = this.getNodeValue(child.children[1]) === 'yes'; break; case 'property': symbol.properties.push(this.parseProperty(child)); break; case 'pin': symbol.pins.push(this.parsePin(child)); break; case 'rectangle': case 'circle': case 'polyline': case 'text': symbol.graphics.push(this.parseGraphic(child)); break; case 'embedded_fonts': symbol.embeddedFonts = this.getNodeValue(child.children[1]) === 'yes'; break; } } } return symbol; } /** * 解析属性 */ parseProperty(node) { if (node.type !== 'list' || !node.children || node.children.length < 3) { throw new Error('无效的属性节点'); } const property = { name: this.getNodeValue(node.children[1]), value: this.getNodeValue(node.children[2]), position: { x: 0, y: 0 }, effects: {} }; // 解析位置和效果 for (let i = 3; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'at': property.position = this.parsePosition(child); break; case 'effects': property.effects = this.parseEffects(child); break; case 'hide': property.hide = true; break; } } } return property; } /** * 解析位置信息 */ parsePosition(node) { if (node.type !== 'list' || !node.children || node.children.length < 3) { return { x: 0, y: 0 }; } const position = { x: this.getNodeValue(node.children[1]) || 0, y: this.getNodeValue(node.children[2]) || 0 }; if (node.children.length > 3) { position.rotation = this.getNodeValue(node.children[3]) || 0; } return position; } /** * 解析文本效果 */ parseEffects(node) { const effects = {}; if (node.type === 'list' && node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'font': effects.font = this.parseFont(child); break; case 'justify': effects.justify = this.parseJustify(child); break; case 'hide': effects.hide = true; break; } } } } return effects; } /** * 解析字体信息 */ parseFont(node) { const font = {}; if (node.type === 'list' && node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'size': if (child.children.length >= 3) { font.size = { x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }; } break; case 'thickness': font.thickness = this.getNodeValue(child.children[1]); break; case 'bold': font.bold = true; break; case 'italic': font.italic = true; break; } } } } return font; } /** * 解析对齐方式 */ parseJustify(node) { const justify = {}; if (node.type === 'list' && node.children) { for (let i = 1; i < node.children.length; i++) { const value = this.getNodeValue(node.children[i]); if (['left', 'center', 'right'].includes(value)) { justify.horizontal = value; } if (['top', 'center', 'bottom'].includes(value)) { justify.vertical = value; } } } return justify; } /** * 解析引脚定义 */ parsePin(node) { if (node.type !== 'list' || !node.children || node.children.length < 3) { throw new Error('无效的引脚节点'); } const pin = { type: this.getNodeValue(node.children[1]) || 'passive', shape: this.getNodeValue(node.children[2]) || 'line', position: { x: 0, y: 0 }, length: 2.54, name: { text: '', effects: {} }, number: { text: '', effects: {} } }; // 解析引脚属性 for (let i = 3; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'at': // 解析位置和旋转 (at x y rotation) if (child.children.length >= 3) { pin.position = { x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }; if (child.children.length > 3) { pin.position.rotation = this.getNodeValue(child.children[3]) || 0; } } break; case 'length': pin.length = this.getNodeValue(child.children[1]) || 2.54; break; case 'name': pin.name = this.parsePinText(child); break; case 'number': pin.number = this.parsePinText(child); break; case 'hide': pin.hide = true; break; } } } return pin; } /** * 解析引脚文本(名称或编号) */ parsePinText(node) { const result = { text: '', effects: {} }; if (node.type === 'list' && node.children && node.children.length > 1) { result.text = this.getNodeValue(node.children[1]) || ''; // 查找effects节点 for (let i = 2; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); if (childType === 'effects') { result.effects = this.parseEffects(child); break; } } } } return result; } /** * 解析图形元素 */ parseGraphic(node) { if (node.type !== 'list' || !node.children) { throw new Error('无效的图形节点'); } const graphicType = this.getNodeValue(node.children[0]); const baseGraphic = { type: graphicType, stroke: { width: 0.1, type: 'default' }, fill: { type: 'none' } }; // 根据图形类型解析特定属性 switch (graphicType) { case 'rectangle': return this.parseRectangle(node, baseGraphic); case 'circle': return this.parseCircle(node, baseGraphic); case 'polyline': return this.parsePolyline(node, baseGraphic); case 'text': return this.parseTextGraphic(node, baseGraphic); default: return this.parseGenericGraphic(node, baseGraphic); } } /** * 解析矩形图形 */ parseRectangle(node, base) { const rectangle = { ...base, start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }; if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'start': if (child.children.length >= 3) { rectangle.start = { x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }; } break; case 'end': if (child.children.length >= 3) { rectangle.end = { x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }; } break; case 'stroke': rectangle.stroke = this.parseStroke(child); break; case 'fill': rectangle.fill = this.parseFill(child); break; } } } } return rectangle; } /** * 解析圆形图形 */ parseCircle(node, base) { const circle = { ...base, center: { x: 0, y: 0 }, radius: 0 }; if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'center': if (child.children.length >= 3) { circle.center = { x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }; } break; case 'radius': circle.radius = this.getNodeValue(child.children[1]) || 0; break; case 'stroke': circle.stroke = this.parseStroke(child); break; case 'fill': circle.fill = this.parseFill(child); break; } } } } return circle; } /** * 解析多边形图形 */ parsePolyline(node, base) { const polyline = { ...base, points: [] }; if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'pts': polyline.points = this.parsePoints(child); break; case 'stroke': polyline.stroke = this.parseStroke(child); break; case 'fill': polyline.fill = this.parseFill(child); break; } } } } return polyline; } /** * 解析文本图形 */ parseTextGraphic(node, base) { const text = { ...base, text: '', position: { x: 0, y: 0 }, effects: {} }; if (node.children && node.children.length > 1) { text.text = this.getNodeValue(node.children[1]) || ''; for (let i = 2; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'at': text.position = this.parsePosition(child); break; case 'effects': text.effects = this.parseEffects(child); break; } } } } return text; } /** * 解析通用图形(未知类型) */ parseGenericGraphic(node, base) { if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'stroke': base.stroke = this.parseStroke(child); break; case 'fill': base.fill = this.parseFill(child); break; } } } } return base; } /** * 解析描边样式 */ parseStroke(node) { const stroke = { width: 0.1, type: 'default' }; if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); switch (childType) { case 'width': stroke.width = this.getNodeValue(child.children[1]) || 0.1; break; case 'type': stroke.type = this.getNodeValue(child.children[1]) || 'default'; break; } } } } return stroke; } /** * 解析填充样式 */ parseFill(node) { const fill = { type: 'none' }; if (node.children && node.children.length > 1) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); if (childType === 'type') { fill.type = this.getNodeValue(child.children[1]) || 'none'; } } } } return fill; } /** * 解析点集合 */ parsePoints(node) { const points = []; if (node.children) { for (let i = 1; i < node.children.length; i++) { const child = node.children[i]; if (child.type === 'list' && child.children) { const childType = this.getNodeValue(child.children[0]); if (childType === 'xy' && child.children.length >= 3) { points.push({ x: this.getNodeValue(child.children[1]) || 0, y: this.getNodeValue(child.children[2]) || 0 }); } } } } return points; } /** * 获取节点值 */ getNodeValue(node) { return node?.value; } }