Commit 1c12d7bf authored by xiegelin's avatar xiegelin

feat: 新增Markdown转DrawIO、示例数据生成及表注释支持

- mysql.js 新增表注释查询和 generateSampleDataXML 生成示例数据表格
- 输出目录从 xml/ 改为 tableStructureXml/ 和 sampleDataXml/
- 新增 mdToDrawio.js 支持将 Markdown 表格转换为 Draw.io XML
- package.json 新增 md2drawio 脚本
- 完善 README.md 命令说明和使用方法
- 新增 CLAUDE.md 项目指引
Co-Authored-By: default avatarClaude Opus 4.6 (1M context) <noreply@anthropic.com>
parent ac9f5b7e
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Node.js CLI utility that generates Draw.io (diagrams.net) XML files from database table schemas or Markdown tables. Supports MySQL, PostgreSQL, and Markdown-to-Draw.io conversion. Queries `information_schema` to extract column metadata, then outputs mxGraph XML importable into Draw.io.
## Commands
```bash
npm install # Install dependencies (mysql2, pg)
npm run dev # Run MySQL version (interactive CLI)
npm run mysql # Same as dev
npm run pgsql # Run PostgreSQL version (interactive CLI)
npm run md2drawio # Convert Markdown table file to Draw.io XML
```
No test suite or linter is configured.
## Architecture
Three entry points:
- `mysql.js` — MySQL via `mysql2/promise`, queries `information_schema.COLUMNS`. Generates both table-structure XML and sample-data XML.
- `postgreSQL.js` — PostgreSQL via `pg`, joins `information_schema.columns` with `pg_catalog.pg_description` for comments. Generates table-structure XML only.
- `mdToDrawio.js` — Parses a Markdown table file (pipe-delimited) and generates a Draw.io table XML.
### Core Flow (mysql.js / postgreSQL.js)
1. **Interactive Input**: readline prompts for comma-separated table names (supports Chinese comma `,`)
2. **Name Processing**: rejects Chinese characters, converts camelCase → snake_case
3. **Database Query**: fetches column names + comments from information_schema
4. **XML Generation**: `generateXMLWithTableColumns` builds mxGraph XML with swimlane container and per-column cells. Audit fields (created_at, updated_at, deleted_at, etc.) are styled gray. MySQL version also calls `generateSampleDataXML` to produce a horizontal table layout with header row and empty data row.
### Output Directories
- `tableStructureXml/` — swimlane-style table structure diagrams (mysql.js writes here; postgreSQL.js also writes here)
- `sampleDataXml/` — horizontal table-layout diagrams for sample data (mysql.js `generateSampleDataXML` and mdToDrawio.js write here)
### Database Configuration
Credentials are hardcoded at the top of each database file. Modify directly in source to point at different databases.
### Known Constraints
- `&` in column comments breaks XML output (no escaping)
- Table names must not contain Chinese characters
- Fixed geometry: x=340, y=140, width=260 for structure XML
- PostgreSQL version hardcodes schema to `public`
- PostgreSQL version does not generate sample-data XML
## Language
README and console messages are in Chinese. Code uses English variable/function names.
### 本地运行
node v18
npm run dev
```bash
npm install
```
### 可用命令
| 命令 | 说明 |
| --- | --- |
| `npm run dev` | 运行 MySQL 版本,交互式输入表名,生成表结构 XML 和示例数据 XML |
| `npm run mysql` | 同 `npm run dev` |
| `npm run pgsql` | 运行 PostgreSQL 版本,交互式输入表名,生成表结构 XML |
| `npm run md2drawio` | 将 Markdown 表格文件转换为 Draw.io XML |
### 使用方法:
1. 配置数据源,见mysql.js
2. npm run dev启动后,输入目标表名,支持多个,按英文逗号拼接
3. 运行成功后,输出xml文件在xml文件夹下,打开xml文件将内容粘贴到drawio编辑器中
1. 配置数据源:MySQL 见 `mysql.js` 顶部,PostgreSQL 见 `postgreSQL.js` 顶部
2. 运行对应命令后,输入目标表名,支持多个,按英文逗号或中文逗号拼接
3. 运行成功后,表结构 XML 输出在 `tableStructureXml/` 目录下,示例数据 XML 输出在 `sampleDataXml/` 目录下
4. 打开 XML 文件将内容粘贴到 Draw.io 编辑器中
5. `npm run md2drawio` 需输入 Markdown 文件路径,输出在 `sampleDataXml/` 目录下
### 注意事项:
1. 生成的xml文件中,不能有&符号。常见问题是这个符号会出现在字段注释中。
2.
\ No newline at end of file
1. 生成的 XML 文件中,不能有 `&` 符号。常见问题是这个符号会出现在字段注释中。
2. 表名不能包含中文字符。
\ No newline at end of file
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function askQuestion(query) {
return new Promise((resolve) => {
rl.question(query, resolve);
});
}
function parseMdTable(content) {
const lines = content.trim().split('\n');
const rows = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
// Skip separator rows (| --- | --- |)
if (/^\|[\s\-:|]+\|$/.test(trimmed)) continue;
const cells = trimmed
.split('|')
.map((c) => c.trim())
.filter((c) => c !== '');
if (cells.length > 0) {
rows.push(cells);
}
}
return rows;
}
function generateDrawioTableXML(rows) {
if (rows.length === 0) return '';
const numCols = rows[0].length;
const numRows = rows.length;
const colWidth = 120;
const rowHeight = 37;
const totalWidth = numCols * colWidth;
const totalHeight = numRows * rowHeight;
let cellId = 3;
let xml = `<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="shape=table;startSize=0;container=1;collapsible=0;childLayout=tableLayout;fontSize=12;" vertex="1" parent="1">
<mxGeometry x="340" y="140" width="${totalWidth}" height="${totalHeight}" as="geometry"/>
</mxCell>`;
rows.forEach((row, rowIndex) => {
const rowId = cellId++;
const y = rowIndex * rowHeight;
xml += `
<mxCell id="${rowId}" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;" vertex="1" parent="2">
<mxGeometry y="${y}" width="${totalWidth}" height="${rowHeight}" as="geometry"/>
</mxCell>`;
row.forEach((cell, colIndex) => {
const id = cellId++;
const x = colIndex * colWidth;
xml += `
<mxCell id="${id}" value="${cell}" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;" vertex="1" parent="${rowId}">
<mxGeometry x="${x}" width="${colWidth}" height="${rowHeight}" as="geometry">
<mxRectangle width="${colWidth}" height="${rowHeight}" as="alternateBounds"/>
</mxGeometry>
</mxCell>`;
});
});
xml += `
</root>
</mxGraphModel>`;
return xml;
}
async function main() {
try {
const mdFile = await askQuestion('Enter markdown file path: ');
if (!mdFile) {
console.log('文件路径不得为空!');
return;
}
const content = fs.readFileSync(mdFile.trim(), 'utf-8');
const rows = parseMdTable(content);
if (rows.length === 0) {
console.error('未找到表格数据。');
return;
}
console.log(`解析到 ${rows.length} 行, ${rows[0].length} 列`);
const outputDir = 'sampleDataXml';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const baseName = mdFile.trim().replace(/^.*[\\/]/, '').replace(/\.md$/, '');
const outputFile = `${outputDir}/${baseName}.xml`;
fs.writeFileSync(outputFile, generateDrawioTableXML(rows));
console.log(`XML 文件已生成: ${outputFile}`);
} catch (error) {
console.error('Error:', error.message);
} finally {
rl.close();
}
}
main();
\ No newline at end of file
......@@ -21,7 +21,7 @@ async function main() {
const port = '3306';
const user = 'root';
const password = '12345678';
const database = 'teaching';
const database = 'hospital';
// const host = '10.10.10.132';
// const port = '3306';
......@@ -83,12 +83,20 @@ async function main() {
processedTableName,
]);
// Get table comment
const [tableInfo] = await connection.execute(
`SELECT TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`,
[database, processedTableName]
);
const tableComment = tableInfo[0]?.TABLE_COMMENT || '';
if (rows.length === 0) {
console.error(
`***** No results found for table ${processedTableName}. *****`
);
} else {
generateXMLWithTableColumns(processedTableName, rows);
generateXMLWithTableColumns(processedTableName, rows, tableComment);
generateSampleDataXML(processedTableName, rows, tableComment);
console.log(
`===== XML file generated successfully for ${processedTableName}! =====`
);
......@@ -105,13 +113,14 @@ async function main() {
}
}
function generateXMLWithTableColumns(tableName, rows) {
function generateXMLWithTableColumns(tableName, rows, tableComment) {
const label = tableComment ? `${tableName} &#10;${tableComment}` : tableName;
// XML template start with the table name in the label
let xmlContent = `<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<object label="${tableName}" id="2">
<object label="${label}" id="2">
<mxCell style="swimlane;fontStyle=1;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="340" y="140" width="260" height="${
30 + rows.length * 30
......@@ -155,7 +164,71 @@ function generateXMLWithTableColumns(tableName, rows) {
</root>
</mxGraphModel>`;
// console.log(xmlContent);
fs.writeFileSync(`xml/${tableName}_diagram.xml`, xmlContent);
fs.writeFileSync(`tableStructureXml/${tableName}.xml`, xmlContent);
}
function generateSampleDataXML(tableName, rows, tableComment) {
const label = tableComment ? `${tableName} ${tableComment}-示例数据` : `${tableName}-示例数据`;
const colWidth = 120;
const rowHeight = 40;
const titleHeight = 30;
const totalWidth = rows.length * colWidth;
const totalHeight = titleHeight + rowHeight * 2;
let cellId = 3;
let xml = `<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="${label}" style="shape=table;startSize=${titleHeight};container=1;collapsible=0;childLayout=tableLayout;strokeColor=#6c8ebf;fontSize=12;fontStyle=1;fillColor=#dae8fc;" vertex="1" parent="1">
<mxGeometry x="340" y="140" width="${totalWidth}" height="${totalHeight}" as="geometry"/>
</mxCell>`;
// Header row
const headerRowId = cellId++;
xml += `
<mxCell id="${headerRowId}" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;" vertex="1" parent="2">
<mxGeometry y="${titleHeight}" width="${totalWidth}" height="${rowHeight}" as="geometry"/>
</mxCell>`;
// Header cells
rows.forEach((row, index) => {
const id = cellId++;
xml += `
<mxCell id="${id}" value="${row.COLUMN_NAME}" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=#000000;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;fontStyle=1" vertex="1" parent="${headerRowId}">
<mxGeometry x="${index * colWidth}" width="${colWidth}" height="${rowHeight}" as="geometry">
<mxRectangle width="${colWidth}" height="${rowHeight}" as="alternateBounds"/>
</mxGeometry>
</mxCell>`;
});
// Empty data row
const dataRowId = cellId++;
xml += `
<mxCell id="${dataRowId}" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;strokeColor=inherit;top=0;left=0;bottom=0;right=0;collapsible=0;dropTarget=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=16;" vertex="1" parent="2">
<mxGeometry y="${titleHeight + rowHeight}" width="${totalWidth}" height="${rowHeight}" as="geometry"/>
</mxCell>`;
// Empty data cells
rows.forEach((_row, index) => {
const id = cellId++;
xml += `
<mxCell id="${id}" value="" style="shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;strokeColor=inherit;overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;pointerEvents=1;fontSize=12;" vertex="1" parent="${dataRowId}">
<mxGeometry x="${index * colWidth}" width="${colWidth}" height="${rowHeight}" as="geometry">
<mxRectangle width="${colWidth}" height="${rowHeight}" as="alternateBounds"/>
</mxGeometry>
</mxCell>`;
});
xml += `
</root>
</mxGraphModel>`;
if (!fs.existsSync('sampleDataXml')) {
fs.mkdirSync('sampleDataXml', { recursive: true });
}
fs.writeFileSync(`sampleDataXml/${tableName}.xml`, xml);
}
main();
......@@ -6,7 +6,8 @@
"scripts": {
"dev": "node mysql.js",
"mysql": "node mysql.js",
"pgsql": "node postgreSQL.js"
"pgsql": "node postgreSQL.js",
"md2drawio": "node mdToDrawio.js"
},
"dependencies": {
"mysql2": "^3.6.0",
......
......@@ -171,7 +171,7 @@ function generateXMLWithTableColumns(tableName, rows) {
</root>
</mxGraphModel>`;
// console.log(xmlContent);
fs.writeFileSync(`xml/${tableName}_diagram.xml`, xmlContent);
fs.writeFileSync(`tableStructureXml/${tableName}.xml`, xmlContent);
}
main();
| source_type | 含义 | 说明 |
| ----------- | ---------- | -------------------------------------------------- |
| LAB | 检验结果 | 从检验结果表取值 |
| VITAL | 体征记录 | 从体征/生命体征表取值 |
| DIAGNOSIS | 诊断 | 从诊断表取列表 |
| MEDICATION | 用药 | 从用药记录表取列表 |
| PATIENT | 基本信息 | 从患者主表取值 |
| HISTORY | 病史标签 | 从病史标签表取列表 |
| CONTEXT | 上下文传入 | 不查数据库,由触发事件直接传入(如拟开药物) |
| COMPUTED | 计算字段 | 由其他变量计算得出(如 AGE = 当前日期 - 出生日期) |
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment