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