1 // ================================================================================================
3 // Wikipedia用の翻訳支援処理実装クラスソース</summary>
5 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
6 // Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
9 // ================================================================================================
11 namespace Honememo.Wptscs.Logics
14 using System.Collections.Generic;
19 using System.Windows.Forms;
20 using Honememo.Models;
21 using Honememo.Parsers;
22 using Honememo.Utilities;
23 using Honememo.Wptscs.Models;
24 using Honememo.Wptscs.Parsers;
25 using Honememo.Wptscs.Properties;
26 using Honememo.Wptscs.Utilities;
27 using Honememo.Wptscs.Websites;
30 /// MediaWiki用の翻訳支援処理実装クラスです。
32 public class MediaWikiTranslator : Translator
37 /// <see cref="Translator.ItemTable"/>用ロックオブジェクト。
39 private LockObject itemTableLock = new LockObject();
46 /// MediaWikiでの翻訳支援処理を行うトランスレータを作成。
49 /// 別途プロパティに必要なパラメータを設定する必要あり。
50 /// 通常は<see cref="Translator.Create"/>にて設定ファイルから作成する。
52 public MediaWikiTranslator()
54 // このクラス用のロガーと、デフォルトの確認処理としてメッセージダイアログ版を設定
55 this.Logger = new MediaWikiLogger();
56 this.IsContinueAtInterwikiExisted = this.IsContinueAtInterwikiExistedWithDialog;
64 /// 対象記事に言語間リンクが存在する場合の確認処理を表すデリゲート。
66 /// <param name="interwiki">言語間リンク先記事。</param>
67 /// <returns>処理を続行する場合<c>true</c>。</returns>
68 public delegate bool IsContinueAtInterwikiExistedDelegate(string interwiki);
77 public new MediaWiki From
81 return base.From as MediaWiki;
93 public new MediaWiki To
97 return base.To as MediaWiki;
107 /// 対象記事に言語間リンクが存在する場合の確認処理。
109 /// <remarks>確認を行わない場合<c>null</c>。</remarks>
110 public IsContinueAtInterwikiExistedDelegate IsContinueAtInterwikiExisted
122 /// ※継承クラスでは、この関数に処理を実装すること
124 /// <param name="name">記事名。</param>
125 /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Logger"/>に出力される。</exception>
126 protected override void RunBody(string name)
129 MediaWikiPage article = this.GetTargetPage(name);
132 throw new ApplicationException("article is not found");
135 // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
136 // ※ 言語間リンク取得中は、処理状態を解析中に変更
137 MediaWikiLink interlanguage;
138 using (var sm = this.StatusManager.Switch(Resources.StatusParsing))
140 interlanguage = article.GetInterlanguage(this.To.Language.Code);
143 if (interlanguage != null)
145 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
146 this.Stopwatch.Stop();
147 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interlanguage.Title))
149 throw new ApplicationException("user canceled");
152 this.Stopwatch.Start();
153 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interlanguage.Title);
157 this.Text += this.CreateOpening(article.Title);
159 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
160 this.Logger.AddSeparator();
161 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace);
162 using (var sm = this.StatusManager.Switch(Resources.StatusParsing))
165 using (MediaWikiParser parser = new MediaWikiParser(this.From))
167 element = parser.Parse(article.Text);
170 this.Text += this.ReplaceElement(element, article).ToString();
173 // 記事の末尾に新しい言語間リンクと、コメントを追記
174 this.Text += this.CreateEnding(article);
176 // ダウンロードされるテキストがLFなので、最後にクライアント環境に合わせた改行コードに変換
177 // ※ダウンロード時に変換するような仕組みが見つかれば、そちらを使う
178 // その場合、上のように\nをべたに吐いている部分を修正する
179 this.Text = this.Text.Replace("\n", Environment.NewLine);
184 #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
187 /// ログ出力によるエラー処理を含んだページ取得処理。
189 /// <param name="title">ページタイトル。</param>
190 /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
191 /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
192 /// <exception cref="ApplicationException"><see cref="Translator.CancellationPending"/>が<c>true</c>の場合。</exception>
194 /// 本メソッドは、大きく3パターンの動作を行う。
195 /// <list type="number">
196 /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
197 /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
198 /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
199 /// or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
201 /// また、実行中は処理状態をサーバー接続中に更新する。
202 /// 実行前後には終了要求のチェックも行う。
204 protected bool TryGetPage(string title, out MediaWikiPage page)
206 // & 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
208 bool success = base.TryGetPage(WebUtility.HtmlDecode(title), out p);
209 page = p as MediaWikiPage;
215 #region 冒頭/末尾ブロックの生成メソッド
218 /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''{{Lang|en|英語記事名}}''')」みたいなのを作成する。
220 /// <param name="title">翻訳支援対象の記事名。</param>
221 /// <returns>冒頭部のテキスト。</returns>
222 protected virtual string CreateOpening(string title)
224 string langPart = String.Empty;
225 IElement langLink = this.GetLanguageLink();
226 if (langLink != null)
228 langPart = langLink.ToString() + ": ";
231 string langBody = this.To.FormatLang(this.From.Language.Code, title);
232 if (String.IsNullOrEmpty(langBody))
237 StringBuilder b = new StringBuilder("'''xxx'''");
238 b.Append(this.To.Language.FormatBracket(langPart + "'''" + langBody + "'''"));
244 /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
246 /// <param name="page">翻訳支援対象の記事。</param>
247 /// <returns>末尾部のテキスト。</returns>
248 protected virtual string CreateEnding(MediaWikiPage page)
250 MediaWikiLink link = new MediaWikiLink();
251 link.Title = page.Title;
252 link.Interwiki = this.From.Language.Code;
253 return "\n\n" + link.ToString() + "\n" + String.Format(
254 Resources.ArticleFooter,
255 FormUtils.ApplicationName(),
256 this.From.Language.Code,
258 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
268 /// <param name="element">ページ要素。</param>
269 /// <param name="parent">ページ要素を取得した変換元記事。</param>
270 /// <returns>変換後のページ要素。</returns>
271 protected virtual IElement ReplaceElement(IElement element, MediaWikiPage parent)
274 this.ThrowExceptionIfCanceled();
276 // 要素の型に応じて、必要な置き換えを行う
277 if (element is MediaWikiTemplate)
280 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
282 else if (element is MediaWikiLink)
285 return this.ReplaceLink((MediaWikiLink)element, parent);
287 else if (element is MediaWikiHeading)
290 return this.ReplaceHeading((MediaWikiHeading)element, parent);
292 else if (element is MediaWikiVariable)
295 return this.ReplaceVariable((MediaWikiVariable)element, parent);
297 else if (element is ListElement)
300 return this.ReplaceListElement((ListElement)element, parent);
303 // それ以外は、特に何もせず元の値を返す
308 /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
310 /// <param name="link">変換元リンク。</param>
311 /// <param name="parent">ページ要素を取得した変換元記事。</param>
312 /// <returns>変換済みリンク。</returns>
313 protected virtual IElement ReplaceLink(MediaWikiLink link, MediaWikiPage parent)
315 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
316 if (!this.IsSectionLink(link, parent.Title))
319 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
322 if (link.IsSubpage())
324 // サブページ(子)の場合だけ後で記事名を復元するので記録
325 child = link.Title.StartsWith("/");
328 string title = parent.Normalize(link);
329 if (parent.Title.StartsWith(title))
331 // サブページ(親)の場合、変換してもしょうがないのでセクションだけチェックして終了
332 if (!String.IsNullOrEmpty(link.Section))
334 link.Section = this.ReplaceLinkSection(link.Section);
335 link.ParsedString = null;
343 else if (!String.IsNullOrEmpty(link.Interwiki))
345 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
346 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
347 return this.ReplaceLinkInterwiki(link);
349 else if (article.IsFile())
351 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
352 return this.ReplaceLinkFile(link, parent);
354 else if (article.IsCategory() && !link.IsColon)
356 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
358 return this.ReplaceLinkCategory(link);
361 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
362 string interWiki = this.GetInterlanguage(link);
363 if (interWiki == null)
365 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
367 else if (interWiki == String.Empty)
369 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
370 if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
372 return this.ReplaceLinkLinkInterwiki(link);
375 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
376 link.Title = this.From.Language.Code + ':' + link.Title;
381 // 言語間リンクが存在してサブページ(子)の場合、親ページ部分を消す
382 // TODO: 兄弟や叔父のパターンも対処したい(ややこしいので現状未対応)
383 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
387 // 普通に言語間リンクが存在する場合、記事名を置き換え
388 link.Title = interWiki;
391 if (link.PipeTexts.Count == 0 && interWiki != null)
393 // 表示名が存在しない場合、元の名前を表示名に設定
396 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
401 // セクション部分([[#関連項目]]とか)を変換
402 if (!String.IsNullOrEmpty(link.Section))
404 link.Section = this.ReplaceLinkSection(link.Section);
407 link.ParsedString = null;
412 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
414 /// <param name="template">変換元テンプレート。</param>
415 /// <param name="parent">ページ要素を取得した変換元記事。</param>
416 /// <returns>変換済みテンプレート。</returns>
417 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, MediaWikiPage parent)
419 // システム変数({{PAGENAME}}とか)の場合は対象外
420 if (this.From.IsMagicWord(template.Title))
425 // テンプレートは通常名前空間が省略されているので補完する
426 string filledTitle = this.FillTemplateName(template, parent);
428 // リンクを辿り、対象記事の言語間リンクを取得
429 string interWiki = this.GetInterlanguage(new MediaWikiTemplate(filledTitle));
430 if (interWiki == null)
432 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
435 else if (interWiki == String.Empty)
437 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
438 // おまけで、元のテンプレートの状態をコメントでつける
439 ListElement list = new ListElement();
440 MediaWikiLink link = new MediaWikiLink();
442 link.Title = this.From.Language.Code + ':' + filledTitle;
444 XmlCommentElement comment = new XmlCommentElement();
445 comment.Raw = ' ' + template.ToString() + ' ';
451 // 言語間リンクが存在する場合、そちらを指すように置換
452 template.Title = interWiki;
453 if (new MediaWikiPage(this.To, interWiki).IsTemplate())
455 // 言語間リンク先がテンプレートの場合、: より前の部分を削除
456 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
459 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
460 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
461 template.ParsedString = null;
467 /// 指定された見出しに対して、対訳表による変換を行う。
469 /// <param name="heading">見出し。</param>
470 /// <param name="parent">ページ要素を取得した変換元記事。</param>
471 /// <returns>変換後の見出し。</returns>
472 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, MediaWikiPage parent)
475 this.Logger.AddSource(heading);
478 StringBuilder oldText = new StringBuilder();
479 foreach (IElement e in heading)
481 oldText.Append(e.ToString());
484 string newText = this.GetHeading(oldText.ToString().Trim());
487 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
489 heading.ParsedString = null;
490 heading.Add(new XmlTextElement(newText));
491 this.Logger.AddDestination(heading);
495 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
496 return this.ReplaceListElement(heading, parent);
500 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
502 /// <param name="variable">変換元変数要素。</param>
503 /// <param name="parent">ページ要素を取得した変換元記事。</param>
504 /// <returns>変換済み変数要素。</returns>
505 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, MediaWikiPage parent)
507 // 変数、これ自体は処理しないが、再帰的に探索
508 string old = variable.Value.ToString();
509 variable.Value = this.ReplaceElement(variable.Value, parent);
510 if (variable.Value.ToString() != old)
512 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
513 variable.ParsedString = null;
520 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
522 /// <param name="listElement">変換元要素。</param>
523 /// <param name="parent">ページ要素を取得した変換元記事。</param>
524 /// <returns>変換済み要素。</returns>
525 protected virtual IElement ReplaceListElement(ListElement listElement, MediaWikiPage parent)
527 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
528 for (int i = 0; i < listElement.Count; i++)
530 string old = listElement[i].ToString();
531 listElement[i] = this.ReplaceElement(listElement[i], parent);
532 if (listElement[i].ToString() != old)
534 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
535 listElement.ParsedString = null;
547 /// 対訳表に指定された記事名の情報が登録されているか?
549 /// <param name="title">記事名。</param>
550 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
551 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
552 protected bool ContainsAtItemTable(string title)
554 if (this.ItemTable == null)
559 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
560 // ※ 対訳表へのアクセス時は記事名をデコードしておく
561 string decodedTitle = WebUtility.HtmlDecode(title);
562 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
564 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
565 return this.ItemTable.ContainsKey(decodedTitle);
570 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
572 /// <param name="heading">翻訳元言語での見出し。</param>
573 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
574 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
575 protected string GetHeading(string heading)
577 if (this.HeadingTable == null)
582 return this.HeadingTable.GetWord(heading);
590 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
592 /// <param name="element">内部リンク要素。</param>
593 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
594 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
595 protected string GetInterlanguage(MediaWikiLink element)
598 this.Logger.AddSource(element);
599 string title = element.Title;
600 TranslationDictionary.Item item;
601 if (this.ItemTable == null)
603 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
604 return this.GetInterlanguageWithCreateCache(title, out item);
607 // 対訳表を使用して言語間リンクを探索。
608 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック(記事名単位)。
609 // また、対訳表へのアクセス時は記事名をデコードしておく。
610 string decodedTitle = WebUtility.HtmlDecode(title);
611 lock (this.itemTableLock.GetObject(decodedTitle.ToLower()))
613 if (this.ItemTable.TryGetValue(decodedTitle, out item))
616 if (!String.IsNullOrWhiteSpace(item.Alias))
618 // リダイレクトがあれば、そのメッセージも表示
619 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
622 if (!String.IsNullOrEmpty(item.Word))
624 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
629 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
634 // 対訳表に存在しない場合は、普通に取得し表に記録
635 // ※ こちらは内部でデコードしているためデコードした記事名を渡してはならない
636 string interlanguage = this.GetInterlanguageWithCreateCache(title, out item);
637 if (interlanguage != null)
639 // ページ自体が存在しない場合を除き、結果を対訳表に登録
640 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
641 this.ItemTable[decodedTitle] = item;
644 return interlanguage;
649 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
650 /// キャッシュ用の処理結果情報も出力する。
652 /// <param name="title">記事名。</param>
653 /// <param name="item">キャッシュ用の処理結果情報。</param>
654 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
655 private string GetInterlanguageWithCreateCache(string title, out TranslationDictionary.Item item)
658 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
659 MediaWikiPage page = this.GetDestinationPage(title);
660 if (page != null && page.IsRedirect())
662 // リダイレクトの場合、リダイレクトである旨出力し、その先の記事を取得
663 this.Logger.AddAlias(new MediaWikiLink(page.Redirect.Title));
664 item.Alias = page.Redirect.Title;
665 page = this.GetDestinationPage(page.Redirect.Title);
670 // ページ自体が存在しない場合はnull
675 MediaWikiLink interlanguage = page.GetInterlanguage(this.To.Language.Code);
676 if (interlanguage != null)
678 item.Word = interlanguage.Title;
679 this.Logger.AddDestination(interlanguage);
684 item.Word = String.Empty;
685 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound));
694 /// <param name="title">ページタイトル。</param>
695 /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
696 /// <remarks>記事が無い場合、通信エラーなど例外が発生した場合は、エラーログを出力する。</remarks>
697 private MediaWikiPage GetDestinationPage(string title)
700 if (this.TryGetPage(title, out page) && page == null)
702 // 記事が存在しない場合だけ、変換先に「記事無し」を出力
703 // ※ エラー時のログはTryGetPageが自動的に出力
704 this.Logger.AddDestination(new TextElement(Resources.LogMessageLinkArticleNotFound));
712 #region 要素の変換関連その他メソッド
715 /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
717 /// <param name="link">判定する内部リンク。</param>
718 /// <param name="parent">内部リンクがあった記事。</param>
719 /// <returns>セクション部分のみ変換済みリンク。</returns>
720 private bool IsSectionLink(MediaWikiLink link, string parent)
722 // 記事名が指定されていない、または記事名が自分の記事名で
723 // 言語コード等も特に無く、かつセクションが指定されている場合
724 // (記事名もセクションも指定されていない・・・というケースもありえるが、
725 // その場合他に指定できるものも思いつかないので通す)
726 return String.IsNullOrEmpty(link.Title)
727 || (link.Title == parent && String.IsNullOrEmpty(link.Interwiki) && !String.IsNullOrEmpty(link.Section));
731 /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
733 /// <param name="section">セクション文字列。</param>
734 /// <returns>セクション部分のみ変換済みリンク。</returns>
735 private string ReplaceLinkSection(string section)
737 // セクションが指定されている場合、定型句変換を通す
738 string heading = this.GetHeading(section);
739 return heading != null ? heading : section;
743 /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
745 /// <param name="link">変換元言語間リンク。</param>
746 /// <returns>変換済み言語間リンク。</returns>
747 private IElement ReplaceLinkInterwiki(MediaWikiLink link)
749 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
750 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
751 if (!link.IsColon && link.Interwiki == this.To.Language.Code)
753 return new TextElement();
760 /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
762 /// <param name="link">変換元カテゴリ。</param>
763 /// <returns>変換済みカテゴリ。</returns>
764 private IElement ReplaceLinkCategory(MediaWikiLink link)
766 // リンクを辿り、対象記事の言語間リンクを取得
767 string interWiki = this.GetInterlanguage(link);
768 if (interWiki == null)
770 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
773 else if (interWiki == String.Empty)
775 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
776 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
777 XmlCommentElement comment = new XmlCommentElement();
778 comment.Raw = ' ' + link.ToString() + ' ';
780 link.Title = this.From.Language.Code + ':' + link.Title;
782 link.PipeTexts.Clear();
783 link.ParsedString = null;
785 ListElement list = new ListElement();
792 // 普通に言語間リンクが存在する場合、記事名を置き換え
793 link.Title = interWiki;
794 link.ParsedString = null;
800 /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
802 /// <param name="link">変換元リンク。</param>
803 /// <param name="parent">ページ要素を取得した変換元記事。</param>
804 /// <returns>変換済みリンク。</returns>
805 private IElement ReplaceLinkFile(MediaWikiLink link, MediaWikiPage parent)
807 // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
808 link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
809 link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
810 link.ParsedString = null;
815 /// 記事名のうち名前空間部分の変換先言語への変換を行う。
817 /// <param name="title">変換元記事名。</param>
818 /// <param name="id">名前空間のID。</param>
819 /// <returns>変換済み記事名。</returns>
820 private string ReplaceLinkNamespace(string title, int id)
824 if (!this.To.Namespaces.TryGetValue(id, out names))
826 // 翻訳先言語に相当する名前空間が無い場合、何もしない
830 // 記事名の名前空間部分を置き換えて返す
831 return names.FirstOrDefault() + title.Substring(title.IndexOf(':'));
835 /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
837 /// <param name="link">変換元言語間リンク。</param>
838 /// <returns>変換済み言語間リンク。</returns>
839 private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
841 // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
842 // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
843 MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
844 string langTitle = title.GetLinkString();
845 if (!String.IsNullOrEmpty(title.Section))
847 // 変換先言語版のセクションは、セクションの変換を通したものにする
848 title.Section = this.ReplaceLinkSection(title.Section);
851 // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
852 string label = langTitle;
853 if (link.PipeTexts.Count > 0)
855 label = link.PipeTexts.Last().ToString();
859 // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
860 return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
864 /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
866 /// <param name="elements">変換元要素リスト。</param>
867 /// <param name="parent">ページ要素を取得した変換元記事。</param>
868 /// <returns>変換済み要素リスト。</returns>
869 private IList<IElement> ReplaceElements(IList<IElement> elements, MediaWikiPage parent)
871 if (elements == null)
876 IList<IElement> result = new List<IElement>();
877 foreach (IElement e in elements)
879 result.Add(this.ReplaceElement(e, parent));
886 /// テンプレート名に必要に応じて名前空間を補完する。
888 /// <param name="template">テンプレート。</param>
889 /// <param name="parent">ページ要素を取得した変換元記事。</param>
890 /// <returns>補完済みのテンプレート名。</returns>
891 private string FillTemplateName(MediaWikiTemplate template, MediaWikiPage parent)
894 string filledTitle = parent.Normalize(template);
895 if (filledTitle == template.Title || template.IsSubpage())
897 // 補完が不要な場合、またはサブページだった場合、ここで終了
901 // プレフィックスが付いた記事名が実際に存在するかを確認
902 // ※ 不要かもしれないが、マジックワードの漏れ等の誤検出を減らしたいので
903 if (this.ContainsAtItemTable(filledTitle))
905 // 対訳表に記事名が確認されている場合、既知の名前として確定
909 // 実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
910 // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
911 MediaWikiPage page = null;
914 // 記事が存在する場合、プレフィックスをつけた名前を使用
915 page = this.From.GetPage(WebUtility.HtmlDecode(filledTitle)) as MediaWikiPage;
918 catch (FileNotFoundException)
920 // 記事が存在しない場合、元のページ名を使用
921 return template.Title;
926 if (!Settings.Default.IgnoreError)
928 // エラーを無視しない場合、ここで翻訳支援処理を中断する
929 this.Logger.AddError(e);
930 throw new ApplicationException(e.Message, e);
933 // 続行する場合は、とりあえずプレフィックスをつけた名前で処理
934 this.Logger.AddResponse(Resources.LogMessageTemplateNameUnidentified, template.Title, filledTitle, e.Message);
946 /// <param name="title">翻訳支援対象の記事名。</param>
947 /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
949 /// ここで取得した記事のURIを、以後の翻訳支援処理で使用するRefererとして登録
950 /// (ここで処理しているのは、リダイレクトの場合のリダイレクト先への
951 /// アクセス時にもRefererを入れたかったから。
952 /// リダイレクトの場合は、最終的には転送先ページのURIとなる)。
954 private MediaWikiPage GetTargetPage(string title)
956 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
957 // ※ この処理ではキャッシュは使用しない。
958 // ※ 万が一相互にリダイレクトしていると無限ループとなるが、特に判定はしない。
959 // ユーザーが画面上から止めることを期待。
960 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
962 for (string s = title; this.TryGetPage(s, out page); s = page.Redirect.Title)
966 // 記事が存在しない場合、メッセージを出力して終了
967 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
971 // 取得した記事のURIを以後のアクセスで用いるRefererとして登録
972 this.From.WebProxy.Referer = page.Uri.AbsoluteUri;
973 this.To.WebProxy.Referer = page.Uri.AbsoluteUri;
975 if (!page.IsRedirect())
981 // リダイレクトであれば、さらにその先の記事を取得
982 this.Logger.AddResponse(Resources.LogMessageRedirect
983 + " " + new MediaWikiLink(page.Redirect.Title).ToString());
990 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
993 /// [[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。
994 /// サーバーにそうした記事が存在しない場合、リンクではなく言語名称or略称の文字列を返す。
996 private IElement GetLanguageLink()
999 Language.LanguageName name;
1000 if (!this.From.Language.Names.TryGetValue(this.To.Language.Code, out name))
1006 IElement shortName = null;
1007 if (!String.IsNullOrEmpty(name.ShortName))
1009 shortName = new TextElement(name.ShortName);
1012 if (this.To.HasLanguagePage)
1014 // サーバーにこの言語の記事が存在することが期待される場合、
1016 MediaWikiLink link = new MediaWikiLink(name.Name);
1017 if (shortName != null)
1019 link.PipeTexts.Add(shortName);
1024 else if (shortName != null)
1026 // 存在しない場合、まずあれば略称を返す
1032 return new TextElement(name.Name);
1037 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
1039 /// <param name="interwiki">言語間リンク先記事。</param>
1040 /// <returns>処理を続行する場合<c>true</c>。</returns>
1041 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
1044 if (MessageBox.Show(
1045 String.Format(Resources.QuestionMessageArticleExisted, interwiki),
1046 Resources.QuestionTitle,
1047 MessageBoxButtons.YesNo,
1048 MessageBoxIcon.Question)
1051 // 中断の場合、同じメッセージをログにも表示
1052 this.Logger.AddSeparator();
1053 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);