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.Parsers;
21 using Honememo.Utilities;
22 using Honememo.Wptscs.Models;
23 using Honememo.Wptscs.Parsers;
24 using Honememo.Wptscs.Properties;
25 using Honememo.Wptscs.Utilities;
26 using Honememo.Wptscs.Websites;
29 /// Wikipedia用の翻訳支援処理実装クラスです。
31 public class MediaWikiTranslator : Translator
38 public MediaWikiTranslator()
40 // このクラス用のロガーと、デフォルトの確認処理としてメッセージダイアログ版を設定
41 this.Logger = new MediaWikiLogger();
42 this.IsContinueAtInterwikiExisted = this.IsContinueAtInterwikiExistedWithDialog;
50 /// 対象記事に言語間リンクが存在する場合の確認処理を表すデリゲート。
52 /// <param name="interwiki">言語間リンク先記事。</param>
53 /// <returns>処理を続行する場合<c>true</c>。</returns>
54 public delegate bool IsContinueAtInterwikiExistedDelegate(string interwiki);
63 public new MediaWiki From
67 return base.From as MediaWiki;
79 public new MediaWiki To
83 return base.To as MediaWiki;
93 /// 対象記事に言語間リンクが存在する場合の確認処理。
95 /// <remarks>確認を行わない場合<c>null</c>。</remarks>
96 public IsContinueAtInterwikiExistedDelegate IsContinueAtInterwikiExisted
108 /// ※継承クラスでは、この関数に処理を実装すること
110 /// <param name="name">記事名。</param>
111 /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Logger"/>に出力される。</exception>
112 protected override void RunBody(string name)
115 MediaWikiPage article = this.GetTargetPage(name);
118 throw new ApplicationException("article is not found");
121 // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
122 // ※ 言語間リンク取得中は、処理状態を解析中に変更
123 string interWiki = null;
124 this.ChangeStatusInExecuting(
125 () => interWiki = article.GetInterWiki(this.To.Language.Code),
126 Resources.StatusParsing);
127 if (!String.IsNullOrEmpty(interWiki))
129 // 確認処理の最中は処理時間をカウントしない(ダイアログ等を想定するため)
130 this.Stopwatch.Stop();
131 if (this.IsContinueAtInterwikiExisted != null && !this.IsContinueAtInterwikiExisted(interWiki))
133 throw new ApplicationException("user canceled");
136 this.Stopwatch.Start();
137 this.Logger.AddResponse(Resources.LogMessageTargetArticleHadInterWiki, interWiki);
141 this.Text += this.CreateOpening(article.Title);
143 // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
144 this.Logger.AddSeparator();
145 this.Logger.AddResponse(Resources.LogMessageStartParseAndReplace, interWiki);
146 this.ChangeStatusInExecuting(
147 () => this.Text += this.ReplaceElement(new MediaWikiParser(this.From).Parse(article.Text), article.Title).ToString(),
148 Resources.StatusParsing);
150 // 記事の末尾に新しい言語間リンクと、コメントを追記
151 this.Text += this.CreateEnding(article);
153 // ダウンロードされるテキストがLFなので、最後にクライアント環境に合わせた改行コードに変換
154 // ※ダウンロード時に変換するような仕組みが見つかれば、そちらを使う
155 // その場合、上のように\nをべたに吐いている部分を修正する
156 this.Text = this.Text.Replace("\n", Environment.NewLine);
161 #region 他のクラスの処理をこのクラスにあわせて拡張したメソッド
164 /// ログ出力によるエラー処理を含んだページ取得処理。
166 /// <param name="title">ページタイトル。</param>
167 /// <param name="page">取得したページ。ページが存在しない場合は <c>null</c> を返す。</param>
168 /// <returns>処理が成功した(404も含む)場合<c>true</c>、失敗した(通信エラーなど)の場合<c>false</c>。</returns>
169 /// <exception cref="ApplicationException"><see cref="Translator.CancellationPending"/>が<c>true</c>の場合。</exception>
171 /// 本メソッドは、大きく3パターンの動作を行う。
172 /// <list type="number">
173 /// <item><description>正常にページが取得できた → <c>true</c>でページを設定、ログ出力無し</description></item>
174 /// <item><description>404など想定内の例外でページが取得できなかった → <c>true</c>でページ無し、ログ出力無し</description></item>
175 /// <item><description>想定外の例外でページが取得できなかった → <c>false</c>でページ無し、ログ出力有り
176 /// or <c>ApplicationException</c>で処理中断(アプリケーション設定のIgnoreErrorによる)。</description></item>
178 /// また、実行中は処理状態をサーバー接続中に更新する。
179 /// 実行前後には終了要求のチェックも行う。
181 protected bool TryGetPage(string title, out MediaWikiPage page)
183 // & 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
185 bool success = base.TryGetPage(WebUtility.HtmlDecode(title), out p);
186 page = p as MediaWikiPage;
192 #region 冒頭/末尾ブロックの生成メソッド
195 /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''{{Lang|en|英語記事名}}''')」みたいなのを作成する。
197 /// <param name="title">翻訳支援対象の記事名。</param>
198 /// <returns>冒頭部のテキスト。</returns>
199 protected virtual string CreateOpening(string title)
201 string langPart = String.Empty;
202 MediaWikiLink langLink = this.GetLanguageLink(this.From, this.To.Language.Code);
203 if (langLink != null)
205 langPart = langLink.ToString() + ": ";
208 string langBody = this.To.FormatLang(this.From.Language.Code, title);
209 if (String.IsNullOrEmpty(langBody))
214 StringBuilder b = new StringBuilder("'''xxx'''");
215 b.Append(this.To.Language.FormatBracket(langPart + "'''" + langBody + "'''"));
221 /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する。
223 /// <param name="page">翻訳支援対象の記事。</param>
224 /// <returns>末尾部のテキスト。</returns>
225 protected virtual string CreateEnding(MediaWikiPage page)
227 MediaWikiLink link = new MediaWikiLink();
228 link.Title = page.Title;
229 link.Code = this.From.Language.Code;
230 return "\n\n" + link.ToString() + "\n" + String.Format(
231 Resources.ArticleFooter,
232 FormUtils.ApplicationName(),
233 this.From.Language.Code,
235 page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
245 /// <param name="element">ページ要素。</param>
246 /// <param name="parent">サブページ用の親記事タイトル。</param>
247 /// <returns>変換後のページ要素。</returns>
248 protected virtual IElement ReplaceElement(IElement element, string parent)
251 this.ThrowExceptionIfCanceled();
253 // 要素の型に応じて、必要な置き換えを行う
254 if (element is MediaWikiTemplate)
257 return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
259 else if (element is MediaWikiLink)
262 return this.ReplaceLink((MediaWikiLink)element, parent);
264 else if (element is MediaWikiHeading)
267 return this.ReplaceHeading((MediaWikiHeading)element, parent);
269 else if (element is MediaWikiVariable)
272 return this.ReplaceVariable((MediaWikiVariable)element, parent);
274 else if (element is ListElement)
277 return this.ReplaceListElement((ListElement)element, parent);
280 // それ以外は、特に何もせず元の値を返す
285 /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
287 /// <param name="link">変換元リンク。</param>
288 /// <param name="parent">サブページ用の親記事タイトル。</param>
289 /// <returns>変換済みリンク。</returns>
290 protected virtual IElement ReplaceLink(MediaWikiLink link, string parent)
292 // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
293 if (!this.IsSectionLink(link, parent))
296 MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
301 link.Title = parent + link.Title;
303 else if (!String.IsNullOrEmpty(link.Code))
305 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
306 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
307 return this.ReplaceLinkInterwiki(link);
309 else if (article.IsFile())
311 // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
312 return this.ReplaceLinkFile(link, parent);
314 else if (article.IsCategory() && !link.IsColon)
316 // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
318 return this.ReplaceLinkCategory(link);
321 // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
322 string interWiki = this.GetInterwiki(link);
323 if (interWiki == null)
325 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
327 else if (interWiki == String.Empty)
329 // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
330 if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
332 return this.ReplaceLinkLinkInterwiki(link);
335 // 設定が無ければ [[:en:xxx]] みたいな形式に置換
336 link.Title = this.From.Language.Code + ':' + link.Title;
339 else if (link.IsSubpage)
341 // 言語間リンクが存在してサブページの場合、親ページ部分を消す
342 link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
346 // 普通に言語間リンクが存在する場合、記事名を置き換え
347 link.Title = interWiki;
350 if (link.PipeTexts.Count == 0 && interWiki != null)
352 // 表示名が存在しない場合、元の名前を表示名に設定
355 new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
360 // セクション部分([[#関連項目]]とか)を変換
361 if (!String.IsNullOrEmpty(link.Section))
363 link.Section = this.ReplaceLinkSection(link.Section);
366 link.ParsedString = null;
371 /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
373 /// <param name="template">変換元テンプレート。</param>
374 /// <param name="parent">サブページ用の親記事タイトル。</param>
375 /// <returns>変換済みテンプレート。</returns>
376 protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, string parent)
378 // システム変数({{PAGENAME}}とか)の場合は対象外
379 if (this.From.IsMagicWord(template.Title))
384 // テンプレートは通常名前空間が省略されているので補完する
385 string filledTitle = this.FillTemplateName(template, parent);
387 // リンクを辿り、対象記事の言語間リンクを取得
388 string interWiki = this.GetInterwiki(new MediaWikiTemplate(filledTitle));
389 if (interWiki == null)
391 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
394 else if (interWiki == String.Empty)
396 // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
397 // おまけで、元のテンプレートの状態をコメントでつける
398 ListElement list = new ListElement();
399 MediaWikiLink link = new MediaWikiLink();
401 link.Title = this.From.Language.Code + ':' + filledTitle;
403 XmlCommentElement comment = new XmlCommentElement();
404 comment.Raw = ' ' + template.ToString() + ' ';
410 // 言語間リンクが存在する場合、そちらを指すように置換
411 // : より前の部分を削除して出力(: が無いときは-1+1で0から)
412 template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
414 // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
415 template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
416 template.ParsedString = null;
422 /// 指定された見出しに対して、対訳表による変換を行う。
424 /// <param name="heading">見出し。</param>
425 /// <param name="parent">サブページ用の親記事タイトル。</param>
426 /// <returns>変換後の見出し。</returns>
427 protected virtual IElement ReplaceHeading(MediaWikiHeading heading, string parent)
430 this.Logger.AddSource(heading);
433 StringBuilder oldText = new StringBuilder();
434 foreach (IElement e in heading)
436 oldText.Append(e.ToString());
439 string newText = this.GetHeading(oldText.ToString().Trim());
442 // 対訳表による変換が行えた場合、変換先をログ出力し処理終了
444 heading.ParsedString = null;
445 heading.Add(new XmlTextElement(newText));
446 this.Logger.AddDestination(heading);
450 // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
451 return this.ReplaceListElement(heading, parent);
455 /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
457 /// <param name="variable">変換元変数要素。</param>
458 /// <param name="parent">サブページ用の親記事タイトル。</param>
459 /// <returns>変換済み変数要素。</returns>
460 protected virtual IElement ReplaceVariable(MediaWikiVariable variable, string parent)
462 // 変数、これ自体は処理しないが、再帰的に探索
463 string old = variable.Value.ToString();
464 variable.Value = this.ReplaceElement(variable.Value, parent);
465 if (variable.Value.ToString() != old)
467 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
468 variable.ParsedString = null;
475 /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
477 /// <param name="listElement">変換元要素。</param>
478 /// <param name="parent">サブページ用の親記事タイトル。</param>
479 /// <returns>変換済み要素。</returns>
480 protected virtual IElement ReplaceListElement(ListElement listElement, string parent)
482 // 値を格納する要素、これ自体は処理しないが、再帰的に探索
483 for (int i = 0; i < listElement.Count; i++)
485 string old = listElement[i].ToString();
486 listElement[i] = this.ReplaceElement(listElement[i], parent);
487 if (listElement[i].ToString() != old)
489 // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
490 listElement.ParsedString = null;
502 /// 対訳表に指定された記事名の情報が登録されているか?
504 /// <param name="title">記事名。</param>
505 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
506 /// <remarks>複数スレッドからのアクセスに対応する。また項目の対訳表が無い場合も動作する。</remarks>
507 protected bool ContainsAtItemTable(string title)
509 if (this.ItemTable == null)
514 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
515 lock (this.ItemTable)
517 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
518 return this.ItemTable.ContainsKey(WebUtility.HtmlDecode(title));
523 /// 対訳表から指定された記事名の情報を取得する。
525 /// <param name="title">記事名。</param>
526 /// <param name="item">翻訳先情報。</param>
527 /// <returns>指定した記事の情報が登録されている場合<c>true</c>。</returns>
528 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
529 protected bool TryGetValueAtItemTable(string title, out TranslationDictionary.Item item)
531 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
532 // ※ 同時アクセスを防いでいるだけで、更新処理とは同期していない。
533 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
534 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
535 lock (this.ItemTable)
537 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
538 return this.ItemTable.TryGetValue(WebUtility.HtmlDecode(title), out item);
543 /// 対訳表に指定された記事名の情報を登録する。
545 /// <param name="title">記事名。</param>
546 /// <param name="item">翻訳先情報。</param>
547 /// <remarks>複数スレッドからのアクセスに対応する。</remarks>
548 protected void PutValueAtItemTable(string title, TranslationDictionary.Item item)
550 // 以下マルチスレッドで使われることも想定して対訳表へのアクセス時はロック
551 // ※ 同時アクセスを防いでいるだけで、読込処理とは同期していない。
552 // 現状では同じページを同時に解析してしまう可能性があり、効率が良いソースではない。
553 // また、効率を目指すならItemTableではなく記事名ごとに一意なオブジェクトをロックすべき
554 lock (this.ItemTable)
556 // 対訳表へのキーとしてはHTMLデコードした記事名を使用する
557 this.ItemTable[WebUtility.HtmlDecode(title)] = item;
562 /// 指定されたコードでの見出しに相当する、別の言語での見出しを取得。
564 /// <param name="heading">翻訳元言語での見出し。</param>
565 /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
566 /// <remarks>見出しの対訳表が無い場合も動作する。</remarks>
567 protected string GetHeading(string heading)
569 if (this.HeadingTable == null)
574 return this.HeadingTable.GetWord(heading);
582 /// ロガーに取得結果を出力しつつ、指定された要素の記事の翻訳先言語への言語間リンクを返す。
584 /// <param name="element">内部リンク要素。</param>
585 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
586 /// <remarks>取得処理では対訳表を使用する。また新たな取得結果は対訳表に追加する。</remarks>
587 protected string GetInterwiki(MediaWikiLink element)
590 this.Logger.AddSource(element);
591 string title = element.Title;
592 TranslationDictionary.Item item;
593 if (this.ItemTable == null)
595 // 対訳表が指定されていない場合は、使わずに言語間リンクを探索して終了
596 return this.GetInterwikiWithCreateCache(title, out item);
600 if (this.TryGetValueAtItemTable(title, out item))
603 if (!String.IsNullOrWhiteSpace(item.Alias))
605 // リダイレクトがあれば、そのメッセージも表示
606 this.Logger.AddAlias(new MediaWikiLink(item.Alias));
609 if (!String.IsNullOrEmpty(item.Word))
611 this.Logger.AddDestination(new MediaWikiLink(item.Word), true);
616 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound), true);
621 // 対訳表に存在しない場合は、普通に取得し表に記録
622 string interWiki = this.GetInterwikiWithCreateCache(title, out item);
623 if (interWiki != null)
625 // ページ自体が存在しない場合を除き、結果を対訳表に登録
626 // ※ キャッシュとしては登録すべきかもしれないが、一応"対訳表"であるので
627 this.PutValueAtItemTable(title, item);
634 /// ロガーに取得結果を出力しつつ、指定された記事の翻訳先言語への言語間リンクを返す。
635 /// キャッシュ用の処理結果情報も出力する。
637 /// <param name="title">記事名。</param>
638 /// <param name="item">キャッシュ用の処理結果情報。</param>
639 /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
640 private string GetInterwikiWithCreateCache(string title, out TranslationDictionary.Item item)
643 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
644 MediaWikiPage page = this.GetDestinationPage(title);
645 if (page != null && page.IsRedirect())
647 // リダイレクトの場合、リダイレクトである旨出力し、その先の記事を取得
648 this.Logger.AddAlias(new MediaWikiLink(page.Redirect.Title));
649 item.Alias = page.Redirect.Title;
650 page = this.GetDestinationPage(page.Redirect.Title);
654 string interWiki = null;
657 interWiki = page.GetInterWiki(this.To.Language.Code);
658 item.Word = interWiki;
659 if (!String.IsNullOrEmpty(interWiki))
661 this.Logger.AddDestination(new MediaWikiLink(interWiki));
665 this.Logger.AddDestination(new TextElement(Resources.LogMessageInterWikiNotFound));
675 /// <param name="title">ページタイトル。</param>
676 /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
677 /// <remarks>記事が無い場合、通信エラーなど例外が発生した場合は、エラーログを出力する。</remarks>
678 private MediaWikiPage GetDestinationPage(string title)
681 if (this.TryGetPage(title, out page) && page == null)
683 // 記事が存在しない場合だけ、変換先に「記事無し」を出力
684 // ※ エラー時のログはTryGetPageが自動的に出力
685 this.Logger.AddDestination(new TextElement(Resources.LogMessageLinkArticleNotFound));
693 #region 要素の変換関連その他メソッド
696 /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
698 /// <param name="link">判定する内部リンク。</param>
699 /// <param name="parent">内部リンクがあった記事。</param>
700 /// <returns>セクション部分のみ変換済みリンク。</returns>
701 private bool IsSectionLink(MediaWikiLink link, string parent)
703 // 記事名が指定されていない、または記事名が自分の記事名で
704 // 言語コード等も特に無く、かつセクションが指定されている場合
705 // (記事名もセクションも指定されていない・・・というケースもありえるが、
706 // その場合他に指定できるものも思いつかないので通す)
707 return String.IsNullOrEmpty(link.Title)
708 || (link.Title == parent && String.IsNullOrEmpty(link.Code) && !String.IsNullOrEmpty(link.Section));
712 /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
714 /// <param name="section">セクション文字列。</param>
715 /// <returns>セクション部分のみ変換済みリンク。</returns>
716 private string ReplaceLinkSection(string section)
718 // セクションが指定されている場合、定型句変換を通す
719 string heading = this.GetHeading(section);
720 return heading != null ? heading : section;
724 /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
726 /// <param name="link">変換元言語間リンク。</param>
727 /// <returns>変換済み言語間リンク。</returns>
728 private IElement ReplaceLinkInterwiki(MediaWikiLink link)
730 // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
731 // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
732 if (!link.IsColon && link.Code == this.To.Language.Code)
734 return new TextElement();
741 /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
743 /// <param name="link">変換元カテゴリ。</param>
744 /// <returns>変換済みカテゴリ。</returns>
745 private IElement ReplaceLinkCategory(MediaWikiLink link)
747 // リンクを辿り、対象記事の言語間リンクを取得
748 string interWiki = this.GetInterwiki(link);
749 if (interWiki == null)
751 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
754 else if (interWiki == String.Empty)
756 // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
757 // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
758 XmlCommentElement comment = new XmlCommentElement();
759 comment.Raw = ' ' + link.ToString() + ' ';
761 link.Title = this.From.Language.Code + ':' + link.Title;
763 link.PipeTexts.Clear();
764 link.ParsedString = null;
766 ListElement list = new ListElement();
773 // 普通に言語間リンクが存在する場合、記事名を置き換え
774 link.Title = interWiki;
775 link.ParsedString = null;
781 /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
783 /// <param name="link">変換元リンク。</param>
784 /// <param name="parent">サブページ用の親記事タイトル。</param>
785 /// <returns>変換済みリンク。</returns>
786 private IElement ReplaceLinkFile(MediaWikiLink link, string parent)
788 // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
789 link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
790 link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
791 link.ParsedString = null;
796 /// 記事名のうち名前空間部分の変換先言語への変換を行う。
798 /// <param name="title">変換元記事名。</param>
799 /// <param name="id">名前空間のID。</param>
800 /// <returns>変換済み記事名。</returns>
801 private string ReplaceLinkNamespace(string title, int id)
805 if (!this.To.Namespaces.TryGetValue(id, out names))
807 // 翻訳先言語に相当する名前空間が無い場合、何もしない
811 // 記事名の名前空間部分を置き換えて返す
812 return names[0] + title.Substring(title.IndexOf(':'));
816 /// 内部リンクを他言語版への{{仮リンク}}等に変換する。。
818 /// <param name="link">変換元言語間リンク。</param>
819 /// <returns>変換済み言語間リンク。</returns>
820 private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
822 // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
823 // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
824 MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
825 string langTitle = title.GetLinkString();
826 if (!String.IsNullOrEmpty(title.Section))
828 // 変換先言語版のセクションは、セクションの変換を通したものにする
829 title.Section = this.ReplaceLinkSection(title.Section);
832 // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
833 string label = langTitle;
834 if (link.PipeTexts.Count > 0)
836 label = link.PipeTexts.Last().ToString();
840 // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
841 return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
845 /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
847 /// <param name="elements">変換元要素リスト。</param>
848 /// <param name="parent">サブページ用の親記事タイトル。</param>
849 /// <returns>変換済み要素リスト。</returns>
850 private IList<IElement> ReplaceElements(IList<IElement> elements, string parent)
852 if (elements == null)
857 IList<IElement> result = new List<IElement>();
858 foreach (IElement e in elements)
860 result.Add(this.ReplaceElement(e, parent));
867 /// テンプレート名に必要に応じて名前空間を補完する。
869 /// <param name="template">テンプレート。</param>
870 /// <param name="parent">サブページ用の親記事タイトル。</param>
871 /// <returns>補完済みのテンプレート名。</returns>
872 private string FillTemplateName(MediaWikiTemplate template, string parent)
874 if (template.IsColon || !new MediaWikiPage(this.From, template.Title).IsMain())
876 // 標準名前空間が指定されている(先頭にコロンが無い)
877 // または何かしらの名前空間が指定されている場合、補完不要
878 return template.Title;
880 else if (template.IsSubpage)
882 // サブページの場合、親記事名での補完のみ
883 return parent + template.Title;
886 // 補完する必要がある場合、名前空間のプレフィックス(Template等)を取得
887 string prefix = this.GetTemplatePrefix();
888 if (String.IsNullOrEmpty(prefix))
890 // 名前空間の設定が存在しない場合、何も出来ないため終了
891 return template.Title;
894 // 頭にプレフィックスを付けた記事名で実在するかをチェック
895 string filledTitle = prefix + ":" + template.Title;
897 // 既に対訳表にプレフィックス付きの記事名が確認されているか?
898 if (this.ContainsAtItemTable(filledTitle))
900 // 記事が存在する場合、プレフィックスをつけた名前を使用
904 // 未確認の場合、実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
905 // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
906 MediaWikiPage page = null;
909 // 記事が存在する場合、プレフィックスをつけた名前を使用
910 page = this.From.GetPage(WebUtility.HtmlDecode(filledTitle)) as MediaWikiPage;
913 catch (FileNotFoundException)
915 // 記事が存在しない場合、元のページ名を使用
916 return template.Title;
921 if (!Settings.Default.IgnoreError)
923 // エラーを無視しない場合、ここで翻訳支援処理を中断する
924 throw new ApplicationException(e.Message, e);
927 // 続行する場合は、とりあえずプレフィックスをつけた名前で処理
928 this.Logger.AddMessage(Resources.LogMessageTemplateNameUnidentified, template.Title, prefix, e.Message);
934 /// テンプレート名前空間のプレフィックスを取得。
936 /// <returns>プレフィックス。取得できない場合<c>null</c></returns>
937 private string GetTemplatePrefix()
939 IList<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
940 if (prefixes != null)
942 return prefixes.FirstOrDefault();
955 /// <param name="title">翻訳支援対象の記事名。</param>
956 /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
957 private MediaWikiPage GetTargetPage(string title)
959 // 指定された記事をWikipediaから取得、リダイレクトの場合その先まで探索
960 // ※ この処理ではキャッシュは使用しない。
961 // ※ 万が一相互にリダイレクトしていると無限ループとなるが、特に判定はしない。
962 // ユーザーが画面上から止めることを期待。
963 this.Logger.AddMessage(Resources.LogMessageGetTargetArticle, this.From.Location, title);
965 for (string s = title; this.TryGetPage(s, out page); s = page.Redirect.Title)
969 // 記事が存在しない場合、メッセージを出力して終了
970 this.Logger.AddResponse(Resources.LogMessageTargetArticleNotFound);
973 else if (!page.IsRedirect())
979 // リダイレクトであれば、さらにその先の記事を取得
980 this.Logger.AddResponse(Resources.LogMessageRedirect
981 + " " + new MediaWikiLink(page.Redirect.Title).ToString());
988 /// 指定した言語での言語名称を [[言語名称|略称]]の内部リンクで取得。
990 /// <param name="site">サイト。</param>
991 /// <param name="code">言語のコード。</param>
992 /// <returns>[[言語名称|略称]]の内部リンク。登録されていない場合<c>null</c>。</returns>
993 private MediaWikiLink GetLanguageLink(Website site, string code)
995 if (!site.Language.Names.ContainsKey(code))
1000 Language.LanguageName name = site.Language.Names[code];
1001 MediaWikiLink link = new MediaWikiLink(name.Name);
1002 if (!String.IsNullOrEmpty(name.ShortName))
1004 link.PipeTexts.Add(new TextElement(name.ShortName));
1011 /// 対象記事に言語間リンクが存在する場合にメッセージダイアログでユーザーに確認する処理。
1013 /// <param name="interwiki">言語間リンク先記事。</param>
1014 /// <returns>処理を続行する場合<c>true</c>。</returns>
1015 private bool IsContinueAtInterwikiExistedWithDialog(string interwiki)
1018 if (MessageBox.Show(
1019 String.Format(Resources.QuestionMessageArticleExisted, interwiki),
1020 Resources.QuestionTitle,
1021 MessageBoxButtons.YesNo,
1022 MessageBoxIcon.Question)
1025 // 中断の場合、同じメッセージをログにも表示
1026 this.Logger.AddSeparator();
1027 this.Logger.AddMessage(Resources.QuestionMessageArticleExisted, interwiki);