OSDN Git Service

svnプロパティをファイルの種類に応じたものに更新
[wptscs/wpts.git] / Wptscs / Models / MediaWikiPage.cs
1 // ================================================================================================
2 // <summary>
3 //      MediaWikiのページをあらわすモデルクラスソース</summary>
4 //
5 // <copyright file="MediaWikiPage.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs.Models
12 {
13     using System;
14     using System.Collections.Generic;
15     using System.Linq;
16     using System.Text;
17     using Honememo.Utilities;
18
19     /// <summary>
20     /// MediaWikiのページをあらわすモデルクラスです。
21     /// </summary>
22     public class MediaWikiPage : Page
23     {
24         #region 定数宣言
25
26         /// <summary>
27         /// nowikiタグ。
28         /// </summary>
29         public static readonly string NowikiTag = "nowiki";
30
31         /// <summary>
32         /// msgnwの書式。
33         /// </summary>
34         public static readonly string Msgnw = "msgnw:";
35
36         #endregion
37
38         #region private変数
39
40         /// <summary>
41         /// リダイレクト先のページ名。
42         /// </summary>
43         private Link redirect;
44
45         #endregion
46
47         #region コンストラクタ
48
49         /// <summary>
50         /// コンストラクタ。
51         /// </summary>
52         /// <param name="website">ページが所属するウェブサイト。</param>
53         /// <param name="title">ページタイトル。</param>
54         /// <param name="text">ページの本文。</param>
55         /// <param name="timestamp">ページのタイムスタンプ。</param>
56         public MediaWikiPage(MediaWiki website, string title, string text, DateTime? timestamp)
57             : base(website, title, text, timestamp)
58         {
59         }
60
61         /// <summary>
62         /// コンストラクタ。
63         /// ページのタイムスタンプには<c>null</c>を設定。
64         /// </summary>
65         /// <param name="website">ページが所属するウェブサイト。</param>
66         /// <param name="title">ページタイトル。</param>
67         /// <param name="text">ページの本文。</param>
68         public MediaWikiPage(MediaWiki website, string title, string text)
69             : base(website, title, text)
70         {
71         }
72
73         /// <summary>
74         /// コンストラクタ。
75         /// ページの本文, タイムスタンプには<c>null</c>を設定。
76         /// </summary>
77         /// <param name="website">ページが所属するウェブサイト。</param>
78         /// <param name="title">ページタイトル。</param>
79         public MediaWikiPage(MediaWiki website, string title)
80             : base(website, title)
81         {
82         }
83
84         #endregion
85
86         #region プロパティ
87
88         /// <summary>
89         /// ページが所属するウェブサイト。
90         /// </summary>
91         public new MediaWiki Website
92         {
93             get
94             {
95                 return base.Website as MediaWiki;
96             }
97
98             protected set
99             {
100                 base.Website = value;
101             }
102         }
103
104         /// <summary>
105         /// ページの本文。
106         /// </summary>
107         public override string Text
108         {
109             get
110             {
111                 return base.Text;
112             }
113
114             protected set
115             {
116                 // 本文は普通に格納
117                 base.Text = value;
118
119                 // 本文格納のタイミングでリダイレクトページ(#REDIRECT等)かを判定
120                 if (!String.IsNullOrEmpty(base.Text))
121                 {
122                     this.TryParseRedirect();
123                 }
124             }
125         }
126
127         /// <summary>
128         /// リダイレクト先へのリンク。
129         /// </summary>
130         public Link Redirect
131         {
132             get
133             {
134                 // Textが設定されている場合のみ有効
135                 this.ValidateIncomplete();
136                 return this.redirect;
137             }
138
139             protected set
140             {
141                 this.redirect = value;
142             }
143         }
144
145         #endregion
146
147         #region 公開静的メソッド
148
149         /// <summary>
150         /// 渡されたテキストがnowikiブロックかを解析する。
151         /// </summary>
152         /// <param name="text">解析するテキスト。</param>
153         /// <param name="nowiki">解析したnowikiブロック。</param>
154         /// <returns>nowikiブロックの場合<c>true</c>。</returns>
155         /// <remarks>
156         /// nowikiブロックと判定するには、1文字目が開始タグである必要がある。
157         /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
158         /// また、入れ子は考慮しない。
159         /// </remarks>
160         public static bool TryParseNowiki(string text, out string nowiki)
161         {
162             nowiki = null;
163             LazyXmlParser parser = new LazyXmlParser();
164             LazyXmlParser.SimpleElement element;
165             if (parser.TryParse(text, out element))
166             {
167                 if (element.Name.ToLower() == MediaWikiPage.NowikiTag)
168                 {
169                     nowiki = element.OuterXml;
170                     return true;
171                 }
172             }
173
174             return false;
175         }
176
177         #endregion
178
179         #region 公開インスタンスメソッド
180
181         /// <summary>
182         /// 指定された言語コードへの言語間リンクを返す。
183         /// </summary>
184         /// <param name="code">言語コード。</param>
185         /// <returns>言語間リンク先の記事名。見つからない場合は空。</returns>
186         /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
187         public string GetInterWiki(string code)
188         {
189             // Textが設定されている場合のみ有効
190             this.ValidateIncomplete();
191
192             // 記事に存在する指定言語への言語間リンクを取得
193             for (int i = 0; i < this.Text.Length; i++)
194             {
195                 char c = this.Text[i];
196                 Link link;
197                 switch (c)
198                 {
199                     case '<':
200                         // コメント(<!--)またはnowiki区間の場合飛ばす
201                         string subtext = this.Text.Substring(i);
202                         string value;
203                         if (LazyXmlParser.TryParseComment(subtext, out value))
204                         {
205                             i += value.Length - 1;
206                         }
207                         else if (MediaWikiPage.TryParseNowiki(subtext, out value))
208                         {
209                             i += value.Length - 1;
210                         }
211
212                         break;
213
214                     case '{':
215                         // テンプレート
216                         if (this.TryParseTemplate(this.Text.Substring(i), out link))
217                         {
218                             i += link.OriginalText.Length - 1;
219
220                             // Documentationテンプレートがある場合は、その中を探索
221                             string interWiki = this.GetDocumentationInterWiki(link, code);
222                             if (!String.IsNullOrEmpty(interWiki))
223                             {
224                                 return interWiki;
225                             }
226                         }
227
228                         break;
229
230                     case '[':
231                         // リンク
232                         if (this.TryParseLink(this.Text.Substring(i), out link))
233                         {
234                             i += link.OriginalText.Length - 1;
235
236                             // 指定言語への言語間リンクの場合、内容を取得し、処理終了
237                             if (link.Code == code && !link.IsColon)
238                             {
239                                 return link.Title;
240                             }
241                         }
242
243                         break;
244                 }
245             }
246
247             // 未発見の場合、空文字列
248             return String.Empty;
249         }
250
251         /// <summary>
252         /// ページがリダイレクトかをチェック。
253         /// </summary>
254         /// <returns><c>true</c> リダイレクト。</returns>
255         public bool IsRedirect()
256         {
257             // Textが設定されている場合のみ有効
258             return this.Redirect != null;
259         }
260
261         /// <summary>
262         /// ページがテンプレートかをチェック。
263         /// </summary>
264         /// <returns><c>true</c> テンプレート。</returns>
265         public bool IsTemplate()
266         {
267             // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
268             return this.IsNamespacePage(this.Website.TemplateNamespace);
269         }
270
271         /// <summary>
272         /// ページがカテゴリーかをチェック。
273         /// </summary>
274         /// <returns><c>true</c> カテゴリー。</returns>
275         public bool IsCategory()
276         {
277             // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
278             return this.IsNamespacePage(this.Website.CategoryNamespace);
279         }
280
281         /// <summary>
282         /// ページが画像かをチェック。
283         /// </summary>
284         /// <returns><c>true</c> 画像。</returns>
285         public bool IsFile()
286         {
287             // 指定されたページ名がファイル(Image:等で始まる)かをチェック
288             return this.IsNamespacePage(this.Website.FileNamespace);
289         }
290
291         /// <summary>
292         /// ページが標準名前空間かをチェック。
293         /// </summary>
294         /// <returns><c>true</c> 標準名前空間。</returns>
295         public bool IsMain()
296         {
297             // 指定されたページ名が標準名前空間以外の名前空間(Wikipedia:等で始まる)かをチェック
298             string title = this.Title.ToLower();
299             foreach (IList<string> prefixes in this.Website.Namespaces.Values)
300             {
301                 foreach (string prefix in prefixes)
302                 {
303                     if (title.StartsWith(prefix.ToLower() + ":"))
304                     {
305                         return false;
306                     }
307                 }
308             }
309
310             return true;
311         }
312
313         #region Linkクラスに移動したいメソッド
314
315         // TODO: 以下の各メソッドのうち、リンクに関するものはLinkクラスに移したい。
316         //       また、余計な依存関係を持っているものを整理したい。
317
318         /// <summary>
319         /// 渡されたWikipediaの内部リンクを解析。
320         /// </summary>
321         /// <param name="text">[[で始まる文字列。</param>
322         /// <param name="link">解析したリンク。</param>
323         /// <returns>解析に成功した場合<c>true</c>。</returns>
324         public bool TryParseLink(string text, out Link link)
325         {
326             // 出力値初期化
327             link = null;
328
329             // 入力値確認
330             if (!text.StartsWith("[["))
331             {
332                 return false;
333             }
334
335             // 構文を解析して、[[]]内部の文字列を取得
336             // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
337             string article = String.Empty;
338             string section = String.Empty;
339             IList<string> pipeTexts = new List<string>();
340             int lastIndex = -1;
341             int pipeCounter = 0;
342             bool sharpFlag = false;
343             for (int i = 2; i < text.Length; i++)
344             {
345                 char c = text[i];
346
347                 // ]]が見つかったら、処理正常終了
348                 if (StringUtils.StartsWith(text, "]]", i))
349                 {
350                     lastIndex = ++i;
351                     break;
352                 }
353
354                 // | が含まれている場合、以降の文字列は表示名などとして扱う
355                 if (c == '|')
356                 {
357                     ++pipeCounter;
358                     pipeTexts.Add(String.Empty);
359                     continue;
360                 }
361
362                 // 変数([[{{{1}}}]]とか)の再帰チェック
363                 string dummy;
364                 string variable;
365                 int index = this.ChkVariable(out variable, out dummy, text, i);
366                 if (index != -1)
367                 {
368                     i = index;
369                     if (pipeCounter > 0)
370                     {
371                         pipeTexts[pipeCounter - 1] += variable;
372                     }
373                     else if (sharpFlag)
374                     {
375                         section += variable;
376                     }
377                     else
378                     {
379                         article += variable;
380                     }
381
382                     continue;
383                 }
384
385                 // | の前のとき
386                 if (pipeCounter <= 0)
387                 {
388                     // 変数以外で { } または < > [ ] \n が含まれている場合、リンクは無効
389                     if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}') || (c == '\n'))
390                     {
391                         break;
392                     }
393
394                     // # の前のとき
395                     if (!sharpFlag)
396                     {
397                         // #が含まれている場合、以降の文字列は見出しへのリンクとして扱う(1つめの#のみ有効)
398                         if (c == '#')
399                         {
400                             sharpFlag = true;
401                         }
402                         else
403                         {
404                             article += c;
405                         }
406                     }
407                     else
408                     {
409                         // # の後のとき
410                         section += c;
411                     }
412                 }
413                 else
414                 {
415                     // | の後のとき
416                     if (c == '<')
417                     {
418                         string subtext = text.Substring(i);
419                         string value;
420                         if (LazyXmlParser.TryParseComment(subtext, out value))
421                         {
422                             // コメント(<!--)が含まれている場合、リンクは無効
423                             break;
424                         }
425                         else if (MediaWikiPage.TryParseNowiki(subtext, out value))
426                         {
427                             // nowikiブロック
428                             i += value.Length - 1;
429                             pipeTexts[pipeCounter - 1] += value;
430                             continue;
431                         }
432                     }
433
434                     // リンク [[ {{ ([[image:xx|[[test]]の画像]]とか)の再帰チェック
435                     Link l;
436                     index = this.ChkLinkText(out l, text, i);
437                     if (index != -1)
438                     {
439                         i = index;
440                         pipeTexts[pipeCounter - 1] += l.OriginalText;
441                         continue;
442                     }
443
444                     pipeTexts[pipeCounter - 1] += c;
445                 }
446             }
447
448             // 解析失敗
449             if (lastIndex < 0)
450             {
451                 return false;
452             }
453
454             // 解析に成功した場合、結果を出力値に設定
455             link = new Link();
456
457             // 変数ブロックの文字列をリンクのテキストに設定
458             link.OriginalText = text.Substring(0, lastIndex + 1);
459
460             // 前後のスペースは削除(見出しは後ろのみ)
461             link.Title = article.Trim();
462             link.Section = section.TrimEnd();
463
464             // | 以降はそのまま設定
465             link.PipeTexts = pipeTexts;
466
467             // 記事名から情報を抽出
468             // サブページ
469             if (link.Title.StartsWith("/"))
470             {
471                 link.IsSubpage = true;
472             }
473             else if (link.Title.StartsWith(":"))
474             {
475                 // 先頭が :
476                 link.IsColon = true;
477                 link.Title = link.Title.TrimStart(':').TrimStart();
478             }
479
480             // 標準名前空間以外で[[xxx:yyy]]のようになっている場合、言語コード
481             if (link.Title.Contains(":") && new MediaWikiPage(this.Website, link.Title).IsMain())
482             {
483                 // ※本当は、言語コード等の一覧を作り、其処と一致するものを・・・とすべきだろうが、
484                 //   メンテしきれないので : を含む名前空間以外を全て言語コード等と判定
485                 link.Code = link.Title.Substring(0, link.Title.IndexOf(':')).TrimEnd();
486                 link.Title = link.Title.Substring(link.Title.IndexOf(':') + 1).TrimStart();
487             }
488
489             return true;
490         }
491
492         /// <summary>
493         /// 渡されたWikipediaのテンプレートを解析。
494         /// </summary>
495         /// <param name="text">{{で始まる文字列。</param>
496         /// <param name="link">解析したテンプレートのリンク。</param>
497         /// <returns>解析に成功した場合<c>true</c>。</returns>
498         public bool TryParseTemplate(string text, out Link link)
499         {
500             // 出力値初期化
501             link = null;
502
503             // 入力値確認
504             if (!text.StartsWith("{{"))
505             {
506                 return false;
507             }
508
509             // 構文を解析して、{{}}内部の文字列を取得
510             // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
511             string article = String.Empty;
512             IList<string> pipeTexts = new List<string>();
513             int lastIndex = -1;
514             int pipeCounter = 0;
515             for (int i = 2; i < text.Length; i++)
516             {
517                 char c = text[i];
518
519                 // }}が見つかったら、処理正常終了
520                 if (StringUtils.StartsWith(text, "}}", i))
521                 {
522                     lastIndex = ++i;
523                     break;
524                 }
525
526                 // | が含まれている場合、以降の文字列は引数などとして扱う
527                 if (c == '|')
528                 {
529                     ++pipeCounter;
530                     pipeTexts.Add(String.Empty);
531                     continue;
532                 }
533
534                 // 変数([[{{{1}}}]]とか)の再帰チェック
535                 string dummy;
536                 string variable;
537                 int index = this.ChkVariable(out variable, out dummy, text, i);
538                 if (index != -1)
539                 {
540                     i = index;
541                     if (pipeCounter > 0)
542                     {
543                         pipeTexts[pipeCounter - 1] += variable;
544                     }
545                     else
546                     {
547                         article += variable;
548                     }
549
550                     continue;
551                 }
552
553                 // | の前のとき
554                 if (pipeCounter <= 0)
555                 {
556                     // 変数以外で < > [ ] { } が含まれている場合、リンクは無効
557                     if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}'))
558                     {
559                         break;
560                     }
561
562                     article += c;
563                 }
564                 else
565                 {
566                     // | の後のとき
567                     if (c == '<')
568                     {
569                         string subtext = text.Substring(i);
570                         string value;
571                         if (LazyXmlParser.TryParseComment(subtext, out value))
572                         {
573                             // コメント(<!--)が含まれている場合、リンクは無効
574                             break;
575                         }
576                         else if (MediaWikiPage.TryParseNowiki(subtext, out value))
577                         {
578                             // nowikiブロック
579                             i += value.Length - 1;
580                             pipeTexts[pipeCounter - 1] += value;
581                             continue;
582                         }
583                     }
584
585                     // リンク [[ {{ ({{test|[[例]]}}とか)の再帰チェック
586                     Link l;
587                     index = this.ChkLinkText(out l, text, i);
588                     if (index != -1)
589                     {
590                         i = index;
591                         pipeTexts[pipeCounter - 1] += l.OriginalText;
592                         continue;
593                     }
594
595                     pipeTexts[pipeCounter - 1] += c;
596                 }
597             }
598
599             // 解析失敗
600             if (lastIndex < 0)
601             {
602                 return false;
603             }
604
605             // 解析に成功した場合、結果を出力値に設定
606             link = new Link();
607             link.IsTemplateTag = true;
608
609             // 変数ブロックの文字列をリンクのテキストに設定
610             link.OriginalText = text.Substring(0, lastIndex + 1);
611
612             // 前後のスペース・改行は削除(見出しは後ろのみ)
613             link.Title = article.Trim();
614
615             // | 以降はそのまま設定
616             link.PipeTexts = pipeTexts;
617
618             // 記事名から情報を抽出
619             // サブページ
620             if (link.Title.StartsWith("/") == true)
621             {
622                 link.IsSubpage = true;
623             }
624             else if (link.Title.StartsWith(":"))
625             {
626                 // 先頭が :
627                 link.IsColon = true;
628                 link.Title = link.Title.TrimStart(':').TrimStart();
629             }
630
631             // 先頭が msgnw:
632             link.IsMsgnw = link.Title.ToLower().StartsWith(Msgnw.ToLower());
633             if (link.IsMsgnw)
634             {
635                 link.Title = link.Title.Substring(Msgnw.Length);
636             }
637
638             // 記事名直後の改行の有無
639             if (article.TrimEnd(' ').EndsWith("\n"))
640             {
641                 link.Enter = true;
642             }
643
644             return true;
645         }
646
647         #endregion
648
649         /// <summary>
650         /// 渡されたテキストの指定された位置に存在するWikipediaの内部リンク・テンプレートをチェック。
651         /// </summary>
652         /// <param name="link">解析したリンク。</param>
653         /// <param name="text">解析するテキスト。</param>
654         /// <param name="index">解析開始インデックス。</param>
655         /// <returns>正常時の戻り値には、]]の後ろの]の位置のインデックスを返す。異常時は-1。</returns>
656         public int ChkLinkText(out Link link, string text, int index)
657         {
658             // 入力値に応じて、処理を振り分け
659             if (StringUtils.StartsWith(text, "[[", index))
660             {
661                 // 内部リンク
662                 if (this.TryParseLink(text.Substring(index), out link))
663                 {
664                     return index + link.OriginalText.Length - 1;
665                 }
666             }
667             else if (StringUtils.StartsWith(text, "{{", index))
668             {
669                 // テンプレート
670                 if (this.TryParseTemplate(text.Substring(index), out link))
671                 {
672                     return index + link.OriginalText.Length - 1;
673                 }
674             }
675
676             // 出力値初期化。リンク以外の場合、nullを返す
677             link = null;
678             return -1;
679         }
680
681         /// <summary>
682         /// 渡されたテキストの指定された位置に存在する変数を解析。
683         /// </summary>
684         /// <param name="variable">解析した変数。</param>
685         /// <param name="value">変数のパラメータ値。</param>
686         /// <param name="text">解析するテキスト。</param>
687         /// <param name="index">解析開始インデックス。</param>
688         /// <returns>正常時の戻り値には、変数の終了位置のインデックスを返す。異常時は-1。</returns>
689         public int ChkVariable(out string variable, out string value, string text, int index)
690         {
691             // 出力値初期化
692             int lastIndex = -1;
693             variable = String.Empty;
694             value = String.Empty;
695
696             // 入力値確認
697             if (!StringUtils.StartsWith(text, "{{{", index))
698             {
699                 return lastIndex;
700             }
701
702             // ブロック終了まで取得
703             bool pipeFlag = false;
704             for (int i = index + 3; i < text.Length; i++)
705             {
706                 // 終了条件のチェック
707                 if (StringUtils.StartsWith(text, "}}}", i))
708                 {
709                     lastIndex = i + 2;
710                     break;
711                 }
712
713                 if (text[i] == '<')
714                 {
715                     string comment;
716                     if (LazyXmlParser.TryParseComment(text.Substring(i), out comment))
717                     {
718                         // コメント(<!--)ブロック
719                         i += comment.Length - 1;
720                         continue;
721                     }
722                 }
723
724                 // | が含まれている場合、以降の文字列は代入された値として扱う
725                 if (text[i] == '|')
726                 {
727                     pipeFlag = true;
728                 }
729                 else if (!pipeFlag)
730                 {
731                     // | の前のとき
732                     // ※Wikipediaの仕様上は、{{{1{|表示}}} のように変数名の欄に { を
733                     //   含めることができるようだが、判別しきれないので、エラーとする
734                     //   (どうせ意図してそんなことする人は居ないだろうし・・・)
735                     if (text[i] == '{')
736                     {
737                         break;
738                     }
739                 }
740                 else
741                 {
742                     // | の後のとき
743                     if (text[i] == '<')
744                     {
745                         string nowiki;
746                         if (MediaWikiPage.TryParseNowiki(text.Substring(i), out nowiki))
747                         {
748                             // nowikiブロック
749                             i += nowiki.Length - 1;
750                             value += nowiki;
751                             continue;
752                         }
753                     }
754
755                     // 変数({{{1|{{{2}}}}}}とか)の再帰チェック
756                     string var;
757                     string dummy;
758                     int subindex = this.ChkVariable(out var, out dummy, text, i);
759                     if (subindex != -1)
760                     {
761                         i = subindex;
762                         value += var;
763                         continue;
764                     }
765
766                     // リンク [[ {{ ({{{1|[[test]]}}}とか)の再帰チェック
767                     Link link;
768                     subindex = this.ChkLinkText(out link, text, i);
769                     if (subindex != -1)
770                     {
771                         i = subindex;
772                         value += link.OriginalText;
773                         continue;
774                     }
775
776                     value += text[i];
777                 }
778             }
779
780             // 変数ブロックの文字列を出力値に設定
781             if (lastIndex != -1)
782             {
783                 variable = text.Substring(index, lastIndex - index + 1);
784             }
785             else
786             {
787                 // 正常な構文ではなかった場合、出力値をクリア
788                 variable = String.Empty;
789                 value = String.Empty;
790             }
791
792             return lastIndex;
793         }
794
795         #endregion
796
797         #region 内部処理用インスタンスメソッド
798
799         /// <summary>
800         /// ページが指定された番号の名前空間に所属するかをチェック。
801         /// </summary>
802         /// <param name="id">名前空間のID。</param>
803         /// <returns><c>true</c> 所属する。</returns>
804         protected bool IsNamespacePage(int id)
805         {
806             // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
807             IList<string> prefixes = this.Website.Namespaces[id];
808             if (prefixes != null)
809             {
810                 string title = this.Title.ToLower();
811                 foreach (string prefix in prefixes)
812                 {
813                     if (title.StartsWith(prefix.ToLower() + ":"))
814                     {
815                         return true;
816                     }
817                 }
818             }
819
820             return false;
821         }
822
823         /// <summary>
824         /// オブジェクトがメソッドの実行に不完全な状態でないか検証する。
825         /// 不完全な場合、例外をスローする。
826         /// </summary>
827         /// <exception cref="InvalidOperationException">オブジェクトは不完全。</exception>
828         protected void ValidateIncomplete()
829         {
830             if (String.IsNullOrEmpty(this.Text))
831             {
832                 // ページ本文が設定されていない場合不完全と判定
833                 throw new InvalidOperationException("Text is unset");
834             }
835         }
836
837         /// <summary>
838         /// 現在のページをリダイレクトとして解析する。
839         /// </summary>
840         /// <returns>リダイレクトの場合<c>true</c>。</returns>
841         /// <remarks>リダイレクトの場合、転送先ページ名をプロパティに格納。</remarks>
842         private bool TryParseRedirect()
843         {
844             // 日本語版みたいに、#REDIRECTと言語固有の#転送みたいなのがあると思われるので、
845             // 翻訳元言語とデフォルトの設定でチェック
846             this.Redirect = null;
847             for (int i = 0; i < 2; i++)
848             {
849                 string format = this.Website.Redirect;
850                 if (i == 1)
851                 {
852                     format = Properties.Settings.Default.MediaWikiRedirect;
853                 }
854
855                 if (!String.IsNullOrEmpty(format)
856                     && this.Text.ToLower().StartsWith(format.ToLower()))
857                 {
858                     Link link;
859                     if (this.TryParseLink(this.Text.Substring(format.Length).TrimStart(), out link))
860                     {
861                         this.Redirect = link;
862                         return true;
863                     }
864                 }
865             }
866
867             return false;
868         }
869
870         /// <summary>
871         /// 渡されたTemplate:Documentationの呼び出しから、指定された言語コードへの言語間リンクを返す。
872         /// </summary>
873         /// <param name="link">テンプレート呼び出しのリンク。</param>
874         /// <param name="code">言語コード。</param>
875         /// <returns>言語間リンク先の記事名。見つからない場合またはパラメータが対象外の場合は空。</returns>
876         /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
877         private string GetDocumentationInterWiki(Link link, string code)
878         {
879             // テンプレートタグか、この言語にTemplate:Documentationの設定がされているかを確認
880             string docTitle = this.Website.DocumentationTemplate;
881             if (!link.IsTemplateTag || String.IsNullOrEmpty(docTitle))
882             {
883                 return String.Empty;
884             }
885
886             // Documentationテンプレートのリンクかを確認
887             if (link.Title.ToLower() != docTitle.ToLower())
888             {
889                 // 名前空間で一致していない可能性があるので、名前空間を取ってもう一度判定
890                 int index = docTitle.IndexOf(':');
891                 if (new MediaWikiPage(this.Website, docTitle).IsTemplate()
892                     && index >= 0 && index + 1 < docTitle.Length)
893                 {
894                     docTitle = docTitle.Substring(docTitle.IndexOf(':') + 1);
895                 }
896
897                 if (link.Title.ToLower() != docTitle.ToLower())
898                 {
899                     // どちらでも一致しない場合は別のテンプレートなりなので無視
900                     return String.Empty;
901                 }
902             }
903
904             // 解説記事名を確認
905             string subtitle = link.PipeTexts.ElementAtOrDefault(0);
906             if (String.IsNullOrWhiteSpace(subtitle) || subtitle.Contains('='))
907             {
908                 // 指定されていない場合はデフォルトのページを探索
909                 subtitle = this.Website.DocumentationTemplateDefaultPage;
910             }
911
912             if (String.IsNullOrEmpty(subtitle))
913             {
914                 return String.Empty;
915             }
916
917             // サブページの場合、親ページのページ名を付加
918             // TODO: サブページの仕組みについては要再検討
919             if (subtitle.StartsWith("/"))
920             {
921                 subtitle = this.Title + subtitle;
922             }
923
924             // 解説ページから言語間リンクを取得
925             MediaWikiPage subpage = null;
926             try
927             {
928                 // ※ 本当はここでの取得状況も画面に見せたいが、今のつくりで
929                 //    そうするとややこしくなるので隠蔽する。
930                 subpage = this.Website.GetPage(subtitle) as MediaWikiPage;
931             }
932             catch (Exception ex)
933             {
934                 System.Diagnostics.Debug.WriteLine(ex.StackTrace);
935             }
936
937             if (subpage != null)
938             {
939                 string interWiki = subpage.GetInterWiki(code);
940                 if (!String.IsNullOrEmpty(interWiki))
941                 {
942                     return interWiki;
943                 }
944             }
945
946             // 未発見の場合、空文字列
947             return String.Empty;
948         }
949
950         #endregion
951
952         #region 内部クラス
953
954         /// <summary>
955         /// Wikipediaのリンクの要素を格納するための構造体。
956         /// </summary>
957         public class Link
958         {
959             #region プロパティ
960
961             /// <summary>
962             /// リンクのオブジェクト作成時の元テキスト([[~]])。
963             /// </summary>
964             public string OriginalText
965             {
966                 get;
967                 //// TODO: このクラスにParseを移動完了したら、protectedにする
968                 set;
969             }
970
971             /// <summary>
972             /// リンクの記事名。
973             /// </summary>
974             /// <remarks>リンクに記載されていた記事名であり、名前空間の情報などは含まない可能性があるため注意。</remarks>
975             public string Title
976             {
977                 get;
978                 set;
979             }
980
981             /// <summary>
982             /// リンクのセクション名(#)。
983             /// </summary>
984             public string Section
985             {
986                 get;
987                 set;
988             }
989
990             /// <summary>
991             /// リンクのパイプ後の文字列(|)。
992             /// </summary>
993             public IList<string> PipeTexts
994             {
995                 get;
996                 set;
997             }
998
999             /// <summary>
1000             /// 言語間または他プロジェクトへのリンクの場合、コード。
1001             /// </summary>
1002             public string Code
1003             {
1004                 get;
1005                 set;
1006             }
1007
1008             /// <summary>
1009             /// テンプレートタグで書かれたリンク({{~}})か?
1010             /// </summary>
1011             /// <remarks>
1012             /// 必ずしもリンク先がテンプレートであることを意味しない。
1013             /// 普通のページをこの書式でテンプレートのように使用することも可能である。
1014             /// </remarks>
1015             public bool IsTemplateTag
1016             {
1017                 get;
1018                 set;
1019             }
1020
1021             /// <summary>
1022             /// 記事名の先頭がサブページを示す / で始まるか?
1023             /// </summary>
1024             /// <remarks>※ 2010年9月現在、この処理には不足あり。</remarks>
1025             public bool IsSubpage
1026             {
1027                 // TODO: サブページには相対パスで[[../~]]や[[../../~]]というような書き方もある模様。
1028                 //       この辺りの処理は[[Help:サブページ]]を元に全面的に見直す必要あり
1029                 get;
1030                 set;
1031             }
1032
1033             /// <summary>
1034             /// リンクの先頭が : で始まるかを示すフラグ。
1035             /// </summary>
1036             public bool IsColon
1037             {
1038                 get;
1039                 set;
1040             }
1041
1042             /// <summary>
1043             /// テンプレートの場合に、テンプレートのソースをそのまま出力することを示す msgnw: が付加されているか?
1044             /// </summary>
1045             public bool IsMsgnw
1046             {
1047                 get;
1048                 set;
1049             }
1050
1051             /// <summary>
1052             /// テンプレートの場合に、記事名の後で改行が入るか?
1053             /// </summary>
1054             public bool Enter
1055             {
1056                 get;
1057                 set;
1058             }
1059
1060             /// <summary>
1061             /// リンクが表すテキスト([[~]])。
1062             /// </summary>
1063             public string Text
1064             {
1065                 get
1066                 {
1067                     // 戻り値初期化
1068                     StringBuilder b = new StringBuilder();
1069
1070                     // 枠の設定
1071                     string startSign = "[[";
1072                     string endSign = "]]";
1073                     if (this.IsTemplateTag)
1074                     {
1075                         startSign = "{{";
1076                         endSign = "}}";
1077                     }
1078
1079                     // 先頭の枠の付加
1080                     b.Append(startSign);
1081
1082                     // 先頭の : の付加
1083                     if (this.IsColon)
1084                     {
1085                         b.Append(':');
1086                     }
1087
1088                     // msgnw: (テンプレートを<nowiki>タグで挟む)の付加
1089                     if (this.IsTemplateTag && this.IsMsgnw)
1090                     {
1091                         b.Append(MediaWikiPage.Msgnw);
1092                     }
1093
1094                     // 言語コード・他プロジェクトコードの付加
1095                     if (!String.IsNullOrEmpty(this.Code))
1096                     {
1097                         b.Append(this.Code);
1098                     }
1099
1100                     // リンクの付加
1101                     if (!String.IsNullOrEmpty(this.Title))
1102                     {
1103                         b.Append(this.Title);
1104                     }
1105
1106                     // セクション名の付加
1107                     if (!String.IsNullOrEmpty(this.Section))
1108                     {
1109                         b.Append('#');
1110                         b.Append(this.Section);
1111                     }
1112
1113                     // 改行の付加
1114                     if (this.Enter)
1115                     {
1116                         b.Append('\n');
1117                     }
1118
1119                     // パイプ後の文字列の付加
1120                     if (this.PipeTexts != null)
1121                     {
1122                         foreach (string s in this.PipeTexts)
1123                         {
1124                             b.Append('|');
1125                             if (!String.IsNullOrEmpty(s))
1126                             {
1127                                 b.Append(s);
1128                             }
1129                         }
1130                     }
1131
1132                     // 終わりの枠の付加
1133                     b.Append(endSign);
1134                     return b.ToString();
1135                 }
1136             }
1137
1138             #endregion
1139
1140             #region 公開メソッド
1141
1142             /// <summary>
1143             /// このオブジェクトを表すリンク文字列を返す。
1144             /// </summary>
1145             /// <returns>オブジェクトを表すリンク文字列。</returns>
1146             public override string ToString()
1147             {
1148                 // リンクを表すテキスト、ならびに元テキストを返す
1149                 return this.Text + "<!-- " + this.OriginalText + " -->";
1150             }
1151
1152             #endregion
1153         }
1154
1155         #endregion
1156     }
1157 }