OSDN Git Service

#27617 各サイトでの動作を確認し設定を精査・ツールチップの文言を姉妹サイトも考慮したものに修正,
[wptscs/wpts.git] / Wptscs / Parsers / MediaWikiParser.cs
1 // ================================================================================================
2 // <summary>
3 //      MediaWikiのページを解析するパーサークラスソース</summary>
4 //
5 // <copyright file="MediaWikiParser.cs" company="honeplusのメモ帳">
6 //      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
7 // <author>
8 //      Honeplus</author>
9 // ================================================================================================
10
11 namespace Honememo.Wptscs.Parsers
12 {
13     using System;
14     using System.Collections.Generic;
15     using System.Text;
16     using Honememo.Parsers;
17     using Honememo.Utilities;
18     using Honememo.Wptscs.Websites;
19
20     /// <summary>
21     /// MediaWikiのページを解析するパーサークラスです。
22     /// </summary>
23     public class MediaWikiParser : XmlParser
24     {
25         #region private変数
26
27         /// <summary>
28         /// このパーサーが対応するMediaWiki。
29         /// </summary>
30         private MediaWiki website;
31
32         #endregion
33
34         #region コンストラクタ
35
36         /// <summary>
37         /// 指定されたMediaWikiサーバーのページを解析するためのパーサーを作成する。
38         /// </summary>
39         /// <param name="site">このパーサーが対応するMediaWiki。</param>
40         /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
41         public MediaWikiParser(MediaWiki site)
42         {
43             // 子パーサーのうち、再帰的に処理を行ういくつかのパーサーについては
44             // 結果をキャッシュするようにする
45             // ※ 通常は意味が無いが、複雑なテンプレート等で解析失敗が多発し、
46             //    何度も同じ文字列を解析してしまうときに非常に時間がかかるため
47             this.Website = site;
48             this.CommentParser = new XmlCommentElementParser();
49             this.NowikiParser = new MediaWikiNowikiParser(this);
50             this.LinkParser = new CacheParser(new MediaWikiLinkParser(this));
51             this.TemplateParser = new CacheParser(new MediaWikiTemplateParser(this));
52             this.VariableParser = new CacheParser(new MediaWikiVariableParser(this));
53             this.HeadingParser = new CacheParser(new MediaWikiHeadingParser(this));
54         }
55
56         #endregion
57         
58         #region デストラクタ
59
60         /// <summary>
61         /// オブジェクトのリソースを破棄する。
62         /// </summary>
63         /// <remarks><see cref="Dispose"/>の呼び出しのみ。</remarks>
64         ~MediaWikiParser()
65         {
66             this.Dispose();
67         }
68
69         #endregion
70
71         #region 公開プロパティ
72
73         /// <summary>
74         /// このパーサーが対応するMediaWiki。
75         /// </summary>
76         /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
77         public MediaWiki Website
78         {
79             get
80             {
81                 return this.website;
82             }
83
84             set
85             {
86                 this.website = Validate.NotNull(value);
87             }
88         }
89
90         #endregion
91
92         #region 関連クラス公開プロパティ
93
94         // ※ 各要素のパーサーについては相互参照しているものが多々あり、
95         //    個別のクラスでnewされると危険なことから、ここで生成して公開する。
96
97         /// <summary>
98         /// パーサー内で使用するXMLコメント要素のパーサー。
99         /// </summary>
100         internal IParser CommentParser
101         {
102             get;
103             private set;
104         }
105
106         /// <summary>
107         /// パーサー内で使用するnowikiブロックのパーサー。
108         /// </summary>
109         internal IParser NowikiParser
110         {
111             get;
112             private set;
113         }
114
115         /// <summary>
116         /// パーサー内で使用するMediaWiki内部リンクのパーサー。
117         /// </summary>
118         internal IParser LinkParser
119         {
120             get;
121             private set;
122         }
123
124         /// <summary>
125         /// パーサー内で使用するMediaWikiテンプレートのパーサー。
126         /// </summary>
127         internal IParser TemplateParser
128         {
129             get;
130             private set;
131         }
132
133         /// <summary>
134         /// パーサー内で使用するMediaWiki変数のパーサー。
135         /// </summary>
136         internal IParser VariableParser
137         {
138             get;
139             private set;
140         }
141
142         /// <summary>
143         /// パーサー内で使用するMediaWiki変数のパーサー。
144         /// </summary>
145         internal IParser HeadingParser
146         {
147             get;
148             private set;
149         }
150
151         #endregion
152
153         #region ITextParserインタフェース実装メソッド
154
155         /// <summary>
156         /// 渡されたMediaWikiページに対して、指定された終了条件を満たすまで解析を行う。
157         /// </summary>
158         /// <param name="s">解析対象の文字列。</param>
159         /// <param name="condition">解析を終了するかの判定を行うデリゲート。</param>
160         /// <param name="result">解析結果。</param>
161         /// <returns>解析に成功した場合<c>true</c>。</returns>
162         /// <remarks>指定された終了条件を満たさない場合、最終位置まで解析を行う。</remarks>
163         /// <exception cref="ObjectDisposedException"><see cref="Dispose"/>が実行済みの場合。</exception>
164         public override bool TryParseToEndCondition(string s, IsEndCondition condition, out IElement result)
165         {
166             if (s == null)
167             {
168                 // nullの場合だけは解析失敗とする
169                 result = null;
170                 return false;
171             }
172             else if (this.NowikiParser == null)
173             {
174                 // 子パーサーが解放済みの場合Dispose済みで処理不可(同時にnullになるので代表でNowikiParser)
175                 throw new ObjectDisposedException(this.GetType().Name);
176             }
177
178             // 文字列を1文字ずつチェックし、その内容に応じた要素のリストを作成する
179             ListElement list = new ListElement();
180             StringBuilder b = new StringBuilder();
181             bool newLine = false;
182             for (int i = 0; i < s.Length; i++)
183             {
184                 // 終了条件のチェック、未指定時は条件なし
185                 if (condition != null && condition(s, i))
186                 {
187                     break;
188                 }
189
190                 IElement innerElement;
191
192                 if (s[i] == '\n')
193                 {
194                     // 改行の場合、次回に見出しの解析が必要なため記録
195                     b.Append(s[i]);
196                     newLine = true;
197                     continue;
198                 }
199                 else if (newLine)
200                 {
201                     // 見出しの解析
202                     newLine = false;
203                     if (this.TryParseAt(s, i, out innerElement, this.HeadingParser))
204                     {
205                         // それまでに解析済みのテキストを吐き出し、
206                         // その後に解析した要素を追加
207                         this.FlashText(ref list, ref b);
208                         list.Add(innerElement);
209                         i += innerElement.ToString().Length - 1;
210                         continue;
211                     }
212                 }
213
214                 // コメントの解析
215                 if (this.TryParseAt(s, i, out innerElement, this.CommentParser))
216                 {
217                     // それまでに解析済みのテキストを吐き出し、
218                     // その後に解析した要素を追加
219                     this.FlashText(ref list, ref b);
220                     list.Add(innerElement);
221                     i += innerElement.ToString().Length - 1;
222
223                     // コメント中に改行が含まれた場合も、見出しの処理を有効化する
224                     if (innerElement.ToString().Contains("\n"))
225                     {
226                         newLine = true;
227                     }
228
229                     continue;
230                 }
231
232                 // それ以外のnowiki, 変数, 内部リンク, テンプレートの各要素のTryParse処理を呼び出し
233                 if (this.TryParseAt(
234                     s,
235                     i,
236                     out innerElement,
237                     this.NowikiParser,
238                     this.VariableParser,
239                     this.LinkParser,
240                     this.TemplateParser))
241                 {
242                     // それまでに解析済みのテキストを吐き出し、
243                     // その後に解析した要素を追加
244                     this.FlashText(ref list, ref b);
245                     list.Add(innerElement);
246                     i += innerElement.ToString().Length - 1;
247                     continue;
248                 }
249
250                 // 通常の文字列はテキスト要素として積み上げる
251                 b.Append(s[i]);
252             }
253
254             // 残っていれば最後に解析済みのテキストを吐き出し
255             this.FlashText(ref list, ref b);
256
257             result = list;
258             if (list.Count == 1)
259             {
260                 // リストが1件であれば、その要素を直に返す
261                 result = list[0];
262             }
263             else if (list.Count == 0)
264             {
265                 // 何もなければ、空文字列だったものとして空のテキスト要素を返す
266                 result = new TextElement();
267             }
268
269             return true;
270         }
271
272         #endregion
273
274         #region IDisposableインタフェース実装メソッド
275
276         /// <summary>
277         /// このパーサーで使用する子パーサーを解放する。
278         /// </summary>
279         public override void Dispose()
280         {
281             // 子パーサーを解放
282             // ※ 以下は循環参照のため、明示的に解放しないとGCされない可能性がある
283             if (this.NowikiParser != null)
284             {
285                 this.NowikiParser = null;
286             }
287
288             if (this.LinkParser != null)
289             {
290                 this.LinkParser = null;
291             }
292
293             if (this.TemplateParser != null)
294             {
295                 this.TemplateParser = null;
296             }
297
298             if (this.VariableParser != null)
299             {
300                 this.VariableParser = null;
301             }
302
303             if (this.HeadingParser != null)
304             {
305                 this.HeadingParser = null;
306             }
307
308             // 親クラスもIDisposableなため呼び出し
309             base.Dispose();
310
311             // ファイナライザ(このクラスではDisposeを呼ぶだけ)が不要であることを通知
312             GC.SuppressFinalize(this);
313         }
314
315         #endregion
316     }
317 }