OSDN Git Service

#27621 言語間リンク取得時に<includeonly>, <noinclude>を考慮するよう修正,
[wptscs/wpts.git] / Wptscs / Logics / Translator.cs
1 // ================================================================================================
2 // <summary>
3 //      翻訳支援処理を実装するための抽象クラスソース</summary>
4 //
5 // <copyright file="Translator.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs.Logics
12 {
13     using System;
14     using System.Diagnostics;
15     using System.IO;
16     using System.Net;
17     using System.Net.NetworkInformation;
18     using System.Reflection;
19     using Honememo.Models;
20     using Honememo.Utilities;
21     using Honememo.Wptscs.Models;
22     using Honememo.Wptscs.Properties;
23     using Honememo.Wptscs.Utilities;
24     using Honememo.Wptscs.Websites;
25
26     /// <summary>
27     /// 翻訳支援処理を実装するための抽象クラスです。
28     /// </summary>
29     public abstract class Translator
30     {
31         #region private変数
32
33         /// <summary>
34         /// 変換後テキスト。
35         /// </summary>
36         private string text = String.Empty;
37
38         /// <summary>
39         /// ログテキスト生成用ロガー。
40         /// </summary>
41         private Logger logger;
42
43         #endregion
44
45         #region コンストラクタ
46
47         /// <summary>
48         /// トランスレータを作成。
49         /// </summary>
50         public Translator()
51         {
52             // ステータス管理については更新イベントを連鎖させる
53             this.Stopwatch = new Stopwatch();
54             this.Logger = new Logger();
55             this.StatusManager = new StatusManager<string>();
56             this.StatusManager.Changed += new EventHandler(
57                 delegate
58                 {
59                     if (this.StatusUpdated != null)
60                     {
61                         this.StatusUpdated(this, EventArgs.Empty);
62                     }
63                 });
64         }
65
66         #endregion
67
68         #region イベント
69
70         /// <summary>
71         /// ログ更新伝達イベント。
72         /// </summary>
73         public event EventHandler LogUpdated;
74
75         /// <summary>
76         /// 処理状態更新伝達イベント。
77         /// </summary>
78         public event EventHandler StatusUpdated;
79
80         #endregion
81
82         #region 公開プロパティ
83
84         /// <summary>
85         /// 翻訳元言語のサイト。
86         /// </summary>
87         public Website From
88         {
89             get;
90             set;
91         }
92
93         /// <summary>
94         /// 翻訳先言語のサイト。
95         /// </summary>
96         public Website To
97         {
98             get;
99             set;
100         }
101
102         /// <summary>
103         /// 言語間の項目の対訳表。
104         /// </summary>
105         public TranslationDictionary ItemTable
106         {
107             get;
108             set;
109         }
110
111         /// <summary>
112         /// 言語間の見出しの対訳表。
113         /// </summary>
114         public TranslationTable HeadingTable
115         {
116             get;
117             set;
118         }
119
120         /// <summary>
121         /// ログメッセージ。
122         /// </summary>
123         public string Log
124         {
125             get
126             {
127                 return this.Logger.ToString();
128             }
129         }
130
131         /// <summary>
132         /// 処理状態メッセージ。
133         /// </summary>
134         public string Status
135         {
136             get
137             {
138                 // 内部的に実際に管理しているのはStatusManager
139                 return StringUtils.DefaultString(this.StatusManager.Status);
140             }
141         }
142
143         /// <summary>
144         /// 処理時間ストップウォッチ。
145         /// </summary>
146         public Stopwatch Stopwatch
147         {
148             get;
149             private set;
150         }
151
152         /// <summary>
153         /// 変換後テキスト。
154         /// </summary>
155         public string Text
156         {
157             get
158             {
159                 return this.text;
160             }
161
162             protected set
163             {
164                 this.text = StringUtils.DefaultString(value);
165             }
166         }
167
168         /// <summary>
169         /// 処理を途中で終了させるためのフラグ。
170         /// </summary>
171         public bool CancellationPending
172         {
173             get;
174             set;
175         }
176
177         #endregion
178
179         #region 実装支援用プロパティ
180
181         /// <summary>
182         /// ログテキスト生成用ロガー。
183         /// </summary>
184         /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
185         protected Logger Logger
186         {
187             get
188             {
189                 return this.logger;
190             }
191
192             set
193             {
194                 // nullは不可。また、ロガー変更後はイベントを設定
195                 this.logger = Validate.NotNull(value);
196                 this.logger.LogUpdate += new EventHandler(
197                     delegate
198                     {
199                         if (this.LogUpdated != null)
200                         {
201                             this.LogUpdated(this, EventArgs.Empty);
202                         }
203                     });
204             }
205         }
206
207         /// <summary>
208         /// ステータス管理用オブジェクト。
209         /// </summary>
210         /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
211         protected StatusManager<string> StatusManager
212         {
213             get;
214             private set;
215         }
216
217         #endregion
218
219         #region 静的メソッド
220
221         /// <summary>
222         /// 翻訳支援処理のインスタンスを作成。
223         /// </summary>
224         /// <param name="config">アプリケーション設定。</param>
225         /// <param name="from">翻訳元言語。</param>
226         /// <param name="to">翻訳先言語。</param>
227         /// <returns>生成したインスタンス。</returns>
228         /// <remarks>
229         /// 設定は設定クラスより取得、無ければ一部自動生成する。
230         /// インスタンス生成失敗時は例外を投げる。
231         /// </remarks>
232         public static Translator Create(Config config, string from, string to)
233         {
234             // 処理対象に応じてTranslatorを継承したオブジェクトを生成
235             ConstructorInfo constructor = config.Translator.GetConstructor(Type.EmptyTypes);
236             if (constructor == null)
237             {
238                 throw new NotImplementedException(config.Translator.FullName + " default constructor is not found");
239             }
240
241             // 設定に指定されたクラスを、引数無しのコンストラクタを用いて生成する
242             Translator translator = (Translator)constructor.Invoke(null);
243
244             // Webサイトの設定
245             translator.From = config.GetWebsite(from);
246             translator.To = config.GetWebsite(to);
247
248             // 対訳表(項目)の設定
249             translator.ItemTable = config.GetItemTableNeedCreate(from, to);
250
251             // 対訳表(見出し)の設定、使用する言語は決まっているので組み合わせを設定
252             translator.HeadingTable = config.HeadingTable;
253             translator.HeadingTable.From = from;
254             translator.HeadingTable.To = to;
255
256             return translator;
257         }
258
259         #endregion
260
261         #region 公開メソッド
262
263         /// <summary>
264         /// 翻訳支援処理実行。
265         /// </summary>
266         /// <param name="name">記事名。</param>
267         /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Logger"/>に出力される。</exception>
268         /// <exception cref="InvalidOperationException"><see cref="From"/>, <see cref="To"/>が設定されていない場合。</exception>
269         public void Run(string name)
270         {
271             // ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す
272             if (this.From == null || this.To == null)
273             {
274                 throw new InvalidOperationException("From or To is null");
275             }
276
277             // 変数を初期化、処理時間を測定開始
278             this.Initialize();
279             this.Stopwatch.Start();
280
281             // サーバー接続チェック
282             string host = new Uri(this.From.Location).Host;
283             if (!String.IsNullOrEmpty(host) && !Settings.Default.IgnoreError)
284             {
285                 if (!this.Ping(host))
286                 {
287                     throw new ApplicationException("ping failed");
288                 }
289             }
290
291             // ここまでの間に終了要求が出ているかを確認
292             this.ThrowExceptionIfCanceled();
293
294             // 翻訳支援処理実行部の本体を実行
295             // ※以降の処理は、継承クラスにて定義
296             try
297             {
298                 this.RunBody(name);
299             }
300             finally
301             {
302                 // 終了後は処理状態をクリア、処理時間を測定終了
303                 this.StatusManager.Clear();
304                 this.Stopwatch.Stop();
305             }
306         }
307         
308         #endregion
309
310         #region 実装が必要なテンプレートメソッド
311
312         /// <summary>
313         /// 翻訳支援処理実行部の本体。
314         /// </summary>
315         /// <param name="name">記事名。</param>
316         /// <exception cref="ApplicationException">処理を中断する場合。中断の理由は<see cref="Logger"/>に出力する。</exception>
317         /// <remarks>テンプレートメソッド的な構造になっています。</remarks>
318         protected abstract void RunBody(string name);
319
320         #endregion
321
322         #region 実装支援用メソッド
323
324         /// <summary>
325         /// ログ出力によるエラー処理を含んだページ取得処理。
326         /// </summary>
327         /// <param name="title">ページタイトル。</param>
328         /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
329         /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
330         /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
331         /// <remarks>
332         /// 本メソッドは、大きく3パターンの動作を行う。
333         /// <list type="number">
334         /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
335         /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
336         /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
337         ///                    or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
338         /// </list>
339         /// また、実行中は処理状態をサーバー接続中に更新する。
340         /// 実行前後には終了要求のチェックも行う。
341         /// </remarks>
342         protected bool TryGetPage(string title, out Page page)
343         {
344             // 通信開始の前に終了要求が出ているかを確認
345             this.ThrowExceptionIfCanceled();
346
347             // ページ取得処理、実行中は処理状態を変更
348             bool success;
349             using (var sm = this.StatusManager.Switch(Resources.StatusDownloading))
350             {
351                 success = this.TryGetPageBody(title, out page);
352             }
353
354             // 通信終了後にも再度終了要求を確認
355             this.ThrowExceptionIfCanceled();
356             return success;
357         }
358
359         /// <summary>
360         /// 終了要求が出ている場合、例外を投げる。
361         /// </summary>
362         /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
363         protected void ThrowExceptionIfCanceled()
364         {
365             if (this.CancellationPending)
366             {
367                 throw new ApplicationException("CancellationPending is true");
368             }
369         }
370
371         #endregion
372
373         #region 内部処理用メソッド
374
375         /// <summary>
376         /// 翻訳支援処理実行時の初期化処理。
377         /// </summary>
378         private void Initialize()
379         {
380             // 変数を初期化
381             this.Logger.Clear();
382             this.StatusManager.Clear();
383             this.Stopwatch.Reset();
384             this.Text = String.Empty;
385             this.CancellationPending = false;
386         }
387
388         /// <summary>
389         /// サーバー接続チェック。
390         /// </summary>
391         /// <param name="server">サーバー名。</param>
392         /// <returns><c>true</c> 接続成功。</returns>
393         /// <remarks>実行中は処理状態をサーバー接続中に更新する。</remarks>
394         private bool Ping(string server)
395         {
396             // サーバー接続チェック、実行中は処理状態を変更
397             using (var sm = this.StatusManager.Switch(Resources.StatusPinging))
398             {
399                 // サーバー接続チェック
400                 Ping ping = new Ping();
401                 try
402                 {
403                     PingReply reply = ping.Send(server);
404                     if (reply.Status != IPStatus.Success)
405                     {
406                         this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, reply.Status.ToString());
407                         return false;
408                     }
409                 }
410                 catch (Exception e)
411                 {
412                     this.Logger.AddMessage(Resources.ErrorMessageConnectionFailed, e.InnerException.Message);
413                     return false;
414                 }
415
416                 return true;
417             }
418         }
419
420         /// <summary>
421         /// ログ出力によるエラー処理を含んだページ取得処理本体。
422         /// </summary>
423         /// <param name="title">ページタイトル。</param>
424         /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
425         /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
426         /// <remarks>
427         /// 本メソッドは、大きく3パターンの動作を行う。
428         /// <list type="number">
429         /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
430         /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
431         /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
432         ///                    or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreError等による)。</description></item>
433         /// </list>
434         /// </remarks>
435         private bool TryGetPageBody(string title, out Page page)
436         {
437             page = null;
438             try
439             {
440                 // 普通に取得できた場合はここで終了
441                 page = this.From.GetPage(title);
442                 return true;
443             }
444             catch (FileNotFoundException)
445             {
446                 // ページ無しによる例外も正常終了
447                 return true;
448             }
449             catch (EndPeriodException)
450             {
451                 // 末尾がピリオドで終わるページが処理できない既知の不具合への対応、警告メッセージを出す
452                 this.Logger.AddResponse(Resources.LogMessageErrorPageName, title);
453                 return false;
454             }
455             catch (Exception e)
456             {
457                 // その他例外の場合、まずエラー情報を出力
458                 this.Logger.AddError(e);
459
460                 // エラーを無視しない場合、ここで翻訳支援処理を中断する
461                 if (!Settings.Default.IgnoreError)
462                 {
463                     throw new ApplicationException(e.Message, e);
464                 }
465
466                 return false;
467             }
468         }
469
470         #endregion
471     }
472 }