diff --git a/win_text_editor/lib/modules/pdf_parse/services/excel_deal_service.dart b/win_text_editor/lib/modules/pdf_parse/services/excel_deal_service.dart index f2e9c48..e79d819 100644 --- a/win_text_editor/lib/modules/pdf_parse/services/excel_deal_service.dart +++ b/win_text_editor/lib/modules/pdf_parse/services/excel_deal_service.dart @@ -6,11 +6,55 @@ import 'package:win_text_editor/app/providers/logger.dart'; class ExcelDealService { static const String _logTag = 'ExcelDealService'; + static const Map _columnTitles = { + 4: '日', + 5: '月', + 6: '水', + 7: '金', + 8: '火', + 9: '木', + 10: '土', + 11: '天', + 12: '海', + 13: '冥', + 14: '南', + 15: '北', + 16: '孛', + 17: '凯', + }; + + static const List> harmonyGroups = [ + {'寅', '午', '戌'}, // 寅午戌合 + {'亥', '卯', '未'}, // 亥卯未合 + {'申', '子', '辰'}, // 申子辰合 + {'巳', '酉', '丑'}, // 巳酉丑合 + ]; + + static const List> conflictGroups = [ + {'子', '午'}, // 子午冲 + {'丑', '未'}, // 丑未冲 + {'寅', '申'}, // 寅申冲 + {'卯', '酉'}, // 卯酉冲 + {'辰', '戌'}, // 辰戌冲 + {'巳', '亥'}, // 巳亥冲 + ]; + + static const List> punishmentGroups = [ + {'子', '卯'}, // 子卯刑 + {'子', '酉'}, // 子酉刑 + {'午', '酉'}, // 午酉刑 + {'午', '卯'}, // 午卯刑 + {'寅', '亥'}, // 寅亥刑 + {'巳', '申'}, // 巳申刑 + {'丑', '辰'}, // 丑辰刑 + {'寅', '巳', '申'}, // 寅巳申刑 + {'未', '戌', '丑'}, // 未戌丑刑 + ]; Future exportToExcel(String processedContent, {String? fileName}) async { try { final excel = Excel.createExcel(); - final sheet = excel['Sheet1']!; + final sheet = excel['Sheet1']; final lines = processedContent.split('\n'); bool isHeaderProcessed = false; @@ -51,27 +95,20 @@ class ExcelDealService { final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: rowIndex)); cell.value = TextCellValue(cellValue); - // 如果是标题行之后的数据行,添加三列空数据 - if (isHeaderProcessed && col == cells.length - 1) { - for (int i = 0; i < 3; i++) { - sheet - .cell( - CellIndex.indexByColumnRow(columnIndex: cells.length + i, rowIndex: rowIndex), - ) - .value = TextCellValue(''); - } - } - // 应用红色格式到7-14列(索引6-13) if (col >= 6 && col <= 13) { _applyConditionalFormatting(cell, cellValue); } } + + // 如果是数据行且已处理过标题行,计算关系 + if (isHeaderProcessed && rowIndex > 0) { + _calculateRelationships(sheet, rowIndex, cells); + } } // 设置列宽 for (var i = 1; i < 18; i++) { - // 增加列宽设置范围以包含新列 sheet.setColumnWidth(i, 8.0); } @@ -98,20 +135,179 @@ class ExcelDealService { } } + void _calculateRelationships(Sheet sheet, int rowIndex, List cells) { + final List validCells = []; + + // 1. Extract valid cells (columns 5-18, excluding 15-16) with degree, zodiac and minute + for (int col = 4; col <= 17; col++) { + if (col == 14 || col == 15) continue; // Skip South/North columns + + if (col < cells.length) { + final data = _parseCellData(cells[col]); + if (data != null && data.isValid()) { + validCells.add(data..columnIndex = col); + } + } + } + + final List heRelations = []; // 合关系 + final List chongRelations = []; // 冲关系 + final List xingRelations = []; // 刑关系 + + // 2. Compare all valid cells pairwise + for (int i = 0; i < validCells.length; i++) { + for (int j = i + 1; j < validCells.length; j++) { + final cell1 = validCells[i]; + final cell2 = validCells[j]; + + // Get column titles + final title1 = _columnTitles[cell1.columnIndex] ?? ''; + final title2 = _columnTitles[cell2.columnIndex] ?? ''; + + // Check for harmony (合) + if (_isHarmony(cell1, cell2)) { + heRelations.add('$title1$title2合'); + } + + // Check for conflict (冲) + if (_isConflict(cell1.zodiac, cell2.zodiac)) { + chongRelations.add('$title1$title2冲'); + } + + // Check for punishment (刑) + if (_isPunishment(cell1.zodiac, cell2.zodiac)) { + xingRelations.add('$title1$title2刑'); + } + } + } + + // 3. 写入关系列 (第19-21列) + if (heRelations.isNotEmpty) { + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: cells.length, rowIndex: rowIndex)) + .value = TextCellValue(heRelations.join(' ')); + } + if (chongRelations.isNotEmpty) { + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: cells.length + 1, rowIndex: rowIndex)) + .value = TextCellValue(chongRelations.join(' ')); + } + if (xingRelations.isNotEmpty) { + sheet + .cell(CellIndex.indexByColumnRow(columnIndex: cells.length + 2, rowIndex: rowIndex)) + .value = TextCellValue(xingRelations.join(' ')); + } + } + + CellData? _parseCellData(String cellValue) { + // 支持两种格式: + // 1. 度地支分(如"12酉34") + // 2. 度地支分'秒(如"10丑48'49"或"11°50'00") + + final match = RegExp( + r"^(\d+)([^\d\s°\']+)(\d+)(?:[\'](\d{1,2}))?$", + ).firstMatch(cellValue.trim()); + if (match == null) return null; + + final degreeStr = match.group(1); + final zodiac = match.group(2); + final minuteStr = match.group(3); + final secondStr = match.group(4); // 可能为null + + if (degreeStr == null || zodiac == null || minuteStr == null) return null; + + // 转换度分秒为double类型的度 + final degree = double.tryParse(degreeStr); + final minute = double.tryParse(minuteStr); + final second = secondStr != null ? double.tryParse(secondStr) : 0.0; + + if (degree == null || minute == null) return null; + + // 将秒转换为分钟的小数部分(60秒=1分钟) + final totalMinutes = minute + (second ?? 0) / 60; + + return CellData(degree: degree, zodiac: zodiac, minute: totalMinutes); + } + + bool _isHarmony(CellData cell1, CellData cell2) { + // 排除相同地支 + if (cell1.zodiac == cell2.zodiac) return false; + + // 1. 先检查度数差是否在1度以内 + final totalDiff = (cell1.totalMinutes - cell2.totalMinutes).abs(); + final isDegreeMatch = totalDiff <= 60; + if (isDegreeMatch) { + // 如果度数差在1度以内,直接返回true + return true; + } + + // 2. 检查地支是否在合局中r + var isZodiacMatch = false; + for (final group in harmonyGroups) { + if (group.contains(cell1.zodiac) && group.contains(cell2.zodiac)) { + isZodiacMatch = true; + break; + } + } + + // 两种条件满足其一即可 + return isDegreeMatch || isZodiacMatch; + } + + bool _isConflict(String zodiac1, String zodiac2) { + // 排除相同地支 + if (zodiac1 == zodiac2) return false; + + for (final group in conflictGroups) { + if (group.contains(zodiac1) && group.contains(zodiac2)) { + return true; + } + } + return false; + } + + bool _isPunishment(String zodiac1, String zodiac2) { + // 排除相同地支 + if (zodiac1 == zodiac2) return false; + + for (final group in punishmentGroups) { + if (group.contains(zodiac1) && group.contains(zodiac2)) { + return true; + } + } + return false; + } + // 跟踪每列的红色区域状态 final List _columnRedStates = List.filled(14, false); void _applyConditionalFormatting(Data cell, String value) { - // 根据标记更新状态 if (value.contains('R')) { _columnRedStates[cell.cellIndex.columnIndex] = true; } else if (value.contains('D')) { _columnRedStates[cell.cellIndex.columnIndex] = false; } - // 在红色区域应用红色 if (_columnRedStates[cell.cellIndex.columnIndex]) { cell.cellStyle = CellStyle(fontColorHex: ExcelColor.red); } } } + +class CellData { + final double? degree; + final String zodiac; + final double? minute; + int columnIndex = -1; + + // 定义有效的地支集合 + static const validZodiacs = {'亥', '戌', '酉', '申', '未', '午', '巳', '辰', '卯', '寅', '丑', '子'}; + + CellData({this.degree, required this.zodiac, this.minute}); + + bool isValid() { + return degree != null && minute != null && validZodiacs.contains(zodiac); + } + + double get totalMinutes => (degree ?? 0) * 60 + (minute ?? 0); +}