using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Excel = Microsoft.Office.Interop.Excel; namespace DwgExtractorManual.Models { /// /// Excel ��Ʈ�� �����͸� ���� �۾��� ����ϴ� Ŭ���� /// internal class ExcelDataWriter { private readonly ExcelManager excelManager; public ExcelDataWriter(ExcelManager excelManager) { this.excelManager = excelManager ?? throw new ArgumentNullException(nameof(excelManager)); } /// /// Title Block �����͸� Excel ��Ʈ�� ��� /// public void WriteTitleBlockData(List titleBlockRows) { if (excelManager.TitleBlockSheet == null || titleBlockRows == null || titleBlockRows.Count == 0) return; int currentRow = 2; // ��� ���� ����� ���� foreach (var row in titleBlockRows) { excelManager.TitleBlockSheet.Cells[currentRow, 1] = row.Type; excelManager.TitleBlockSheet.Cells[currentRow, 2] = row.Name; excelManager.TitleBlockSheet.Cells[currentRow, 3] = row.Tag; excelManager.TitleBlockSheet.Cells[currentRow, 4] = row.Prompt; excelManager.TitleBlockSheet.Cells[currentRow, 5] = row.Value; excelManager.TitleBlockSheet.Cells[currentRow, 6] = row.Path; excelManager.TitleBlockSheet.Cells[currentRow, 7] = row.FileName; currentRow++; } } /// /// Text Entity �����͸� Excel ��Ʈ�� ��� /// public void WriteTextEntityData(List textEntityRows) { if (excelManager.TextEntitiesSheet == null || textEntityRows == null || textEntityRows.Count == 0) return; int currentRow = 2; // ��� ���� ����� ���� foreach (var row in textEntityRows) { excelManager.TextEntitiesSheet.Cells[currentRow, 1] = row.Type; excelManager.TextEntitiesSheet.Cells[currentRow, 2] = row.Layer; excelManager.TextEntitiesSheet.Cells[currentRow, 3] = row.Text; excelManager.TextEntitiesSheet.Cells[currentRow, 4] = row.Path; excelManager.TextEntitiesSheet.Cells[currentRow, 5] = row.FileName; currentRow++; } } /// /// Note 엔터티 데이터를 Excel 시트에 쓰기 (테이블 및 셀 병합 포함) /// public void WriteNoteEntityData(List noteEntityRows) { if (excelManager.NoteEntitiesSheet == null || noteEntityRows == null || noteEntityRows.Count == 0) return; int excelRow = 2; // 헤더 다음 행부터 시작 foreach (var note in noteEntityRows) { // 기본 Note 정보 쓰기 excelManager.NoteEntitiesSheet.Cells[excelRow, 1] = note.Type; excelManager.NoteEntitiesSheet.Cells[excelRow, 2] = note.Layer; excelManager.NoteEntitiesSheet.Cells[excelRow, 3] = note.Text; excelManager.NoteEntitiesSheet.Cells[excelRow, 4] = note.X; excelManager.NoteEntitiesSheet.Cells[excelRow, 5] = note.Y; excelManager.NoteEntitiesSheet.Cells[excelRow, 6] = note.SortOrder; excelManager.NoteEntitiesSheet.Cells[excelRow, 8] = note.Path; excelManager.NoteEntitiesSheet.Cells[excelRow, 9] = note.FileName; int tableRowCount = 0; if (note.Cells != null && note.Cells.Count > 0) { // 테이블 데이터 처리 foreach (var cell in note.Cells) { int startRow = excelRow + cell.Row; int startCol = 7 + cell.Column; // G열부터 시작 int endRow = startRow + cell.RowSpan - 1; int endCol = startCol + cell.ColumnSpan - 1; Excel.Range cellRange = excelManager.NoteEntitiesSheet.Range[ excelManager.NoteEntitiesSheet.Cells[startRow, startCol], excelManager.NoteEntitiesSheet.Cells[endRow, endCol]]; // 병합 먼저 수행 if (cell.RowSpan > 1 || cell.ColumnSpan > 1) { cellRange.Merge(); } // 값 설정 및 서식 지정 cellRange.Value = cell.CellText; cellRange.VerticalAlignment = Excel.XlVAlign.xlVAlignCenter; cellRange.HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter; } // 이 테이블이 차지하는 총 행 수를 계산 tableRowCount = note.Cells.Max(c => c.Row + c.RowSpan); } // 다음 Note를 기록할 위치로 이동 // 테이블이 있으면 테이블 높이만큼, 없으면 한 칸만 이동 excelRow += (tableRowCount > 0) ? tableRowCount : 1; } } /// /// ���� �����͸� Excel ��Ʈ�� ��� /// public void WriteMappingDataToExcel(Dictionary> mappingData) { try { if (excelManager.MappingSheet == null || mappingData == null) return; int currentRow = 2; // ��� ���� ����� ���� Debug.WriteLine($"[DEBUG] Writing mapping data to Excel. Total files: {mappingData.Count}"); foreach (var fileEntry in mappingData) { string fileName = fileEntry.Key; var fileMappingData = fileEntry.Value; Debug.WriteLine($"[DEBUG] Processing file: {fileName}, entries: {fileMappingData.Count}"); foreach (var mapEntry in fileMappingData) { string mapKey = mapEntry.Key; (string aiLabel, string dwgTag, string attValue, string pdfValue) = mapEntry.Value; if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(mapKey)) { continue; } try { // ��ġ ������Ʈ�� ���� �迭 ��� object[,] rowData = new object[1, 6]; rowData[0, 0] = fileName; rowData[0, 1] = mapKey; rowData[0, 2] = aiLabel ?? ""; rowData[0, 3] = dwgTag ?? ""; rowData[0, 4] = attValue ?? ""; rowData[0, 5] = pdfValue ?? ""; Excel.Range range = excelManager.MappingSheet.Range[ excelManager.MappingSheet.Cells[currentRow, 1], excelManager.MappingSheet.Cells[currentRow, 6]]; range.Value = rowData; } catch (System.Exception ex) { Debug.WriteLine($"? Error writing row {currentRow}: {ex.Message}"); } currentRow++; } } Debug.WriteLine($"[DEBUG] Mapping data written to Excel. Total rows: {currentRow - 2}"); } catch (System.Exception ex) { Debug.WriteLine($"? ���� ������ Excel ��� �� ����: {ex.Message}"); throw; } } /// /// Excel ���� ��Ʈ���� FileName�� AILabel�� ��Ī�Ǵ� ���� ã�� Pdf_value�� ������Ʈ /// public bool UpdateExcelRow(string fileName, string aiLabel, string pdfValue) { try { if (excelManager.MappingSheet == null) return false; Excel.Range usedRange = excelManager.MappingSheet.UsedRange; if (usedRange == null) return false; int lastRow = usedRange.Rows.Count; for (int row = 2; row <= lastRow; row++) { var cellFileName = ((Excel.Range)excelManager.MappingSheet.Cells[row, 1]).Value?.ToString() ?? ""; var cellAiLabel = ((Excel.Range)excelManager.MappingSheet.Cells[row, 3]).Value?.ToString() ?? ""; if (string.Equals(cellFileName.Trim(), fileName.Trim(), StringComparison.OrdinalIgnoreCase) && string.Equals(cellAiLabel.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)) { excelManager.MappingSheet.Cells[row, 6] = pdfValue; return true; } } return false; } catch (System.Exception ex) { Debug.WriteLine($"? Excel �� ������Ʈ �� ����: {ex.Message}"); return false; } } /// /// DWG ���� ���� ��ũ���� �����ϰ� ���� (PDF �÷� ����) /// public void SaveDwgOnlyMappingWorkbook(Dictionary> mappingData, string resultFolderPath) { try { string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string savePath = System.IO.Path.Combine(resultFolderPath, $"{timestamp}_DwgOnly_Mapping.xlsx"); Debug.WriteLine($"[DEBUG] DWG ���� ���� ��ũ�� ���� ����: {savePath}"); var dwgOnlyWorkbook = excelManager.CreateNewWorkbook(); var dwgOnlyWorksheet = (Excel.Worksheet)dwgOnlyWorkbook.Worksheets[1]; dwgOnlyWorksheet.Name = "DWG Mapping Data"; // ��� ���� (PDF Value �÷� ����) dwgOnlyWorksheet.Cells[1, 1] = "���ϸ�"; dwgOnlyWorksheet.Cells[1, 2] = "Map Key"; dwgOnlyWorksheet.Cells[1, 3] = "AI Label"; dwgOnlyWorksheet.Cells[1, 4] = "DWG Tag"; dwgOnlyWorksheet.Cells[1, 5] = "DWG Value"; // ��� ��Ÿ�� ���� var headerRange = dwgOnlyWorksheet.Range["A1:E1"]; headerRange.Font.Bold = true; headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray); headerRange.Borders.LineStyle = Excel.XlLineStyle.xlContinuous; // ������ �Է� int totalRows = mappingData.Sum(f => f.Value.Count); if (totalRows > 0) { object[,] data = new object[totalRows, 5]; int row = 0; foreach (var fileEntry in mappingData) { string fileName = fileEntry.Key; foreach (var mapEntry in fileEntry.Value) { string mapKey = mapEntry.Key; var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value; data[row, 0] = fileName; data[row, 1] = mapKey; data[row, 2] = aiLabel; data[row, 3] = dwgTag; data[row, 4] = dwgValue; row++; } } Excel.Range dataRange = dwgOnlyWorksheet.Range[ dwgOnlyWorksheet.Cells[2, 1], dwgOnlyWorksheet.Cells[totalRows + 1, 5]]; dataRange.Value = data; } dwgOnlyWorksheet.Columns.AutoFit(); excelManager.SaveWorkbookAs(dwgOnlyWorkbook, savePath); Debug.WriteLine($"? DWG ���� ���� ��ũ�� ���� �Ϸ�: {System.IO.Path.GetFileName(savePath)}"); dwgOnlyWorkbook.Close(false); System.GC.Collect(); System.GC.WaitForPendingFinalizers(); } catch (System.Exception ex) { Debug.WriteLine($"? DWG ���� ���� ��ũ�� ���� �� ����: {ex.Message}"); throw; } } /// /// Height ���ĵ� Excel ���� ���� /// public void WriteHeightSortedData(List textEntities, Excel.Worksheet worksheet, string fileName) { // ��� ���� worksheet.Cells[1, 1] = "Height"; worksheet.Cells[1, 2] = "Type"; worksheet.Cells[1, 3] = "Layer"; worksheet.Cells[1, 4] = "Tag"; worksheet.Cells[1, 5] = "FileName"; worksheet.Cells[1, 6] = "Text"; // ��� ��Ÿ�� ���� var headerRange = worksheet.Range["A1:F1"]; headerRange.Font.Bold = true; headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); // �����ʹ� ExtractTextEntitiesWithHeight���� �̹� ���ĵǾ����Ƿ� �ٽ� �������� �ʽ��ϴ�. // ������ �Է� int row = 2; foreach (var entity in textEntities) // sortedEntities�� textEntities�� ���� { worksheet.Cells[row, 1] = entity.Height; worksheet.Cells[row, 2] = entity.Type; worksheet.Cells[row, 3] = entity.Layer; worksheet.Cells[row, 4] = entity.Tag; worksheet.Cells[row, 5] = fileName; worksheet.Cells[row, 6] = entity.Text; row++; } worksheet.Columns.AutoFit(); } /// /// Note 엔티티들을 Excel 워크시트에 기록합니다 (기존 데이터 아래에 추가). /// CellBoundary 데이터를 사용하여 병합된 셀의 텍스트를 적절히 처리합니다. /// public void WriteNoteEntities(List noteEntities, Excel.Worksheet worksheet, string fileName) { if (noteEntities == null || noteEntities.Count == 0) { Debug.WriteLine("[DEBUG] Note 엔티티가 없습니다."); return; } try { // 현재 워크시트의 마지막 사용된 행 찾기 Excel.Range usedRange = null; int lastRow = 1; try { usedRange = worksheet.UsedRange; lastRow = usedRange?.Rows.Count ?? 1; } catch (System.Exception ex) { Debug.WriteLine($"[DEBUG] UsedRange 접근 오류, 기본값 사용: {ex.Message}"); lastRow = 1; } int startRow = lastRow + 2; // 한 줄 띄우고 시작 Debug.WriteLine($"[DEBUG] Note 데이터 기록 시작: {startRow}행부터 {noteEntities.Count}개 항목"); // Note 섹션 헤더 추가 (표 컬럼 포함) try { worksheet.Cells[startRow - 1, 1] = "=== Notes (with Cell Boundary Tables) ==="; worksheet.Cells[startRow - 1, 2] = ""; worksheet.Cells[startRow - 1, 3] = ""; worksheet.Cells[startRow - 1, 4] = ""; worksheet.Cells[startRow - 1, 5] = ""; worksheet.Cells[startRow - 1, 6] = ""; // 표 컬럼 헤더 추가 (G열부터 최대 20개 컬럼) for (int col = 7; col <= 26; col++) // G~Z열 (20개 컬럼) { worksheet.Cells[startRow - 1, col] = $"Table Col {col - 6}"; var tableHeaderCell = (Excel.Range)worksheet.Cells[startRow - 1, col]; tableHeaderCell.Font.Bold = true; tableHeaderCell.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); tableHeaderCell.Font.Size = 9; // 작은 폰트로 설정 } // 헤더 스타일 적용 (개별 셀로 처리) var headerCell = (Excel.Range)worksheet.Cells[startRow - 1, 1]; headerCell.Font.Bold = true; headerCell.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); } catch (System.Exception ex) { Debug.WriteLine($"[DEBUG] Note 헤더 작성 오류: {ex.Message}"); } // Note 데이터 입력 (CellBoundary 데이터 사용) int row = startRow; try { foreach (var noteEntity in noteEntities) { // 기본 Note 정보 입력 (F열까지) worksheet.Cells[row, 1] = 0; // Height는 0으로 설정 worksheet.Cells[row, 2] = noteEntity.Type ?? ""; worksheet.Cells[row, 3] = noteEntity.Layer ?? ""; worksheet.Cells[row, 4] = ""; // Tag는 빈 값 worksheet.Cells[row, 5] = fileName ?? ""; worksheet.Cells[row, 6] = noteEntity.Text ?? ""; // 일반 텍스트만 (표 데이터 제외) int currentRow = row; // 현재 처리 중인 행 번호 // CellBoundary 데이터가 있으면 G열부터 테이블 데이터 처리 if (noteEntity.CellBoundaries != null && noteEntity.CellBoundaries.Count > 0) { Debug.WriteLine($"[DEBUG] CellBoundary 데이터 처리: Row {row}, 셀 수={noteEntity.CellBoundaries.Count}"); // CellBoundary의 각 셀을 해당 위치에 직접 배치 int maxTableRow = 0; foreach (var cellBoundary in noteEntity.CellBoundaries) { var (sRow, sCol, eRow, eCol) = ParseCellRangeFromLabel(cellBoundary.Label); Debug.WriteLine($"[DEBUG] CellBoundary 처리: {cellBoundary.Label} → Range=R{sRow}C{sCol}:R{eRow}C{eCol}, Text='{cellBoundary.CellText}'"); if (sRow > 0 && sCol > 0 && eRow > 0 && eCol > 0) { // 병합된 영역의 셀 개수 계산: (eRow - sRow) × (eCol - sCol) int rowCount = eRow - sRow; // 행 개수 (bottomRight - topLeft) int colCount = eCol - sCol; // 열 개수 (bottomRight - topLeft) Debug.WriteLine($"[DEBUG] 병합 영역 크기: {rowCount+1}행 × {colCount+1}열"); // 병합된 영역의 모든 셀에 텍스트 복사 (topLeft부터 bottomRight-1까지) for (int r = sRow; r < eRow; r++) // < eRow (bottomRight 제외) { for (int c = sCol; c < eCol; c++) // < eCol (bottomRight 제외) { // Excel에서 테이블 위치 계산: // R1 → Note의 현재 행 (currentRow) // R2 → Note의 다음 행 (currentRow + 1) // C1 → G열(7), C2 → H열(8) int excelRow = currentRow + (r - 1); // R1=currentRow, R2=currentRow+1, ... int excelCol = 7 + (c - 1); // C1=G열(7), C2=H열(8), ... // Excel 범위 체크 (최대 20개 컬럼까지) if (excelCol <= 26) // Z열까지 { // CellText가 비어있어도 일단 배치해보기 (디버그용) var cellValue = string.IsNullOrEmpty(cellBoundary.CellText) ? "[빈셀]" : cellBoundary.CellText; // 텍스트 형식으로 설정하여 "0:0" 같은 값이 시간으로 포맷되지 않도록 함 var cell = (Excel.Range)worksheet.Cells[excelRow, excelCol]; cell.NumberFormat = "@"; // 텍스트 형식 cell.Value = cellValue; Debug.WriteLine($"[DEBUG] ✅ 셀 복사: R{r}C{c} → Excel[{excelRow},{excelCol}] = '{cellValue}'"); } else { Debug.WriteLine($"[DEBUG] ❌ Excel 컬럼 범위 초과: {excelCol} > 26"); } // 테이블이 차지하는 최대 행 수 추적 maxTableRow = Math.Max(maxTableRow, r); } } } else { Debug.WriteLine($"[DEBUG] ❌ 잘못된 Range: {cellBoundary.Label} → R{sRow}C{sCol}:R{eRow}C{eCol}"); } } // 테이블이 여러 행을 차지하는 경우 currentRow 업데이트 if (maxTableRow > 1) { currentRow += (maxTableRow - 1); Debug.WriteLine($"[DEBUG] 테이블 행 수만큼 currentRow 업데이트: +{maxTableRow - 1} → {currentRow}"); } } else if (!string.IsNullOrEmpty(noteEntity.TableCsv)) { // 기존 TableCsv 방식 (백업용) var tableRows = noteEntity.TableCsv.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); Debug.WriteLine($"[DEBUG] 기존 표 데이터 처리: Row {row}, 표 행 수={tableRows.Length}"); for (int tableRowIndex = 0; tableRowIndex < tableRows.Length; tableRowIndex++) { var tableCells = tableRows[tableRowIndex].Split(','); // 각 셀을 G열부터 배치 (최대 15개 컬럼까지) for (int cellIndex = 0; cellIndex < Math.Min(tableCells.Length, 15); cellIndex++) { var cellValue = tableCells[cellIndex].Trim().Trim('"'); // 따옴표 제거 // 텍스트 형식으로 설정하여 "0:0" 같은 값이 시간으로 포맷되지 않도록 함 var cell = (Excel.Range)worksheet.Cells[currentRow, 7 + cellIndex]; cell.NumberFormat = "@"; // 텍스트 형식 cell.Value = cellValue; // G열(7)부터 시작 } // 표의 첫 번째 행이 아니면 새로운 Excel 행 추가 if (tableRowIndex > 0) { currentRow++; // 새로운 행에는 기본 Note 정보 복사 (Type, Layer 등) worksheet.Cells[currentRow, 1] = 0; worksheet.Cells[currentRow, 2] = noteEntity.Type ?? ""; worksheet.Cells[currentRow, 3] = noteEntity.Layer ?? ""; worksheet.Cells[currentRow, 4] = ""; worksheet.Cells[currentRow, 5] = fileName ?? ""; worksheet.Cells[currentRow, 6] = "(continued)"; // 연속 표시 } Debug.WriteLine($"[DEBUG] 표 행 {tableRowIndex + 1}/{tableRows.Length}: Excel Row {currentRow}, 셀 수={tableCells.Length}"); } } // "NOTE" 타입인 경우 행 배경색 변경 (표 영역 포함) if (noteEntity.Type == "Note") { Excel.Range noteRowRange = worksheet.Range[worksheet.Cells[row, 1], worksheet.Cells[currentRow, 26]]; // Z열까지 noteRowRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); noteRowRange.Font.Bold = true; } Debug.WriteLine($"[DEBUG] Excel 기록: Row {row}~{currentRow}, Order {noteEntity.SortOrder}, Type {noteEntity.Type}, Pos({noteEntity.X:F1},{noteEntity.Y:F1}), Text: '{noteEntity.Text}', HasCellBoundaries: {noteEntity.CellBoundaries?.Count > 0} (Count: {noteEntity.CellBoundaries?.Count ?? 0}), HasTableCsv: {!string.IsNullOrEmpty(noteEntity.TableCsv)}"); // CellBoundaries 상세 디버그 if (noteEntity.CellBoundaries != null && noteEntity.CellBoundaries.Count > 0) { Debug.WriteLine($"[DEBUG] CellBoundaries 상세:"); foreach (var cb in noteEntity.CellBoundaries.Take(5)) // 처음 5개만 출력 { Debug.WriteLine($"[DEBUG] {cb.Label}: '{cb.CellText}'"); } } // 다음 Note는 현재 행의 다음 행부터 시작 row = currentRow + 1; } Debug.WriteLine($"[DEBUG] Note 데이터 기록 완료: {row - startRow}개 항목"); } catch (System.Exception ex) { Debug.WriteLine($"[DEBUG] Note 데이터 입력 오류: {ex.Message}"); Debug.WriteLine($"[DEBUG] 처리된 행: {row - startRow}개"); } // AutoFit 시도 (오류 발생시 무시) try { worksheet.Columns.AutoFit(); } catch (System.Exception ex) { Debug.WriteLine($"[DEBUG] AutoFit 오류 (무시됨): {ex.Message}"); } } catch (System.Exception ex) { Debug.WriteLine($"❌ WriteNoteEntities 전체 오류: {ex.Message}"); Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); throw; // 상위로 예외 전파 } } /// /// 라벨에서 셀 범위 정보를 파싱합니다. /// 예: "R1C2" → (1, 2, 1, 2) 또는 "R2C2→R3C4" → (2, 2, 3, 4) /// private (int sRow, int sCol, int eRow, int eCol) ParseCellRangeFromLabel(string label) { try { if (string.IsNullOrEmpty(label)) return (0, 0, 0, 0); if (label.Contains("→")) { // "R2C2→R3C4" 형태 - 범위 파싱 var parts = label.Split('→'); if (parts.Length == 2) { var (sRow, sCol) = ParseSingleCell(parts[0]); var (eRow, eCol) = ParseSingleCell(parts[1]); return (sRow, sCol, eRow, eCol); } } else { // "R1C2" 형태 - 단일 셀 var (row, col) = ParseSingleCell(label); return (row, col, row, col); } return (0, 0, 0, 0); } catch { return (0, 0, 0, 0); } } /// /// 단일 셀 위치를 파싱합니다. (예: "R2C3" → (2, 3)) /// private (int row, int col) ParseSingleCell(string cellStr) { try { if (string.IsNullOrEmpty(cellStr)) return (0, 0); var rIndex = cellStr.IndexOf('R'); var cIndex = cellStr.IndexOf('C'); if (rIndex >= 0 && cIndex > rIndex) { var rowStr = cellStr.Substring(rIndex + 1, cIndex - rIndex - 1); var colStr = cellStr.Substring(cIndex + 1); if (int.TryParse(rowStr, out int row) && int.TryParse(colStr, out int col)) { return (row, col); } } return (0, 0); } catch { return (0, 0); } } } }