From 66dd64306c5d023ee36bb5facbd2cd0e73834f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=8A=B9=EC=9A=B0=EC=9A=B0?= Date: Tue, 29 Jul 2025 14:06:12 +0900 Subject: [PATCH] =?UTF-8?q?ExportExcel=20=EC=A0=95=EB=A6=AC,=20height?= =?UTF-8?q?=EC=A0=95=EB=A0=AC=20attRef=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Models/AppSettings.cs | 0 Models/DwgDataExtractor.cs | 397 +++++++ Models/ExcelDataWriter.cs | 279 +++++ Models/ExcelManager.cs | 309 +++++ Models/ExportExcel.cs | 2172 +++++------------------------------ Models/JsonDataProcessor.cs | 317 +++++ Models/SettingsManager.cs | 0 7 files changed, 1613 insertions(+), 1861 deletions(-) create mode 100644 Models/AppSettings.cs create mode 100644 Models/DwgDataExtractor.cs create mode 100644 Models/ExcelDataWriter.cs create mode 100644 Models/ExcelManager.cs create mode 100644 Models/JsonDataProcessor.cs create mode 100644 Models/SettingsManager.cs diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs new file mode 100644 index 0000000..e69de29 diff --git a/Models/DwgDataExtractor.cs b/Models/DwgDataExtractor.cs new file mode 100644 index 0000000..c000cc0 --- /dev/null +++ b/Models/DwgDataExtractor.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Teigha.DatabaseServices; +using Teigha.Runtime; + +namespace DwgExtractorManual.Models +{ + /// + /// DWG Ͽ ؽƮ ƼƼ ϴ Ŭ + /// + internal class DwgDataExtractor + { + private readonly FieldMapper fieldMapper; + + public DwgDataExtractor(FieldMapper fieldMapper) + { + this.fieldMapper = fieldMapper ?? throw new ArgumentNullException(nameof(fieldMapper)); + } + + /// + /// DWG Ͽ ͸ Ͽ ExcelRowData Ʈ ȯ + /// + public DwgExtractionResult ExtractFromDwgFile(string filePath, IProgress? progress = null, CancellationToken cancellationToken = default) + { + var result = new DwgExtractionResult(); + + if (!File.Exists(filePath)) + { + Debug.WriteLine($"? ʽϴ: {filePath}"); + return result; + } + + try + { + progress?.Report(0); + cancellationToken.ThrowIfCancellationRequested(); + + using (var database = new Database(false, true)) + { + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + progress?.Report(10); + + using (var tran = database.TransactionManager.StartTransaction()) + { + var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; + using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) + { + int totalEntities = btr.Cast().Count(); + int processedCount = 0; + var fileName = Path.GetFileNameWithoutExtension(database.Filename); + + if (string.IsNullOrEmpty(fileName)) + { + fileName = "Unknown_File"; + } + + foreach (ObjectId entId in btr) + { + cancellationToken.ThrowIfCancellationRequested(); + + using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) + { + string layerName = GetLayerName(ent.LayerId, tran, database); + + ProcessEntity(ent, tran, database, layerName, fileName, result); + } + + processedCount++; + double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0; + progress?.Report(Math.Min(currentProgress, 90.0)); + } + } + + tran.Commit(); + } + } + + progress?.Report(100); + return result; + } + catch (OperationCanceledException) + { + Debug.WriteLine("? ۾ ҵǾϴ."); + progress?.Report(0); + return result; + } + catch (Teigha.Runtime.Exception ex) + { + progress?.Report(0); + Debug.WriteLine($"? DWG ó Teigha ߻: {ex.Message}"); + return result; + } + catch (System.Exception ex) + { + progress?.Report(0); + Debug.WriteLine($"? Ϲ ߻: {ex.Message}"); + return result; + } + } + + /// + /// DWG Ͽ ؽƮ ƼƼ Ͽ Height Բ ȯմϴ. + /// + public List ExtractTextEntitiesWithHeight(string filePath) + { + var attRefEntities = new List(); + var otherTextEntities = new List(); + + try + { + using (var database = new Database(false, true)) + { + database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); + + using (var tran = database.TransactionManager.StartTransaction()) + { + var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; + using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) + { + foreach (ObjectId entId in btr) + { + using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) + { + string layerName = GetLayerName(ent.LayerId, tran, database); + + // AttributeReference ó + if (ent is BlockReference blr) + { + foreach (ObjectId attId in blr.AttributeCollection) + { + using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) + { + if (attRef != null) + { + var textString = attRef.TextString == null ? "" : attRef.TextString; + attRefEntities.Add(new TextEntityInfo + { + Height = attRef.Height, + Type = "AttRef", + Layer = layerName, + Tag = attRef.Tag, + Text = textString, + }); + } + } + } + } + // DBText ó + else if (ent is DBText dbText) + { + otherTextEntities.Add(new TextEntityInfo + { + Height = dbText.Height, + Type = "DBText", + Layer = layerName, + Tag = "", + Text = dbText.TextString + }); + } + // MText ó + else if (ent is MText mText) + { + otherTextEntities.Add(new TextEntityInfo + { + Height = mText.Height, + Type = "MText", + Layer = layerName, + Tag = "", + Text = mText.Contents + }); + } + } + } + } + + tran.Commit(); + } + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"? ؽƮ ƼƼ ({Path.GetFileName(filePath)}): {ex.Message}"); + } + + var sortedAttRefEntities = attRefEntities.OrderByDescending(e => e.Height).ToList(); + var sortedOtherTextEntities = otherTextEntities.OrderByDescending(e => e.Height).ToList(); + + sortedAttRefEntities.AddRange(sortedOtherTextEntities); + return sortedAttRefEntities; + } + + private void ProcessEntity(Entity ent, Transaction tran, Database database, string layerName, string fileName, DwgExtractionResult result) + { + // AttributeDefinition + if (ent is AttributeDefinition attDef) + { + var titleBlockRow = new TitleBlockRowData + { + Type = attDef.GetType().Name, + Name = attDef.BlockName, + Tag = attDef.Tag, + Prompt = attDef.Prompt, + Value = attDef.TextString, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename) + }; + + result.TitleBlockRows.Add(titleBlockRow); + + // + AddMappingData(fileName, attDef.Tag, attDef.TextString, result); + } + // BlockReference AttributeReference + else if (ent is BlockReference blr) + { + foreach (ObjectId attId in blr.AttributeCollection) + { + using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) + { + if (attRef != null && attRef.TextString.Trim() != "") + { + var titleBlockRow = new TitleBlockRowData + { + Type = attRef.GetType().Name, + Name = blr.Name, + Tag = attRef.Tag, + Prompt = GetPromptFromAttributeReference(tran, blr, attRef.Tag), + Value = attRef.TextString, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename) + }; + + result.TitleBlockRows.Add(titleBlockRow); + + // + var aiLabel = fieldMapper.ExpresswayToAilabel(attRef.Tag); + if (aiLabel != null) + { + AddMappingData(fileName, attRef.Tag, attRef.TextString, result); + } + } + } + } + } + // DBText ƼƼ ( Ʈ) + else if (ent is DBText dbText) + { + var textEntityRow = new TextEntityRowData + { + Type = "DBText", + Layer = layerName, + Text = dbText.TextString, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename) + }; + + result.TextEntityRows.Add(textEntityRow); + } + // MText ƼƼ ( Ʈ) + else if (ent is MText mText) + { + var textEntityRow = new TextEntityRowData + { + Type = "MText", + Layer = layerName, + Text = mText.Contents, + Path = database.Filename, + FileName = Path.GetFileName(database.Filename) + }; + + result.TextEntityRows.Add(textEntityRow); + } + } + + private void AddMappingData(string fileName, string tag, string attValue, DwgExtractionResult result) + { + var aiLabel = fieldMapper.ExpresswayToAilabel(tag); + var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel); + + if (!string.IsNullOrEmpty(aiLabel)) + { + var finalMapKey = mapKey ?? aiLabel; + result.AddMappingData(fileName, finalMapKey, aiLabel, tag, attValue, ""); + } + else + { + var finalMapKey = mapKey ?? tag; + if (!string.IsNullOrEmpty(finalMapKey)) + { + result.AddMappingData(fileName, finalMapKey, tag, tag, attValue, ""); + } + } + } + + private string? GetPromptFromAttributeReference(Transaction tr, BlockReference blockref, string tag) + { + string? prompt = null; + + BlockTableRecord? blockDef = tr.GetObject(blockref.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; + if (blockDef == null) return null; + + foreach (ObjectId objId in blockDef) + { + AttributeDefinition? attDef = tr.GetObject(objId, OpenMode.ForRead) as AttributeDefinition; + if (attDef != null) + { + if (attDef.Tag.Equals(tag, System.StringComparison.OrdinalIgnoreCase)) + { + prompt = attDef.Prompt; + break; + } + } + } + + return prompt; + } + + private string GetLayerName(ObjectId layerId, Transaction transaction, Database database) + { + try + { + using (var layerTableRecord = transaction.GetObject(layerId, OpenMode.ForRead) as LayerTableRecord) + { + return layerTableRecord?.Name ?? ""; + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"Layer ̸ : {ex.Message}"); + return ""; + } + } + } + + /// + /// DWG Ŭ + /// + public class DwgExtractionResult + { + public List TitleBlockRows { get; set; } = new List(); + public List TextEntityRows { get; set; } = new List(); + public Dictionary> FileToMapkeyToLabelTagValuePdf { get; set; } + = new Dictionary>(); + + public void AddMappingData(string fileName, string mapKey, string aiLabel, string dwgTag, string dwgValue, string pdfValue) + { + if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName)) + { + FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary(); + } + + FileToMapkeyToLabelTagValuePdf[fileName][mapKey] = (aiLabel, dwgTag, dwgValue, pdfValue); + } + } + + /// + /// Title Block + /// + public class TitleBlockRowData + { + public string Type { get; set; } = ""; + public string Name { get; set; } = ""; + public string Tag { get; set; } = ""; + public string Prompt { get; set; } = ""; + public string Value { get; set; } = ""; + public string Path { get; set; } = ""; + public string FileName { get; set; } = ""; + } + + /// + /// Text Entity + /// + public class TextEntityRowData + { + public string Type { get; set; } = ""; + public string Layer { get; set; } = ""; + public string Text { get; set; } = ""; + public string Path { get; set; } = ""; + public string FileName { get; set; } = ""; + } + + /// + /// ؽƮ ƼƼ Ŭ + /// + public class TextEntityInfo + { + public double Height { get; set; } + public string Type { get; set; } = ""; + public string Layer { get; set; } = ""; + public string Tag { get; set; } = ""; + public string Text { get; set; } = ""; + } +} \ No newline at end of file diff --git a/Models/ExcelDataWriter.cs b/Models/ExcelDataWriter.cs new file mode 100644 index 0000000..829650f --- /dev/null +++ b/Models/ExcelDataWriter.cs @@ -0,0 +1,279 @@ +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++; + } + } + + /// + /// ͸ 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 = excelManager.MappingSheet.Cells[row, 1]?.Value?.ToString() ?? ""; + var cellAiLabel = 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(); + } + } +} \ No newline at end of file diff --git a/Models/ExcelManager.cs b/Models/ExcelManager.cs new file mode 100644 index 0000000..cefa558 --- /dev/null +++ b/Models/ExcelManager.cs @@ -0,0 +1,309 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using Excel = Microsoft.Office.Interop.Excel; + +namespace DwgExtractorManual.Models +{ + /// + /// Excel COM ü ⺻ ۾ ϴ Ŭ + /// + internal class ExcelManager : IDisposable + { + public Excel.Application? ExcelApplication { get; private set; } + public Excel.Workbook? TitleBlockWorkbook { get; private set; } + public Excel.Workbook? MappingWorkbook { get; private set; } + + public Excel.Worksheet? TitleBlockSheet { get; private set; } + public Excel.Worksheet? TextEntitiesSheet { get; private set; } + public Excel.Worksheet? MappingSheet { get; private set; } + + /// + /// Excel ø̼ ũƮ ʱȭ + /// + public void InitializeExcel() + { + try + { + var excelApp = new Excel.Application(); + ExcelApplication = excelApp; + ExcelApplication.Visible = false; // WPF ó + Excel.Workbook workbook = excelApp.Workbooks.Add(); + TitleBlockWorkbook = workbook; + + // Title Block Sheet (⺻ Sheet1) + TitleBlockSheet = (Excel.Worksheet)workbook.Sheets[1]; + TitleBlockSheet.Name = "Title Block"; + SetupTitleBlockHeaders(); + + // Text Entities Sheet ߰ + TextEntitiesSheet = (Excel.Worksheet)workbook.Sheets.Add(); + TextEntitiesSheet.Name = "Text Entities"; + SetupTextEntitiesHeaders(); + + // Ϳ ũ Ʈ + MappingWorkbook = excelApp.Workbooks.Add(); + MappingSheet = (Excel.Worksheet)MappingWorkbook.Sheets[1]; + MappingSheet.Name = "Mapping Data"; + SetupMappingHeaders(); + } + catch (System.Exception ex) + { + Debug.WriteLine($"Excel ʱȭ ߻: {ex.Message}"); + ReleaseExcelObjects(); + throw; + } + } + + /// + /// Excel Ʈ + /// + public bool OpenExistingFile(string excelFilePath) + { + try + { + if (!File.Exists(excelFilePath)) + { + Debug.WriteLine($"? Excel ʽϴ: {excelFilePath}"); + return false; + } + + if (ExcelApplication == null) + { + ExcelApplication = new Excel.Application(); + ExcelApplication.Visible = false; + } + + MappingWorkbook = ExcelApplication.Workbooks.Open(excelFilePath); + MappingSheet = (Excel.Worksheet)MappingWorkbook.Sheets["Mapping Data"]; + + if (MappingSheet == null) + { + Debug.WriteLine("? 'Mapping Data' Ʈ ã ϴ."); + return false; + } + + return true; + } + catch (System.Exception ex) + { + Debug.WriteLine($"? Excel : {ex.Message}"); + return false; + } + } + + /// + /// ο ũ (Height Ŀ) + /// + public Excel.Workbook CreateNewWorkbook() + { + if (ExcelApplication == null) + { + ExcelApplication = new Excel.Application(); + ExcelApplication.Visible = false; + } + return ExcelApplication.Workbooks.Add(); + } + + // Title Block Ʈ + private void SetupTitleBlockHeaders() + { + if (TitleBlockSheet == null) return; + + TitleBlockSheet.Cells[1, 1] = "Type"; // : AttributeReference, AttributeDefinition + TitleBlockSheet.Cells[1, 2] = "Name"; // BlockReference ̸ Ǵ BlockDefinition ̸ + TitleBlockSheet.Cells[1, 3] = "Tag"; // Attribute Tag + TitleBlockSheet.Cells[1, 4] = "Prompt"; // Attribute Prompt + TitleBlockSheet.Cells[1, 5] = "Value"; // Attribute (TextString) + TitleBlockSheet.Cells[1, 6] = "Path"; // DWG ü + TitleBlockSheet.Cells[1, 7] = "FileName"; // DWG ̸ + + // Ÿ + Excel.Range headerRange = TitleBlockSheet.Range["A1:G1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); + } + + // Text Entities Ʈ + private void SetupTextEntitiesHeaders() + { + if (TextEntitiesSheet == null) return; + + TextEntitiesSheet.Cells[1, 1] = "Type"; // DBText, MText + TextEntitiesSheet.Cells[1, 2] = "Layer"; // Layer ̸ + TextEntitiesSheet.Cells[1, 3] = "Text"; // ؽƮ + TextEntitiesSheet.Cells[1, 4] = "Path"; // DWG ü + TextEntitiesSheet.Cells[1, 5] = "FileName"; // DWG ̸ + + // Ÿ + Excel.Range headerRange = TextEntitiesSheet.Range["A1:E1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGreen); + } + + // Ʈ + private void SetupMappingHeaders() + { + if (MappingSheet == null) return; + + MappingSheet.Cells[1, 1] = "FileName"; // ̸ + MappingSheet.Cells[1, 2] = "MapKey"; // Ű + MappingSheet.Cells[1, 3] = "AILabel"; // AI + MappingSheet.Cells[1, 4] = "DwgTag"; // DWG Tag + MappingSheet.Cells[1, 5] = "Att_value"; // DWG + MappingSheet.Cells[1, 6] = "Pdf_value"; // PDF ( ) + + // Ÿ + Excel.Range headerRange = MappingSheet.Range["A1:F1"]; + headerRange.Font.Bold = true; + headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); + } + + /// + /// ũ + /// + public bool SaveWorkbook(Excel.Workbook? workbook = null) + { + try + { + if (workbook != null) + { + workbook.Save(); + return true; + } + + if (MappingWorkbook != null) + { + MappingWorkbook.Save(); + return true; + } + + if (TitleBlockWorkbook != null) + { + TitleBlockWorkbook.Save(); + return true; + } + + return false; + } + catch (System.Exception ex) + { + Debug.WriteLine($"? Excel : {ex.Message}"); + return false; + } + } + + /// + /// ũ ο + /// + public void SaveWorkbookAs(Excel.Workbook? workbook, string savePath) + { + if (workbook == null) return; + + string? directory = Path.GetDirectoryName(savePath); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + workbook.SaveAs(savePath, + FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, + AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); + } + + /// + /// Excel Ʈ ִ ȿ ̸ մϴ. + /// + public string GetValidSheetName(string originalName) + { + if (string.IsNullOrEmpty(originalName)) + return "Sheet"; + + // Excel Ʈ ʴ + string validName = originalName; + char[] invalidChars = { '\\', '/', '?', '*', '[', ']', ':' }; + + foreach (char c in invalidChars) + { + validName = validName.Replace(c, '_'); + } + + // 31ڷ (Excel Ʈ ִ ) + if (validName.Length > 31) + { + validName = validName.Substring(0, 31); + } + + return validName; + } + + public void CloseWorkbooks() + { + if (TitleBlockWorkbook != null) + { + try { TitleBlockWorkbook.Close(false); } + catch { } + } + if (MappingWorkbook != null) + { + try { MappingWorkbook.Close(false); } + catch { } + } + if (ExcelApplication != null) + { + try { ExcelApplication.Quit(); } + catch { } + } + } + + private void ReleaseExcelObjects() + { + ReleaseComObject(TitleBlockSheet); + ReleaseComObject(TextEntitiesSheet); + ReleaseComObject(MappingSheet); + ReleaseComObject(TitleBlockWorkbook); + ReleaseComObject(MappingWorkbook); + ReleaseComObject(ExcelApplication); + + TitleBlockSheet = null; + TextEntitiesSheet = null; + MappingSheet = null; + TitleBlockWorkbook = null; + MappingWorkbook = null; + ExcelApplication = null; + } + + private void ReleaseComObject(object? obj) + { + try + { + if (obj != null && Marshal.IsComObject(obj)) + { + Marshal.ReleaseComObject(obj); + } + } + catch (System.Exception) + { + // ߻ + } + } + + public void Dispose() + { + try + { + Debug.WriteLine("[DEBUG] ExcelManager Dispose "); + CloseWorkbooks(); + ReleaseExcelObjects(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Debug.WriteLine("[DEBUG] ExcelManager Dispose Ϸ"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DEBUG] ExcelManager Dispose : {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Models/ExportExcel.cs b/Models/ExportExcel.cs index c3a2e33..7149a9b 100644 --- a/Models/ExportExcel.cs +++ b/Models/ExportExcel.cs @@ -3,48 +3,37 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.InteropServices; // COM 객체 해제를 위해 필요 using System.Threading; using System.Threading.Tasks; -using Teigha.DatabaseServices; -using Teigha.Geometry; using Teigha.Runtime; -using Excel = Microsoft.Office.Interop.Excel; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace DwgExtractorManual.Models { /// - /// DWG 파일에서 Excel로 데이터 내보내기 클래스 - /// AttributeReference, AttributeDefinition, DBText, MText 추출 지원 + /// DWG 파일에서 Excel로 데이터 내보내기 메인 클래스 + /// 리팩토링된 구조로 각 기능별 클래스를 조합하여 사용 /// internal class ExportExcel : IDisposable { - // ODA 서비스 객체 (managed by singleton) - private Services appServices; + // 컴포넌트들 + private readonly ExcelManager excelManager; + private readonly DwgDataExtractor dwgExtractor; + private readonly JsonDataProcessor jsonProcessor; + private readonly ExcelDataWriter excelWriter; + private readonly FieldMapper fieldMapper; - // Excel COM 객체들 - private Excel.Application excelApplication; - private Excel.Workbook workbook1; - private Excel.Worksheet titleBlockSheet; // Title Block용 시트 - private Excel.Worksheet textEntitiesSheet; // Text Entities용 시트 - private Excel.Workbook mappingWorkbook; // 매핑 데이터용 워크북 - private Excel.Worksheet mappingSheet; // 매핑 데이터용 시트 + // ODA 서비스 관리 + private Services? appServices; + + // 매핑 데이터 저장용 + private Dictionary> FileToMapkeyToLabelTagValuePdf + = new Dictionary>(); readonly List MapKeys; - - // 각 시트의 현재 행 번호 - private int titleBlockCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 - private int textEntitiesCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 - private int mappingDataCurrentRow = 2; // 헤더가 1행이므로 데이터는 2행부터 시작 - - // 매핑 데이터 저장용 딕셔너리 - (AILabel, DwgTag, DwgValue, PdfValue) - private Dictionary> FileToMapkeyToLabelTagValuePdf = new Dictionary>(); - - private FieldMapper fieldMapper; - // 생성자: ODA 및 Excel 초기화 + /// + /// 생성자: 모든 컴포넌트 초기화 + /// public ExportExcel() { try @@ -54,36 +43,303 @@ namespace DwgExtractorManual.Models Debug.WriteLine("✅ FieldMapper 로딩 성공"); MapKeys = fieldMapper.GetAllDocAiKeys(); Debug.WriteLine($"📊 총 DocAI 키 개수: {MapKeys?.Count ?? 0}"); - + // 매핑 테스트 (디버깅용) TestFieldMapper(); + + Debug.WriteLine("🔄 ODA 초기화 중..."); + InitializeTeighaServices(); + + // 컴포넌트들 초기화 + excelManager = new ExcelManager(); + dwgExtractor = new DwgDataExtractor(fieldMapper); + jsonProcessor = new JsonDataProcessor(); + excelWriter = new ExcelDataWriter(excelManager); + + Debug.WriteLine("🔄 Excel 초기화 중..."); + excelManager.InitializeExcel(); } catch (System.Exception ex) { - Debug.WriteLine($"❌ FieldMapper 로딩 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); + Debug.WriteLine($"❌ ExportExcel 초기화 오류: {ex.Message}"); throw; } - - Debug.WriteLine("🔄 ODA 초기화 중..."); - InitializeTeighaServices(); - Debug.WriteLine("🔄 Excel 초기화 중..."); - InitializeExcel(); } - // 필드 매퍼 테스트 + /// + /// 단일 DWG 파일에서 데이터를 추출하여 Excel에 추가 + /// + public bool ExportDwgToExcel(string filePath, IProgress? progress = null, CancellationToken cancellationToken = default) + { + Debug.WriteLine($"[DEBUG] ExportDwgToExcel 시작: {filePath}"); + + if (excelManager.ExcelApplication == null) + { + Debug.WriteLine("❌ Excel이 초기화되지 않았습니다."); + return false; + } + + if (!File.Exists(filePath)) + { + Debug.WriteLine($"❌ 파일이 존재하지 않습니다: {filePath}"); + return false; + } + + try + { + // DWG 데이터 추출 + var extractionResult = dwgExtractor.ExtractFromDwgFile(filePath, progress, cancellationToken); + + if (extractionResult == null) + { + return false; + } + + // Excel에 데이터 기록 + excelWriter.WriteTitleBlockData(extractionResult.TitleBlockRows); + excelWriter.WriteTextEntityData(extractionResult.TextEntityRows); + + // 매핑 데이터 병합 + foreach (var fileEntry in extractionResult.FileToMapkeyToLabelTagValuePdf) + { + if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileEntry.Key)) + { + FileToMapkeyToLabelTagValuePdf[fileEntry.Key] = new Dictionary(); + } + + foreach (var mapEntry in fileEntry.Value) + { + FileToMapkeyToLabelTagValuePdf[fileEntry.Key][mapEntry.Key] = mapEntry.Value; + } + } + + // 매핑 데이터를 Excel에 기록 + excelWriter.WriteMappingDataToExcel(FileToMapkeyToLabelTagValuePdf); + + return true; + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ ExportDwgToExcel 오류: {ex.Message}"); + return false; + } + } + + /// + /// 기존 Excel 파일을 열어 JSON 파일의 PDF 분석 결과로 업데이트 + /// + public bool UpdateExistingExcelWithJson(string excelFilePath, string jsonFilePath) + { + try + { + Debug.WriteLine($"[DEBUG] 기존 Excel 파일 업데이트 시작: {excelFilePath}"); + + if (!excelManager.OpenExistingFile(excelFilePath)) + { + return false; + } + + Debug.WriteLine("✅ 기존 Excel 파일 열기 성공"); + return UpdateMappingSheetFromJson(jsonFilePath); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 기존 Excel 파일 업데이트 중 오류: {ex.Message}"); + return false; + } + } + + /// + /// JSON 파일에서 PDF 분석 결과를 읽어 Excel 매핑 시트 업데이트 + /// + public bool UpdateMappingSheetFromJson(string jsonFilePath) + { + if (!File.Exists(jsonFilePath)) + { + Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); + return false; + } + + // JSON 처리를 통해 매핑 데이터를 업데이트하고, Excel에 반영 + return jsonProcessor.UpdateMappingDataFromJson(FileToMapkeyToLabelTagValuePdf, jsonFilePath) && + UpdateExcelFromMappingData(); + } + + /// + /// DWG 파일들을 처리하여 Height 순으로 정렬된 Excel 파일 생성 + /// + public void ExportDwgToExcelHeightSorted(string[] dwgFiles, string resultFolder) + { + try + { + Debug.WriteLine($"[DEBUG] Height 정렬 Excel 생성 시작: {dwgFiles.Length}개 파일"); + + string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); + string savePath = Path.Combine(resultFolder, $"{timestamp}_HeightSorted.xlsx"); + + var heightSortedWorkbook = excelManager.CreateNewWorkbook(); + bool firstSheetProcessed = false; + + foreach (string dwgFile in dwgFiles) + { + if (!File.Exists(dwgFile)) + { + continue; + } + + string fileName = Path.GetFileNameWithoutExtension(dwgFile); + + try + { + var worksheet = firstSheetProcessed ? + heightSortedWorkbook.Worksheets.Add() : + (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; + + worksheet.Name = excelManager.GetValidSheetName(fileName); + firstSheetProcessed = true; + + var textEntities = dwgExtractor.ExtractTextEntitiesWithHeight(dwgFile); + excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); + + Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ {fileName} 처리 중 오류: {ex.Message}"); + continue; + } + } + + if (!firstSheetProcessed) + { + var defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; + defaultSheet.Name = "No_DWG_Files"; + defaultSheet.Cells[1, 1] = "No DWG files found in this folder"; + } + + excelManager.SaveWorkbookAs(heightSortedWorkbook, savePath); + heightSortedWorkbook.Close(false); + + Debug.WriteLine($"✅ Height 정렬 Excel 파일 저장 완료: {Path.GetFileName(savePath)}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ Height 정렬 Excel 생성 중 오류: {ex.Message}"); + throw; + } + } + + /// + /// 모든 DWG 파일들을 하나의 Excel 파일로 처리하여 Height 순으로 정렬 + /// + public void ExportAllDwgToExcelHeightSorted(List<(string filePath, string folderName)> allDwgFiles, string savePath) + { + try + { + Debug.WriteLine($"[DEBUG] 단일 Excel 파일로 Height 정렬 생성 시작: {allDwgFiles.Count}개 파일"); + + var heightSortedWorkbook = excelManager.CreateNewWorkbook(); + bool firstSheetProcessed = false; + + foreach (var (filePath, folderName) in allDwgFiles) + { + if (!File.Exists(filePath)) + { + continue; + } + + string fileName = Path.GetFileNameWithoutExtension(filePath); + + try + { + var worksheet = firstSheetProcessed ? + heightSortedWorkbook.Worksheets.Add() : + (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; + + worksheet.Name = excelManager.GetValidSheetName(fileName); + firstSheetProcessed = true; + + var textEntities = dwgExtractor.ExtractTextEntitiesWithHeight(filePath); + excelWriter.WriteHeightSortedData(textEntities, worksheet, fileName); + + Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {textEntities.Count}개 엔티티"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ {fileName} 처리 중 오류: {ex.Message}"); + continue; + } + } + + if (!firstSheetProcessed) + { + var defaultSheet = (Microsoft.Office.Interop.Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; + defaultSheet.Name = "No_DWG_Files"; + defaultSheet.Cells[1, 1] = "No DWG files found in any folder"; + } + + excelManager.SaveWorkbookAs(heightSortedWorkbook, savePath); + heightSortedWorkbook.Close(false); + + Debug.WriteLine($"✅ 단일 Height 정렬 Excel 파일 저장 완료: {Path.GetFileName(savePath)}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"❌ 단일 Height 정렬 Excel 생성 중 오류: {ex.Message}"); + throw; + } + } + + // Helper methods and legacy support methods + public bool SaveExcel() => excelManager.SaveWorkbook(); + + public void SaveMappingWorkbookOnly(string savePath) => excelManager.SaveWorkbookAs(excelManager.MappingWorkbook, savePath); + + public void SaveDwgOnlyMappingWorkbook(string resultFolderPath) => excelWriter.SaveDwgOnlyMappingWorkbook(FileToMapkeyToLabelTagValuePdf, resultFolderPath); + + public void SaveAndCloseExcel(string savePath) + { + try + { + excelManager.SaveWorkbookAs(excelManager.TitleBlockWorkbook, savePath); + + if (excelManager.MappingWorkbook != null) + { + string directory = Path.GetDirectoryName(savePath) ?? ""; + string mappingPath = Path.Combine(directory, Path.GetFileNameWithoutExtension(savePath) + "_Mapping.xlsx"); + excelManager.SaveWorkbookAs(excelManager.MappingWorkbook, mappingPath); + } + } + catch (System.Exception ex) + { + Debug.WriteLine($"Excel 파일 저장 중 오류 발생: {ex.Message}"); + } + finally + { + excelManager.CloseWorkbooks(); + } + } + + public void CloseExcelObjectsWithoutSaving() => excelManager.CloseWorkbooks(); + + public void SaveMappingDictionary(string filePath) => jsonProcessor.SaveMappingDictionary(FileToMapkeyToLabelTagValuePdf, filePath); + + public void LoadMappingDictionary(string filePath) + { + FileToMapkeyToLabelTagValuePdf = jsonProcessor.LoadMappingDictionary(filePath); + } + + public void WriteCompleteMapping() => excelWriter.WriteMappingDataToExcel(FileToMapkeyToLabelTagValuePdf); + + public void UpdateWithPdfData(string jsonFilePath) => jsonProcessor.UpdateMappingDataFromJson(FileToMapkeyToLabelTagValuePdf, jsonFilePath); + + public void ClearAccumulatedData() => FileToMapkeyToLabelTagValuePdf.Clear(); + + // Private helper methods private void TestFieldMapper() { Debug.WriteLine("[DEBUG] Testing field mapper..."); - - // 몇 가지 알려진 expressway 필드로 테스트 - var testFields = new[] { "TD_DNAME_MAIN", "TD_DWGNO", "TD_DWGCODE", "TB_MTITIL"}; + var testFields = new[] { "TD_DNAME_MAIN", "TD_DWGNO", "TD_DWGCODE", "TB_MTITIL" }; foreach (var field in testFields) { @@ -93,7 +349,6 @@ namespace DwgExtractorManual.Models } } - // Teigha 서비스 초기화 (싱글톤 사용) private void InitializeTeighaServices() { try @@ -109,1829 +364,37 @@ namespace DwgExtractorManual.Models } } - // Excel 애플리케이션 및 워크시트 초기화 - private void InitializeExcel() + private bool UpdateExcelFromMappingData() { try { - var excelApp = new Excel.Application(); - excelApplication = excelApp; - excelApplication.Visible = false; // WPF에서는 숨김 처리 - Excel.Workbook workbook = excelApp.Workbooks.Add(); - workbook1 = workbook; - - // Title Block Sheet 설정 (기본 Sheet1) - titleBlockSheet = (Excel.Worksheet)workbook.Sheets[1]; - titleBlockSheet.Name = "Title Block"; - SetupTitleBlockHeaders(); - - // Text Entities Sheet 추가 - textEntitiesSheet = (Excel.Worksheet)workbook.Sheets.Add(); - textEntitiesSheet.Name = "Text Entities"; - SetupTextEntitiesHeaders(); - - // 매핑 데이터용 워크북 및 시트 생성 - mappingWorkbook = excelApp.Workbooks.Add(); - mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets[1]; - mappingSheet.Name = "Mapping Data"; - SetupMappingHeaders(); - } - catch (System.Exception ex) - { - Debug.WriteLine($"Excel 초기화 중 오류 발생: {ex.Message}"); - ReleaseExcelObjects(); - throw; - } - } - - // Title Block 시트 헤더 설정 - private void SetupTitleBlockHeaders() - { - titleBlockSheet.Cells[1, 1] = "Type"; // 예: AttributeReference, AttributeDefinition - titleBlockSheet.Cells[1, 2] = "Name"; // BlockReference 이름 또는 BlockDefinition 이름 - titleBlockSheet.Cells[1, 3] = "Tag"; // Attribute Tag - titleBlockSheet.Cells[1, 4] = "Prompt"; // Attribute Prompt - titleBlockSheet.Cells[1, 5] = "Value"; // Attribute 값 (TextString) - titleBlockSheet.Cells[1, 6] = "Path"; // 원본 DWG 파일 전체 경로 - titleBlockSheet.Cells[1, 7] = "FileName"; // 원본 DWG 파일 이름만 - - // 헤더 행 스타일 - Excel.Range headerRange = titleBlockSheet.Range["A1:G1"]; - headerRange.Font.Bold = true; - headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightBlue); - } - - // Text Entities 시트 헤더 설정 - private void SetupTextEntitiesHeaders() - { - textEntitiesSheet.Cells[1, 1] = "Type"; // DBText, MText - textEntitiesSheet.Cells[1, 2] = "Layer"; // Layer 이름 - textEntitiesSheet.Cells[1, 3] = "Text"; // 실제 텍스트 내용 - textEntitiesSheet.Cells[1, 4] = "Path"; // 원본 DWG 파일 전체 경로 - textEntitiesSheet.Cells[1, 5] = "FileName"; // 원본 DWG 파일 이름만 - - // 헤더 행 스타일 - Excel.Range headerRange = textEntitiesSheet.Range["A1:E1"]; - headerRange.Font.Bold = true; - headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGreen); - } - - // 매핑 데이터 시트 헤더 설정 - private void SetupMappingHeaders() - { - mappingSheet.Cells[1, 1] = "FileName"; // 파일 이름 - mappingSheet.Cells[1, 2] = "MapKey"; // 매핑 키 - mappingSheet.Cells[1, 3] = "AILabel"; // AI 라벨 - mappingSheet.Cells[1, 4] = "DwgTag"; // DWG Tag - mappingSheet.Cells[1, 5] = "Att_value"; // DWG 값 - mappingSheet.Cells[1, 6] = "Pdf_value"; // PDF 값 (현재는 빈 값) - - // 헤더 행 스타일 - Excel.Range headerRange = mappingSheet.Range["A1:F1"]; - headerRange.Font.Bold = true; - headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightYellow); - } - - /// - /// 단일 DWG 파일에서 AttributeReference/AttributeDefinition 데이터를 추출하여 - /// 초기화된 Excel 워크시트에 추가합니다. - /// - /// 처리할 DWG 파일 경로 - /// 진행 상태 보고를 위한 IProgress 객체 - /// 작업 취소를 위한 CancellationToken - /// 성공 시 true, 실패 시 false 반환 - public bool ExportDwgToExcel(string filePath, IProgress progress = null, CancellationToken cancellationToken = default) - { - Debug.WriteLine($"[DEBUG] ExportDwgToExcel 시작: {filePath}"); - - if (excelApplication == null) - { - Debug.WriteLine("❌ Excel이 초기화되지 않았습니다."); - return false; - } - - if (!File.Exists(filePath)) - { - Debug.WriteLine($"❌ 파일이 존재하지 않습니다: {filePath}"); - return false; - } - - try - { - Debug.WriteLine("[DEBUG] 진행률 0% 보고"); - progress?.Report(0); - cancellationToken.ThrowIfCancellationRequested(); - - Debug.WriteLine("[DEBUG] ODA Database 객체 생성 중..."); - // ODA Database 객체 생성 및 DWG 파일 읽기 - using (var database = new Database(false, true)) - { - Debug.WriteLine($"[DEBUG] DWG 파일 읽기 시도: {filePath}"); - database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); - Debug.WriteLine("[DEBUG] DWG 파일 읽기 성공"); - - cancellationToken.ThrowIfCancellationRequested(); - Debug.WriteLine("[DEBUG] 진행률 10% 보고"); - progress?.Report(10); - - Debug.WriteLine("[DEBUG] 트랜잭션 시작 중..."); - using (var tran = database.TransactionManager.StartTransaction()) - { - Debug.WriteLine("[DEBUG] BlockTable 접근 중..."); - var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; - Debug.WriteLine("[DEBUG] ModelSpace 접근 중..."); - using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) - { - int totalEntities = btr.Cast().Count(); - Debug.WriteLine($"[DEBUG] 총 엔티티 수: {totalEntities}"); - int processedCount = 0; - - foreach (ObjectId entId in btr) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) - { - // Layer 이름 가져오기 (공통) - string layerName = GetLayerName(ent.LayerId, tran, database); - var fileName = Path.GetFileNameWithoutExtension(database.Filename); - - // 파일명 유효성 검사 - if (string.IsNullOrEmpty(fileName)) - { - fileName = "Unknown_File"; - Debug.WriteLine($"[DEBUG] Using default filename: {fileName}"); - } - - // 파일별 매핑 딕셔너리 초기화 - if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileName)) - { - FileToMapkeyToLabelTagValuePdf[fileName] = new Dictionary(); - } - - // AttributeDefinition 추출 - if (ent is AttributeDefinition attDef) - { - titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attDef.GetType().Name; - titleBlockSheet.Cells[titleBlockCurrentRow, 2] = attDef.BlockName; - titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attDef.Tag; - titleBlockSheet.Cells[titleBlockCurrentRow, 4] = attDef.Prompt; - titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attDef.TextString; - titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename; - titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename); - titleBlockCurrentRow++; - - var tag = attDef.Tag; - var aiLabel = fieldMapper.ExpresswayToAilabel(tag); - var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel); - var attValue = attDef.TextString; - Debug.WriteLine($"[DEBUG] AttributeDefinition - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}"); - - // 매핑 데이터 저장 - if (!string.IsNullOrEmpty(aiLabel)) - { - // mapKey가 null이면 aiLabel을 mapKey로 사용 - var finalMapKey = mapKey ?? aiLabel; - FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, ""); - Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})"); - } - else - { - // aiLabel이 null이면 tag를 사용하여 저장 - var finalMapKey = mapKey ?? tag; - if (!string.IsNullOrEmpty(finalMapKey)) - { - FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, ""); - Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})"); - } - else - { - Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeDefinition"); - } - } - } - // BlockReference 및 그 안의 AttributeReference 추출 - else if (ent is BlockReference blr) - { - foreach (ObjectId attId in blr.AttributeCollection) - { - cancellationToken.ThrowIfCancellationRequested(); - - using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) - { - if (attRef != null && attRef.TextString.Trim() != "") - { - titleBlockSheet.Cells[titleBlockCurrentRow, 1] = attRef.GetType().Name; - titleBlockSheet.Cells[titleBlockCurrentRow, 2] = blr.Name; - titleBlockSheet.Cells[titleBlockCurrentRow, 3] = attRef.Tag; - titleBlockSheet.Cells[titleBlockCurrentRow, 4] = GetPromptFromAttributeReference(tran, blr, attRef.Tag); - - titleBlockSheet.Cells[titleBlockCurrentRow, 5] = attRef.TextString; - titleBlockSheet.Cells[titleBlockCurrentRow, 6] = database.Filename; - titleBlockSheet.Cells[titleBlockCurrentRow, 7] = Path.GetFileName(database.Filename); - - titleBlockCurrentRow++; - - var tag = attRef.Tag; - var aiLabel = fieldMapper.ExpresswayToAilabel(tag); - if (aiLabel == null) continue; - var mapKey = fieldMapper.AilabelToDocAiKey(aiLabel); - var attValue = attRef.TextString; - - Debug.WriteLine($"[DEBUG] AttributeReference - Tag: {tag}, AILabel: {aiLabel}, MapKey: {mapKey}"); - - // 매핑 데이터 저장 - if (!string.IsNullOrEmpty(aiLabel)) - { - // mapKey가 null이면 aiLabel을 mapKey로 사용 - var finalMapKey = mapKey ?? aiLabel; - FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (aiLabel, tag, attValue, ""); - Debug.WriteLine($"[DEBUG] Added mapping: {tag} -> {aiLabel} (MapKey: {finalMapKey})"); - } - else - { - // aiLabel이 null이면 tag를 사용하여 저장 - var finalMapKey = mapKey ?? tag; - if (!string.IsNullOrEmpty(finalMapKey)) - { - FileToMapkeyToLabelTagValuePdf[fileName][finalMapKey] = (tag, tag, attValue, ""); - Debug.WriteLine($"[DEBUG] Added unmapped tag: {tag} -> {tag} (MapKey: {finalMapKey})"); - } - else - { - Debug.WriteLine($"[DEBUG] Skipped empty tag for AttributeReference"); - } - } - } - } - } - } - // DBText 엔티티 추출 (별도 시트) - else if (ent is DBText dbText) - { - textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "DBText"; // Type - textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer - textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = dbText.TextString; // Text - textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path - textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName - - textEntitiesCurrentRow++; - } - // MText 엔티티 추출 (별도 시트) - else if (ent is MText mText) - { - textEntitiesSheet.Cells[textEntitiesCurrentRow, 1] = "MText"; // Type - textEntitiesSheet.Cells[textEntitiesCurrentRow, 2] = layerName; // Layer - textEntitiesSheet.Cells[textEntitiesCurrentRow, 3] = mText.Contents; // Text - textEntitiesSheet.Cells[textEntitiesCurrentRow, 4] = database.Filename; // Path - textEntitiesSheet.Cells[textEntitiesCurrentRow, 5] = Path.GetFileName(database.Filename); // FileName - - textEntitiesCurrentRow++; - } - } - - // write values in new excel file with FileToMapkeyToLabelTag - - processedCount++; - double currentProgress = 10.0 + (double)processedCount / totalEntities * 80.0; - progress?.Report(Math.Min(currentProgress, 90.0)); - } - } - - tran.Commit(); - } - - Debug.WriteLine($"[DEBUG] Transaction committed. Total mapping entries collected: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); - - // 매핑 데이터를 Excel 시트에 기록 - Debug.WriteLine("[DEBUG] 매핑 데이터를 Excel에 기록 중..."); - WriteMappingDataToExcel(); - Debug.WriteLine("[DEBUG] 매핑 데이터 Excel 기록 완료"); - } - - progress?.Report(100); - return true; - } - catch (OperationCanceledException) - { - Debug.WriteLine("❌ 작업이 취소되었습니다."); - progress?.Report(0); - return false; - } - catch (Teigha.Runtime.Exception ex) - { - progress?.Report(0); - Debug.WriteLine($"❌ DWG 파일 처리 중 Teigha 오류 발생:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" ErrorStatus: {ex.ErrorStatus}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - return false; - } - catch (System.Exception ex) - { - progress?.Report(0); - Debug.WriteLine($"❌ 일반 오류 발생:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - return false; - } - } - - // 매핑 데이터를 Excel 시트에 기록 - private void WriteMappingDataToExcel() - { - try - { - int currentRow = 2; // 헤더 다음 행부터 시작 - - Debug.WriteLine($"[DEBUG] Writing mapping data to Excel. Total files: {FileToMapkeyToLabelTagValuePdf.Count}"); - Debug.WriteLine($"[DEBUG] 시작 행: {currentRow}"); - - foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) - { - string fileName = fileEntry.Key; - var mappingData = fileEntry.Value; - - Debug.WriteLine($"[DEBUG] Processing file: {fileName}, entries: {mappingData.Count}"); - - foreach (var mapEntry in mappingData) - { - string mapKey = mapEntry.Key; - (string aiLabel, string dwgTag, string attValue, string pdfValue) = mapEntry.Value; - - // null 값 방지 - if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(mapKey)) - { - Debug.WriteLine($"[DEBUG] Skipping entry with null/empty values: fileName={fileName}, mapKey={mapKey}"); - continue; - } - - Debug.WriteLine($"[DEBUG] Writing row {currentRow}: {fileName} | {mapKey} | {aiLabel} | {dwgTag} | PDF: {pdfValue}"); - - try - { - // 배치 업데이트를 위한 배열 사용 - object[,] rowData = new object[1, 6]; - rowData[0, 0] = fileName; // FileName - rowData[0, 1] = mapKey; // MapKey - rowData[0, 2] = aiLabel ?? ""; // AILabel - rowData[0, 3] = dwgTag ?? ""; // DwgTag - rowData[0, 4] = attValue ?? ""; // DwgValue (Att_value) - rowData[0, 5] = pdfValue ?? ""; // PdfValue (Pdf_value) - - Excel.Range range = mappingSheet.Range[mappingSheet.Cells[currentRow, 1], mappingSheet.Cells[currentRow, 6]]; - range.Value = rowData; - - Debug.WriteLine($"[DEBUG] Row {currentRow} written successfully"); - } - 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}"); - Debug.WriteLine($"[DEBUG] 최종 행 번호: {currentRow}"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 매핑 데이터 Excel 기록 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; // 상위 메서드로 예외 전파 - } - } - - /// - /// 기존 Excel 파일을 열어 JSON 파일의 PDF 분석 결과로 매핑 시트를 업데이트합니다. - /// - /// 기존 Excel 파일 경로 - /// PDF 분석 결과 JSON 파일 경로 - /// 성공 시 true, 실패 시 false - public bool UpdateExistingExcelWithJson(string excelFilePath, string jsonFilePath) - { - try - { - Debug.WriteLine($"[DEBUG] 기존 Excel 파일 업데이트 시작: {excelFilePath}"); - - if (!File.Exists(excelFilePath)) - { - Debug.WriteLine($"❌ Excel 파일이 존재하지 않습니다: {excelFilePath}"); - return false; - } - - if (!File.Exists(jsonFilePath)) - { - Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); - return false; - } - - Debug.WriteLine($"[DEBUG] Excel 애플리케이션 초기화 중..."); - - // 기존 Excel 파일 열기 - if (excelApplication == null) - { - excelApplication = new Excel.Application(); - excelApplication.Visible = false; - Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨"); - } - - Debug.WriteLine($"[DEBUG] Excel 파일 열기 시도: {excelFilePath}"); - mappingWorkbook = excelApplication.Workbooks.Open(excelFilePath); - Debug.WriteLine("[DEBUG] Excel 파일 열기 성공"); - - Debug.WriteLine("[DEBUG] 'Mapping Data' 시트 찾는 중..."); - mappingSheet = (Excel.Worksheet)mappingWorkbook.Sheets["Mapping Data"]; - - if (mappingSheet == null) - { - Debug.WriteLine("❌ 'Mapping Data' 시트를 찾을 수 없습니다."); - // 사용 가능한 시트 목록 출력 - Debug.WriteLine("사용 가능한 시트:"); - foreach (Excel.Worksheet sheet in mappingWorkbook.Sheets) - { - Debug.WriteLine($" - {sheet.Name}"); - } - return false; - } - - Debug.WriteLine("✅ 기존 Excel 파일 열기 성공"); - - // JSON에서 PDF 값 업데이트 - Debug.WriteLine("[DEBUG] JSON 파싱 및 업데이트 시작"); - bool result = UpdateMappingSheetFromJson(jsonFilePath); - - Debug.WriteLine($"[DEBUG] 업데이트 결과: {result}"); - return result; - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 기존 Excel 파일 업데이트 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - return false; - } - } - - /// - /// JSON 파일에서 PDF 분석 결과를 읽어 Excel 매핑 시트의 Pdf_value 컬럼을 업데이트합니다. - /// - /// PDF 분석 결과 JSON 파일 경로 - /// 성공 시 true, 실패 시 false - public bool UpdateMappingSheetFromJson(string jsonFilePath) - { - try - { - Debug.WriteLine($"[DEBUG] JSON 파일에서 PDF 값 업데이트 시작: {jsonFilePath}"); - - if (!File.Exists(jsonFilePath)) - { - Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); - return false; - } - - if (mappingSheet == null) - { - Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다."); - return false; - } - - // JSON 파일 읽기 및 정리 - string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8); - Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes"); - - // JSON 내용 정리 (주석 제거 등) - jsonContent = CleanJsonContent(jsonContent); - - JObject jsonData; - try - { - jsonData = JObject.Parse(jsonContent); - } - catch (Newtonsoft.Json.JsonReaderException jsonEx) - { - Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); - Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); - Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); - throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}"); - } - - var results = jsonData["results"] as JArray; - if (results == null) - { - Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다."); - Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}"); - return false; - } - - int updatedCount = 0; - int totalEntries = 0; - - foreach (JObject result in results) - { - var fileInfo = result["file_info"]; - var pdfAnalysis = result["pdf_analysis"]; - - if (fileInfo == null || pdfAnalysis == null) continue; - - string fileName = fileInfo["name"]?.ToString(); - if (string.IsNullOrEmpty(fileName)) continue; - - // 파일 확장자 제거 - string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); - Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}"); - - // PDF 분석 결과의 각 필드 처리 - foreach (var property in pdfAnalysis.Cast()) - { - string aiLabel = property.Name; // 예: "설계공구_Station_col1" - var valueObj = property.Value as JObject; - - if (valueObj == null) continue; - - string pdfValue = valueObj["value"]?.ToString(); - if (string.IsNullOrEmpty(pdfValue)) continue; - - totalEntries++; - Debug.WriteLine($"[DEBUG] Searching for match: FileName={fileNameWithoutExt}, AILabel={aiLabel}, Value={pdfValue}"); - - // Excel 시트에서 매칭되는 행 찾기 및 업데이트 - if (UpdateExcelRow(fileNameWithoutExt, aiLabel, pdfValue)) - { - updatedCount++; - Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}"); - } - else - { - Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}"); - } - } - } - - Debug.WriteLine($"[DEBUG] PDF 값 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨"); - return true; - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ JSON에서 PDF 값 업데이트 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 예외 타입: {ex.GetType().Name}"); - if (ex.InnerException != null) - { - Debug.WriteLine($" 내부 예외: {ex.InnerException.Message}"); - } - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - return false; - } - } - - /// - /// Excel 매핑 시트에서 FileName과 AILabel이 매칭되는 행을 찾아 Pdf_value를 업데이트합니다. - /// - /// 파일명 (확장자 제외) - /// AI 라벨 (예: "설계공구_Station_col1") - /// PDF에서 추출된 값 - /// 업데이트 성공 시 true - private bool UpdateExcelRow(string fileName, string aiLabel, string pdfValue) - { - try - { - // Excel 시트의 마지막 사용된 행 찾기 - Excel.Range usedRange = mappingSheet.UsedRange; - if (usedRange == null) return false; - - int lastRow = usedRange.Rows.Count; - - // 2행부터 검색 (1행은 헤더) - for (int row = 2; row <= lastRow; row++) - { - // Column 1: FileName, Column 3: AILabel 확인 - var cellFileName = mappingSheet.Cells[row, 1]?.Value?.ToString() ?? ""; - var cellAiLabel = mappingSheet.Cells[row, 3]?.Value?.ToString() ?? ""; - - Debug.WriteLine($"[DEBUG] Row {row}: FileName='{cellFileName}', AILabel='{cellAiLabel}'"); - - // 매칭 확인 (대소문자 구분 없이) - if (string.Equals(cellFileName.Trim(), fileName.Trim(), StringComparison.OrdinalIgnoreCase) && - string.Equals(cellAiLabel.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)) - { - // Column 6: Pdf_value에 값 기록 - mappingSheet.Cells[row, 6] = pdfValue; - Debug.WriteLine($"[DEBUG] Updated row {row}, column 6 with value: {pdfValue}"); - return true; - } - } - - return false; // 매칭되는 행을 찾지 못함 - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ Excel 행 업데이트 중 오류: {ex.Message}"); - return false; - } - } - - // Paste the helper function from above here - public string GetPromptFromAttributeReference(Transaction tr, BlockReference blockref, string tag) - { - - string prompt = null; - - - BlockTableRecord blockDef = tr.GetObject(blockref.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord; - if (blockDef == null) return null; - - foreach (ObjectId objId in blockDef) - { - AttributeDefinition attDef = tr.GetObject(objId, OpenMode.ForRead) as AttributeDefinition; - if (attDef != null) - { - if (attDef.Tag.Equals(tag, System.StringComparison.OrdinalIgnoreCase)) - { - prompt = attDef.Prompt; - break; - } - } - } - - - return prompt; - } - - /// - /// 현재 열려있는 Excel 워크북을 저장합니다. - /// - /// 성공 시 true, 실패 시 false - public bool SaveExcel() - { - try - { - if (mappingWorkbook != null) - { - mappingWorkbook.Save(); - Debug.WriteLine("✅ Excel 파일 저장 완료"); - return true; - } - - if (workbook1 != null) - { - workbook1.Save(); - Debug.WriteLine("✅ Excel 파일 저장 완료"); - return true; - } - - Debug.WriteLine("❌ 저장할 워크북이 없습니다."); - return false; - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ Excel 파일 저장 중 오류: {ex.Message}"); - return false; - } - } - - /// - /// 매핑 워크북만 저장하고 닫습니다 (완전한 매핑용). - /// - /// 저장할 파일 경로 - public void SaveMappingWorkbookOnly(string savePath) - { - try - { - Debug.WriteLine($"[DEBUG] 매핑 워크북 저장 시작: {savePath}"); - - if (mappingWorkbook == null) - { - Debug.WriteLine("❌ 매핑 워크북이 초기화되지 않았습니다."); - return; - } - - string directory = Path.GetDirectoryName(savePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - // 매핑 워크북만 저장 (Excel 2007+ 형식으로) - mappingWorkbook.SaveAs(savePath, - FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, - AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - - Debug.WriteLine($"✅ 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 매핑 워크북 저장 중 오류: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// DWG 전용 매핑 워크북을 생성하고 저장합니다 (PDF 컬럼 제외). - /// - /// 결과 파일 저장 폴더 경로 - public void SaveDwgOnlyMappingWorkbook(string resultFolderPath) - { - try - { - string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); - string savePath = Path.Combine(resultFolderPath, $"{timestamp}_DwgOnly_Mapping.xlsx"); - - Debug.WriteLine($"[DEBUG] DWG 전용 매핑 워크북 생성 시작: {savePath}"); - - // Excel 애플리케이션 초기화 확인 - if (excelApplication == null) - { - excelApplication = new Excel.Application(); - excelApplication.Visible = false; - Debug.WriteLine("[DEBUG] 새 Excel 애플리케이션 생성됨 (DWG 전용)"); - } - - // DWG 전용 워크북 생성 - var dwgOnlyWorkbook = excelApplication.Workbooks.Add(); - 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 = FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count); - if (totalRows > 0) - { - object[,] data = new object[totalRows, 5]; - int row = 0; - - foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) - { - 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(); - - // 파일 저장 - string directory = Path.GetDirectoryName(savePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - dwgOnlyWorkbook.SaveAs(savePath, - FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, - AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - - Debug.WriteLine($"✅ DWG 전용 매핑 워크북 저장 완료: {Path.GetFileName(savePath)}"); - - // 워크북 정리 - dwgOnlyWorkbook.Close(false); - ReleaseComObject(dwgOnlyWorksheet); - ReleaseComObject(dwgOnlyWorkbook); - - // 가비지 컬렉션 강제 실행으로 메모리 해제 - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ DWG 전용 매핑 워크북 저장 중 오류: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// 현재 Excel 워크북을 지정된 경로에 저장하고 Excel 애플리케이션을 종료합니다. - /// - /// Excel 파일을 저장할 전체 경로 - public void SaveAndCloseExcel(string savePath) - { - if (workbook1 == null) return; - - try - { - string directory = Path.GetDirectoryName(savePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - workbook1.SaveAs(savePath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - - // 매핑 데이터 워크북도 저장 - if (mappingWorkbook != null) - { - string mappingPath = Path.Combine(directory, Path.GetFileNameWithoutExtension(savePath) + "_Mapping.xlsx"); - mappingWorkbook.SaveAs(mappingPath, AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - } - } - catch (System.Exception ex) - { - Debug.WriteLine($"Excel 파일 저장 중 오류 발생: {ex.Message}"); - } - finally - { - CloseExcelObjects(); - } - } - - /// - /// Excel 객체들을 닫습니다 (저장하지 않음). - /// - public void CloseExcelObjectsWithoutSaving() - { - try - { - Debug.WriteLine("[DEBUG] Excel 객체 정리 시작"); - - if (workbook1 != null) - { - try { workbook1.Close(false); } - catch { } - } - if (mappingWorkbook != null) - { - try { mappingWorkbook.Close(false); } - catch { } - } - if (excelApplication != null) - { - try { excelApplication.Quit(); } - catch { } - } - - ReleaseExcelObjects(); - - Debug.WriteLine("✅ Excel 객체 정리 완료"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ Excel 객체 정리 중 오류: {ex.Message}"); - } - } - - private void CloseExcelObjects() - { - if (workbook1 != null) - { - try { workbook1.Close(false); } - catch { } - } - if (mappingWorkbook != null) - { - try { mappingWorkbook.Close(false); } - catch { } - } - if (excelApplication != null) - { - try { excelApplication.Quit(); } - catch { } - } - - ReleaseExcelObjects(); - } - - private void ReleaseExcelObjects() - { - ReleaseComObject(titleBlockSheet); - ReleaseComObject(textEntitiesSheet); - ReleaseComObject(mappingSheet); - ReleaseComObject(workbook1); - ReleaseComObject(mappingWorkbook); - ReleaseComObject(excelApplication); - - titleBlockSheet = null; - textEntitiesSheet = null; - mappingSheet = null; - workbook1 = null; - mappingWorkbook = null; - excelApplication = null; - } - - private void ReleaseComObject(object obj) - { - try - { - if (obj != null && Marshal.IsComObject(obj)) - { - Marshal.ReleaseComObject(obj); - } - } - catch (System.Exception) - { - // 해제 중 오류 발생 시 무시 - } - finally - { - obj = null; - } - } - - /// - /// Layer ID로부터 Layer 이름을 가져옵니다. - /// - /// Layer ObjectId - /// 현재 트랜잭션 - /// 데이터베이스 객체 - /// Layer 이름 또는 빈 문자열 - private string GetLayerName(ObjectId layerId, Transaction transaction, Database database) - { - try - { - using (var layerTableRecord = transaction.GetObject(layerId, OpenMode.ForRead) as LayerTableRecord) - { - return layerTableRecord?.Name ?? ""; - } - } - catch (System.Exception ex) - { - Debug.WriteLine($"Layer 이름 가져오기 오류: {ex.Message}"); - return ""; - } - } - - /// - /// 매핑 딕셔너리를 JSON 파일로 저장합니다. - /// - /// 저장할 JSON 파일 경로 - public void SaveMappingDictionary(string filePath) - { - try - { - Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 저장 시작: {filePath}"); - - // 딕셔너리를 직렬화 가능한 형태로 변환 - var serializableData = new Dictionary>(); - foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf) { - var fileData = new Dictionary(); foreach (var mapEntry in fileEntry.Value) - { - fileData[mapEntry.Key] = new - { - AILabel = mapEntry.Value.Item1, - DwgTag = mapEntry.Value.Item2, - DwgValue = mapEntry.Value.Item3, - PdfValue = mapEntry.Value.Item4 - }; - } - serializableData[fileEntry.Key] = fileData; - } - - // JSON으로 직렬화 - string jsonContent = JsonConvert.SerializeObject(serializableData, Formatting.Indented); - File.WriteAllText(filePath, jsonContent, System.Text.Encoding.UTF8); - - Debug.WriteLine($"✅ 매핑 딕셔너리 저장 완료: {Path.GetFileName(filePath)}"); - Debug.WriteLine($"📊 저장된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}"); - - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 매핑 딕셔너리 저장 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// JSON 파일에서 매핑 딕셔너리를 로드합니다. - /// - /// 로드할 JSON 파일 경로 - public void LoadMappingDictionary(string filePath) - { - try - { - Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 로드 시작: {filePath}"); - - if (!File.Exists(filePath)) - { - Debug.WriteLine($"⚠️ 매핑 파일이 존재하지 않습니다: {filePath}"); - return; - } - - string jsonContent = File.ReadAllText(filePath, System.Text.Encoding.UTF8); - Debug.WriteLine($"[DEBUG] JSON 내용 길이: {jsonContent.Length}"); - - // JSON 내용 정리 (주석 제거 등) - jsonContent = CleanJsonContent(jsonContent); - - Dictionary> deserializedData; - try - { - deserializedData = JsonConvert.DeserializeObject>>(jsonContent); - } - catch (Newtonsoft.Json.JsonReaderException jsonEx) - { - Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); - Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); - Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); - throw new System.Exception($"매핑 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {filePath}"); - } - - // 새로운 딕셔너리 초기화 - FileToMapkeyToLabelTagValuePdf.Clear(); - Debug.WriteLine("[DEBUG] 기존 딕셔너리 초기화됨"); - - if (deserializedData != null) - { - Debug.WriteLine($"[DEBUG] 역직렬화된 파일 수: {deserializedData.Count}"); - - foreach (var fileEntry in deserializedData) - { - Debug.WriteLine($"[DEBUG] 파일 처리 중: {fileEntry.Key}"); - var fileData = new Dictionary(); - - foreach (var mapEntry in fileEntry.Value) - { - var valueObj = mapEntry.Value; - string aiLabel = valueObj["AILabel"]?.ToString() ?? ""; - string dwgTag = valueObj["DwgTag"]?.ToString() ?? ""; - string dwgValue = valueObj["DwgValue"]?.ToString() ?? ""; - string pdfValue = valueObj["PdfValue"]?.ToString() ?? ""; - - fileData[mapEntry.Key] = (aiLabel, dwgTag, dwgValue, pdfValue); - Debug.WriteLine($"[DEBUG] 항목 로드: {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, PDF:{pdfValue}"); - } - - FileToMapkeyToLabelTagValuePdf[fileEntry.Key] = fileData; - Debug.WriteLine($"[DEBUG] 파일 {fileEntry.Key}: {fileData.Count}개 항목 로드됨"); - } - } - - Debug.WriteLine($"✅ 매핑 딕셔너리 로드 완료"); - Debug.WriteLine($"📊 로드된 파일 수: {FileToMapkeyToLabelTagValuePdf.Count}"); - Debug.WriteLine($"📊 총 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); - - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 매핑 딕셔너리 로드 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// 매핑 딕셔너리 데이터를 Excel 시트에 기록합니다 (완전한 매핑용). - /// - public void WriteCompleteMapping() - { - try - { - Debug.WriteLine("[DEBUG] WriteCompleteMapping 시작"); - Debug.WriteLine($"[DEBUG] 매핑 딕셔너리 항목 수: {FileToMapkeyToLabelTagValuePdf.Count}"); - - if (FileToMapkeyToLabelTagValuePdf.Count == 0) - { - Debug.WriteLine("⚠️ 매핑 딕셔너리가 비어있습니다."); - return; - } - - if (mappingSheet == null) - { - Debug.WriteLine("❌ 매핑 시트가 초기화되지 않았습니다."); - return; - } - - Debug.WriteLine($"[DEBUG] 매핑 시트 이름: {mappingSheet.Name}"); - Debug.WriteLine($"[DEBUG] 전체 데이터 항목 수: {FileToMapkeyToLabelTagValuePdf.Sum(f => f.Value.Count)}"); - - // 샘플 데이터 출력 (처음 3개) - int sampleCount = 0; - foreach (var fileEntry in FileToMapkeyToLabelTagValuePdf.Take(2)) - { - Debug.WriteLine($"[DEBUG] 파일: {fileEntry.Key}"); - foreach (var mapEntry in fileEntry.Value.Take(3)) { var (aiLabel, dwgTag, dwgValue, pdfValue) = mapEntry.Value; - Debug.WriteLine($"[DEBUG] {mapEntry.Key} -> AI:{aiLabel}, DWG:{dwgTag}, DWGVAL:{dwgValue}, PDF:{pdfValue}"); - sampleCount++; + if (!string.IsNullOrEmpty(pdfValue)) + { + excelWriter.UpdateExcelRow(fileEntry.Key, aiLabel, pdfValue); + } } } - - // 기존 WriteMappingDataToExcel 메서드 호출 - WriteMappingDataToExcel(); - - Debug.WriteLine("✅ WriteCompleteMapping 완료"); + return true; } catch (System.Exception ex) { - Debug.WriteLine($"❌ WriteCompleteMapping 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; + Debug.WriteLine($"❌ Excel 업데이트 중 오류: {ex.Message}"); + return false; } } - /// - /// PDF 분석 결과 JSON으로 매핑 딕셔너리의 PdfValue를 업데이트합니다. - /// - /// PDF 분석 결과 JSON 파일 경로 - public void UpdateWithPdfData(string jsonFilePath) - { - try - { - Debug.WriteLine($"[DEBUG] PDF 데이터로 매핑 딕셔너리 업데이트 시작: {jsonFilePath}"); - - if (!File.Exists(jsonFilePath)) - { - Debug.WriteLine($"❌ JSON 파일이 존재하지 않습니다: {jsonFilePath}"); - return; - } - - // JSON 파일 읽기 및 정리 - string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8); - Debug.WriteLine($"[DEBUG] JSON 파일 크기: {jsonContent.Length} bytes"); - - // JSON 내용 정리 (주석 제거 등) - jsonContent = CleanJsonContent(jsonContent); - - JObject jsonData; - try - { - jsonData = JObject.Parse(jsonContent); - } - catch (Newtonsoft.Json.JsonReaderException jsonEx) - { - Debug.WriteLine($"❌ JSON 파싱 오류: {jsonEx.Message}"); - Debug.WriteLine($"❌ JSON 내용 미리보기 (첫 500자):"); - Debug.WriteLine(jsonContent.Length > 500 ? jsonContent.Substring(0, 500) + "..." : jsonContent); - throw new System.Exception($"PDF 분석 JSON 파일 파싱 실패: {jsonEx.Message}\n파일: {jsonFilePath}"); - } - - var results = jsonData["results"] as JArray; - if (results == null) - { - Debug.WriteLine("❌ JSON에서 'results' 배열을 찾을 수 없습니다."); - Debug.WriteLine($"❌ JSON 루트 키들: {string.Join(", ", jsonData.Properties().Select(p => p.Name))}"); - return; - } - - int updatedCount = 0; - int totalEntries = 0; - - foreach (JObject result in results) - { - var fileInfo = result["file_info"]; - var pdfAnalysis = result["pdf_analysis"]; - - if (fileInfo == null || pdfAnalysis == null) continue; - - string fileName = fileInfo["name"]?.ToString(); - if (string.IsNullOrEmpty(fileName)) continue; - - // 파일 확장자 제거 - string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); - Debug.WriteLine($"[DEBUG] Processing PDF file: {fileNameWithoutExt}"); - - // 해당 파일의 매핑 데이터 확인 - if (!FileToMapkeyToLabelTagValuePdf.ContainsKey(fileNameWithoutExt)) - { - Debug.WriteLine($"⚠️ 매핑 데이터에 파일이 없습니다: {fileNameWithoutExt}"); - continue; - } - - // PDF 분석 결과의 각 필드 처리 - foreach (var property in pdfAnalysis.Cast()) - { - string aiLabel = property.Name; // 예: "설계공구_Station_col1" - var valueObj = property.Value as JObject; - - if (valueObj == null) continue; - - string pdfValue = valueObj["value"]?.ToString(); - if (string.IsNullOrEmpty(pdfValue)) continue; - - totalEntries++; - - // 매핑 딕셔너리에서 해당 항목 찾기 - var fileData = FileToMapkeyToLabelTagValuePdf[fileNameWithoutExt]; - - // AILabel로 매칭 찾기 - var matchingEntry = fileData.FirstOrDefault(kvp => - string.Equals(kvp.Value.Item1.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrEmpty(matchingEntry.Key)) - { - // 기존 값 유지하면서 PdfValue만 업데이트 - var existingValue = matchingEntry.Value; - fileData[matchingEntry.Key] = (existingValue.Item1, existingValue.Item2, existingValue.Item3, pdfValue); - updatedCount++; - - Debug.WriteLine($"✅ Updated: {fileNameWithoutExt} -> {aiLabel} = {pdfValue}"); - } - else - { - Debug.WriteLine($"⚠️ No match found: {fileNameWithoutExt} -> {aiLabel}"); - } - } - } - - Debug.WriteLine($"[DEBUG] PDF 데이터 업데이트 완료: {updatedCount}/{totalEntries} 업데이트됨"); - - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ PDF 데이터 업데이트 중 오류:"); - Debug.WriteLine($" 메시지: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// 폴더 간 처리를 위해 누적된 데이터를 정리합니다. - /// - public void ClearAccumulatedData() - { - try - { - Debug.WriteLine("[DEBUG] 누적 데이터 정리 시작"); - - FileToMapkeyToLabelTagValuePdf.Clear(); - - titleBlockCurrentRow = 2; - textEntitiesCurrentRow = 2; - mappingDataCurrentRow = 2; - - Debug.WriteLine("✅ 누적 데이터 정리 완료"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 누적 데이터 정리 중 오류: {ex.Message}"); - } - } - - /// - /// JSON 내용을 정리하여 파싱 가능한 상태로 만듭니다. - /// 주석 제거 및 기타 무효한 문자 처리 - /// - /// 원본 JSON 내용 - /// 정리된 JSON 내용 - private string CleanJsonContent(string jsonContent) - { - if (string.IsNullOrEmpty(jsonContent)) - return jsonContent; - - try - { - // 줄별로 처리하여 주석 제거 - var lines = jsonContent.Split('\n'); - var cleanedLines = new List(); - - bool inMultiLineComment = false; - - foreach (string line in lines) - { - string processedLine = line; - - // 멀티라인 주석 처리 (/* */) - if (inMultiLineComment) - { - int endIndex = processedLine.IndexOf("*/"); - if (endIndex >= 0) - { - processedLine = processedLine.Substring(endIndex + 2); - inMultiLineComment = false; - } - else - { - continue; // 전체 라인이 주석 - } - } - - // 멀티라인 주석 시작 확인 - int multiLineStart = processedLine.IndexOf("/*"); - if (multiLineStart >= 0) - { - int multiLineEnd = processedLine.IndexOf("*/", multiLineStart + 2); - if (multiLineEnd >= 0) - { - // 같은 라인에서 시작하고 끝나는 주석 - processedLine = processedLine.Substring(0, multiLineStart) + - processedLine.Substring(multiLineEnd + 2); - } - else - { - // 멀티라인 주석 시작 - processedLine = processedLine.Substring(0, multiLineStart); - inMultiLineComment = true; - } - } - - // 싱글라인 주석 제거 (//) - 문자열 내부의 //는 제외 - bool inString = false; - bool escaped = false; - int commentIndex = -1; - - for (int i = 0; i < processedLine.Length - 1; i++) - { - char current = processedLine[i]; - char next = processedLine[i + 1]; - - if (escaped) - { - escaped = false; - continue; - } - - if (current == '\\') - { - escaped = true; - continue; - } - - if (current == '"') - { - inString = !inString; - continue; - } - - if (!inString && current == '/' && next == '/') - { - commentIndex = i; - break; - } - } - - if (commentIndex >= 0) - { - processedLine = processedLine.Substring(0, commentIndex); - } - - // 빈 라인이 아니면 추가 - if (!string.IsNullOrWhiteSpace(processedLine)) - { - cleanedLines.Add(processedLine); - } - } - - string result = string.Join("\n", cleanedLines); - Debug.WriteLine($"[DEBUG] JSON 정리 완료: {jsonContent.Length} -> {result.Length} bytes"); - return result; - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ JSON 정리 중 오류: {ex.Message}"); - // 정리 실패시 원본 반환 - return jsonContent; - } - } - - /// - /// DWG 파일들을 처리하여 각 파일별로 시트를 생성하고 Height 순으로 정렬된 Excel 파일을 생성합니다. - /// - /// 처리할 DWG 파일 경로 배열 - /// 결과 파일 저장 폴더 - public void ExportDwgToExcelHeightSorted(string[] dwgFiles, string resultFolder) - { - try - { - Debug.WriteLine($"[DEBUG] Height 정렬 Excel 생성 시작: {dwgFiles.Length}개 파일"); - - string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); - string savePath = Path.Combine(resultFolder, $"{timestamp}_HeightSorted.xlsx"); - - // 새로운 Excel 워크북 생성 - if (excelApplication == null) - { - excelApplication = new Excel.Application(); - excelApplication.Visible = false; - } - - var heightSortedWorkbook = excelApplication.Workbooks.Add(); - - Debug.WriteLine($"[DEBUG] DWG 파일 처리 시작"); - - bool firstSheetProcessed = false; - - foreach (string dwgFile in dwgFiles) - { - if (!File.Exists(dwgFile)) - { - Debug.WriteLine($"[DEBUG] 파일이 존재하지 않음: {dwgFile}"); - continue; - } - - string fileName = Path.GetFileNameWithoutExtension(dwgFile); - Debug.WriteLine($"[DEBUG] 처리 중인 파일: {fileName}"); - - try - { - Excel.Worksheet worksheet; - if (!firstSheetProcessed) - { - // 첫 번째 파일은 기본 시트 사용 - worksheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; - firstSheetProcessed = true; - } - else - { - // 이후 파일들은 새 시트 생성 - worksheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets.Add(); - } - worksheet.Name = GetValidSheetName(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); - - // DWG 파일에서 텍스트 엔티티 추출 - var textEntities = ExtractTextEntitiesWithHeight(dwgFile); - - // Height 순으로 내림차순 정렬 - var sortedEntities = textEntities.OrderByDescending(entity => entity.Height).ToList(); - - Debug.WriteLine($"[DEBUG] {fileName}: {sortedEntities.Count}개 텍스트 엔티티 추출됨"); - - // 데이터 입력 - int row = 2; - foreach (var entity in sortedEntities) - { - 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(); - - Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {sortedEntities.Count}개 행"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ {fileName} 처리 중 오류: {ex.Message}"); - continue; - } - } - - // DWG 파일이 하나도 없었다면 기본 시트에 메시지 추가 - if (!firstSheetProcessed) - { - var defaultSheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; - defaultSheet.Name = "No_DWG_Files"; - defaultSheet.Cells[1, 1] = "No DWG files found in this folder"; - Debug.WriteLine("[DEBUG] DWG 파일이 없어 기본 메시지 시트 생성"); - } - - // 파일 저장 - string directory = Path.GetDirectoryName(savePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - heightSortedWorkbook.SaveAs(savePath, - FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, - AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - - Debug.WriteLine($"✅ Height 정렬 Excel 파일 저장 완료: {Path.GetFileName(savePath)}"); - - // 워크북 정리 - heightSortedWorkbook.Close(false); - ReleaseComObject(heightSortedWorkbook); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ Height 정렬 Excel 생성 중 오류: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// DWG 파일에서 텍스트 엔티티들을 추출하여 Height 정보와 함께 반환합니다. - /// - /// DWG 파일 경로 - /// 텍스트 엔티티 정보 리스트 - private List ExtractTextEntitiesWithHeight(string filePath) - { - var textEntities = new List(); - - try - { - using (var database = new Database(false, true)) - { - database.ReadDwgFile(filePath, FileOpenMode.OpenForReadAndWriteNoShare, false, null); - - using (var tran = database.TransactionManager.StartTransaction()) - { - var bt = tran.GetObject(database.BlockTableId, OpenMode.ForRead) as BlockTable; - using (var btr = tran.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord) - { - foreach (ObjectId entId in btr) - { - using (var ent = tran.GetObject(entId, OpenMode.ForRead) as Entity) - { - string layerName = GetLayerName(ent.LayerId, tran, database); - - // AttributeReference 처리 - if (ent is BlockReference blr) - { - foreach (ObjectId attId in blr.AttributeCollection) - { - using (var attRef = tran.GetObject(attId, OpenMode.ForRead) as AttributeReference) - { - if (attRef != null && !string.IsNullOrWhiteSpace(attRef.TextString)) - { - textEntities.Add(new TextEntityInfo - { - Height = attRef.Height, - Type = "AttRef", - Layer = layerName, - Tag = attRef.Tag, - Text = attRef.TextString - }); - } - } - } - } - // DBText 처리 - else if (ent is DBText dbText) - { - textEntities.Add(new TextEntityInfo - { - Height = dbText.Height, - Type = "DBText", - Layer = layerName, - Tag = "", - Text = dbText.TextString - }); - } - // MText 처리 - else if (ent is MText mText) - { - textEntities.Add(new TextEntityInfo - { - Height = mText.Height, - Type = "MText", - Layer = layerName, - Tag = "", - Text = mText.Contents - }); - } - } - } - } - - tran.Commit(); - } - } - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 텍스트 엔티티 추출 중 오류 ({Path.GetFileName(filePath)}): {ex.Message}"); - } - - return textEntities; - } - - /// - /// 모든 DWG 파일들을 하나의 Excel 파일로 처리하여 각 파일별로 시트를 생성하고 Height 순으로 정렬합니다. - /// - /// 처리할 DWG 파일 리스트 (파일경로, 폴더명) - /// 결과 Excel 파일 저장 경로 - public void ExportAllDwgToExcelHeightSorted(List<(string filePath, string folderName)> allDwgFiles, string savePath) - { - try - { - Debug.WriteLine($"[DEBUG] 단일 Excel 파일로 Height 정렬 생성 시작: {allDwgFiles.Count}개 파일"); - - // 새로운 Excel 워크북 생성 - if (excelApplication == null) - { - excelApplication = new Excel.Application(); - excelApplication.Visible = false; - } - - var heightSortedWorkbook = excelApplication.Workbooks.Add(); - - Debug.WriteLine($"[DEBUG] DWG 파일 처리 시작"); - - bool firstSheetProcessed = false; - - foreach (var (filePath, folderName) in allDwgFiles) - { - if (!File.Exists(filePath)) - { - Debug.WriteLine($"[DEBUG] 파일이 존재하지 않음: {filePath}"); - continue; - } - - string fileName = Path.GetFileNameWithoutExtension(filePath); - Debug.WriteLine($"[DEBUG] 처리 중인 파일: {fileName} (폴더: {folderName})"); - - try - { - Excel.Worksheet worksheet; - if (!firstSheetProcessed) - { - // 첫 번째 파일은 기본 시트 사용 - worksheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; - firstSheetProcessed = true; - } - else - { - // 이후 파일들은 새 시트 생성 - worksheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets.Add(); - } - worksheet.Name = GetValidSheetName(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); - - // DWG 파일에서 텍스트 엔티티 추출 - var textEntities = ExtractTextEntitiesWithHeight(filePath); - - // Height 순으로 내림차순 정렬 - var sortedEntities = textEntities.OrderByDescending(entity => entity.Height).ToList(); - - Debug.WriteLine($"[DEBUG] {fileName}: {sortedEntities.Count}개 텍스트 엔티티 추출됨"); - - // 데이터 입력 - int row = 2; - foreach (var entity in sortedEntities) - { - 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(); - - Debug.WriteLine($"[DEBUG] {fileName} 시트 완료: {sortedEntities.Count}개 행"); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ {fileName} 처리 중 오류: {ex.Message}"); - continue; - } - } - - // DWG 파일이 하나도 없었다면 기본 시트에 메시지 추가 - if (!firstSheetProcessed) - { - var defaultSheet = (Excel.Worksheet)heightSortedWorkbook.Worksheets[1]; - defaultSheet.Name = "No_DWG_Files"; - defaultSheet.Cells[1, 1] = "No DWG files found in any folder"; - Debug.WriteLine("[DEBUG] DWG 파일이 없어 기본 메시지 시트 생성"); - } - - // 파일 저장 - string directory = Path.GetDirectoryName(savePath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - heightSortedWorkbook.SaveAs(savePath, - FileFormat: Excel.XlFileFormat.xlOpenXMLWorkbook, - AccessMode: Excel.XlSaveAsAccessMode.xlNoChange); - - Debug.WriteLine($"✅ 단일 Height 정렬 Excel 파일 저장 완료: {Path.GetFileName(savePath)}"); - - // 워크북 정리 - heightSortedWorkbook.Close(false); - ReleaseComObject(heightSortedWorkbook); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - } - catch (System.Exception ex) - { - Debug.WriteLine($"❌ 단일 Height 정렬 Excel 생성 중 오류: {ex.Message}"); - Debug.WriteLine($" 스택 트레이스: {ex.StackTrace}"); - throw; - } - } - - /// - /// Excel 시트명으로 사용할 수 있는 유효한 이름을 생성합니다. - /// - /// 원본 파일명 - /// 유효한 시트명 - private string GetValidSheetName(string originalName) - { - if (string.IsNullOrEmpty(originalName)) - return "Sheet"; - - // Excel 시트명에서 허용되지 않는 문자 제거 - string validName = originalName; - char[] invalidChars = { '\\', '/', '?', '*', '[', ']', ':' }; - - foreach (char c in invalidChars) - { - validName = validName.Replace(c, '_'); - } - - // 31자로 제한 (Excel 시트명 최대 길이) - if (validName.Length > 31) - { - validName = validName.Substring(0, 31); - } - - return validName; - } - public void Dispose() { try { Debug.WriteLine("[DEBUG] ExportExcel Dispose 시작"); - if (excelApplication != null) - { - Debug.WriteLine("[DEBUG] Excel 객체 정리 중..."); - CloseExcelObjects(); - } + excelManager?.Dispose(); if (appServices != null) { @@ -1956,20 +419,7 @@ namespace DwgExtractorManual.Models catch (System.Exception ex) { Debug.WriteLine($"[DEBUG] ExportExcel Dispose 중 전역 오류: {ex.Message}"); - // Disposal 오류는 로그만 남기고 계속 진행 } } } - - /// - /// 텍스트 엔티티 정보를 담는 클래스 - /// - public class TextEntityInfo - { - public double Height { get; set; } - public string Type { get; set; } - public string Layer { get; set; } - public string Tag { get; set; } - public string Text { get; set; } - } } diff --git a/Models/JsonDataProcessor.cs b/Models/JsonDataProcessor.cs new file mode 100644 index 0000000..65df6c3 --- /dev/null +++ b/Models/JsonDataProcessor.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace DwgExtractorManual.Models +{ + /// + /// JSON ó ϴ Ŭ + /// + internal class JsonDataProcessor + { + /// + /// JSON Ͽ PDF м о ͸ Ʈ + /// + public bool UpdateMappingDataFromJson(Dictionary> mappingData, string jsonFilePath) + { + try + { + Debug.WriteLine($"[DEBUG] JSON Ͽ PDF Ʈ : {jsonFilePath}"); + + if (!File.Exists(jsonFilePath)) + { + Debug.WriteLine($"? JSON ʽϴ: {jsonFilePath}"); + return false; + } + + // JSON б + string jsonContent = File.ReadAllText(jsonFilePath, System.Text.Encoding.UTF8); + jsonContent = CleanJsonContent(jsonContent); + + JObject jsonData; + try + { + jsonData = JObject.Parse(jsonContent); + } + catch (Newtonsoft.Json.JsonReaderException jsonEx) + { + Debug.WriteLine($"? JSON Ľ : {jsonEx.Message}"); + throw new System.Exception($"PDF м JSON Ľ : {jsonEx.Message}\n: {jsonFilePath}"); + } + + var results = jsonData["results"] as JArray; + if (results == null) + { + Debug.WriteLine("? JSON 'results' 迭 ã ϴ."); + return false; + } + + int updatedCount = 0; + int totalEntries = 0; + + foreach (JObject result in results) + { + var fileInfo = result["file_info"]; + var pdfAnalysis = result["pdf_analysis"]; + + if (fileInfo == null || pdfAnalysis == null) continue; + + string fileName = fileInfo["name"]?.ToString(); + if (string.IsNullOrEmpty(fileName)) continue; + + string fileNameWithoutExt = Path.GetFileNameWithoutExtension(fileName); + + if (!mappingData.ContainsKey(fileNameWithoutExt)) + { + Debug.WriteLine($"?? Ϳ ϴ: {fileNameWithoutExt}"); + continue; + } + + foreach (var property in pdfAnalysis.Cast()) + { + string aiLabel = property.Name; + var valueObj = property.Value as JObject; + + if (valueObj == null) continue; + + string pdfValue = valueObj["value"]?.ToString(); + if (string.IsNullOrEmpty(pdfValue)) continue; + + totalEntries++; + + var fileData = mappingData[fileNameWithoutExt]; + var matchingEntry = fileData.FirstOrDefault(kvp => + string.Equals(kvp.Value.Item1.Trim(), aiLabel.Trim(), StringComparison.OrdinalIgnoreCase)); + + if (!string.IsNullOrEmpty(matchingEntry.Key)) + { + var existingValue = matchingEntry.Value; + fileData[matchingEntry.Key] = (existingValue.Item1, existingValue.Item2, existingValue.Item3, pdfValue); + updatedCount++; + } + } + } + + Debug.WriteLine($"[DEBUG] PDF Ʈ Ϸ: {updatedCount}/{totalEntries} Ʈ"); + return true; + } + catch (System.Exception ex) + { + Debug.WriteLine($"? JSON PDF Ʈ : {ex.Message}"); + return false; + } + } + + /// + /// ųʸ JSON Ϸ + /// + public void SaveMappingDictionary(Dictionary> mappingData, string filePath) + { + try + { + Debug.WriteLine($"[DEBUG] ųʸ : {filePath}"); + + var serializableData = new Dictionary>(); + + foreach (var fileEntry in mappingData) + { + var fileData = new Dictionary(); + foreach (var mapEntry in fileEntry.Value) + { + fileData[mapEntry.Key] = new + { + AILabel = mapEntry.Value.Item1, + DwgTag = mapEntry.Value.Item2, + DwgValue = mapEntry.Value.Item3, + PdfValue = mapEntry.Value.Item4 + }; + } + serializableData[fileEntry.Key] = fileData; + } + + string jsonContent = JsonConvert.SerializeObject(serializableData, Formatting.Indented); + File.WriteAllText(filePath, jsonContent, System.Text.Encoding.UTF8); + + Debug.WriteLine($"? ųʸ Ϸ: {Path.GetFileName(filePath)}"); + Debug.WriteLine($"?? : {mappingData.Count}"); + } + catch (System.Exception ex) + { + Debug.WriteLine($"? ųʸ : {ex.Message}"); + throw; + } + } + + /// + /// JSON Ͽ ųʸ ε + /// + public Dictionary> LoadMappingDictionary(string filePath) + { + var result = new Dictionary>(); + + try + { + Debug.WriteLine($"[DEBUG] ųʸ ε : {filePath}"); + + if (!File.Exists(filePath)) + { + Debug.WriteLine($"?? ʽϴ: {filePath}"); + return result; + } + + string jsonContent = File.ReadAllText(filePath, System.Text.Encoding.UTF8); + jsonContent = CleanJsonContent(jsonContent); + + Dictionary> deserializedData; + try + { + deserializedData = JsonConvert.DeserializeObject>>(jsonContent); + } + catch (Newtonsoft.Json.JsonReaderException jsonEx) + { + Debug.WriteLine($"? JSON Ľ : {jsonEx.Message}"); + throw new System.Exception($" JSON Ľ : {jsonEx.Message}\n: {filePath}"); + } + + if (deserializedData != null) + { + foreach (var fileEntry in deserializedData) + { + var fileData = new Dictionary(); + + foreach (var mapEntry in fileEntry.Value) + { + var valueObj = mapEntry.Value; + string aiLabel = valueObj["AILabel"]?.ToString() ?? ""; + string dwgTag = valueObj["DwgTag"]?.ToString() ?? ""; + string dwgValue = valueObj["DwgValue"]?.ToString() ?? ""; + string pdfValue = valueObj["PdfValue"]?.ToString() ?? ""; + + fileData[mapEntry.Key] = (aiLabel, dwgTag, dwgValue, pdfValue); + } + + result[fileEntry.Key] = fileData; + } + } + + Debug.WriteLine($"? ųʸ ε Ϸ"); + Debug.WriteLine($"?? ε : {result.Count}"); + + return result; + } + catch (System.Exception ex) + { + Debug.WriteLine($"? ųʸ ε : {ex.Message}"); + throw; + } + } + + /// + /// JSON Ͽ Ľ · ϴ. + /// + private string CleanJsonContent(string jsonContent) + { + if (string.IsNullOrEmpty(jsonContent)) + return jsonContent; + + try + { + var lines = jsonContent.Split('\n'); + var cleanedLines = new List(); + bool inMultiLineComment = false; + + foreach (string line in lines) + { + string processedLine = line; + + // Ƽ ּ ó + if (inMultiLineComment) + { + int endIndex = processedLine.IndexOf("*/"); + if (endIndex >= 0) + { + processedLine = processedLine.Substring(endIndex + 2); + inMultiLineComment = false; + } + else + { + continue; + } + } + + int multiLineStart = processedLine.IndexOf("/*"); + if (multiLineStart >= 0) + { + int multiLineEnd = processedLine.IndexOf("*/", multiLineStart + 2); + if (multiLineEnd >= 0) + { + processedLine = processedLine.Substring(0, multiLineStart) + + processedLine.Substring(multiLineEnd + 2); + } + else + { + processedLine = processedLine.Substring(0, multiLineStart); + inMultiLineComment = true; + } + } + + // ̱۶ ּ + bool inString = false; + bool escaped = false; + int commentIndex = -1; + + for (int i = 0; i < processedLine.Length - 1; i++) + { + char current = processedLine[i]; + char next = processedLine[i + 1]; + + if (escaped) + { + escaped = false; + continue; + } + + if (current == '\\') + { + escaped = true; + continue; + } + + if (current == '"') + { + inString = !inString; + continue; + } + + if (!inString && current == '/' && next == '/') + { + commentIndex = i; + break; + } + } + + if (commentIndex >= 0) + { + processedLine = processedLine.Substring(0, commentIndex); + } + + if (!string.IsNullOrWhiteSpace(processedLine)) + { + cleanedLines.Add(processedLine); + } + } + + return string.Join("\n", cleanedLines); + } + catch (System.Exception ex) + { + Debug.WriteLine($"? JSON : {ex.Message}"); + return jsonContent; + } + } + } +} \ No newline at end of file diff --git a/Models/SettingsManager.cs b/Models/SettingsManager.cs new file mode 100644 index 0000000..e69de29