@ry-krystal/kicad-converter
Version:
专业的KiCad符号文件与JSON互转工具
779 lines (778 loc) • 27.8 kB
JavaScript
/**
* 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;
}
}