diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc78471 --- /dev/null +++ b/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea/ + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/Class1.cs b/Class1.cs new file mode 100644 index 0000000..6fe840b --- /dev/null +++ b/Class1.cs @@ -0,0 +1,6 @@ +namespace KssSmaPlaCoreLib; + +public class Class1 +{ + +} diff --git a/Commons/Conversion/KanaConvertUxer.cs b/Commons/Conversion/KanaConvertUxer.cs new file mode 100644 index 0000000..4f9cf13 --- /dev/null +++ b/Commons/Conversion/KanaConvertUxer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons.Conversion +{ + public static class KanaConvertUxer + { + /// + /// 半角カタカナを全角カタカナに変換します。 + /// 空文字やnullはそのまま返却します。 + /// + // public static bool TryConvertHankakuToZenkaku(string input, out string result, out string resultMessage) + // { + // result = input; + // resultMessage = string.Empty; + + // if (string.IsNullOrEmpty(input)) + // return true; + + // // 半角カタカナが含まれているかチェック + // if (!Regex.IsMatch(input, @"[\uFF61-\uFF9F]")) + // return true; + + // // 変換処理 + // result = Microsoft.VisualBasic.Strings.StrConv(input, Microsoft.VisualBasic.VbStrConv.Katakana | Microsoft.VisualBasic.VbStrConv.Wide, 0); + // return true; + // } + + /// + /// 全角カタカナを半角カタカナに変換します。 + /// 空文字やnullはそのまま返却します。 + /// + // public static bool TryConvertZenkakuToHankaku(string input, out string result, out string resultMessage) + // { + // result = input; + // resultMessage = string.Empty; + + // if (string.IsNullOrEmpty(input)) + // return true; + + // // 全角カタカナが含まれているかチェック(ざっくり) + // if (!Regex.IsMatch(input, @"[\u30A0-\u30FF]")) + // return true; + + // // 変換処理 + // result = Microsoft.VisualBasic.Strings.StrConv(input, Microsoft.VisualBasic.VbStrConv.Katakana | Microsoft.VisualBasic.VbStrConv.Narrow, 0); + // return true; + // } + } +} diff --git a/Commons/DateHelperUxer.cs b/Commons/DateHelperUxer.cs new file mode 100644 index 0000000..974dfee --- /dev/null +++ b/Commons/DateHelperUxer.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public static class DateHelperUxer + { + + + /// + /// 指定した年と月の末日を返します。 + /// + /// 年(例: 2025) + /// 月(1~12) + /// 末日(28~31) + public static bool TryGetLastDayOfMonth(int year, int month, out int matsubi,out string resultMessage) + { + matsubi = -1; + resultMessage = string.Empty; + + if (month < 1 || month > 12) + { + resultMessage = "月が1~12の範囲ではありません。"; + return false; + } + + // DateTime構造体を使って末日を取得 + matsubi = DateTime.DaysInMonth(year, month); + return true; + } + } +} diff --git a/Commons/DictionaryUxer.cs b/Commons/DictionaryUxer.cs new file mode 100644 index 0000000..4356354 --- /dev/null +++ b/Commons/DictionaryUxer.cs @@ -0,0 +1,55 @@ +using KssSmaPlaLib.IO.File; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public static class DictionaryUxer + { + // /// + // /// 指定されたキーに対応する値(string)を取得します。キーが存在しない場合は 指定されたデフォルト値(未指定時はnull) を返します。 + // /// + // public static TValue GetValueOrDefault( + // Dictionary dict, + // TKey key, + // TValue defaultValue = default) + // { + // return dict.TryGetValue(key, out TValue value) ? value : defaultValue; + // } + + public static bool CreateNameToIdDic(string pcsv, out Dictionary dic, out string resultMessage) + { + resultMessage = string.Empty; + dic = new Dictionary(); + + if (string.IsNullOrEmpty(pcsv)) return true; + + var csvArray = pcsv.Split('|'); + foreach (var csv in csvArray) + { + var items = csv.Split(','); + if (items.Length != 2) + { + resultMessage = $"要素数が{items.Length}個のCSVあり[CSV:{csv}]"; + return false; + } + + // IDの数値チェック + if (!int.TryParse(items[1], out int id)) + { + resultMessage = $"IDが数値でないCSVあり[CSV:{csv}]"; + return false; + } + + // Nameのカンマ記号復元 + items[0] = items[0].Replace("@@@COMMA@@@", ","); + + dic[items[0]] = id; + } + return true; + } + } +} diff --git a/Commons/EncodingUxer.cs b/Commons/EncodingUxer.cs new file mode 100644 index 0000000..7c3381d --- /dev/null +++ b/Commons/EncodingUxer.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public class EncodingUxer + { + #region 列挙体 + + public enum EncodingType + { + Utf8WithBom, + Utf8WithoutBom, + ShiftJIS + } + + #endregion + + public static Encoding GetEncoding(EncodingType type) + { + switch (type) + { + case EncodingType.Utf8WithBom: + return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true); // BOMあり + case EncodingType.Utf8WithoutBom: + return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); // BOMなし + case EncodingType.ShiftJIS: + return Encoding.GetEncoding("shift_jis"); // ANSI(Windows-932) + default: + throw new ArgumentOutOfRangeException(nameof(type), "未対応のエンコーディングです"); + } + } + + public static EncodingType? GetEncodingType(string typeString) + { + typeString = typeString.ToLower(); + + if (StringUxer.WildcardMatch(typeString, "*utf*8*without*bom*")) + return EncodingType.Utf8WithoutBom; + if (StringUxer.WildcardMatch(typeString, "*utf*8*with*bom*")) + return EncodingType.Utf8WithBom; + if (StringUxer.WildcardMatch(typeString, "*utf*8*")) + return EncodingType.Utf8WithoutBom; + if (StringUxer.WildcardMatch(typeString, "*s*jis*")) + return EncodingType.ShiftJIS; + if (StringUxer.WildcardMatch(typeString, "*シ*jis*")) + return EncodingType.ShiftJIS; + return null; + } + } +} diff --git a/Commons/IntUxer.cs b/Commons/IntUxer.cs new file mode 100644 index 0000000..3c5a28c --- /dev/null +++ b/Commons/IntUxer.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public static class IntUxer + { + public static int StrToInt(string str, int defaultValue) + { + if (int.TryParse(str, out int intValue)) + { + return intValue; + } + else + { + return defaultValue; + } + } + } +} diff --git a/Commons/LoggerUxer.cs b/Commons/LoggerUxer.cs new file mode 100644 index 0000000..1eb5ce6 --- /dev/null +++ b/Commons/LoggerUxer.cs @@ -0,0 +1,85 @@ +using KssSmaPlaLib.Ini; +using Serilog; +using Serilog.Events; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace KssSmaPlaLib.Commons +{ + /// + /// ロガー + /// + public static class LoggerUxer + { + /// + /// 初期化 + /// + /// ログINIファイルパス + public static void Initialize(string iniPath) + { + EnsureIniExists(iniPath); + // var config = IniReferenceResolveUxer.ReadSectionWithReference(iniPath, "Logging"); + + var level = Enum.TryParse( + //config["LogLevel"], + IniHelper.ReadValue(iniPath, "Logging", "LogLevel"), + out LogEventLevel parsedLevel) + ? parsedLevel + : LogEventLevel.Information; + + // var path = config["LogFilePath"]; + var path = IniHelper.ReadValue(iniPath, "Logging", "LogFilePath"); + var rolling = IniHelper.ReadValue(iniPath, "Logging", "RollingInterval"); + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(level) + .WriteTo.Console() + .WriteTo.File(path, rollingInterval: (RollingInterval)Enum.Parse(typeof(RollingInterval), rolling)) + .CreateLogger(); + } + + public static void Info(string message) => Log.Information(message); + public static void Warn(string message) => Log.Warning(message); + public static void Error(string message) => Log.Error(message); + public static void Close() => Log.CloseAndFlush(); + + public static void StartInfo() + { + // DLL(自身)のアセンブリ + var dllAssembly = Assembly.GetExecutingAssembly(); + var dllVersion = dllAssembly.GetName().Version?.ToString(); + var dllProduct = dllAssembly.GetCustomAttribute()?.Product ?? "不明"; + + // EXE(呼び出し元)のアセンブリ + var exeAssembly = Assembly.GetEntryAssembly(); + var exeVersion = exeAssembly?.GetName().Version?.ToString(); + var exeProduct = exeAssembly?.GetCustomAttribute()?.Product ?? "不明"; + + // ログ出力 + Info($"[EXE] 製品名: {exeProduct}, バージョン: {exeVersion}"); + Info($"[DLL] 製品名: {dllProduct}, バージョン: {dllVersion}"); + } + + /// + /// ログINI初期化(無ければ) + /// + /// ログINIファイルパス + private static void EnsureIniExists(string path) + { + if (!File.Exists(path)) + { + File.WriteAllLines(path, new[] + { + "[Logging]", + "LogLevel=Information", + "LogFilePath=log.txt", + "RollingInterval=Day" + }); + } + } + } +} diff --git a/Commons/RetCodeUxer.cs b/Commons/RetCodeUxer.cs new file mode 100644 index 0000000..41f70f0 --- /dev/null +++ b/Commons/RetCodeUxer.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public static class RetCodeUxer + { + /// + /// 戻り値(プロセス終了値) + /// + public enum RetCode + { + /// + /// 成功 + /// + Success = 0, + + /// + /// ファイルバックアップ時エラー + /// + FileBackupError = -1, + + /// + /// ファイルクリーンナップエラー + /// + CleanupError = -2, + + /// + /// 開始日エラー + /// + StartDateError = -3, + + /// + /// DB接続エラー + /// + DbConnectError = -4, + + /// + /// SQLフォルダパス未設定 + /// + SqlFolderPathNoSetting = -5, + + /// + /// SQLファイルロードエラー + /// + SqlFileLoadError = -6, + + /// + /// CSVファイル名パターン群が未設定 + /// + CsvFileNamePatternsNotSetting = -7, + + /// + /// 設定ファイルの要素数エラー + /// + IniFileItemCountError = -8, + + /// + /// 挿入前の削除時に例外発生 + /// + DeleteBeforeInsertException = -9, + + /// + /// CSVデータをDB登録時エラー + /// + InsertDbFromCsvFilesError = -10, + + /// + /// CSVからDB登録時に例外発生 + /// + CsvToDbException = -11, + + /// + /// ファイル削除でフォルダパス未設定 + /// + FileDeleteFolderPathNotSetting = -12, + + /// + /// ファイル削除でフォルダパス未存在 + /// + FileDeleteFolderPathNotExist = -13, + + /// + /// ファイル削除でファイル名パターン未設定 + /// + FileDeleteFileNamePatternNotSetting = -14, + + /// + /// ファイル削除でファイル名パターン削除エラー + /// + FileDeletePatternFilesError = -15, + + /// + /// SFTPオブジェクト生成エラー + /// + SftpObjectCreateError = -16, + + /// + /// SFTPアップロードエラー + /// + SftpUploadError = -17, + + /// + /// SFTPアップロードで入力フォルダ未存在 + /// + SftpInFolderPathNotExist = -18, + + /// + /// SFTPでCSVファイル名パターンが未設定 + /// + SftpCsvFileNamePatternsNotSetting = -19, + + /// + /// SFTPダウンロード時に例外発生 + /// + SftpDownloadException = -20, + + /// + /// SFTPファイル削除エラー + /// + SftpDeleteMatchingFilesError = -21, + + /// + /// SFTPパターンマッチファイルダウンロード時エラー + /// + SftpMatchFilesDownloadError = -22, + + /// + /// SFTP共通:テスト接続エラー + /// + SftpTestConnectError = -23, + } + } +} diff --git a/Commons/StringUxer.cs b/Commons/StringUxer.cs new file mode 100644 index 0000000..075ca45 --- /dev/null +++ b/Commons/StringUxer.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public class StringUxer + { + public static bool WildcardMatch(string input, string pattern) + { + // "*" を ".*" に変換し、正規表現にする + string regexPattern = "^" + Regex.Escape(pattern).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase); + } + // public static bool TryParseIniSignature(string iniValue, out string functionName, out List args) + // { + // functionName = null; + // args = null; + + // var pattern = @"^([a-zA-Z_][a-zA-Z0-9_]*)\((.*?)\)$"; + // var match = Regex.Match(iniValue.Trim(), pattern); + + // if (!match.Success) + // return false; + + // functionName = match.Groups[1].Value; + // var argsString = match.Groups[2].Value; + + // args = string.IsNullOrEmpty(argsString) + // ? new List() + // : argsString.Split(',').Select(arg => arg.Trim()).ToList(); + + // return true; + // } + + public static bool TryExtractYearFromString(string str, out int year) + { + // var match = Regex.Match(str, @"\b(20\d{2})\b"); // 条件厳しめ + var match = Regex.Match(str, @"(20\d{2})"); // 条件緩め + year = match.Success ? int.Parse(match.Groups[1].Value) : -1; + return year != -1; + } + + /// + /// CSVから文字列取得(インデックス指定:範囲外の時既定値) + /// + /// CSV文字列(カンマ区切り) + /// インデックス + /// 範囲外の時の既定値 + /// 取得した文字列 + public static string GetStringByIndexFormCsv(string csv, int index, string noString = "(No name...)") + { + string[] strSplit = csv.Split(','); + if (index < 0 || index > strSplit.Length - 1) return noString; + return strSplit[index]; + } + } +} diff --git a/Commons/VariableUxer.cs b/Commons/VariableUxer.cs new file mode 100644 index 0000000..f563e7e --- /dev/null +++ b/Commons/VariableUxer.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Commons +{ + public static class VariableUxer + { + private static List _args = new List(); + + public static List Args { get => _args; } + public static void SetArgs(string[] args) { _args = args.ToList(); } + } +} diff --git a/Crypto/SecureConfigServiceUxer.cs b/Crypto/SecureConfigServiceUxer.cs new file mode 100644 index 0000000..832683d --- /dev/null +++ b/Crypto/SecureConfigServiceUxer.cs @@ -0,0 +1,41 @@ +namespace KssSmaPlaCoreLib.Crypto; +using System.Security.Cryptography; +using System.Text; + +public class SecureConfigServiceUxer(string masterKey) +{ + private readonly byte[] _key = SHA256.HashData(Encoding.UTF8.GetBytes(masterKey)); + + public string Encrypt(string plainText) + { + var plainBytes = Encoding.UTF8.GetBytes(plainText); + var nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize); + var tag = new byte[AesGcm.TagByteSizes.MaxSize]; + var cipherText = new byte[plainBytes.Length]; + + using var aes = new AesGcm(_key, tag.Length); + aes.Encrypt(nonce, plainBytes, cipherText, tag); + + // [Nonce(12)] + [Tag(16)] + [Cipher] + var result = new byte[nonce.Length + tag.Length + cipherText.Length]; + Buffer.BlockCopy(nonce, 0, result, 0, nonce.Length); + Buffer.BlockCopy(tag, 0, result, nonce.Length, tag.Length); + Buffer.BlockCopy(cipherText, 0, result, nonce.Length + tag.Length, cipherText.Length); + + return Convert.ToBase64String(result); + } + + public string Decrypt(string base64Text) + { + var data = Convert.FromBase64String(base64Text); + var nonce = data[..12]; + var tag = data[12..28]; + var cipherText = data[28..]; + var plainBytes = new byte[cipherText.Length]; + + using var aes = new AesGcm(_key, tag.Length); + aes.Decrypt(nonce, cipherText, tag, plainBytes); + + return Encoding.UTF8.GetString(plainBytes); + } +} diff --git a/Exchange/CsvDbSyncUxer.cs b/Exchange/CsvDbSyncUxer.cs new file mode 100644 index 0000000..8e00bfd --- /dev/null +++ b/Exchange/CsvDbSyncUxer.cs @@ -0,0 +1,96 @@ +using KssSmaPlaLib.Commons; +using KssSmaPlaLib.IO.Database; +using KssSmaPlaLib.IO.File; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Exchange +{ + public static class CsvDbSyncUxer + { + // public static bool InsertDbFromCsvFiles( + // DbUxer con, + // string csvFilePath, + // string dupCheckSql, + // string insertSqlName, + // out string resultMessage, + // EncodingUxer.EncodingType encodingType = EncodingUxer.EncodingType.Utf8WithoutBom) + // { + // var messageForMe = string.Empty; + + // resultMessage = string.Empty; + + // // CSV読み込み + // if (!CsvUxer.ImportFromCsv(csvFilePath, out var rs, out messageForMe, encodingType)) + // { + // resultMessage = messageForMe; + // return false; + // } + + // LoggerUxer.Info($"[CSV件数:{rs.Count}]"); + + // if (string.IsNullOrEmpty(dupCheckSql)) + // { + // LoggerUxer.Info($"重複チェックSQLが未指定のため重複チェックは行いません。"); + // } + // else + // { + // LoggerUxer.Info($"重複チェックを行います。[SQLファイル:{dupCheckSql}]"); + // } + + // foreach (var rec in rs) + // { + // var recObj = rec.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value); + + // if (!string.IsNullOrEmpty(dupCheckSql)) + // { + // // 重複チェック + // if (!con.ExecuteQuery(dupCheckSql, recObj, out var dupRs, out messageForMe)) + // { + // resultMessage = "重複チェックエラー:" + messageForMe; + // return false; + // } + // if (dupRs.Count != 1) + // { + // resultMessage = $"重複チェックで取得件数エラーです。[取得件数:{dupRs.Count}]"; + // return false; + // } + // // ↓COUNT(*)の結果型はintにあらず(ポスグレの場合long)ゆえに文字列化してint化する + // if (!int.TryParse(dupRs[0].Values.ElementAt(0).ToString(), out var intCnt)) + // { + // resultMessage = $"重複チェックで取得値(件数)が数値ではありません。[取得値:{dupRs[0].Values.ElementAt(0)}]"; + // return false; + // } + // // 重複レコードありの場合、飛ばす + // if (intCnt > 0) + // { + // LoggerUxer.Info($"重複レコードのため、飛ばします。[該当件数:{intCnt}]"); + // continue; + // } + // LoggerUxer.Info($"重複チェックにて重複レコードは見つかりませんでした。"); + // } + + // // DB挿入 + // if (!string.IsNullOrEmpty(insertSqlName)) + // { + // LoggerUxer.Info($"[DB挿入SQL名:{insertSqlName}]"); + // if (!con.ExecuteNonQuery(insertSqlName, recObj, out var resultCount, out messageForMe)) + // { + // resultMessage = messageForMe; + // return false; + // } + // LoggerUxer.Info($"[DB挿入件数:{resultCount}]"); + // } + // else + // { + // LoggerUxer.Info($"DB挿入SQL名が未指定のためDB挿入は行いません。"); + // } + // } + // return true; + // } + } +} diff --git a/Exchange/Interfaces/ICsvExportableUxer.cs b/Exchange/Interfaces/ICsvExportableUxer.cs new file mode 100644 index 0000000..ad07172 --- /dev/null +++ b/Exchange/Interfaces/ICsvExportableUxer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Exchange.Interfaces +{ + public interface ICsvExportableUxer + { + string CsvHeader { get; } // ヘッダー行 + string ToCsvLine(); // データ行 + } +} diff --git a/IO/Database/DbUxer.cs b/IO/Database/DbUxer.cs new file mode 100644 index 0000000..2453ffd --- /dev/null +++ b/IO/Database/DbUxer.cs @@ -0,0 +1,605 @@ +using DocumentFormat.OpenXml.Drawing; +using DocumentFormat.OpenXml.Office.Word; +using DocumentFormat.OpenXml.Spreadsheet; +using DocumentFormat.OpenXml.Wordprocessing; +using KssSmaPlaLib.Commons; +using Serilog; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using static KssSmaPlaLib.IO.Database.DbUxer; +using Path = System.IO.Path; + +namespace KssSmaPlaLib.IO.Database +{ + /// + /// DBアクセスアシスタント + /// + public class DbUxer : IDisposable + { + public enum DbProvider + { + PostgreSQL, + SQLServer, + MySQL, + Oracle, + SQLite, + } + + /// + /// プロバイダ名ディクショナリ + /// + private static readonly Dictionary _providerNameDic = new Dictionary() + { + { DbProvider.PostgreSQL, "Npgsql" }, + { DbProvider.SQLServer, "System.Data.SqlClient" }, + { DbProvider.MySQL, "MySql.Data.MySqlClient" }, + { DbProvider.Oracle, "Oracle.ManagedDataAccess.Client" }, + { DbProvider.SQLite, "System.Data.SQLite" }, + }; + + /// + /// プロバイダ名(文字列)からenum値に変更 + /// + /// プロバイダ名(文字列) + /// enum値(該当なし時はnull) + public static DbProvider? GetDbProviderEnumFromString(string dbProviderName = "PostgreSQL") + { + switch(dbProviderName.ToLower().Replace(" ","")) + { + case "postgresql": + case "ポスグレ": + return DbProvider.PostgreSQL; + case "sqlserver": + return DbProvider.SQLServer; + case "mysql": + return DbProvider.MySQL; + case "oracle": + case "oracledatabase": + return DbProvider.Oracle; + case "sqlite": + case "sqlite3": + return DbProvider.SQLite; + default: + return null; + } + } + + /// + /// コミット済みフラグ + /// + private bool _isCommitted = false; + + /// + /// SQLディクショナリ + /// + private static readonly Dictionary _sqlDic = new Dictionary(); + + /// + /// 指定フォルダからSQLファイルを読み込む(ディクショナリ保存) + /// + /// フォルダパス + /// 成否 + public static bool TryLoadSqlFromText(string folderPath, out string resultMessage) + { + resultMessage = string.Empty; + var fileCount = 0; + List files = new List(); + + try + { + if (string.IsNullOrEmpty(folderPath)) + { + resultMessage = "フォルダパスが指定されていません。"; + return false; + } + if (!Directory.Exists(folderPath)) + { + resultMessage = $"フォルダが存在しません。[フォルダパス:{folderPath}]"; + return false; + } + _sqlDic.Clear(); + foreach (var file in Directory.GetFiles(folderPath, "*.sql.txt")) + { + _sqlDic.Add(Path.GetFileNameWithoutExtension(file).ToLower().Replace(".sql", ""), System.IO.File.ReadAllText(file)); + files.Add(Path.GetFileNameWithoutExtension(file).Replace(".sql", "")); + fileCount++; + } + resultMessage = $"SQLファイルをロードしました。[ファイル数:{fileCount},ファイル:{string.Join(",", files)}]"; + return true; + } + catch (Exception ex) + { + resultMessage = $"SQLファイル読み込み時にエラーが発生しました。[エラー内容:{ex.Message}]"; + return false; + } + } + + // public static bool TestConnectByDic(Dictionary iniDic, out string resultMessage) + // { + // const string KEY_DB_PROVIDER_NAME = "DbProviderName"; + // const string KEY_DB_CONNECTION_STRING = "DbConnectionString"; + + // var dbProviderName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_PROVIDER_NAME, string.Empty); + // var connectionString = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_CONNECTION_STRING, string.Empty); + // return TestConnect(dbProviderName, connectionString, out resultMessage); + // } + + /// + /// テスト接続 + /// + /// 成否 + // public static bool TestConnect(string dbPrividerName, string dbConnectionString, out string resultMessage) + // { + // // パラメータチェック&DBプロバイダ取得 + // if (!TryConnectParameter(dbPrividerName, dbConnectionString, out DbProvider? _, out resultMessage)) + // { + // return false; + // } + + // if (!TryCreate(dbPrividerName, dbConnectionString, out var dbUxer, out resultMessage, false)) + // { + // return false; + // } + // dbUxer.Dispose(); + // return true; + // } + + /// + /// DB接続 + /// + private DbConnection? _con = null; + + /// + /// トランザクション + /// + private DbTransaction? _tran = null; + + // public static bool TryCreateByDic(Dictionary iniDic, out DbUxer dbUxer, out string resultMessage, bool useTransaction = false) + // { + // const string KEY_DB_PROVIDER_NAME = "DbProviderName"; + // const string KEY_DB_CONNECTION_STRING = "DbConnectionString"; + + // var dbProviderName = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_PROVIDER_NAME, string.Empty); + // var connectionString = DictionaryUxer.GetValueOrDefault(iniDic, KEY_DB_CONNECTION_STRING, string.Empty); + + // return TryCreate(dbProviderName, connectionString, out dbUxer, out resultMessage, useTransaction); + // } + + /// + /// 生成挑戦 + /// + /// + /// + /// + /// + /// + /// + // public static bool TryCreate(string dbPrividerName, string dbConnectionString, out DbUxer dbUxer, out string resultMessage, bool useTransaction = false) + // { + // dbUxer = null; + + // // パラメータチェック&DBプロバイダ取得 + // if (!TryConnectParameter(dbPrividerName, dbConnectionString, out DbProvider? dbp, out resultMessage)) + // { + // return false; + // } + + // DbProvider dbpp = (DbProvider)dbp; + + // try + // { + // // 生成 + // dbUxer = new DbUxer(dbpp, dbConnectionString, useTransaction); + // return true; + // } + // catch (Exception ex) + // { + // // 接続NG回答 + // resultMessage = $"DB接続に失敗しました。[エラー内容:{ex.Message}]"; + // return false; + // } + // } + + private static bool TryConnectParameter(string dbPrividerName, string dbConnectionString, out DbProvider? dbp, out string resultMessage) + { + dbp = DbProvider.PostgreSQL; + resultMessage = string.Empty; + + if (string.IsNullOrEmpty(dbPrividerName)) + { + // DBプロバイダ指定なしの場合、デフォルトはポスグレとする + // Infoメッセージ + resultMessage = "DBプロバイダ名が未指定のため、デフォルトのPostgreSQLを使用します。"; + } + + if (string.IsNullOrEmpty(dbConnectionString)) + { + resultMessage = "接続文字列が設定されていません。"; + return false; + } + + if (!string.IsNullOrEmpty(dbPrividerName)) + { + dbp = GetDbProviderEnumFromString(dbPrividerName); + } + + if (dbp == null) + { + resultMessage = $"DBプロバイダ名が不正です。[DBプロバイダ名:{dbPrividerName}]"; + return false; + } + + return true; + } + + /// + /// コンストラクタ(DB接続) + /// + /// DBプロバイダ(enum値) + /// 接続文字列 + /// トランザクション有無 + private DbUxer(DbProvider dbProvider, string connectionString, bool useTransaction = false) + { + // try + // { + // // DBプロバイダファクトリー作成 + // DbProviderFactory factory; + // if (dbProvider == DbProvider.PostgreSQL) + // { + // // ポスグレの場合、Npgsqlのインスタンをを直接取得 + // factory = Npgsql.NpgsqlFactory.Instance; + // } + // else + // { + // factory = DbProviderFactories.GetFactory(_providerNameDic[dbProvider]); + // } + + // // DB接続作成 + // _con = factory.CreateConnection(); + + // // 接続文字列設定 + // _con.ConnectionString = connectionString; + + // // 接続 + // _con.Open(); + + // // トランザクション + // if (useTransaction) _tran = _con.BeginTransaction(); + // } + // catch (Exception ex) + // { + // LoggerUxer.Error($"DB接続に失敗しました。[プロバイダ:{dbProvider},接続文字列:{connectionString},エラー内容:{ex.Message}]"); + // throw ex; + // } + } + + // public bool ExecuteNonQuery(string sqlOrName, Dictionary parameters, out int resultCount, out string resultMessage) + // { + // resultCount = 0; + // resultMessage = string.Empty; + + // if (_con == null || _con.State != ConnectionState.Open) + // { + // resultMessage = "DBと接続されていません。"; + // return false; + // } + + // try + // { + // using (var cmd = _con.CreateCommand()) + // { + // // コパンドプロパティを注入 + // if (!TryInjectSoulToCmd(cmd, sqlOrName, parameters, out var messageForMe)) + // { + // resultMessage = messageForMe; + // return false; + // } + + // resultCount = cmd.ExecuteNonQuery(); + // LoggerUxer.Info($"[Rec Count:{resultCount}]"); + // return true; + // } + // } + // catch (Exception ex) + // { + // resultMessage = $"SQL実行でエラーが発生しました。[SQL名:{sqlOrName},エラー内容:{ex.Message}]"; + // return false; + // } + // } + + // public bool ExecuteQuery(string sqlOrName, Dictionary parameters, out List> recordSet, out string resultMessage) + // { + // recordSet = new List>(); + // resultMessage = string.Empty; + + // if (_con == null || _con.State != ConnectionState.Open) + // { + // resultMessage = "DBと接続されていません。"; + // return false; + // } + + // try + // { + // using (var cmd = _con.CreateCommand()) + // { + // // コパンドプロパティを注入 + // if (!TryInjectSoulToCmd(cmd, sqlOrName, parameters, out var messageForMe)) + // { + // resultMessage = messageForMe; + // return false; + // } + // using (var reader = cmd.ExecuteReader()) + // { + // while (reader.Read()) + // { + // var row = new Dictionary(); + // for (int i = 0; i < reader.FieldCount; i++) + // { + // row[reader.GetName(i)] = reader.GetValue(i); + // } + // recordSet.Add(row); + // } + // LoggerUxer.Info($"[Rec Count:{recordSet.Count}]"); + // } + // } + // return true; + // } + // catch (Exception ex) + // { + // resultMessage = $"DBからデータ取得時にエラーが発生しました。[SQL名:{sqlOrName},エラー内容:{ex.Message}]"; + // return false; + // } + // } + + // private bool TryInjectSoulToCmd(DbCommand cmd, string sqlOrName, Dictionary parameters, out string resultMessage) + // { + // resultMessage = string.Empty; + + // // SQLの取得 + // sqlOrName = sqlOrName.ToLower(); + // var sql = _sqlDic.ContainsKey(sqlOrName) ? _sqlDic[sqlOrName] : sqlOrName; + // //LoggerUxer.Info($"[SQL:{sql}]"); + + // // SQL中のコマンドパラメータ名取得 + // var paraList = ExtractSqlParams(sql); + // //oggerUxer.Info($"[SQL Para names:{string.Join(",", paraList)}]"); + + // // コマンドパラメータ値の型変換 + // if (!TryChangeParamTypeFromSqlParams(ref parameters, ref paraList, out var messageForMe)) + // { + // resultMessage = $"コマンドパラメータ値の型変換に失敗しました。[エラー内容:{messageForMe}]"; + // return false; + // } + + // // SQL文中のコマンドパラメータ型定義をカット + // cmd.CommandText = sql = CutParamTypeOfSQL(sql); + // //LoggerUxer.Info($"[SQL(Param Type Cut):{sql}]"); + + // // 再度、SQL中のコマンドパラメータ名取得 + // paraList = ExtractSqlParams(sql); + // //LoggerUxer.Info($"[SQL Para names(Type cut):{string.Join(",", paraList)}]"); + + // // SQLに合わせたコマンドパラメータ登録 + // foreach (var p in parameters) + // { + // if (!paraList.Contains(p.Key)) continue; + // var param = cmd.CreateParameter(); + // param.ParameterName = p.Key; + // param.Value = p.Value; + // cmd.Parameters.Add(param); + // } + + // //LoggerUxer.Info($"[SQL Params:{GetCommandParametersCsv(cmd)}]"); + + // if (cmd.Parameters.Count != paraList.Count) + // { + // resultMessage = $"必要なパラメータが不足しています。[SQL名:{sqlOrName},SQL項目:{string.Join(",", paraList)}, CSV項目:{string.Join(",", parameters.Keys)}]"; + // return false; + // } + // return true; + // } + + public void Commit() + { + _tran?.Commit(); + _isCommitted = true; + } + + /// + /// 終了処理 + /// + /// + public void Dispose() + { + if (_tran != null && !_isCommitted) + { + Log.Warning("トランザクション未コミット → 自動ロールバックされました"); + _tran.Rollback(); // 明示的にロールバック + } + _con?.Dispose(); + _con = null; + _tran = null; + } + + public static List ExtractSqlParams(string sql) + { + //var matches = System.Text.RegularExpressions.Regex.Matches(sql, @":([a-zA-Z_][a-zA-Z0-9_]*)"); + //return matches.Cast() + // .Select(m => m.Groups[1].Value) + // .Distinct() + // .ToList(); + + //var result = new List(); + //var lines = sql.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + + //foreach (var line in lines) + //{ + // // コメント部分を除去 + // var codePart = line.Contains("--") ? line.Substring(0, line.IndexOf("--")) : line; + + // // パラメータ抽出 + // var matches = System.Text.RegularExpressions.Regex.Matches( + // codePart, + // @":(\[[a-zA-Z]\][a-zA-Z_][a-zA-Z0-9_]*)|:([a-zA-Z_][a-zA-Z0-9_]*)" + // ); + // foreach (System.Text.RegularExpressions.Match match in matches) + // { + // var paramName = match.Groups[1].Success ? match.Groups[1].Value : match.Groups[2].Value; + // if (!result.Contains(paramName)) + // result.Add(paramName); + // } + //} + //return result; + + var result = new List(); + var lines = sql.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + + foreach (var line in lines) + { + // コメント部分を除去 + var codePart = line.Contains("--") ? line.Substring(0, line.IndexOf("--")) : line; + + // パラメータ抽出(::で始まるものは除外) + var matches = System.Text.RegularExpressions.Regex.Matches( + codePart, + @"(?(); + + // foreach (DbParameter param in command.Parameters) + // { + // string name = param.ParameterName?.TrimStart('@'); // @を除去(任意) + // object value = param.Value; + + // string formattedValue; + + // if (value == null || value == DBNull.Value) + // { + // formattedValue = "NULL"; + // } + // else if (value is string || value is char) + // { + // formattedValue = $"'{value}'"; // シングルクォートで括る + // } + // else if (value is DateTime dt) + // { + // formattedValue = $"'{dt:yyyy-MM-dd HH:mm:ss}'"; // 日付も文字列扱い + // } + // else + // { + // formattedValue = value.ToString(); + // } + + // list.Add($"{name}={formattedValue}"); + // } + + // return string.Join(",", list); + // } + // private static bool TryChangeParamTypeFromSqlParams(ref Dictionary parameters, ref List paraNameList, out string resultMessage) + // { + // resultMessage = string.Empty; + + // foreach (var key in parameters.Keys.ToList()) + // { + // var name = paraNameList + // .FirstOrDefault(m => m == key || m.Substring(0, 1) == "[" && m.Substring(3) == key); + // if (name != null && name.Substring(0, 1) == "[") + // { + // switch (name.Substring(0, 3).ToUpper()) + // { + // case "[I]": + // if (string.IsNullOrEmpty(parameters[key].ToString())) + // { + // parameters[key] = DBNull.Value; + // } + // else if (int.TryParse(parameters[key].ToString(), out int intValue)) + // { + // parameters[key] = intValue; + // } + // else + // { + // resultMessage = $"パラメータ値[I]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; + // return false; + // } + // break; + // //case "[IN": + // // if (string.IsNullOrEmpty(parameters[key].ToString())) + // // { + // // parameters[key] = DBNull.Value; + // // } + // // else if (int.TryParse(parameters[key].ToString(), out intValue)) + // // { + // // parameters[key] = intValue; + // // } + // // else + // // { + // // resultMessage = $"パラメータ値[IN]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; + // // return false; + // // } + // // break; + // case "[F]": + // if (double.TryParse(parameters[key].ToString(), out double doubleValue)) + // { + // parameters[key] = doubleValue; + // } + // else + // { + // resultMessage = $"パラメータ値[F]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; + // return false; + // } + // break; + // case "[D]": + // if (DateTime.TryParse(parameters[key].ToString(), out DateTime dtValue)) + // { + // parameters[key] = dtValue; + // } + // else + // { + // resultMessage = $"パラメータ値[D]型変換に失敗しました。[キー:{key}値:{parameters[key]}]"; + // return false; + // } + // break; + // case "[S]": + // parameters[key] = parameters[key].ToString(); + // break; + // } + // } + // } + // return true; + // } + + // SQL文中のコマンドパラメータ型定義をカット + private static string CutParamTypeOfSQL(string sql) + { + string[] cutItems = new string[] { "[I]", "[F]", "[D]", "[S]" }; + foreach (var cutItem in cutItems) + { + sql = sql.Replace(cutItem.ToUpper(), ""); + sql = sql.Replace(cutItem.ToLower(), ""); + } + return sql; + } + } +} diff --git a/IO/Excel/ExcelCellValidatorUxer.cs b/IO/Excel/ExcelCellValidatorUxer.cs new file mode 100644 index 0000000..0bbda69 --- /dev/null +++ b/IO/Excel/ExcelCellValidatorUxer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.IO.Excel +{ + public static class ExcelCellValidatorUxer + { + /// + /// ExcelのA1形式(例: "A1", "Z99", "AA1000")かどうかを判定します。 + /// + /// セル参照文字列 + /// 有効なA1形式ならtrue、無効ならfalse + public static bool IsValidA1Format(string cellRef) + { + if (string.IsNullOrWhiteSpace(cellRef)) + return false; + + // 正規表現でA1形式をチェック(列: A〜XFD、行: 1〜1048576) + var match = System.Text.RegularExpressions.Regex.Match( + cellRef.ToUpperInvariant(), + @"^([A-Z]{1,3})([1-9][0-9]{0,6})$" + ); + + if (!match.Success) + return false; + + string colPart = match.Groups[1].Value; + string rowPart = match.Groups[2].Value; + + // 列がXFD(Excel最大列)以内かチェック + if (ColumnNameToNumber(colPart) > 16384) + return false; + + // 行がExcel最大行以内かチェック + if (int.TryParse(rowPart, out int row)) + { + return row >= 1 && row <= 1048576; + } + + return false; + } + + /// + /// 列名(例: "A", "Z", "AA")を列番号に変換(例: 1, 26, 27) + /// + private static int ColumnNameToNumber(string columnName) + { + int sum = 0; + foreach (char c in columnName) + { + sum *= 26; + sum += (c - 'A' + 1); + } + return sum; + } + } +} diff --git a/IO/Excel/ExcelColumnHelperUxer.cs b/IO/Excel/ExcelColumnHelperUxer.cs new file mode 100644 index 0000000..44b261a --- /dev/null +++ b/IO/Excel/ExcelColumnHelperUxer.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.IO.Excel +{ + public static class ExcelColumnHelperUxer + { + public static string GetExcelColumnLetter(int columnNumber) + { + if (columnNumber < 1) + throw new ArgumentOutOfRangeException(nameof(columnNumber), "Column number must be >= 1"); + + string columnLetter = string.Empty; + while (columnNumber > 0) + { + columnNumber--; // Adjust for 0-indexed calculation + columnLetter = (char)('A' + (columnNumber % 26)) + columnLetter; + columnNumber /= 26; + } + return columnLetter; + } + } +} diff --git a/IO/Excel/XlUxer.cs b/IO/Excel/XlUxer.cs new file mode 100644 index 0000000..7b149b7 --- /dev/null +++ b/IO/Excel/XlUxer.cs @@ -0,0 +1,95 @@ +using ClosedXML.Excel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.IO.Excel +{ + public class XlUxer + { + public static IXLCell GCell(IXLWorksheet sheet, string a1Address) + { + var cell = sheet.Cell(a1Address); + return GCell(cell); + } + + public static IXLCell GCell(IXLWorksheet sheet, int row, int col) + { + var cell = sheet.Cell(row, col); + return GCell(cell); + } + + public static IXLCell GCell(IXLCell cell) + { + return cell.IsMerged() ? cell.MergedRange().FirstCell() : cell; + } + + // public static bool TryValidateA1CellAddress(string a1Address, out string normalizedAddress) + // { + // normalizedAddress = null; + + // if (string.IsNullOrWhiteSpace(a1Address)) + // return false; + + // // 正規表現でA1形式(例:A1, B2, AA10など)をチェック + // var regex = new Regex(@"^[A-Z]{1,3}[1-9][0-9]*$", RegexOptions.IgnoreCase); + // if (!regex.IsMatch(a1Address)) + // return false; + + // // 正規化(大文字化) + // normalizedAddress = a1Address.ToUpperInvariant(); + // return true; + // } + + /// + /// Shorter + /// + /// + /// + public static int GetColNo(string colLetter) + { + if (!TryConvertColLetterToNumber(colLetter, out var no)) + { + return int.MinValue; + } + return no; + } + + /// + /// 列アルファベットを列番号(1オリジン)に変換 + /// + /// 列アルファベット + /// [出力]列番号(1オリジン) + /// 成否 + public static bool TryConvertColLetterToNumber(string colLetter, out int colNo) + { + colNo = int.MinValue; + + // バリデ(A~XFD) + if(!Regex.IsMatch(colLetter, @"^(?i)([A-Z]|[A-Z]{2}|[A-E][A-Z]{2})$")) + { + return false; + } + + // 大文字化 + colLetter = colLetter.ToUpper(); + + // アルファベット1文字ずつ見て列番号を算出 + int no = 0; + foreach (char c in colLetter) + { + no = no * 26 + (c - 'A' + 1); + } + + // 列番号範囲外なら失敗 + if (no > 16384 || no < 1) return false; + + // 成功 + colNo = no; + return true; + } + } +} diff --git a/IO/File/CSVUxer.cs b/IO/File/CSVUxer.cs new file mode 100644 index 0000000..c0d3204 --- /dev/null +++ b/IO/File/CSVUxer.cs @@ -0,0 +1,92 @@ +using KssSmaPlaLib.Commons; +using KssSmaPlaLib.Exchange.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace KssSmaPlaLib.IO.File +{ + public class CsvUxer + { + public static bool ExportToCsv(List list, string filePath, out string resultMessage, EncodingUxer.EncodingType encodingType = EncodingUxer.EncodingType.Utf8WithoutBom) where T : ICsvExportableUxer + { + resultMessage = string.Empty; + try + { + var lines = new List { list.FirstOrDefault()?.CsvHeader ?? "" }; + lines.AddRange(list.Select(item => item.ToCsvLine())); + System.IO.File.WriteAllLines(filePath, lines, EncodingUxer.GetEncoding(encodingType)); + return true; + } + catch (Exception ex) + { + resultMessage = $"CSV出力に失敗しました。[ファイルパス:{filePath},エラー内容:{ex.Message}]"; + return false; + } + } + + /// + /// CSVインポート + /// + /// CSVファイルパス + /// [出力]レコードセット + /// [出力]メッセージ + /// [オプション]エンコード + /// 成否 + public static bool ImportFromCsv(string csvFilePath, out List> recordSet, out string resultMessage, EncodingUxer.EncodingType encodingType = EncodingUxer.EncodingType.Utf8WithoutBom) + { + recordSet = new List>(); + resultMessage = string.Empty; + + try + { + // 全行読み込み + var lines = System.IO.File.ReadAllLines(csvFilePath, EncodingUxer.GetEncoding(encodingType)); + + // データ行0件 + if (lines.Length < 2) return true; + + // ヘッダ行項目分割 + var header = Regex.Split(lines[0], ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + for (var cnt = 0; cnt < header.Length; cnt++) header[cnt] = header[cnt].Trim('\"'); + + // データ行件数分 + for (int i = 1; i < lines.Length; i++) + { + // 項目分割 + var values = Regex.Split(lines[i], ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + for (var cnt = 0; cnt < values.Length; cnt++) values[cnt] = values[cnt].Trim('\"'); + + if (values.Length != header.Length) + { + resultMessage = $"CSV読み込みでヘッダの項目数とデータの項目数が異なります。[CSVファイルパス:{csvFilePath},行番号:{i + 1},ヘッダ行項目数:{header.Length},データ行項目数:{values.Length}]"; + return false; + } + + var record = new Dictionary(); + + // 全項目 + for (int j = 0; j < header.Length && j < values.Length; j++) + { + var key = header[j].Trim('\"'); + var value = values[j].Trim('\"'); + record[key] = value; + } + recordSet.Add(record); + } + return true; + } + catch (Exception ex) + { + resultMessage = $"CSV読み込み処理でエラーが発生しました。[CSVファイルパス:{csvFilePath},エラー内容:{ex.Message}]"; + return false; + } + } + + public static string Escape(string value) => $"\"{value.Replace("\"", "\"\"")}\""; + // public static string Join(params object[] values) => string.Join(",", values.Select(m => Escape(m == null ? "" : m.ToString()))); + } +} diff --git a/IO/File/FileUxer.cs b/IO/File/FileUxer.cs new file mode 100644 index 0000000..78d7f98 --- /dev/null +++ b/IO/File/FileUxer.cs @@ -0,0 +1,140 @@ +using DocumentFormat.OpenXml.Vml; +using KssSmaPlaLib.Commons; +using KssSmaPlaLib.Exchange.Interfaces; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Path = System.IO.Path; + +namespace KssSmaPlaLib.IO.File +{ + public static class FileUxer + { + /// + /// テキストファイル出力(全テキスト一括) + /// + /// 全テキスト + /// ファイルパス + /// メッセージ + /// 文字コード + /// 成否 + public static bool WriteAllText(string allLineText, string filePath, out string resultMessage, EncodingUxer.EncodingType encodingType = EncodingUxer.EncodingType.Utf8WithoutBom) + { + resultMessage = string.Empty; + try + { + System.IO.File.WriteAllText(filePath, allLineText, EncodingUxer.GetEncoding(encodingType)); + return true; + } + catch (Exception ex) + { + resultMessage = $"CSV出力に失敗しました。[ファイルパス:{filePath},エラー内容:{ex.Message}]"; + return false; + } + } + + public static bool TryDeletePatternFiles( + string folderPath, + string fileNamePattern, + out int fileCount, + out string resultMessage) + { + fileCount = 0; + resultMessage = string.Empty; + if (!Directory.Exists(folderPath)) + { + resultMessage = $"フォルダが存在しません。[フォルダパス:{folderPath}]"; + return false; + } + try + { + var files = Directory.GetFiles(folderPath, fileNamePattern); + + foreach (var filePath in files) + { + System.IO.File.Delete(filePath); + fileCount++; + } + return true; + } + catch (Exception ex) + { + resultMessage = $"ファイル削除中にエラーが発生しました。[フォルダパス:{folderPath},ファイル名パターン:{fileNamePattern},エラー内容:{ex.Message}]"; + return false; + } + } + + public static string GetTimestampedFileName(string originalName, DateTime dt, string prefixDtFormat, string suffixDtFormat) + { + string nameWithoutExt = Path.GetFileNameWithoutExtension(originalName); + string ext = Path.GetExtension(originalName); + string prefixDtStr = dt.ToString(prefixDtFormat); + string suffixDtStr = dt.ToString(suffixDtFormat); + + string newName = prefixDtStr + nameWithoutExt + suffixDtStr; + return newName + ext; + } + + // public static bool CheckParentFolderExists(string folderPath, out string parentPath) + // { + // parentPath = Path.GetDirectoryName(folderPath) ?? string.Empty; + // return Directory.Exists(parentPath); + // } + + // public static bool TryApplyActionToFiles( + // string folderPath, + // Func condition, + // Action action, + // out int fileCount, + // out string resultMessage, + // string actionName = "操作", + // bool isDescending = true, + // Func? sortKey = null, + // int skip = 0) + // { + // fileCount = 0; + // resultMessage = string.Empty; + + // try + // { + // // 条件未指定時は全件対象 + // condition = condition ?? (_ => true); + + // // 一旦条件で取得 + // var files = new DirectoryInfo(folderPath) + // .GetFiles() + // .Where(condition); + + // // ソートキー作成 + // var keySelector = + // sortKey != null ? (Func) + // (f => sortKey(f)) : f => f.LastWriteTime; + + // // ソート+スキップ+リスト化 + // var filesList = + // (isDescending ? + // files.OrderByDescending(keySelector) : + // files.OrderBy(keySelector)) + // .Skip(skip) + // .ToList(); + + // foreach (var file in filesList) + // { + // action(file); + // LoggerUxer.Info($"{actionName}しました。[{file}]"); + // } + + // fileCount = filesList.Count; + // return true; + // } + // catch (Exception ex) + // { + // resultMessage = $"ファイル群の{actionName}に失敗しました。[エラー内容:{ex.Message}]"; + // return false; + // } + // } + } +} diff --git a/IO/File/JsonFileUxer.cs b/IO/File/JsonFileUxer.cs new file mode 100644 index 0000000..f60c8c2 --- /dev/null +++ b/IO/File/JsonFileUxer.cs @@ -0,0 +1,206 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.IO.File +{ + public static class JsonFileUxer + { + /// + /// JSONファイルのピンポイント1項目更新 + /// + /// JSONファイルパス + /// JSONノードパス + /// 更新値 + /// + // public static void UpdateJsonKeyAtomic(string path, string jsonPath, object newValue) + // { + // //// 使い方例:パスワード更新 + // //JsonFiileUxer.UpdateJsonKeyAtomic("config.json", "app.auth.password", "new_pw_2026-01"); + + // var jtoken = JToken.FromObject(newValue); + + // // 1) 読み込み(UTF-8) + // var json = System.IO.File.ReadAllText(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + // var jo = JObject.Parse(json); + + // // 2) ピンポイント更新(途中が無ければ作る) + // var token = jo.SelectToken(jsonPath); + // if (token is null) + // { + // // 例: "app.auth.password" を分解して動的に作る + // var parts = jsonPath.Split('.'); + // JToken cur = jo; + // for (int i = 0; i < parts.Length; i++) + // { + // var name = parts[i]; + // var obj = cur as JObject ?? throw new InvalidOperationException("親がオブジェクトではありません"); + // if (i == parts.Length - 1) + // { + // obj[name] = jtoken; + // } + // else + // { + // if (obj[name] == null) obj[name] = new JObject(); + // cur = obj[name]; + // } + // } + // } + // else + // { + // token.Replace(jtoken); + // } + + // // 3) 一時ファイルへ書き出し + // var dir = System.IO.Path.GetDirectoryName(path) ?? "."; + // var tmp = System.IO.Path.Combine(dir, ".tmp_config.json"); + // var backup = path + $"_{DateTime.Now.ToString("yyyyMMddHHmmss")}" + ".bak"; + + // //var output = jo.ToString(Formatting.Indented) + Environment.NewLine; + // var output = JsonConvert.SerializeObject(jo, Formatting.Indented) + Environment.NewLine; + // System.IO.File.WriteAllText(tmp, output, new UTF8Encoding(false)); + + // // 4) バックアップ作成 → 原子置換 + // if (System.IO.File.Exists(path)) System.IO.File.Copy(path, backup, overwrite: true); + // System.IO.File.Replace(tmp, path, backup); + // } + + /// + /// JSONファイルから、jsonPathに一致する単一項目を取得します。 + /// 取得に成功したら true を返し、 out value に JToken を設定します。 + /// キーが見つからない・JSON無効・ファイル無し等は false を返し、 value は null。 + /// + // public static bool TryGetJsonKey(string path, string jsonPath, out JToken value) + // { + // value = null; + + // // 1) 読み込み(UTF-8 BOMなし) + // string json; + // try + // { + // json = System.IO.File.ReadAllText(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + // } + // catch (Exception) + // { + // // ファイルがない/読み取り不可 + // return false; + // } + + // // 2) 解析 + // JObject jo; + // try + // { + // jo = JObject.Parse(json); + // } + // catch (JsonReaderException) + // { + // // JSON不正 + // return false; + // } + + // // 3) ピンポイント取得 + // // 例: "app.auth.password" / "items[0].name" / "users['alice'].age" 等 + // try + // { + // var token = jo.SelectToken(jsonPath); + // if (token is null) + // { + // return false; // キー未存在 + // } + + // value = token; // JValue/JObject/JArray いずれもOK + // return true; + // } + // catch (ArgumentException) + // { + // // JSONPathの書式誤り(SelectTokenが例外) + // return false; + // } + // } + + /// + /// 文字列として取得(JToken型を意識したくない場合の軽量ヘルパー)。 + /// + // public static bool TryGetJsonString(string path, string jsonPath, out string result) + // { + // result = string.Empty; + // if (!TryGetJsonKey(path, jsonPath, out var token)) return false; + + // // JValue以外(オブジェクト/配列)の場合は ToString() を許容するか判断(ここではJValueのみ厳密) + // if (token is JValue jv) + // { + // // 文字列ならそのまま + // if (jv.Type == JTokenType.String) + // { + // result = (string)jv.Value; + // return true; + // } + + // // 数値・真偽・Null などは ToString() で返す設計でもOK + // // ここでは文字列のみ厳密に true とします(お好みで分岐調整) + // return false; + // } + // return false; + // } + + /// + /// 符号付き整数(Int64)で取得(intの範囲超えも想定)。 + /// + // public static bool TryGetJsonInt64(string path, string jsonPath, out long result) + // { + // result = default; + // if (!TryGetJsonKey(path, jsonPath, out var token)) return false; + + // if (token is JValue jv) + // { + // // JValueが数値型なら long へ + // if (jv.Type == JTokenType.Integer) + // { + // // Newtonsoftは内部的に long を持つのでキャスト可能 + // try + // { + // result = Convert.ToInt64(jv.Value); + // return true; + // } + // catch + // { + // return false; + // } + // } + + // // 文字列数値を許可する場合("42" → 42) + // if (jv.Type == JTokenType.String && long.TryParse((string)jv.Value, out var parsed)) + // { + // result = parsed; + // return true; + // } + // } + // return false; + // } + + /// + /// 任意型の既定値付き取得(存在しなければ defaultValue を返す)。 + /// 例:GetJsonKeyOrDefault(path, "app.auth.retries", 3) + /// + // public static T GetJsonKeyOrDefault(string path, string jsonPath, T defaultValue) + // { + // if (!TryGetJsonKey(path, jsonPath, out var token) || token is null) + // return defaultValue; + + // try + // { + // // JToken → 任意型に変換(失敗時は既定値) + // var val = token.ToObject(); + // return val == null ? defaultValue : val; + // } + // catch + // { + // return defaultValue; + // } + // } + } +} diff --git a/IO/File/PathUxer.cs b/IO/File/PathUxer.cs new file mode 100644 index 0000000..521a0bd --- /dev/null +++ b/IO/File/PathUxer.cs @@ -0,0 +1,174 @@ +using KssSmaPlaLib.Commons; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.IO.File +{ + public static class PathUxer + { + public static string CreateFullPathEx( + string folderPath, + string fileName, + string dtPrefix = "", + string dtSuffix = "") + { + DateTime now = DateTime.Now; + + // フォルダ:未指定時はカレントフォルダ + folderPath = string.IsNullOrEmpty(folderPath) ? "." : folderPath; + + // ファイル名:未指定時は"noname.txt"とする + fileName = string.IsNullOrEmpty(fileName) ? "noname.txt" : fileName; + + if (fileName.IndexOf('.') == -1) + { + // 拡張子がないとき、付与する + fileName += ".txt"; + } + + // ファイルパス + var path = Path.Combine( + Path.GetFullPath(folderPath), + (string.IsNullOrEmpty(dtPrefix) ? "" : now.ToString(dtPrefix)) + + fileName.Split('.')[0] + + (string.IsNullOrEmpty(dtSuffix) ? "" : now.ToString(dtSuffix)) + + "." + + fileName.Split('.')[1]); + + return path; + } + + public static string CombineUnixPath(params string[] parts) + { + return string.Join("/", parts.Select(p => p.Trim('/'))); + } + + + /// + /// ファイル名(パス不可)を受け取り、存在すればフルパスを返す。 + /// 探索順:①このDLLのフォルダ → ②エントリEXEのフォルダ → ③AppDomain.BaseDirectory + /// (C)昼コパソフトウェア㈱ + /// + /// パスではないファイル名のみ(例: "config.json")。 + /// 見つかったファイルのフルパス。未発見時は null。 + /// 発見した場合 true、未発見の場合 false。 + // public static bool TryResolveInAssemblyFolder(string fileName, out string fullPath) + // { + // fullPath = null; + + // // --- ガード:パスを排除(「ファイル名のみ」の前提を厳守) --- + // if (string.IsNullOrWhiteSpace(fileName)) + // return false; + + // // ディレクトリ成分が入っていたら拒否 + // if (!string.Equals(fileName, Path.GetFileName(fileName), StringComparison.Ordinal)) + // return false; + + // // 予約文字の簡易チェック(Windows前提の一般的なNG文字) + // foreach (var ch in Path.GetInvalidFileNameChars()) + // { + // if (fileName.IndexOf(ch) >= 0) + // return false; + // } + + // // --- ① このメソッドを含むライブラリ(DLL)のフォルダ --- + // var dllFolder = SafeGetDirectoryName(SafeGetAssemblyLocation(Assembly.GetExecutingAssembly())); + // if (TryCombineAndExists(dllFolder, fileName, out fullPath)) + // return true; + + // // --- ② エントリアセンブリ(EXE)のフォルダ --- + // string exeFolder = null; + // var entry = Assembly.GetEntryAssembly(); + // if (entry != null) + // { + // exeFolder = SafeGetDirectoryName(SafeGetAssemblyLocation(entry)); + // } + // else + // { + // // サービスや特殊ホストで EntryAssembly が null の場合のフォールバック + // try + // { + // exeFolder = SafeGetDirectoryName(Process.GetCurrentProcess()?.MainModule?.FileName); + // } + // catch + // { + // // 取れないケースもあるので無理せず次へ + // exeFolder = null; + // } + // } + + // if (TryCombineAndExists(exeFolder, fileName, out fullPath)) + // return true; + + // // --- ③ AppDomain 基準(ClickOnce等のケースでも安定) --- + // var baseDir = AppDomain.CurrentDomain?.BaseDirectory; + // if (TryCombineAndExists(baseDir, fileName, out fullPath)) + // return true; + + // // --- 未発見 --- + // fullPath = null; + // return false; + // } + + /// + /// 指定フォルダに fileName を結合し、存在すれば out に設定して true。 + /// + // private static bool TryCombineAndExists(string folder, string fileName, out string path) + // { + // path = null; + // if (string.IsNullOrWhiteSpace(folder)) + // return false; + + // try + // { + // var candidate = Path.Combine(folder, fileName); + // if (System.IO.File.Exists(candidate)) + // { + // path = candidate; + // return true; + // } + // } + // catch + // { + // // パス結合や存在確認で例外が出てもスキップして未発見扱い + // } + // return false; + // } + + /// + /// Assembly.Location を安全に取得(Reflection-only / 影響ケースを考慮)。 + /// + // private static string SafeGetAssemblyLocation(Assembly asm) + // { + // try + // { + // return asm?.Location; + // } + // catch + // { + // return null; + // } + // } + + /// + /// DirectoryName を安全に取得。 + /// + // private static string SafeGetDirectoryName(string path) + // { + // try + // { + // return string.IsNullOrEmpty(path) ? null : Path.GetDirectoryName(path); + // } + // catch + // { + // return null; + // } + // } + } +} diff --git a/Inbox/ProcessUxer.cs b/Inbox/ProcessUxer.cs new file mode 100644 index 0000000..2006d16 --- /dev/null +++ b/Inbox/ProcessUxer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace KssSmaPlaLib.Inbox +{ + public static class ProcessUxer + { + /// + /// EXEの同期起動(終了待ち) + /// + /// 実行するEXEパス + /// 起動時引数 + /// 終了コード + /// メッセージ + /// 成否 + public static bool TryRunExeSync(string exePath, string arguments, out int retCode, out string resultMessage) + { + resultMessage = string.Empty; + retCode = -1; + + // StartInfoで起動オプションを細かく制御 + var startInfo = new ProcessStartInfo + { + FileName = exePath, + Arguments = arguments, + CreateNoWindow = true, // 背後でこっそり動かす(UXの邪魔をしない) + UseShellExecute = false, // Win32 API直叩きに近い挙動(INI派ならこっち!) + RedirectStandardError = true, // エラー出力を捕まえる準備 + RedirectStandardOutput = true, // 標準出力を捕まえる準備 + WorkingDirectory = Path.GetDirectoryName(exePath), // 作業ディレクトリを切り替える(EXE格納フォルダ) + }; + + using var process = Process.Start(startInfo); + if (process == null) + { + resultMessage = "起動(Process.Start())に失敗しました。"; + return false; + } + + // 【ここが心臓部】完全終了を待つ + process.WaitForExit(); + + // 終了コードを返して、呼び出し側で「驚きのリカバリ」をさせる + retCode = process.ExitCode; + return true; + } + } +} diff --git a/Ini/IniHelper.cs b/Ini/IniHelper.cs new file mode 100644 index 0000000..ec051b0 --- /dev/null +++ b/Ini/IniHelper.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Ini +{ + public static class IniHelper + { + private const string INI_PATH = @".\Settings.ini"; + + // Win32 API宣言 + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString( + string lpAppName, + string lpKeyName, + string lpDefault, + StringBuilder lpReturnedString, + int nSize, + string lpFileName); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern bool WritePrivateProfileString( + string lpAppName, + string lpKeyName, + string lpString, + string lpFileName); + + /// + /// INIファイルから値を読み込み(セクション+キー指定) + /// + public static string ReadValue(string? iniPath, string section, string key, string defaultValue = "") + { + if (string.IsNullOrEmpty(iniPath)) iniPath = INI_PATH; + var sb = new StringBuilder(1024); + GetPrivateProfileString(section, key, defaultValue, sb, sb.Capacity, iniPath); + var str = sb.ToString(); + str = string.IsNullOrEmpty(str) ? defaultValue : str; + return str; + } + + /// + /// INIファイルに値を書き込み(セクション+キー+値指定) + /// + public static bool WriteValue(string iniPath, string section, string key, string value) + { + if (string.IsNullOrEmpty(iniPath)) iniPath = INI_PATH; + return WritePrivateProfileString(section, key, value, iniPath); + } + } +} diff --git a/Ini/IniReaderUxer.cs b/Ini/IniReaderUxer.cs new file mode 100644 index 0000000..c4ae7a0 --- /dev/null +++ b/Ini/IniReaderUxer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using KssSmaPlaLib.Commons; + +namespace KssSmaPlaLib.Ini +{ + /// + /// INIリーダー + /// + public static class IniReaderUxer + { + /// + /// 指定セクションのINI値読み込み + /// + /// INIパス + /// セクション + /// 指定のディクショナリ + public static Dictionary ReadSection(string path, string section) + { + var result = new Dictionary(); + var lines = File.ReadAllLines(path); + bool inSection = false; + + foreach (var line in lines) + { + var trimmed = line.Trim(); + + // コメント行は飛ばす + if (trimmed.StartsWith(";")) continue; + + if (trimmed.StartsWith("[") && trimmed.EndsWith("]")) + inSection = trimmed == $"[{section}]"; + else if (inSection && trimmed.Contains("=")) + { + var index = trimmed.IndexOf('='); + if (index > 0) + { + var key = trimmed.Substring(0, index).Trim(); + var value = trimmed.Substring(index + 1).Trim(); + result[key] = value; + } + } + } + return result; + } + // public static bool IsEmptyByIniDic(Dictionary iniDic,string key) + // { + // return string.IsNullOrEmpty(DictionaryUxer.GetValueOrDefault(iniDic, key, string.Empty)); + // } + } +} diff --git a/Ini/IniReferenceResolveUxer.cs b/Ini/IniReferenceResolveUxer.cs new file mode 100644 index 0000000..f479f3c --- /dev/null +++ b/Ini/IniReferenceResolveUxer.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KssSmaPlaLib.Ini +{ + public static class IniReferenceResolveUxer + { + // public static Dictionary ReadSectionWithReference(string path, string section, int maxDepth = 3) + // { + // var allLines = File.ReadAllLines(path); + // var allSections = ParseAllSections(allLines); + // var result = new Dictionary(); + + // if (!allSections.ContainsKey(section)) return result; + + // foreach (var kvp in allSections[section]) + // { + // result[kvp.Key] = ResolveValue(allSections, kvp.Value, maxDepth); + // } + + // return result; + // } + + // private static Dictionary> ParseAllSections(string[] lines) + // { + // var sections = new Dictionary>(); + // string currentSection = null; + + // foreach (var line in lines) + // { + // var trimmed = line.Trim(); + // if (trimmed.StartsWith(";") || string.IsNullOrEmpty(trimmed)) continue; + + // if (trimmed.StartsWith("[") && trimmed.EndsWith("]")) + // { + // currentSection = trimmed.Substring(1, trimmed.Length - 2); + // sections[currentSection] = new Dictionary(); + // } + // else if (currentSection != null && trimmed.Contains("=")) + // { + // var index = trimmed.IndexOf('='); + // var key = trimmed.Substring(0, index).Trim(); + // var value = trimmed.Substring(index + 1).Trim(); + // sections[currentSection][key] = value; + // } + // } + + // return sections; + // } + + private static string ResolveValue(Dictionary> sections, string value, int depth) + { + if (depth <= 0 || string.IsNullOrEmpty(value)) return value; + + var match = System.Text.RegularExpressions.Regex.Match(value, @"^\{\[(.+?)\](.+?)\}$"); + if (match.Success) + { + var refSection = match.Groups[1].Value; + var refKey = match.Groups[2].Value; + + if (sections.ContainsKey(refSection) && sections[refSection].ContainsKey(refKey)) + { + var nextValue = sections[refSection][refKey]; + return ResolveValue(sections, nextValue, depth - 1); + } + } + + return value; + } + } +} diff --git a/KssSmaPlaCoreLib.csproj b/KssSmaPlaCoreLib.csproj new file mode 100644 index 0000000..9829319 --- /dev/null +++ b/KssSmaPlaCoreLib.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + diff --git a/KssSmaPlaCoreLib.sln b/KssSmaPlaCoreLib.sln new file mode 100644 index 0000000..428abda --- /dev/null +++ b/KssSmaPlaCoreLib.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36908.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KssSmaPlaCoreLib", "KssSmaPlaCoreLib.csproj", "{680CA95F-659F-DE7C-BF8A-20EFEA388670}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {680CA95F-659F-DE7C-BF8A-20EFEA388670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {680CA95F-659F-DE7C-BF8A-20EFEA388670}.Debug|Any CPU.Build.0 = Debug|Any CPU + {680CA95F-659F-DE7C-BF8A-20EFEA388670}.Release|Any CPU.ActiveCfg = Release|Any CPU + {680CA95F-659F-DE7C-BF8A-20EFEA388670}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {46965DA1-5866-490A-9CE6-A0D86383B664} + EndGlobalSection +EndGlobal