123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- #!/usr/bin/env node
- const fs = require('fs');
- const path = require('path');
- const xlsx = require('xlsx');
- // Usage:
- // node gen_pojo_from_xls_json.js <sourceJsonDir> <sourceXlsDir> <packageName> <outputDir> [Tables...] [--ignore=a,b,c]
- // Tables can be names with or without .json/.xls, case-insensitive. Multiple can be space- or comma-separated.
- const [,, jsonDir, xlsDir, packageName, outputDir, ...rest] = process.argv;
- if (!jsonDir || !xlsDir || !packageName || !outputDir) {
- console.log('Usage: node gen_pojo_from_xls_json.js <sourceJsonDir> <sourceXlsDir> <packageName> <outputDir> [Tables...] [--ignore=a,b,c]');
- process.exit(1);
- }
- function normalizeName(n) {
- return String(n).toLowerCase().replace(/\.[^.]+$/, '');
- }
- function parseArgs(args) {
- const whitelist = [];
- const blacklist = [];
- for (let i = 0; i < args.length; i++) {
- const a = String(args[i]);
- if (a.startsWith('--ignore=')) {
- const v = a.substring('--ignore='.length);
- v.split(',').map(s => s.trim()).filter(Boolean).forEach(x => blacklist.push(normalizeName(x)));
- continue;
- }
- if (a === '--ignore' && i + 1 < args.length) {
- const v = String(args[++i]);
- v.split(',').map(s => s.trim()).filter(Boolean).forEach(x => blacklist.push(normalizeName(x)));
- continue;
- }
- whitelist.push(normalizeName(a));
- }
- return { whitelist: new Set(whitelist.filter(Boolean)), blacklist: new Set(blacklist) };
- }
- const { whitelist: whiteSet, blacklist: blackSet } = parseArgs(rest);
- function listJsonFiles(dir) {
- return fs.readdirSync(dir)
- .filter(name => name.toLowerCase().endsWith('.json'))
- .map(name => path.join(dir, name));
- }
- function readJson(filePath) {
- let content = fs.readFileSync(filePath, 'utf8');
- if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
- return JSON.parse(content);
- }
- function pickSample(root) {
- if (Array.isArray(root)) return root.length > 0 ? root[0] : null;
- if (root && typeof root === 'object') {
- const keys = Object.keys(root);
- return keys.length > 0 ? root[keys[0]] : null;
- }
- return null;
- }
- function upperFirst(str) {
- if (!str) return str;
- return str.charAt(0).toUpperCase() + str.slice(1);
- }
- // Convert filename like M_item.json -> MItem
- function toClassName(fileName) {
- const base = fileName.replace(/\.[^.]+$/, '');
- return base.split(/[_\-\s]+/).filter(Boolean).map(upperFirst).join('');
- }
- function isInteger(num) {
- return typeof num === 'number' && Number.isInteger(num);
- }
- function inferJavaType(value) {
- if (value === null || value === undefined) return 'String';
- if (typeof value === 'number') return isInteger(value) ? 'Integer' : 'Double';
- if (typeof value === 'boolean') return 'Boolean';
- if (typeof value === 'string') return 'String';
- if (Array.isArray(value)) {
- if (value.length === 0) return 'List<Object>';
- return `List<${inferJavaType(value[0])}>`;
- }
- if (typeof value === 'object') return 'Object';
- return 'String';
- }
- const JAVA_KEYWORDS = new Set([
- 'abstract','assert','boolean','break','byte','case','catch','char','class','const','continue','default','do','double','else','enum','extends','final','finally','float','for','goto','if','implements','import','instanceof','int','interface','long','native','new','package','private','protected','public','return','short','static','strictfp','super','switch','synchronized','this','throw','throws','transient','try','void','volatile','while','null','true','false'
- ]);
- function sanitizeFieldName(name) {
- let safe = name.replace(/[^A-Za-z0-9_]/g, '_');
- if (!/^[A-Za-z_]/.test(safe)) safe = '_' + safe;
- if (JAVA_KEYWORDS.has(safe)) safe = safe + '_';
- return safe;
- }
- function readXlsComments(xlsFile) {
- try {
- const wb = xlsx.readFile(xlsFile);
- const sheetName = wb.SheetNames[0];
- const ws = wb.Sheets[sheetName];
- const rows = xlsx.utils.sheet_to_json(ws, { header: 1, raw: false });
- // Heuristics: within first 5 rows, pick field row as the one with max ASCII ratio and contains 'ID' or many word tokens
- const maxCheck = Math.min(5, rows.length);
- let fieldRowIdx = -1, commentRowIdx = -1;
- let bestScore = -1;
- for (let i = 0; i < maxCheck; i++) {
- const row = rows[i] || [];
- const joined = row.join(' ').trim();
- if (!joined) continue;
- const asciiCount = (joined.match(/[\x20-\x7E]/g) || []).length;
- const ratio = asciiCount / Math.max(1, joined.length);
- const wordCount = (joined.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []).length;
- const score = ratio * 0.7 + Math.min(wordCount / Math.max(1, row.length), 1) * 0.3 + (joined.includes('ID') ? 0.2 : 0);
- if (score > bestScore) { bestScore = score; fieldRowIdx = i; }
- }
- // Comment row: prefer a different row with lower ASCII ratio (more likely Chinese)
- let bestCR = -1;
- for (let i = 0; i < maxCheck; i++) {
- if (i === fieldRowIdx) continue;
- const row = rows[i] || [];
- const joined = row.join(' ').trim();
- if (!joined) continue;
- const asciiCount = (joined.match(/[\x20-\x7E]/g) || []).length;
- const ratio = asciiCount / Math.max(1, joined.length);
- const score = 1 - ratio; // prefer non-ASCII
- if (score > bestCR) { bestCR = score; commentRowIdx = i; }
- }
- const fields = rows[fieldRowIdx] || [];
- const comments = rows[commentRowIdx] || [];
- const map = new Map();
- for (let i = 0; i < fields.length; i++) {
- const k = String(fields[i] || '').trim();
- if (!k) continue;
- const v = String(comments[i] || '').trim();
- if (k) map.set(k, v);
- }
- return { sheetName, comments: map };
- } catch (e) {
- return { sheetName: '', comments: new Map() };
- }
- }
- function findXlsForBase(xlsDir, baseName) {
- // Try .xls then .xlsx
- const cands = [path.join(xlsDir, baseName + '.xls'), path.join(xlsDir, baseName + '.xlsx')];
- for (const f of cands) {
- if (fs.existsSync(f)) return f;
- }
- // Case-insensitive search fallback
- const files = fs.readdirSync(xlsDir).filter(n => /\.xlsx?$/.test(n));
- for (const n of files) {
- if (normalizeName(n) === normalizeName(baseName)) return path.join(xlsDir, n);
- }
- return null;
- }
- function generateJavaClass(pkg, className, fieldsOrder, fieldTypes, fieldComments, classComment, jsonFileName) {
- const lines = [];
- lines.push(`package ${pkg};`);
- lines.push('');
- lines.push('import lombok.Data;');
-
- // Only add imports that are actually used
- const usedTypes = new Set();
- for (const fname of fieldsOrder) {
- const type = fieldTypes.get(fname) || 'String';
- if (type === 'List<Integer>') {
- usedTypes.add('java.util.List');
- } else if (type === 'List<String>') {
- usedTypes.add('java.util.List');
- } else if (type === 'List<Object>') {
- usedTypes.add('java.util.List');
- }
- }
-
- // Add only the imports that are actually used
- usedTypes.forEach(importType => {
- lines.push(`import ${importType};`);
- });
-
- if (usedTypes.size > 0) {
- lines.push('');
- }
-
- if (classComment) {
- lines.push('/**');
- lines.push(` * 配置表:${classComment}`);
- lines.push(' */');
- }
- lines.push('@Data');
- lines.push(`public class ${className} {`);
-
- // Add path field using the actual JSON filename
- lines.push('\t/** JSON 配置文件路径 */');
- lines.push('\tpublic final String path = "table/' + jsonFileName + '";');
- lines.push('');
-
- // Add fields
- for (const fname of fieldsOrder) {
- const type = fieldTypes.get(fname) || 'String';
- const cmt = fieldComments.get(fname);
- const safe = sanitizeFieldName(fname);
- if (cmt) {
- lines.push(`\t/** ${cmt} */`);
- }
- if (safe !== fname) {
- lines.push(`\t@JsonProperty("${fname}")`);
- }
- lines.push(`\tpublic ${type} ${safe};`);
- }
-
- lines.push('');
- lines.push('}');
- return lines.join('\n');
- }
- (function main() {
- if (!fs.existsSync(jsonDir)) {
- console.error('JSON dir not found:', jsonDir);
- process.exit(2);
- }
- if (!fs.existsSync(xlsDir)) {
- console.error('XLS dir not found:', xlsDir);
- process.exit(2);
- }
- const outPkgDir = path.join(outputDir, packageName.replace(/\./g, path.sep));
- fs.mkdirSync(outPkgDir, { recursive: true });
- let files = listJsonFiles(jsonDir);
- // Apply whitelist filter if provided
- if (whiteSet.size > 0) {
- files = files.filter(f => whiteSet.has(normalizeName(path.basename(f))));
- }
- // Apply blacklist filter if provided
- if (blackSet.size > 0) {
- files = files.filter(f => !blackSet.has(normalizeName(path.basename(f))));
- }
- if (files.length === 0) {
- console.log('No JSON files to process.');
- process.exit(0);
- }
- let generated = 0;
- for (const jf of files) {
- try {
- const base = path.basename(jf).replace(/\.[^.]+$/, '');
- const jsonRoot = readJson(jf);
- const sample = pickSample(jsonRoot);
- if (!sample || typeof sample !== 'object') { console.warn('Skip (no object sample):', jf); continue; }
- const xlsPath = findXlsForBase(xlsDir, base);
- const { sheetName, comments } = xlsPath ? readXlsComments(xlsPath) : { sheetName: '', comments: new Map() };
- const className = toClassName(base) + 'Pojo';
- const fieldsOrder = Object.keys(sample);
- const fieldTypes = new Map();
- for (const k of fieldsOrder) fieldTypes.set(k, inferJavaType(sample[k]));
- const classComment = sheetName ? `${base}(${sheetName})` : `${base}`;
- const jsonFileName = path.basename(jf);
- const content = generateJavaClass(packageName, className, fieldsOrder, fieldTypes, comments, classComment, jsonFileName);
- const outPath = path.join(outPkgDir, `${className}.java`);
- fs.writeFileSync(outPath, content, 'utf8');
- generated++;
- console.log('Generated', outPath);
- } catch (e) {
- console.warn('Failed for', jf, e.message);
- }
- }
- console.log(`Done. Generated ${generated} classes.`);
- })();
|