353 lines
15 KiB
C#
353 lines
15 KiB
C#
using ClosedXML.Excel;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.OleDb;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
namespace LFP_Manager.Function
|
|
{
|
|
public class csExcelFunction
|
|
{
|
|
#region EXCEL IMPORT / EXPORT
|
|
public static DataTable[] ExcelImport(string Ps_FileName, string[] sheetName)
|
|
{
|
|
DataTable[] result = null;
|
|
|
|
try
|
|
{
|
|
string ExcelConn = string.Empty;
|
|
|
|
if (Ps_FileName.IndexOf(".xlsx") > -1) // 확장자에 따라서 provider 주의
|
|
{
|
|
ExcelConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + Ps_FileName
|
|
+ ";Extended Properties='Excel 12.0;HDR=YES'";
|
|
}
|
|
else
|
|
{
|
|
ExcelConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Ps_FileName
|
|
+ ";Extended Properties='Excel 8.0;HDR=YES'";
|
|
}
|
|
|
|
// 첫 번째 시트의 이름을 가져옮
|
|
using (OleDbConnection con = new OleDbConnection(ExcelConn))
|
|
{
|
|
using (OleDbCommand cmd = new OleDbCommand())
|
|
{
|
|
cmd.Connection = con;
|
|
con.Open();
|
|
|
|
DataTable dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
|
|
|
|
if (dtExcelSchema.Rows.Count > 0)
|
|
{
|
|
sheetName = new string[dtExcelSchema.Rows.Count];
|
|
|
|
for (int i = 0; i < dtExcelSchema.Rows.Count; i++)
|
|
sheetName[i] = dtExcelSchema.Rows[i]["TABLE_NAME"].ToString();
|
|
}
|
|
con.Close();
|
|
}
|
|
}
|
|
string msg = string.Empty;
|
|
for (int i = 0; i < sheetName.Length; i++)
|
|
msg += sheetName[i] + "\r\n";
|
|
//MessageBox.Show("sheetName = " + msg);
|
|
|
|
// 첫 번째 쉬트의 데이타를 읽어서 datagridview 에 보이게 함.
|
|
using (OleDbConnection con = new OleDbConnection(ExcelConn))
|
|
{
|
|
using (OleDbCommand cmd = new OleDbCommand())
|
|
{
|
|
using (OleDbDataAdapter oda = new OleDbDataAdapter())
|
|
{
|
|
result = new DataTable[sheetName.Length];
|
|
|
|
for (int i = 0; i < sheetName.Length; i++)
|
|
{
|
|
result[i] = new DataTable();
|
|
result[i].TableName = sheetName[i];
|
|
cmd.CommandText = "SELECT * From [" + sheetName[i] + "]";
|
|
cmd.Connection = con;
|
|
con.Open();
|
|
oda.SelectCommand = cmd;
|
|
try
|
|
{
|
|
oda.Fill(result[i]);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
//MessageBox.Show(e1.Message);
|
|
}
|
|
con.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
MessageBox.Show(e.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static void ExcelExport(string Ps_FileName, string[] sheetName)
|
|
{
|
|
string ExcelConn = string.Empty;
|
|
|
|
if (Ps_FileName.IndexOf(".xlsx") > -1) // 확장자에 따라서 provider 주의
|
|
{
|
|
ExcelConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + Ps_FileName
|
|
+ ";Extended Properties='Excel 12.0;HDR=YES;IMEX=3;READONLY=FALSE'";
|
|
}
|
|
else
|
|
{
|
|
ExcelConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Ps_FileName
|
|
+ ";Extended Properties='Excel 8.0;HDR=YES;IMEX=3;READONLY=FALSE'";
|
|
}
|
|
|
|
// 첫 번째 시트의 이름을 가져옮
|
|
using (OleDbConnection con = new OleDbConnection(ExcelConn))
|
|
{
|
|
using (OleDbCommand cmd = new OleDbCommand())
|
|
{
|
|
cmd.Connection = con;
|
|
con.Open();
|
|
|
|
DataTable dtExcelSchema = con.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
|
|
|
|
if (dtExcelSchema.Rows.Count > 0)
|
|
{
|
|
sheetName = new string[dtExcelSchema.Rows.Count];
|
|
|
|
for (int i = 0; i < dtExcelSchema.Rows.Count; i++)
|
|
sheetName[i] = dtExcelSchema.Rows[i]["TABLE_NAME"].ToString();
|
|
}
|
|
con.Close();
|
|
}
|
|
}
|
|
using (OleDbConnection connection =
|
|
new OleDbConnection(@"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + @"C:\Users\[...]\Classeur.xls"
|
|
+ ";Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;READONLY=FALSE\""))
|
|
{
|
|
connection.Open();
|
|
OleDbCommand commande = new OleDbCommand(
|
|
"INSERT INTO [Feuil1$](F1,F2,F3) VALUES ('A3','B3','C3');", connection);
|
|
commande.ExecuteNonQuery();
|
|
|
|
connection.Close();
|
|
connection.Dispose();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region EXCEL CLOSED XML IMPORT / EXPORT
|
|
|
|
private static readonly string[] IsoDateFormats = new string[]
|
|
{
|
|
"yyyy-MM-dd HH:mm:ss",
|
|
"yyyy-MM-ddTHH:mm:ss",
|
|
"yyyy-MM-ddTHH:mm:ss.fff",
|
|
"yyyy-MM-ddTHH:mm:ssZ",
|
|
"yyyy-MM-ddTHH:mm:ss.fffZ",
|
|
"yyyy-MM-dd"
|
|
};
|
|
|
|
public static bool ExportDgvWithClosedXml(string filePath, DataTable[] dt, bool overwiteFile = true)
|
|
{
|
|
if (dt == null || dt.Length == 0) return false;
|
|
|
|
// .xls로 들어오면 .xlsx로 변환하여 저장 경로 결정
|
|
var outputPath = EnsureXlsxPath(filePath);
|
|
|
|
// 저장 경로 폴더 보장
|
|
var dir = Path.GetDirectoryName(outputPath);
|
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
Directory.CreateDirectory(dir);
|
|
|
|
if (File.Exists(outputPath))
|
|
{
|
|
if (overwiteFile) File.Delete(outputPath);
|
|
else throw new IOException("File already exists: " + outputPath);
|
|
}
|
|
|
|
try
|
|
{
|
|
using (var wb = new XLWorkbook()) // C# 7.3 호환
|
|
{
|
|
var nameCount = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
for (int i = 0; i < dt.Length; i++)
|
|
{
|
|
var table = dt[i];
|
|
if (table == null) continue;
|
|
|
|
string baseName = !string.IsNullOrWhiteSpace(table.TableName) ? table.TableName : ("Sheet" + (i + 1));
|
|
string sheetName = SanitizeSheetName(baseName);
|
|
|
|
// 중복 시트명 처리: _2, _3...
|
|
int dup;
|
|
if (nameCount.TryGetValue(sheetName, out dup))
|
|
{
|
|
dup++;
|
|
nameCount[sheetName] = dup;
|
|
string prefix = sheetName.Length > 28 ? sheetName.Substring(0, 28) : sheetName; // "_n" 자리 고려
|
|
sheetName = prefix + "_" + dup;
|
|
}
|
|
else
|
|
{
|
|
nameCount[sheetName] = 1;
|
|
}
|
|
|
|
// DataTable 전체를 시트로 추가 (헤더 포함)
|
|
var ws = wb.Worksheets.Add(table, sheetName);
|
|
|
|
// 자동 필터
|
|
var tbl = ws.Tables.FirstOrDefault();
|
|
if (tbl != null) tbl.ShowAutoFilter = true;
|
|
|
|
// ====== [추가] 문자열 날짜 → 진짜 DateTime으로 변환 ======
|
|
// 1) 날짜 컬럼 후보 탐지 (타입/컬럼명/샘플링)
|
|
bool[] isDateCol = new bool[table.Columns.Count];
|
|
for (int c = 0; c < table.Columns.Count; c++)
|
|
{
|
|
var colType = table.Columns[c].DataType;
|
|
if (colType == typeof(DateTime)) { isDateCol[c] = true; continue; }
|
|
|
|
if (colType == typeof(string))
|
|
{
|
|
string colName = table.Columns[c].ColumnName ?? "";
|
|
// 컬럼명 힌트
|
|
if (colName.IndexOf("date", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
|
colName.IndexOf("time", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
|
colName.EndsWith("_at", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
isDateCol[c] = true;
|
|
continue;
|
|
}
|
|
|
|
// 값 샘플링 (최대 20개)
|
|
int seen = 0, hits = 0;
|
|
for (int r = 0; r < table.Rows.Count && seen < 20; r++)
|
|
{
|
|
var obj = table.Rows[r][c];
|
|
if (obj == null || obj == DBNull.Value) continue;
|
|
var s = obj.ToString();
|
|
if (string.IsNullOrWhiteSpace(s)) continue;
|
|
seen++;
|
|
DateTime tmp;
|
|
if (TryParseIsoDate(s, out tmp)) hits++;
|
|
}
|
|
if (seen > 0 && (hits * 1.0 / seen) >= 0.6) isDateCol[c] = true;
|
|
}
|
|
}
|
|
|
|
// 2) 열 서식 지정 + 각 셀 값 DateTime으로 재설정
|
|
for (int c = 0; c < table.Columns.Count; c++)
|
|
{
|
|
var col = ws.Column(c + 1);
|
|
Type t = table.Columns[c].DataType;
|
|
|
|
if (isDateCol[c])
|
|
{
|
|
// 열 서식(표시): 날짜/시간
|
|
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
|
|
|
|
// 데이터 행(2행부터) 변환
|
|
int rowCount = table.Rows.Count;
|
|
for (int r = 0; r < rowCount; r++)
|
|
{
|
|
var cell = ws.Cell(r + 2, c + 1);
|
|
|
|
// 이미 DateTime 셀은 스킵
|
|
if (cell.DataType == ClosedXML.Excel.XLDataType.DateTime)
|
|
continue;
|
|
|
|
var s = cell.GetString();
|
|
if (string.IsNullOrWhiteSpace(s)) continue;
|
|
|
|
DateTime dtv;
|
|
if (TryParseIsoDate(s, out dtv))
|
|
{
|
|
cell.Value = dtv; // ✅ 진짜 날짜로 저장 (엑셀 DateTime)
|
|
// cell.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss"; // per-cell 필요시
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 기존 숫자/텍스트 서식 유지
|
|
if (t == typeof(DateTime))
|
|
col.Style.DateFormat.Format = "yyyy-MM-dd HH:mm:ss";
|
|
else if (t == typeof(int) || t == typeof(long) || t == typeof(short) || t == typeof(byte))
|
|
col.Style.NumberFormat.Format = "0"; // 정수
|
|
else if (t == typeof(decimal) || t == typeof(float) || t == typeof(double))
|
|
col.Style.NumberFormat.Format = "General"; // 일반 숫자
|
|
else
|
|
col.Style.NumberFormat.Format = "@"; // 텍스트
|
|
}
|
|
}
|
|
// ====== [추가 끝] ======
|
|
|
|
// 열 너비 자동 조정
|
|
ws.Columns().AdjustToContents();
|
|
}
|
|
wb.SaveAs(outputPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// 필요 시 로깅 추가
|
|
throw new Exception(ex.Message);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static string EnsureXlsxPath(string filePath)
|
|
{
|
|
if (string.IsNullOrEmpty(filePath)) throw new ArgumentNullException(nameof(filePath));
|
|
var ext = Path.GetExtension(filePath);
|
|
if (!string.IsNullOrEmpty(ext) && ext.Equals(".xls", StringComparison.OrdinalIgnoreCase))
|
|
return Path.ChangeExtension(filePath, ".xlsx");
|
|
return filePath;
|
|
}
|
|
|
|
private static string SanitizeSheetName(string name)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) return "Sheet1";
|
|
// Excel 금지문자: : \ / ? * [ ]
|
|
char[] invalid = { ':', '\\', '/', '?', '*', '[', ']' };
|
|
var arr = name.ToCharArray();
|
|
for (int i = 0; i < arr.Length; i++)
|
|
for (int j = 0; j < invalid.Length; j++)
|
|
if (arr[i] == invalid[j]) { arr[i] = '_'; break; }
|
|
|
|
var sanitized = new string(arr);
|
|
if (sanitized.Length > 31) sanitized = sanitized.Substring(0, 31);
|
|
if (string.IsNullOrWhiteSpace(sanitized)) sanitized = "Sheet1";
|
|
return sanitized;
|
|
}
|
|
|
|
private static bool TryParseIsoDate(string s, out DateTime dt)
|
|
{
|
|
// 로컬 시간 가정(오프셋 보정 없음). DB 문자열이 UTC라면 AdjustToUniversal로 변경하세요.
|
|
return DateTime.TryParseExact(
|
|
s, IsoDateFormats,
|
|
CultureInfo.InvariantCulture,
|
|
DateTimeStyles.AssumeLocal,
|
|
out dt)
|
|
|| DateTime.TryParse(
|
|
s, CultureInfo.InvariantCulture,
|
|
DateTimeStyles.AssumeLocal,
|
|
out dt);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|