(PowerShell) ドラッグ&ドロップしたフォルダ内のショートカットを置換
概要
やりたいこと:
ドラッグアンドドロップしたフォルダを再帰的に走査してショートカットファイルのパスを置換。
必要な準備:
「*.ps1」スクリプトを動かす設定。
重要:
動作確認はまだ。ツギハギだし言語知識不足なのでおそらく動かない。
コード
#=============================================================================== # 定数 #=============================================================================== # 置換前ディレクトリ Set-Variable -Name replacingDirBef -Value 'path\to\before' -Option Constant # 置換後ディレクトリ Set-Variable -Name replacingDirAft -Value 'path\to\after' -Option Constant #=============================================================================== # パス置換関数 #=============================================================================== function ReplacePath([string] $path) { $path = $path -ireplace $replacingDirBef, $replacingDirAft return $path } #=============================================================================== # メイン #=============================================================================== Write-Output "処理開始" # ショートカット編集用WSHオブジェクト $wsShell = New-Object -ComObject WScript.Shell # 引数すべて処理 foreach($arg in $Args) { # ショートカットファイル一覧の取得 # サブディレクトリを含む # 隠しファイルや読み取り専用ファイルは対象外 $shortcuts = Get-ChildItem -LiteralPath $arg *.lnk -Recurse # 各ショートカットの置換 $shortcuts | ForEach-Object { # ショートカット作成 $link = $wsShell.CreateShortcut($_.FullName) # 置換後のパス生成 $newTargetPath = ReplacePath($link.TargetPath) # 置換前後で変更がない場合はメッセージ出力して次のファイルの処理へ if ($link.TargetPath -eq $newTargetPath) { Write-Output ("置換不要: " + $_.FullName) return } # ショートカットの参照先と作業フォルダを更新 $link.TargetPath = $newTargetPath $link.WorkingDirectory = ReplacePath($link.WorkingDirectory) $link.Save(); Write-Output ("置換実施: " + $_.FullName) } } Write-Output "処理完了" Read-Host "press any key to close."
(C#) 変更を検知して再読み込みする設定ファイルクラス
概要
目標は以下のとおり。とりあえず狙った感じに動いた。
- 設定ファイル用クラスをつくる
- ファイルが変更されたことを検知して再読み込みする
- 再読み込みに失敗したら前回値を保持する
- 再読み込みするかどうか自体も設定値とする
環境
.NET Framework | 4.7.2 |
C# | 4.7 |
OS | Windows 10 1903 |
ソースコード
main
using System; using System.Threading; namespace ReloadableConfiguration { class Program { static void Main(string[] args) { // SimpleConfigの使い方サンプル UseSimpleConfig(); // ReloadableConfigの使い方サンプル Console.WriteLine(); UseReloadableConfig(); // キー入力待ち Console.ReadKey(); } /// <summary> /// SimpleConfigの使い方サンプル /// </summary> static void UseSimpleConfig() { // // // WriteTitle("(a-1) 初回アクセス時に読み込み"); // ファイルパスを指定 SimpleConfig.FilePath = @"SampleFile\Sample1.xml"; // 読み込んだ内容を出力 Console.WriteLine("Name : " + SimpleConfig.Config.Name); // 設定内容を出力 Console.WriteLine("--------------------------------"); Console.WriteLine(SimpleConfig.Config.ToXmlStirng()); // // // WriteTitle("(a-2) 明示的読み込み"); // ファイルパスを指定 SimpleConfig.FilePath = @"SampleFile\Sample2.xml"; // 明示的に読み込み、成否を出力 bool isLoadSuccess = SimpleConfig.Load(); Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗")); // 読み込んだ内容を出力 Console.WriteLine("Name : " + SimpleConfig.Config.Name); // // // WriteTitle("(a-3) 明示的読み込み(失敗)"); // 存在しないファイルパスを指定 SimpleConfig.FilePath = @"SampleFile\InvalidFilePath.xml"; // 明示的に読み込み、成否を出力 isLoadSuccess = SimpleConfig.Load(); Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗")); // 読み込んだ内容を出力 // 成功時の内容が保持される Console.WriteLine("Name : " + SimpleConfig.Config.Name); } /// <summary> /// ReloadableConfigの使い方サンプル /// </summary> static void UseReloadableConfig() { // // // WriteTitle("(b-1) 初回アクセス時に読み込み"); // ファイルパスを指定 ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReload1.xml"; // 読み込んだ内容を出力 Console.WriteLine("Name : " + ReloadableConfig.Config.Name); // 設定内容を出力 Console.WriteLine("--------------------------------"); Console.WriteLine(ReloadableConfig.Config.ToXmlStirng()); // 設定値の"名前"について値を変更し、ファイルを生成 // -> 検知されないことを期待 ReloadableConfig.Config.Name = "expect : no detect 1"; XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true); Thread.Sleep(500); // // // WriteTitle("(b-2) 明示的読み込み"); // ファイルパスを指定 ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReload2.xml"; // 明示的に読み込み、成否を出力 bool isLoadSuccess = ReloadableConfig.Load(); Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗")); // 読み込んだ内容を出力 Console.WriteLine("Name : " + ReloadableConfig.Config.Name); // // // WriteTitle("(b-3) 明示的読み込み(失敗)"); // 存在しないファイルパスを指定 ReloadableConfig.FilePath = @"SampleFileReloadable\InvalidFilePath.xml"; // 明示的に読み込み、成否を出力 isLoadSuccess = ReloadableConfig.Load(); Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗")); // 読み込んだ内容を出力 // 成功時の内容が保持される Console.WriteLine("Name : " + ReloadableConfig.Config.Name); // // // WriteTitle("(b-4) 生成の検知"); // ファイルパスを指定 ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReloadEnabled1.xml"; // 明示的に読み込み、成否を出力 isLoadSuccess = ReloadableConfig.Load(); Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗")); // 読み込んだ内容を出力 Console.WriteLine("Name : " + ReloadableConfig.Config.Name); Console.WriteLine("EnableFileWatch : " + ReloadableConfig.Config.EnableFileWatch); // ファイルを削除 System.IO.File.Delete(ReloadableConfig.FilePath); Thread.Sleep(500); // 設定値の"名前"について値を変更し、ファイルを生成 // -> この時点でCreatedイベントハンドラが動作することを期待 // -> Created がハンドルされた ReloadableConfig.Config.Name = "sample reloadable enabled - created"; XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true); Thread.Sleep(500); // // // WriteTitle("(b-5) 更新の検知"); // 設定値の"名前"について値を変更し、ファイルを生成 // -> この時点でChangedイベントハンドラが動作することを期待 // -> Changed -> Changed がハンドルされた ReloadableConfig.Config.Name = "sample reloadable enabled - changed"; XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true); Thread.Sleep(500); // // // WriteTitle("(b-6) 名前変更の検知"); // 設定ファイルをリネームし、監視対象から外す // -> 検知されないことを期待 // -> Changed がハンドルされた if (System.IO.File.Exists(ReloadableConfig.FilePath + @".AfterRename")) { System.IO.File.Delete(ReloadableConfig.FilePath + @".AfterRename"); } System.IO.File.Move(ReloadableConfig.FilePath, ReloadableConfig.FilePath + @".AfterRename"); Thread.Sleep(500); // 検知されたことが分かるように設定値の"名前"について値を変更し、ファイルを生成 ReloadableConfig.Config.Name = "sample reloadable enabled - renamed"; XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath + @".BeforeRename", true); // 設定ファイルをリネームし、監視対象にする // -> この時点でChangedイベントハンドラが動作することを期待 // -> Changed がハンドルされた System.IO.File.Move(ReloadableConfig.FilePath + @".BeforeRename", ReloadableConfig.FilePath); Thread.Sleep(500); // // // WriteTitle("(b-7) ファイル監視状態の変更"); // ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReloadDisabled1.xml"; ReloadableConfig.Load(); // 設定値の"名前"について値を変更し、ファイルを生成 // -> 検知されないことを期待 // -> 検知されない ReloadableConfig.Config.Name = "reload is disabled??"; XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true); Thread.Sleep(500); Console.WriteLine(Environment.NewLine + "end."); // finalize ReloadableConfig.FinalizeFileWatch(); } static void WriteTitle(string title) { Console.WriteLine(); Console.WriteLine("================================"); Console.WriteLine($" {title}"); Console.WriteLine("================================"); } } }
(比較的)シンプルな設定値クラス
目標その1。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using System.Xml.Serialization; namespace ReloadableConfiguration { /// <summary> /// 設定 /// </summary> [Serializable] public class SimpleConfig { #region inner class [Serializable] public class BoxVolume { [XmlAttribute] public int Value { get; set; } [XmlAttribute] public string Unit { get; set; } } public enum BoxItemType { Undefined, Fruit, Fish } [Serializable] public class BoxItem { [XmlAttribute] public BoxItemType BoxItemType { get; set; } [XmlElement] public string Name { get; set; } [XmlElement] public int Count { get; set; } } #endregion #region クラスフィールド/クラスプロパティ /// <summary> /// 設定 /// </summary> private static SimpleConfig config; /// <summary> /// 設定 /// </summary> public static SimpleConfig Config { get { if (config == null) { Load(); } return config; } } /// <summary> /// ファイルパス /// </summary> public static string FilePath { get; set; } #endregion [XmlAttribute] public string Name { get; set; } public BoxVolume Volume { get; set; } public List<BoxItem> Items { get; set; } #region インスタンスメソッド /// <summary> /// コンストラクタ /// </summary> private SimpleConfig() { } /// <summary> /// XML文字列化 /// </summary> /// <returns>XML文字列化</returns> public string ToXmlStirng() { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(config.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, config); xmlString = writer.ToString(); } } catch { } return xmlString; } #endregion #region クラスメソッド /// <summary> /// 読込 /// </summary> /// <returns>成否</returns> public static bool Load() { bool result = true; try { XmlSerializer serializer = new XmlSerializer(typeof(SimpleConfig)); using (StreamReader reader = new StreamReader(FilePath)) { config = (SimpleConfig)serializer.Deserialize(reader); } } catch { result = false; } return result; } #endregion } }
再読み込みする設定値クラス
目標その2~4。
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using System.Xml.Serialization; using System.Windows; namespace ReloadableConfiguration { /// <summary> /// 設定 /// </summary> [Serializable] public class ReloadableConfig { #region inner class [Serializable] public class BoxVolume { [XmlAttribute] public int Value { get; set; } [XmlAttribute] public string Unit { get; set; } } public enum BoxItemType { Undefined, Fruit, Fish } [Serializable] public class BoxItem { [XmlAttribute] public BoxItemType BoxItemType { get; set; } [XmlElement] public string Name { get; set; } [XmlElement] public int Count { get; set; } } #endregion #region クラスフィールド/クラスプロパティ /// <summary> /// 設定 /// </summary> private static ReloadableConfig config; /// <summary> /// 設定 /// </summary> public static ReloadableConfig Config { get { if (config == null) { Load(); } return config; } } /// <summary> /// ファイルパス /// </summary> public static string FilePath { get; set; } /// <summary> /// ファイル監視 /// </summary> private static FileSystemWatcher watcher; #endregion /// <summary> /// ファイル監視有効化状態 /// </summary> public Nullable<bool> EnableFileWatch { get; set; } = null; [XmlAttribute] public string Name { get; set; } public BoxVolume Volume { get; set; } public List<BoxItem> Items { get; set; } #region インスタンスメソッド /// <summary> /// コンストラクタ /// </summary> private ReloadableConfig() { } /// <summary> /// XML文字列化 /// </summary> /// <returns>XML文字列化</returns> public string ToXmlStirng() { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(config.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, config); xmlString = writer.ToString(); } } catch { } return xmlString; } #endregion #region クラスメソッド /// <summary> /// 読込 /// </summary> /// <returns>成否</returns> public static bool Load() { bool result = true; try { XmlSerializer serializer = new XmlSerializer(typeof(ReloadableConfig)); using (StreamReader reader = new StreamReader(FilePath)) { config = (ReloadableConfig)serializer.Deserialize(reader); } // ファイル監視を設定 ConfigureFileWatch(); } catch { result = false; } return result; } /// <summary> /// ファイル監視設定 /// </summary> private static void ConfigureFileWatch() { // インスタンス生成が未だの場合、インスタンス生成と初期化を実施 if (watcher == null) { watcher = new FileSystemWatcher(); watcher.Created += Watcher_Created; watcher.Changed += Watcher_Changed; watcher.Renamed += Watcher_Renamed; watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName; watcher.IncludeSubdirectories = false; } // 一時的にファイル監視を無効化 watcher.EnableRaisingEvents = false; // 監視対象ファイルパスを設定 watcher.Path = Path.GetDirectoryName(FilePath); watcher.Filter = Path.GetFileName(FilePath); // 設定値に従いファイル監視の有効/無効を変更 watcher.EnableRaisingEvents = config.EnableFileWatch.GetValueOrDefault(false); } /// <summary> /// ファイル監視終了 /// </summary> public static void FinalizeFileWatch() { watcher?.Dispose(); watcher = null; } /// <summary> /// ファイル監視イベント(作成) /// </summary> /// <param name="sender">イベント元</param> /// <param name="e">イベント引数</param> private static void Watcher_Created(object sender, FileSystemEventArgs e) { Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()"); Console.WriteLine(" " + e.ChangeType); Console.WriteLine(" " + e.FullPath); if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath) { Load(); Console.WriteLine(" reloaded!! : " + config.Name); } } /// <summary> /// ファイル監視イベント(変更) /// </summary> /// <param name="sender">イベント元</param> /// <param name="e">イベント引数</param> private static void Watcher_Changed(object sender, FileSystemEventArgs e) { Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()"); Console.WriteLine(" " + e.ChangeType); Console.WriteLine(" " + e.FullPath); if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath) { Load(); Console.WriteLine(" reloaded!! : " + config.Name); } } /// <summary> /// ファイル監視イベント(リネーム) /// </summary> /// <param name="sender">イベント元</param> /// <param name="e">イベント引数</param> private static void Watcher_Renamed(object sender, RenamedEventArgs e) { Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()"); Console.WriteLine(" " + e.ChangeType); Console.WriteLine(" " + e.FullPath); if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath) { Load(); Console.WriteLine(" reloaded!! : " + config.Name); } } #endregion } }
XMLファイル生成クラス(動作確認用のおまけ)
using System.IO; using System.Xml.Serialization; namespace ReloadableConfiguration { /// <summary> /// XMLファイルヘルパー /// </summary> public static class XmlFileHelper { /// <summary> /// XML文字列生成 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="filepath">ファイルパス</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static void GenerateXmlFile(object obj, string filepath, bool enableException = false) { try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StreamWriter writer = new StreamWriter(filepath)) { serializer.Serialize(writer, obj); } } catch { if (enableException) { throw; } } } } }
設定ファイル
「シンプルな設定値クラス」用
SampleFile/Sample1.xml
<SimpleConfig Name="sample 1"> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </SimpleConfig>
SampleFile/Sample2.xml
<SimpleConfig Name="sample 2"> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </SimpleConfig>
「再読み込みする設定値クラス」用
SampleFileReloadable/SampleReload1.xml
<ReloadableConfig Name="sample reloadable 1"> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>3</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>6</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>9</Count> </BoxItem> </Items> </ReloadableConfig>
SampleFileReloadable/SampleReload2.xml
<ReloadableConfig Name="sample reloadable 2"> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </ReloadableConfig>
SampleFileReloadable/SampleDisabled1.xml
<ReloadableConfig Name="sample reloadable disabled 1"> <EnableFileWatch>false</EnableFileWatch> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </ReloadableConfig>
SampleFileReloadable/SampleReloadEnabled1.xml
<ReloadableConfig Name="sample reloadable enabled 1"> <EnableFileWatch>true</EnableFileWatch> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </ReloadableConfig>
実行結果
「シンプルな設定値クラス」分
================================ (a-1) 初回アクセス時に読み込み ================================ Name : sample 1 -------------------------------- <?xml version="1.0" encoding="utf-16"?> <SimpleConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="sample 1"> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>4</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </SimpleConfig> ================================ (a-2) 明示的読み込み ================================ 読込成功 Name : sample 2 ================================ (a-3) 明示的読み込み(失敗) ================================ 読込失敗 Name : sample 2
「再読み込みする設定値クラス」分
================================ (b-1) 初回アクセス時に読み込み ================================ Name : sample reloadable 1 -------------------------------- <?xml version="1.0" encoding="utf-16"?> <ReloadableConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="sample reloadable 1"> <EnableFileWatch xsi:nil="true" /> <Volume Value="10" Unit="kL" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>3</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>6</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>9</Count> </BoxItem> </Items> </ReloadableConfig> ================================ (b-2) 明示的読み込み ================================ 読込成功 Name : sample reloadable 2 ================================ (b-3) 明示的読み込み(失敗) ================================ 読込失敗 Name : sample reloadable 2 ================================ (b-4) 生成の検知 ================================ 読込成功 Name : sample reloadable enabled 1 EnableFileWatch : True Watcher_Created() Created SampleFileReloadable\SampleReloadEnabled1.xml reloaded!! : sample reloadable enabled - created ================================ (b-5) 更新の検知 ================================ Watcher_Changed() Changed SampleFileReloadable\SampleReloadEnabled1.xml reloaded!! : sample reloadable enabled - changed ================================ (b-6) 名前変更の検知 ================================ Watcher_Renamed() Renamed SampleFileReloadable\SampleReloadEnabled1.xml.AfterRename Watcher_Renamed() Renamed SampleFileReloadable\SampleReloadEnabled1.xml reloaded!! : sample reloadable enabled - renamed ================================ (b-7) ファイル監視状態の変更 ================================ end.
所感
軽い気持ちで作り始めたけど結構かかってしまった。それなりの量なので動作確認をちゃんとテストコードに起こした方がいい。けどもう眠いので諦める。。説明つける気力もない。。反省点としては「設定値」と「ファイル監視」をもっと疎にすればよかった…かな?大して変わらないかも。
(C#) シリアライズ可能なオブジェクトを階層化して表示
概要
前々回と前回(以下)からの続き。
シリアライズ可能なオブジェクトをXML文字列にしたり、さらにそれをXDocumentを使って解析して、見やすい形で表示したりしてみた。
バージョン
.NET Framework | 4.7.2 |
C# | 4.7 |
目次
ソースコード
StringBuilder拡張クラス
表示用に拡張メソッド用意しただけ。今回の本題とはあまり関係ない。ほぼ前回からの流用。
using System; using System.Globalization; using System.Text; namespace ObjectToXmlString { /// <summary> /// StringBuilder拡張クラス /// </summary> public static class StringBuilderExtensions { /// <summary> /// 書式指定文字列追加 /// 改行付き /// </summary> /// <param name="stringBuilder">StringBuilder</param> /// <param name="format">書式</param> /// <param name="args">可変長引数</param> public static void AppendFormatLine(this StringBuilder stringBuilder, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}", string.Format(format, args), Environment.NewLine); } /// <summary> /// 書式指定文字列追加 /// 改行および先頭空白インデント付き /// </summary> /// <param name="stringBuilder">StringBuilder</param> /// <param name="indent">インデント</param> /// <param name="format">書式</param> /// <param name="args">可変長引数</param> public static void AppendFormatLine(this StringBuilder stringBuilder, int indent, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}{2}", new string(' ', indent), string.Format(format, args), Environment.NewLine); } } }
XML文字列化クラス
オブジェクトをXML文字列にするだけのクラス。前回からの流用。
using System.IO; using System.Xml.Serialization; namespace ObjectToXmlString { /// <summary> /// XML文字列ヘルパー /// </summary> public static class XmlStringHelper { /// <summary> /// XML文字列生成 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStirng(object obj, bool enableException = false) { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, obj); xmlString = writer.ToString(); } } catch { if (enableException) { throw; } } return xmlString; } } }
加工して表示するクラス
今回の本題。ただし説明するより実行結果見た方が早そうなので説明なし。
using System; using System.Linq; using System.Text; using System.Xml.Linq; using System.Collections.Generic; namespace ObjectToXmlString { /// <summary> /// ダンプヘルパー /// </summary> public static class DumpHelper { /// <summary> /// XML文字列生成(軽量版) /// XML宣言とルート要素のXMLNS名前空間の属性を削除したXML文字列化 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStirngSlim(object obj, bool enableException = false) { var xmlString = string.Empty; try { XDocument xDoc = XDocument.Parse( XmlStringHelper.GenerateXmlStirng(obj, enableException)); xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); xmlString = xDoc.ToString(); } catch { if (enableException) { throw; } } return xmlString; } /// <summary> /// XML文字列生成(1行版) /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="newLineReplacingString">改行置換文字列</param> /// <param name="enableException">例外送出有無</param> /// <returns>XML文字列</returns> public static string GenerateXmlStringOneLine(object obj, string newLineReplacingString, bool enableException = false) { var xmlString = string.Empty; try { xmlString = GenerateXmlStirngSlim(obj, enableException) .Replace(Environment.NewLine, newLineReplacingString); } catch { if (enableException) { throw; } } return xmlString; } /// <summary> /// 階層化文字列生成 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="enableException">例外送出有無</param> /// <returns>階層化文字列</returns> public static string GenerateLayeredString(object obj, bool enableException = false) { var sb = new StringBuilder(); try { var xmlString = GenerateXmlStirngSlim(obj, enableException); XDocument xDoc = XDocument.Parse(xmlString); // 階層化表示用の文字列を生成 foreach (var item in xDoc.Elements()) { sb.Append(GenerateLayeredStringOfElement(0, item)); } } catch { if (enableException) { throw; } sb.Clear(); } return sb.ToString(); } /// <summary> /// XML要素の階層化文字列生成 /// 再帰呼び出し用 /// </summary> /// <param name="indent">インデント</param> /// <param name="xElm">XML要素</param> /// <param name="xElmIndex">XML要素の配列インデックス</param> /// <returns階層化文字列</returns> private static string GenerateLayeredStringOfElement(int indent, XElement xElm, int xElmIndex = -1) { var sb = new StringBuilder(); // 表示用書式の生成 // 配下のXML属性およびXML要素について最長のローカル名長を取得し // "key=value"のうちkey部分の桁数合わせに使用 var elementsNoChilds = xElm.Elements().Where(e => !e.HasAttributes && !e.HasElements); var maxLocalNameLength = new[] { xElm.HasAttributes ? xElm.Attributes().Max(a => a.Name.LocalName.Length) : 0, elementsNoChilds.Any() ? elementsNoChilds.Max(e => e.Name.LocalName.Length) : 0, }.Max(); var keyValueFormat = $"{{0,-{maxLocalNameLength}}} = {{1}}"; // 配下にXML属性あるいはXML要素をもつ場合、自身のローカル名を表示 if (xElm.HasAttributes || xElm.HasElements) { // 自身が配列の構成要素の場合、末尾にインデックスを付与 if (xElmIndex >= 0) { sb.AppendFormatLine(indent, "{0}[{1}]", xElm.Name.LocalName, xElmIndex); } else { sb.AppendFormatLine(indent, xElm.Name.LocalName); } } // XML属性の表示 foreach (var item in xElm.Attributes()) { sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value); } // 配下のXML要素が配列か判定 var areChildArray = (xElm.Elements().Count() > 1 && xElm.Elements().All(e => e.Name == xElm.Elements().First().Name)); // XML要素の表示 foreach (var (item, index) in xElm.Elements().Select((item, index) => (item, index))) { // 配下にXML属性もXML要素も持たない場合、"key=value"形式で表示 if (!item.HasAttributes && !item.HasElements) { sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value); continue; } // 配下のXML要素が配列の場合、配列インデックスを渡す if (areChildArray) { sb.Append(GenerateLayeredStringOfElement(indent + 2, item, index)); } else { sb.Append(GenerateLayeredStringOfElement(indent + 2, item)); } } return sb.ToString(); } } }
呼び出し側コード
サンプルとして使うオブジェクトと、前述したクラスを呼び出すコード。
using System; using System.Xml.Serialization; using System.Collections.Generic; namespace ObjectToXmlString { // // 文字列化対象 // [Serializable] public class Box { public class BoxVolume { [XmlAttribute] public int Value { get; set; } = 10; [XmlAttribute] public string Unit { get; set; } = "L"; } public enum BoxItemType { Undefined, Fruit, Fish } public class BoxItem { [XmlAttribute] public BoxItemType BoxItemType { get; set; } public string Name { get; set; } public int Count { get; set; } } [XmlAttribute] public string Name { get; set; } = "box a"; public BoxVolume Volume { get; set; } = new BoxVolume(); public List<BoxItem> Items { get; } = new List<BoxItem>() { new BoxItem(){ BoxItemType = BoxItemType.Fruit, Name = "Orange", Count = 1 }, new BoxItem(){ BoxItemType = BoxItemType.Fish, Name = "Tuna", Count = 2 }, new BoxItem(){ BoxItemType = BoxItemType.Undefined, Name = null, Count = 0 }, }; } // // 実行コード // class Program { static void Main(string[] args) { Box boxA = new Box(); // // XML文字列 // WriteResult("XML文字列", XmlStringHelper.GenerateXmlStirng(boxA)); // // 加工して表示 // WriteResult("XML文字列(軽量版)", DumpHelper.GenerateXmlStirngSlim(boxA)); WriteResult("XML文字列生成(1行版)", DumpHelper.GenerateXmlStringOneLine(boxA, "#")); WriteResult("階層化文字列", DumpHelper.GenerateLayeredString(boxA)); Console.ReadKey(); } static void WriteResult(string title, string result) { Console.WriteLine("----------------"); Console.WriteLine($" {title}"); Console.WriteLine("----------------"); Console.WriteLine($"{result}"); Console.WriteLine(); } } }
実行結果
-------------------------------- XML文字列 -------------------------------- <?xml version="1.0" encoding="utf-16"?> <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </Box> -------------------------------- XML文字列(軽量版) -------------------------------- <Box Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem BoxItemType="Fruit"> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem BoxItemType="Fish"> <Name>Tuna</Name> <Count>2</Count> </BoxItem> <BoxItem BoxItemType="Undefined"> <Count>0</Count> </BoxItem> </Items> </Box> -------------------------------- XML文字列生成(1行版) -------------------------------- <Box Name="box a"># <Volume Value="10" Unit="L" /># <Items># <BoxItem BoxItemType="Fruit"># <Name>Orange</Name># <Count>1</Count># </BoxItem># <BoxItem BoxItemType="Fish"># <Name>Tuna</Name># <Count>2</Count># </BoxItem># <BoxItem BoxItemType="Undefined"># <Count>0</Count># </BoxItem># </Items>#</Box> -------------------------------- 階層化文字列 -------------------------------- Box Name = box a Volume Value = 10 Unit = L Items BoxItem[0] BoxItemType = Fruit Name = Orange Count = 1 BoxItem[1] BoxItemType = Fish Name = Tuna Count = 2 BoxItem[2] BoxItemType = Undefined Count = 0
所感
XDocumentを使ってみるついでに見やすい階層化表示なんてやってみたけど、試験不十分なので、下手に表示されないリスク負うくらいなら、素直にXML文字列でもいいような気もする。でも夏休みの暇つぶしにはちょうどよかった。
(C#) XDocumentと戯れる
概要
前回の記事((C#) オブジェクトをXML文字列にする - scrap book)の続き。
作業中にXDocumentというものを見つけて、ざっと調べたら、簡単にXMLの操作や参照ができるクラスらしかったので、使ってみる。試したことは以下の目次参照。
バージョン
.NET Framework | 4.7.2 |
C# | 4.7 |
目次
ソースコード
XML文字列化クラス(前回の流用)
オブジェクトをXML文字列にするだけのクラス。
using System; using System.IO; using System.Xml.Serialization; namespace ObjectToXmlString { /// <summary> /// XML文字列ヘルパー /// </summary> public static class XmlStringHelper { /// <summary> /// XML文字列化 /// </summary> /// <param name="obj">オブジェクト</param> /// <param name="throwException">例外送出状態</param> /// <returns>XML文字列</returns> public static string ConvertToXmlStirng(object obj, bool throwException = false) { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, obj); xmlString = writer.ToString(); } } catch { if (throwException) { throw; } } return xmlString; } } }
StringBuilder拡張クラス
StringBuilderに用意されているメソッドが「書式指定付きで追加」か「改行付きで追加」しか無いので、両方付きのメソッドを用意しただけ。今回の主題とは何も関係ない。
using System; using System.Globalization; using System.Text; namespace ObjectToXmlString { public static class StringBuilderExtensions { public static void AppendFormatLine(this StringBuilder stringBuilder, string format, params object[] args) { stringBuilder.AppendFormat( CultureInfo.CurrentCulture, "{0}{1}", string.Format(format, args), Environment.NewLine); } } }
XDocumentお試しコード1~4
ここからが本題。
お試し1 .. XML文字列 から XDocumentインスタンス生成
まずはXML文字列からXDocumentの生成。XDocument#Parse()で可能だった。このほかにも、XDocument#Load()でファイルから読み込めたりする。
ついでにそのまま XDocument.ToString() して分かった((2)の部分)けど、 ToString() ではXML本文のみが出力されるみたい。XML宣言部は (1) で出力できる。
using System.Linq; using System.Text; using System.Xml.Linq; namespace ObjectToXmlString { public static class XDocumentTrial { /// <summary> /// XML文字列 から XDocumentインスタンス生成 /// &文字列出力 /// </summary> public static string Trial1(object obj) { var sb = new StringBuilder(); // オブジェクトをXML文字列化 var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); // XML文字列を解析 XDocument xDoc = XDocument.Parse(xmlString); // (1) sb.AppendLine("[ Declaration ]"); sb.AppendLine(xDoc.Declaration.ToString()); sb.AppendLine(); // (2) sb.AppendLine("[ ToString() ]"); sb.AppendLine(xDoc.ToString()); return sb.ToString(); } } }
お試し2 .. 属性(Attribute)や要素(Element)の表示
属性や要素の表示を試したコード。それぞれXElement.Attributes()、XElement.Elements()でコレクションとして取得できた。ほか、XElement.Annotations()で注釈(コメントのこと?)も取得できそう。
ちなみに再帰的に辿らずにRoot要素にだけ使ってみたら、リストの部分は値が繋がってしまった。これについてはお試し4でうまく表示できた。
/// <summary> /// 属性(Attribute)や要素(Element)の表示 /// </summary> public static string Trial2(object obj) { var sb = new StringBuilder(); var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); XDocument xDoc = XDocument.Parse(xmlString); // (1) 属性の出力 foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("Attributes[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); } sb.AppendLine(); // (2) 要素の出力 foreach (var (item, index) in xDoc.Root.Elements().Select((item, index) => (item, index))) { sb.AppendFormatLine("Elements[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); sb.AppendFormatLine(" HasAttributes = {0}", item.HasAttributes); sb.AppendFormatLine(" HasElements = {0}", item.HasElements); } return sb.ToString(); }
お試し3 .. 属性の削除
出力結果を見ていて、xmlns名前空間の属性が邪魔だったので、消してみた。(1)の部分。
/// <summary> /// xmlns名前空間の属性を削除 /// </summary> public static string Trial3(object obj) { var sb = new StringBuilder(); var xmlString = XmlStringHelper.ConvertToXmlStirng(obj); XDocument xDoc = XDocument.Parse(xmlString); // (1) 名前空間が"xmlns"の属性をすべて削除 xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); // 属性の出力 foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("Attributes[{0}]", index); sb.AppendFormatLine(" Name.NamespaceName = {0}", item.Name.NamespaceName); sb.AppendFormatLine(" Name.LocalName = {0}", item.Name.LocalName); sb.AppendFormatLine(" Value = {0}", item.Value); } return sb.ToString(); }
お試し4 .. 再帰的に走査して階層化表示
属性を再帰的に走査して、いい感じに階層化して表示してみた。十分なパターンで試験していないけど、おおむねいい感じ。
/// <summary> /// 再帰的に走査して階層化表示 /// </summary> public static string Trial4(object obj, bool throwException = false) { var sb = new StringBuilder(); try { var xmlString = XmlStringHelper.ConvertToXmlStirng(obj, throwException); XDocument xDoc = XDocument.Parse(xmlString); // 名前空間が"xmlns"の属性をすべて削除 xDoc.Root.Attributes() .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/") .ToList().ForEach(attr => attr.Remove()); // 再帰的に走査して階層化表示 foreach (var item in xDoc.Elements()) { sb.Append(Trial4Core(item)); } } catch { if (throwException) { throw; } sb.Clear(); } return sb.ToString(); } private static string Trial4Core(XElement xElement, int offset = 0) { var sb = new StringBuilder(); var spaces = new string(' ', offset); // Append Attributes foreach (var (item, index) in xElement.Attributes().Select((item, index) => (item, index))) { sb.AppendFormatLine("{0}Attributes[{1}]", spaces, index); sb.AppendFormatLine("{0} Name.LocalName = {1}", spaces, item.Name.LocalName); sb.AppendFormatLine("{0} Value = {1}", spaces, item.Value); } // Append Elements foreach (var (item, index) in xElement.Elements().Select((item, index) => (item, index))) { // Append Attributes and Elements recursively if (item.HasAttributes || item.HasElements) { sb.AppendFormatLine("{0}{1}", spaces, item.Name.LocalName); sb.Append(Trial4Core(item, offset + 2)); continue; } sb.AppendFormatLine("{0}Elements[{1}]", spaces, index); sb.AppendFormatLine("{0} Name.LocalName = {1}", spaces, item.Name.LocalName); sb.AppendFormatLine("{0} Value = {1}", spaces, item.Value); } return sb.ToString(); }
呼び出し側コード
using System; using System.Xml.Serialization; using System.Collections.Generic; namespace ObjectToXmlString { // // 文字列化対象 // [Serializable] public class Box { public class BoxVolume { [XmlAttribute] public int Value { get; set; } = 10; [XmlAttribute] public string Unit { get; set; } = "L"; } public class BoxItem { public string Name { get; set; } public int Count { get; set; } } [XmlAttribute] public string Name { get; set; } = "box a"; public BoxVolume Volume { get; set; } = new BoxVolume(); public List<BoxItem> Items { get; } = new List<BoxItem>() { new BoxItem(){ Name = "Orange", Count = 1 }, new BoxItem(){ Name = "Apple", Count = 2 }, new BoxItem(){ Name = null, Count = 0 }, }; } // // 実行コード // class Program { static void Main(string[] args) { Box boxA = new Box(); // // XML文字列 // WriteResult("XML文字列", XmlStringHelper.ConvertToXmlStirng(boxA)); // // XDocumentお試し // WriteResult("Trial1", XDocumentTrial.Trial1(boxA)); WriteResult("Trial2", XDocumentTrial.Trial2(boxA)); WriteResult("Trial3", XDocumentTrial.Trial3(boxA)); WriteResult("Trial4", XDocumentTrial.Trial4(boxA)); Console.ReadKey(); } static void WriteResult(string title, string result) { Console.WriteLine("----------------"); Console.WriteLine($"{title}"); Console.WriteLine("----------------"); Console.WriteLine($"{result}"); Console.WriteLine(); } } }
実行結果
---------------- XML文字列 ---------------- <?xml version="1.0" encoding="utf-16"?> <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem> <Name>Apple</Name> <Count>2</Count> </BoxItem> <BoxItem> <Count>0</Count> </BoxItem> </Items> </Box> ---------------- Trial1 ---------------- [ Declaration ] <?xml version="1.0" encoding="utf-16"?> [ ToString() ] <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a"> <Volume Value="10" Unit="L" /> <Items> <BoxItem> <Name>Orange</Name> <Count>1</Count> </BoxItem> <BoxItem> <Name>Apple</Name> <Count>2</Count> </BoxItem> <BoxItem> <Count>0</Count> </BoxItem> </Items> </Box> ---------------- Trial2 ---------------- Attributes[0] Name.NamespaceName = http://www.w3.org/2000/xmlns/ Name.LocalName = xsi Value = http://www.w3.org/2001/XMLSchema-instance Attributes[1] Name.NamespaceName = http://www.w3.org/2000/xmlns/ Name.LocalName = xsd Value = http://www.w3.org/2001/XMLSchema Attributes[2] Name.NamespaceName = Name.LocalName = Name Value = box a Elements[0] Name.NamespaceName = Name.LocalName = Volume Value = HasAttributes = True HasElements = False Elements[1] Name.NamespaceName = Name.LocalName = Items Value = Orange1Apple20 HasAttributes = False HasElements = True ---------------- Trial3 ---------------- Attributes[0] Name.NamespaceName = Name.LocalName = Name Value = box a ---------------- Trial4 ---------------- Attributes[0] Name.LocalName = Name Value = box a Volume Attributes[0] Name.LocalName = Value Value = 10 Attributes[1] Name.LocalName = Unit Value = L Items BoxItem Elements[0] Name.LocalName = Name Value = Orange Elements[1] Name.LocalName = Count Value = 1 BoxItem Elements[0] Name.LocalName = Name Value = Apple Elements[1] Name.LocalName = Count Value = 2 BoxItem Elements[0] Name.LocalName = Count Value = 0
解説?は上述したので割愛。
ところでBoxItem[2].Nameにnull入れてみたのだけど表示されない(XML文字列の時点で無い)。nullならnullと出て欲しいんだけどどうにかならないかな。。たしかNullable<int>やら自前の型なら<Name value=nil />みたいな文字列になったと思うんだけどなぁ。。記憶違いかな。。
参考(その他)
他の方法。XDocumentが使いやすそうだったので今回はパスしたけどメモ。
(C#) オブジェクトをXML文字列にする
概要
ログ出力のためにオブジェクトをXML文字列化することが多かったので、それ用のクラスをつくった。
ログを汚さないために1行で出力するバージョン付き。置換すれば元のXML文字列に戻せるので、いざというときは解析に使える。
中身は単なるXMLシリアライズなので渡すクラスにはSerializableAttributeが必要。
つくったクラス
using System; using System.IO; using System.Xml.Serialization; namespace ObjectToXmlString { public static class XmlStringHelper { // XML文字列化 public static string ConvertToXmlStirng(object obj, bool throwException = false) { var xmlString = string.Empty; try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, obj); xmlString = writer.ToString(); } } catch { if (throwException) { throw; } } return xmlString; } // XML文字列化(1行) public static string ConvertToXmlStringAsOneLine(object obj, string newLineReplacingString, bool throwException = false) { var xmlString = string.Empty; try { xmlString = ConvertToXmlStirng(obj, throwException); xmlString = xmlString.Replace(Environment.NewLine, newLineReplacingString); } catch { if (throwException) { throw; } } return xmlString; } } }
おためしコード
using System; using System.Collections.Generic; namespace ObjectToXmlString { [Serializable] public class Box { public int Volume { get; set; } = 10; public string Name { get; set; } = "box a"; public List<string> Items { get; } = new List<string>() { "Apple", "Orange" }; } class Program { static void Main(string[] args) { Box boxA = new Box(); WriteResult("XML文字列", XmlStringHelper.ConvertToXmlStirng(boxA)); WriteResult("XML文字列(1行)", XmlStringHelper.ConvertToXmlStringAsOneLine(boxA, "#")); Console.ReadKey(); } static void WriteResult(string title, string result) { Console.WriteLine("----------------"); Console.WriteLine($"{title}"); Console.WriteLine("----------------"); Console.WriteLine($"{result}"); Console.WriteLine(); } } }
実行結果
---------------- XML文字列 ---------------- <?xml version="1.0" encoding="utf-16"?> <Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Volume>10</Volume> <Name>box a</Name> <Items> <string>Apple</string> <string>Orange</string> </Items> </Box> ---------------- XML文字列(1行) ---------------- <?xml version="1.0" encoding="utf-16"?>#<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"># <Volume>10</Volume># <Name>box a</Name># <Items># <string>Apple</string># <string>Orange</string># </Items>#</Box>
windows10のアクティビティ機能を無効化する
概要
Windows10でEdgeを使うと、Win+Tabで表示されるタスクビュー(?)に「アクティビティ」として表示されて邪魔。これを表示されなくする方法を忘れないようにメモしておく。アクティビティの削除だとかは参考[1]を参照のこと。
- 手順1: レジストリエディタを開く。[Win+R] -> 「regedit」
- 以下を開く。
コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\System
- 「EnableActivityFeed」キーを開く。無ければ追加(DWORD)。
- 値を「0」にする。
TortoiseSVNで作ったリポジトリのコミットログを編集可能にする
TortoiseSVNでリポジトリを作ったものの、コミットログが編集できず地味に面倒だったのでメモ。
結果としてバッチファイルを1つおけばいいだけだった。
TortoiseSVNでリポジトリを作るといくつかフォルダができる。
そのうち「hook」というフォルダに以下のバッチファイルを置けばOK。
set REPOS="%1" set REV="%2" set USER="%3" set PROPNAME="%4" set ACTION="%5" if %ACTION%=="M" ( if %PROPNAME%=="svn:log" ( exit 0 ) ) echo "Changing revision properties other than svn:log is prohibited" >&2 exit 1