OSDN Git Service

Wikipedia翻訳支援ツール Ver1.10時点のソース
authorhoneplus <honeplus@users.osdn.me>
Mon, 30 Jan 2012 16:30:00 +0000 (16:30 +0000)
committerhoneplus <honeplus@users.osdn.me>
Mon, 30 Jan 2012 16:30:00 +0000 (16:30 +0000)
git-svn-id: http://svn.osdn.net/svnroot/wptscs/trunk@9 7cc79d57-4d93-40a1-83d5-ec7b38613dec

127 files changed:
HmLib/HmLib.csproj [new file with mode: 0644]
HmLib/Models/IgnoreCaseDictionary.cs [moved from Wptscs/Models/IgnoreCaseDictionary.cs with 99% similarity]
HmLib/Models/TreeNode.cs [new file with mode: 0644]
HmLib/Parsers/AbstractElement.cs [new file with mode: 0644]
HmLib/Parsers/AbstractParser.cs [new file with mode: 0644]
HmLib/Parsers/AbstractTextParser.cs [new file with mode: 0644]
HmLib/Parsers/HtmlElement.cs [new file with mode: 0644]
HmLib/Parsers/IElement.cs [new file with mode: 0644]
HmLib/Parsers/IParser.cs [new file with mode: 0644]
HmLib/Parsers/ITextParser.cs [new file with mode: 0644]
HmLib/Parsers/ListElement.cs [new file with mode: 0644]
HmLib/Parsers/TextElement.cs [new file with mode: 0644]
HmLib/Parsers/XmlCommentElement.cs [new file with mode: 0644]
HmLib/Parsers/XmlCommentElementParser.cs [new file with mode: 0644]
HmLib/Parsers/XmlElement.cs [new file with mode: 0644]
HmLib/Parsers/XmlElementParser.cs [new file with mode: 0644]
HmLib/Parsers/XmlParser.cs [new file with mode: 0644]
HmLib/Parsers/XmlTextElement.cs [new file with mode: 0644]
HmLib/Properties/AssemblyInfo.cs [new file with mode: 0644]
HmLib/Utilities/ObjectUtils.cs [moved from Wptscs/Utilities/ObjectUtils.cs with 98% similarity]
HmLib/Utilities/StringUtils.cs [moved from Wptscs/Utilities/StringUtils.cs with 52% similarity]
HmLib/Utilities/Validate.cs [moved from Wptscs/Utilities/Validate.cs with 98% similarity]
HmLib/Utilities/XmlUtils.cs [moved from Wptscs/Utilities/XmlUtils.cs with 72% similarity]
HmLibTest/HmLibTest.csproj [new file with mode: 0644]
HmLibTest/Models/IgnoreCaseDictionaryTest.cs [moved from WptscsTest/Models/IgnoreCaseDictionaryTest.cs with 99% similarity]
HmLibTest/Models/TreeNodeTest.cs [new file with mode: 0644]
HmLibTest/Parsers/HtmlElementTest.cs [new file with mode: 0644]
HmLibTest/Parsers/ListElementTest.cs [new file with mode: 0644]
HmLibTest/Parsers/TextElementTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlCommentElementParserTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlCommentElementTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlElementParserTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlElementTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlParserTest.cs [new file with mode: 0644]
HmLibTest/Parsers/XmlTextElementTest.cs [new file with mode: 0644]
HmLibTest/Properties/AssemblyInfo.cs [new file with mode: 0644]
HmLibTest/Utilities/ObjectUtilsTest.cs [moved from WptscsTest/Utilities/ObjectUtilsTest.cs with 97% similarity]
HmLibTest/Utilities/StringUtilsTest.cs [new file with mode: 0644]
HmLibTest/Utilities/ValidateTest.cs [moved from WptscsTest/Utilities/ValidateTest.cs with 99% similarity]
HmLibTest/Utilities/XmlUtilsTest.cs [moved from WptscsTest/Utilities/XmlUtilsTest.cs with 59% similarity]
Wikipedia 翻訳支援ツール.asta
Wikipedia 翻訳支援ツール.sln
Wptscs/ConfigForm.Designer.cs
Wptscs/ConfigForm.cs
Wptscs/ConfigForm.resx
Wptscs/Logics/MediaWikiTranslator.cs
Wptscs/Logics/Translator.cs
Wptscs/MainForm.Designer.cs
Wptscs/MainForm.cs
Wptscs/MainForm.resx
Wptscs/Models/Config.cs
Wptscs/Models/Language.cs
Wptscs/Models/MediaWikiPage.cs [deleted file]
Wptscs/Models/TranslationDictionary.cs
Wptscs/Models/TranslationTable.cs
Wptscs/Parsers/MediaWikiHeading.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiHeadingParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiLink.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiLinkParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiNowikiParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiRedirectParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiTemplate.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiTemplateParser.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiVariable.cs [new file with mode: 0644]
Wptscs/Parsers/MediaWikiVariableParser.cs [new file with mode: 0644]
Wptscs/Program.cs
Wptscs/Properties/AssemblyInfo.cs
Wptscs/Properties/Resources.Designer.cs
Wptscs/Properties/Resources.resx
Wptscs/Properties/Settings.Designer.cs
Wptscs/Properties/Settings.settings
Wptscs/Readme.txt
Wptscs/Settings.cs
Wptscs/Utilities/AppDefaultWebProxy.cs [new file with mode: 0644]
Wptscs/Utilities/FormUtils.cs
Wptscs/Utilities/IWebProxy.cs [new file with mode: 0644]
Wptscs/Utilities/LazyXmlParser.cs [deleted file]
Wptscs/Websites/MediaWiki.cs [moved from Wptscs/Models/MediaWiki.cs with 85% similarity]
Wptscs/Websites/MediaWikiPage.cs [new file with mode: 0644]
Wptscs/Websites/Page.cs [moved from Wptscs/Models/Page.cs with 99% similarity]
Wptscs/Websites/Website.cs [moved from Wptscs/Models/Website.cs with 59% similarity]
Wptscs/Wptscs.csproj
Wptscs/app.config
Wptscs/config.xml
WptscsTest/Data/MediaWiki/en/Discovery Channel.xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/en/Fuji (Spacecraft).xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/en/Template_Citation needed.xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/en/Template_Citation needed_doc.xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/en/Template_Planetbox begin.xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/result/config.xml [new file with mode: 0644]
WptscsTest/Data/MediaWiki/result/example.txt
WptscsTest/Data/MediaWiki/result/example_キャッシュ使用.txt
WptscsTest/Data/MediaWiki/result/example_仮リンク有効.txt [new file with mode: 0644]
WptscsTest/Data/MediaWiki/result/example_定型句なし.txt
WptscsTest/Data/MediaWiki/result/スペースシップツー.txt
WptscsTest/Data/config.xml
WptscsTest/Logics/MediaWikiTranslatorTest.cs
WptscsTest/Logics/TranslatorTest.cs [new file with mode: 0644]
WptscsTest/Models/ConfigTest.cs
WptscsTest/Models/LanguageTest.cs
WptscsTest/Models/MockFoctory.cs [new file with mode: 0644]
WptscsTest/Models/TestingConfig.cs [deleted file]
WptscsTest/Models/TranslationDictionaryTest.cs
WptscsTest/Models/TranslationTableTest.cs
WptscsTest/Parsers/MediaWikiHeadingParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiHeadingTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiLinkParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiLinkTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiNowikiParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiRedirectParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiTemplateParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiTemplateTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiVariableParserTest.cs [new file with mode: 0644]
WptscsTest/Parsers/MediaWikiVariableTest.cs [new file with mode: 0644]
WptscsTest/PrivateAccessor.cs
WptscsTest/Properties/AssemblyInfo.cs
WptscsTest/Utilities/AppDefaultWebProxyTest.cs [new file with mode: 0644]
WptscsTest/Utilities/FormUtilsTest.cs
WptscsTest/Utilities/LazyXmlParserTest.cs [deleted file]
WptscsTest/Utilities/StringUtilsTest.cs [deleted file]
WptscsTest/Websites/MediaWikiPageTest.cs [moved from WptscsTest/Models/MediaWikiPageTest.cs with 73% similarity]
WptscsTest/Websites/MediaWikiTest.cs [moved from WptscsTest/Models/MediaWikiTest.cs with 73% similarity]
WptscsTest/Websites/PageTest.cs [moved from WptscsTest/Models/PageTest.cs with 98% similarity]
WptscsTest/Websites/WebsiteTest.cs [moved from WptscsTest/Models/WebsiteTest.cs with 98% similarity]
WptscsTest/WptscsTest.csproj

diff --git a/HmLib/HmLib.csproj b/HmLib/HmLib.csproj
new file mode 100644 (file)
index 0000000..71272a8
--- /dev/null
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{CC7F6106-0C00-427B-9BF2-1EE65448907B}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Honememo</RootNamespace>
+    <AssemblyName>hmlib</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Models\IgnoreCaseDictionary.cs" />
+    <Compile Include="Models\TreeNode.cs" />
+    <Compile Include="Parsers\IElement.cs" />
+    <Compile Include="Parsers\IParser.cs" />
+    <Compile Include="Parsers\ListElement.cs" />
+    <Compile Include="Parsers\TextElement.cs" />
+    <Compile Include="Parsers\XmlElement.cs" />
+    <Compile Include="Parsers\XmlCommentElement.cs" />
+    <Compile Include="Parsers\AbstractParser.cs" />
+    <Compile Include="Parsers\AbstractElement.cs" />
+    <Compile Include="Parsers\HtmlElement.cs" />
+    <Compile Include="Parsers\XmlTextElement.cs" />
+    <Compile Include="Parsers\XmlElementParser.cs" />
+    <Compile Include="Parsers\XmlCommentElementParser.cs" />
+    <Compile Include="Parsers\ITextParser.cs" />
+    <Compile Include="Parsers\AbstractTextParser.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Parsers\XmlParser.cs" />
+    <Compile Include="Utilities\ObjectUtils.cs" />
+    <Compile Include="Utilities\StringUtils.cs" />
+    <Compile Include="Utilities\Validate.cs" />
+    <Compile Include="Utilities\XmlUtils.cs" />
+  </ItemGroup>
+  <ItemGroup />
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="$(ProgramFiles)\MSBuild\Microsoft\StyleCop\v4.4\Microsoft.StyleCop.Targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
similarity index 99%
rename from Wptscs/Models/IgnoreCaseDictionary.cs
rename to HmLib/Models/IgnoreCaseDictionary.cs
index 07cf984..36f75c9 100644 (file)
@@ -3,7 +3,7 @@
 //      大文字小文字を区別しないIDictionary実装のラッパークラスソース</summary>
 //
 // <copyright file="IgnoreCaseDictionary.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/HmLib/Models/TreeNode.cs b/HmLib/Models/TreeNode.cs
new file mode 100644 (file)
index 0000000..1e1c788
--- /dev/null
@@ -0,0 +1,170 @@
+// ================================================================================================
+// <summary>
+//      ツリー構造のデータを扱うためのクラスソース</summary>
+//
+// <copyright file="TreeNode.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Models
+{
+    using System;
+    using System.Collections.Generic;
+
+    /// <summary>
+    /// ツリー構造のデータを扱うためのクラスです。
+    /// </summary>
+    /// <typeparam name="T">ノード内の値の型。</typeparam>
+    /// <remarks>
+    /// 特に循環などはチェックしていないため、そうしたデータは作成しないよう注意。
+    /// </remarks>
+    public class TreeNode<T> : IEnumerable<TreeNode<T>>
+    {
+        #region private変数
+
+        /// <summary>
+        /// このノードにぶら下がる子ノード。
+        /// </summary>
+        private IList<TreeNode<T>> children = new List<TreeNode<T>>();
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// ノードを作成する。
+        /// </summary>
+        /// <param name="value">このノードが保持するオブジェクト。未指定の場合その型のデフォルト値。</param>
+        public TreeNode(T value = default(T))
+        {
+            this.Value = value;
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// このノードが保持するオブジェクト。
+        /// </summary>
+        public T Value
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// このノードがぶら下がる親ノード。
+        /// </summary>
+        public TreeNode<T> Parent
+        {
+            get;
+            protected set;
+        }
+
+        /// <summary>
+        /// このノードにぶら下がる子ノード。
+        /// </summary>
+        /// <remarks>この属性への変更はツリー構造には反映されない。</remarks>
+        public IList<TreeNode<T>> Children
+        {
+            get
+            {
+                return new List<TreeNode<T>>(this.children);
+            }
+        }
+
+        #endregion
+
+        #region 公開メソッド
+
+        /// <summary>
+        /// このノードに子ノードを追加する。
+        /// </summary>
+        /// <param name="node">追加するノード。</param>
+        public void Add(TreeNode<T> node)
+        {
+            // 循環参照となる場合例外を投げる
+            if (node == this || this.IsParent(node))
+            {
+                throw new InvalidOperationException("circular reference.");
+            }
+
+            // 子ノードを登録、親ノードを更新
+            this.children.Add(node);
+            node.Parent = this;
+        }
+
+        /// <summary>
+        /// このノードから子ノードを取り除く。
+        /// </summary>
+        /// <param name="node">取り除くノード。</param>
+        /// <returns>ノードが取り除かれた場合<c>true</c>。それ以外の場合<c>false</c>。</returns>
+        public bool Remove(TreeNode<T> node)
+        {
+            // 子ノードを除去、親ノードを更新
+            if (this.children.Remove(node))
+            {
+                node.Parent = null;
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// このツリーの全ノードを取得する<c>IEnumerator</c>を返す。
+        /// </summary>
+        /// <returns>コレクションを反復処理するために使用できる<c>IEnumerator</c>オブジェクト。</returns>
+        public IEnumerator<TreeNode<T>> GetEnumerator()
+        {
+            // 再帰的にツリーの全ノードを返す
+            yield return this;
+            foreach (TreeNode<T> child in this.children)
+            {
+                foreach (TreeNode<T> node in child)
+                {
+                    yield return node;
+                }
+            }
+        }
+
+        /// <summary>
+        /// このツリーの全ノードを取得する<c>IEnumerator</c>を返す。
+        /// </summary>
+        /// <returns>コレクションを反復処理するために使用できる<c>IEnumerator</c>オブジェクト。</returns>
+        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+        {
+            return this.GetEnumerator();
+        }
+
+        #endregion
+
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// 指定されたノードが自分の祖先に存在するかを判定。
+        /// </summary>
+        /// <param name="node">確認するノード。</param>
+        /// <returns>祖先の場合<c>true</c>。</returns>
+        private bool IsParent(TreeNode<T> node)
+        {
+            if (this.Parent == null)
+            {
+                return false;
+            }
+
+            if (this.Parent == node)
+            {
+                return true;
+            }
+
+            // 再帰的に親ノードを探索
+            return this.Parent.IsParent(node);
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/AbstractElement.cs b/HmLib/Parsers/AbstractElement.cs
new file mode 100644 (file)
index 0000000..6e73679
--- /dev/null
@@ -0,0 +1,64 @@
+// ================================================================================================
+// <summary>
+//      IElementを実装するための実装支援用抽象クラスソース</summary>
+//
+// <copyright file="AbstractElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// IElementを実装するための実装支援用抽象クラスです。
+    /// </summary>
+    public abstract class AbstractElement : IElement
+    {        
+        #region インタフェース実装プロパティ
+
+        /// <summary>
+        /// 要素がParse等により生成された場合の解析元文字列。
+        /// </summary>
+        /// <remarks>
+        /// <see cref="ToString"/>に生成した値を返して欲しい場合、この値を明示的に<c>null</c>にすべき。
+        /// 元の文字列と完全に同じ文字列を生成できるクラスであれば、常に未設定でも問題ない。
+        /// </remarks>
+        public virtual string ParsedString
+        {
+            get;
+            set;
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 各要素を書式化したテキスト形式で返す。
+        /// </summary>
+        /// <returns>書式化したテキスト。<c>null</c>は返さない。</returns>
+        /// <remarks>
+        /// <see cref="ParsedString"/>が設定されている場合は、その値を返す。
+        /// </remarks>
+        public override string ToString()
+        {
+            return this.ParsedString != null ? this.ParsedString : StringUtils.DefaultString(this.ToStringImpl());
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド
+
+        /// <summary>
+        /// 各要素を書式化したテキスト形式で返す。
+        /// </summary>
+        /// <returns>書式化したテキスト。</returns>
+        protected abstract string ToStringImpl();
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/AbstractParser.cs b/HmLib/Parsers/AbstractParser.cs
new file mode 100644 (file)
index 0000000..a3fb782
--- /dev/null
@@ -0,0 +1,99 @@
+// ================================================================================================
+// <summary>
+//      IParserを実装するための実装支援用抽象クラスソース</summary>
+//
+// <copyright file="AbstractParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+
+    /// <summary>
+    /// IParserを実装するための実装支援用抽象クラスです。
+    /// </summary>
+    public abstract class AbstractParser : IParser
+    {        
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡された文字列の解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <returns>解析結果。</returns>
+        /// <exception cref="FormatException">文字列が解析できないフォーマットの場合。</exception>
+        /// <remarks><see cref="TryParse"/>を呼び出し。解析に失敗した場合は、各種例外を投げる。</remarks>
+        public virtual IElement Parse(string s)
+        {
+            IElement result;
+            if (this.TryParse(s, out result))
+            {
+                return result;
+            }
+
+            throw new FormatException("Invalid String : " + s);
+        }
+
+        /// <summary>
+        /// 渡された文字列の解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        public abstract bool TryParse(string s, out IElement result);
+
+        /// <summary>
+        /// 渡された文字が<see cref="Parse"/>, <see cref="TryParse"/>の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public virtual bool IsPossibleParse(char c)
+        {
+            return true;
+        }
+
+        #endregion
+
+        #region 実装支援用メソッド
+
+        /// <summary>
+        /// 渡されたテキストの指定されたインデックス位置を各種解析処理で解析する。
+        /// </summary>
+        /// <param name="s">解析するテキスト。</param>
+        /// <param name="index">処理インデックス。</param>
+        /// <param name="result">解析した結果要素。</param>
+        /// <param name="parsers">解析に用いるパーサー。指定された順に使用。</param>
+        /// <returns>いずれかのパーサーで解析できた場合<c>true</c>。</returns>
+        /// <exception cref="ArgumentOutOfRangeException">インデックスが文字列の範囲外の場合。</exception>
+        protected virtual bool TryParseAt(string s, int index, out IElement result, params IParser[] parsers)
+        {
+            char c = s[index];
+            string substr = null;
+            foreach (IParser parser in parsers)
+            {
+                if (parser.IsPossibleParse(c))
+                {
+                    if (substr == null)
+                    {
+                        // Substringする負荷も気になるので、TryParseが必要な場合だけ
+                        substr = s.Substring(index);
+                    }
+
+                    if (parser.TryParse(substr, out result))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            result = null;
+            return false;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/AbstractTextParser.cs b/HmLib/Parsers/AbstractTextParser.cs
new file mode 100644 (file)
index 0000000..b8a9080
--- /dev/null
@@ -0,0 +1,162 @@
+// ================================================================================================
+// <summary>
+//      ITextParserを実装するための実装支援用抽象クラスソース</summary>
+//
+// <copyright file="AbstractTextParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Text;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// ITextParserを実装するための実装支援用抽象クラスです。
+    /// </summary>
+    public abstract class AbstractTextParser : AbstractParser, ITextParser
+    {        
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡された文字列の解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>
+        /// このクラスの実装は、XMLを丸ごと解析するような大きな処理を想定。
+        /// 実装として <see cref="TryParseElementAt"/> を呼び出し。
+        /// </remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 終了条件を指定するメソッドを条件なしで呼び出し
+            return this.TryParseToEndCondition(s, (string str, int index) => false, out result);
+        }
+
+        /// <summary>
+        /// 渡された文字列に対して、指定された文字列に遭遇するまで解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析結果。</param>
+        /// <param name="delimiters">解析を終了する文字列(複数指定可)。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>指定された文字列が出現しない場合、最終位置まで解析を行う。</remarks>
+        public virtual bool TryParseToDelimiter(string s, out IElement result, params string[] delimiters)
+        {
+            // 終了条件のデリゲートに置き換え、そちらの処理にまとめる
+            return this.TryParseToEndCondition(
+                s,
+                (string str, int index)
+                    =>
+                {
+                    foreach (string delimiter in delimiters)
+                    {
+                        if (StringUtils.StartsWith(str, delimiter, index))
+                        {
+                            return true;
+                        }
+                    }
+
+                    return false;
+                },
+                out result);
+        }
+
+        /// <summary>
+        /// 渡された文字列に対して、指定された終了条件を満たすまで解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="condition">解析を終了するかの判定を行うデリゲート。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>指定された終了条件を満たさない場合、最終位置まで解析を行う。</remarks>
+        public virtual bool TryParseToEndCondition(string s, IsEndCondition condition, out IElement result)
+        {
+            // 文字列を1文字ずつチェックし、その内容に応じた要素のリストを作成する
+            ListElement list = new ListElement();
+            StringBuilder b = new StringBuilder();
+            for (int i = 0; i < s.Length; i++)
+            {
+                // 終了条件のチェック、未指定時は条件なし
+                if (condition != null && condition(s, i))
+                {
+                    break;
+                }
+
+                // 各要素のTryParse処理を呼び出し
+                IElement innerElement;
+                if (this.TryParseElementAt(s, i, out innerElement))
+                {
+                    // それまでに解析済みのテキストを吐き出し、
+                    // その後に解析した要素を追加
+                    this.FlashText(ref list, ref b);
+                    list.Add(innerElement);
+                    i += innerElement.ToString().Length - 1;
+                    continue;
+                }
+
+                // 通常の文字列はテキスト要素として積み上げる
+                b.Append(s[i]);
+            }
+
+            // 残っていれば最後に解析済みのテキストを吐き出し
+            this.FlashText(ref list, ref b);
+
+            result = list;
+            if (list.Count == 1)
+            {
+                // リストが1件であれば、その要素を直に返す
+                result = list[0];
+            }
+            else if (list.Count == 0)
+            {
+                // 何もなければ、空文字列だったものとして空のテキスト要素を返す
+                result = new TextElement();
+            }
+
+            return true;
+        }
+
+        #endregion
+
+        #region 実装支援用メソッド
+
+        /// <summary>
+        /// 渡されたテキストの指定されたインデックス位置を各種解析処理で解析する。
+        /// </summary>
+        /// <param name="s">解析するテキスト。</param>
+        /// <param name="index">処理インデックス。</param>
+        /// <param name="result">解析した結果要素。</param>
+        /// <returns>解析できた場合<c>true</c>。</returns>
+        /// <exception cref="ArgumentOutOfRangeException">インデックスが文字列の範囲外の場合。</exception>
+        /// <exception cref="NotImplementedException">このクラスでは未実装。</exception>
+        /// <remarks>
+        /// このクラスの<see cref="TryParseToEndCondition"/>実装を用いる場合、
+        /// ここで<c>TryParseAt</c>等を用いてそのParserで必要な解析処理呼び出しを列挙する。
+        /// </remarks>
+        protected virtual bool TryParseElementAt(string s, int index, out IElement result)
+        {
+            throw new NotImplementedException(this.GetType() + " is not implemented");
+        }
+
+        /// <summary>
+        /// 文字列が空でない場合、リストにTextエレメントを追加して、文字列をリセットする。
+        /// </summary>
+        /// <param name="list">追加されるリスト。</param>
+        /// <param name="b">追加する文字列。</param>
+        protected virtual void FlashText(ref ListElement list, ref StringBuilder b)
+        {
+            if (b.Length > 0)
+            {
+                list.Add(new TextElement(b.ToString()));
+                b.Clear();
+            }
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/HtmlElement.cs b/HmLib/Parsers/HtmlElement.cs
new file mode 100644 (file)
index 0000000..552abbb
--- /dev/null
@@ -0,0 +1,102 @@
+// ================================================================================================
+// <summary>
+//      ページのHTML要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="HtmlElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Net;
+    using System.Text;
+
+    /// <summary>
+    /// ページのHTML要素をあらわすモデルクラスです。
+    /// </summary>
+    /// <remarks>解析処理は複雑なため、<see cref="XmlParser"/>として別途実装。</remarks>
+    public class HtmlElement : XmlElement
+    {
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたタグ名・属性・値からHTML要素を生成する。
+        /// </summary>
+        /// <param name="name">タグ名。</param>
+        /// <param name="attributes">属性。</param>
+        /// <param name="innerElements">値。</param>
+        /// <param name="parsedString">Parse解析時の元の文字列。</param>
+        public HtmlElement(
+            string name,
+            IDictionary<string, string> attributes,
+            ICollection<IElement> innerElements,
+            string parsedString = null)
+            : base(name, attributes, innerElements, parsedString)
+        {
+        }
+
+        /// <summary>
+        /// 指定されたタグ名・値からHTML要素を生成する。
+        /// </summary>
+        /// <param name="name">タグ名。</param>
+        /// <param name="value">値。未指定時は<c>null</c>。</param>
+        public HtmlElement(string name, string value = null)
+            : base(name, value)
+        {
+        }
+
+        #endregion
+
+        #region 内部実装メソッド
+
+        /// <summary>
+        /// このXML要素を表す文字列を返す。
+        /// </summary>
+        /// <returns>このXML要素を表す文字列。</returns>
+        protected override string ToStringImpl()
+        {
+            StringBuilder b = new StringBuilder();
+
+            // 開始タグ
+            b.Append('<');
+            b.Append(WebUtility.HtmlEncode(this.Name));
+            
+            // 属性
+            foreach (KeyValuePair<string, string> attr in this.Attributes)
+            {
+                b.Append(' ');
+                b.Append(WebUtility.HtmlEncode(attr.Key));
+                b.Append("=\"");
+                b.Append(WebUtility.HtmlEncode(attr.Value));
+                b.Append('"');
+            }
+
+            // 開始タグ閉じ文字
+            b.Append('>');
+
+            // コンテンツ
+            foreach (IElement element in this)
+            {
+                // エンコードする/しないは中身の責任として、ここではエンコードしない
+                // ※ エンコードするテキストを用意したい場合はHtmlTextElementを使うなど
+                b.Append(element.ToString());
+            }
+
+            // 閉じタグは中身がある場合のみ
+            if (this.Count > 0)
+            {
+                b.Append("</");
+                b.Append(WebUtility.HtmlEncode(this.Name));
+                b.Append('>');
+            }
+
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/IElement.cs b/HmLib/Parsers/IElement.cs
new file mode 100644 (file)
index 0000000..0a76746
--- /dev/null
@@ -0,0 +1,49 @@
+// ================================================================================================
+// <summary>
+//      ページや文字列の各要素をあらわすモデルインタフェースソース</summary>
+//
+// <copyright file="IElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    /// <summary>
+    /// ページや文字列の各要素をあらわすモデルのインタフェースです。
+    /// </summary>
+    public interface IElement
+    {
+        #region プロパティ
+
+        /// <summary>
+        /// 要素がParse等により生成された場合の解析元文字列。
+        /// </summary>
+        /// <remarks>
+        /// <see cref="ToString"/>に生成した値を返して欲しい場合、この値を明示的に<c>null</c>にすべき。
+        /// 元の文字列と完全に同じ文字列を生成できるクラスであれば、常に未設定でも問題ない。
+        /// </remarks>
+        string ParsedString
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region メソッド
+
+        /// <summary>
+        /// 各要素を書式化したテキスト形式で返す。
+        /// </summary>
+        /// <returns>書式化したテキスト。<c>null</c>は返さないこと。</returns>
+        /// <remarks>
+        /// Parse系の処理を実装する上で元の文字列が必要なため、
+        /// <see cref="ParsedString"/>が設定されている場合は、その値を返すべき。
+        /// </remarks>
+        string ToString();
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/IParser.cs b/HmLib/Parsers/IParser.cs
new file mode 100644 (file)
index 0000000..21a4f76
--- /dev/null
@@ -0,0 +1,49 @@
+// ================================================================================================
+// <summary>
+//      文字列解析処理用パーサーのインタフェースソース</summary>
+//
+// <copyright file="IParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+
+    /// <summary>
+    /// 文字列解析処理用パーサーのインタフェースです。
+    /// </summary>
+    public interface IParser
+    {
+        #region メソッド
+
+        /// <summary>
+        /// 渡された文字列の解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <returns>解析結果。</returns>
+        /// <exception cref="FormatException">文字列が解析できないフォーマットの場合。</exception>
+        /// <remarks>解析に失敗した場合は、各種例外を投げる。</remarks>
+        IElement Parse(string s);
+
+        /// <summary>
+        /// 渡された文字列の解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        bool TryParse(string s, out IElement result);
+
+        /// <summary>
+        /// 渡された文字が<see cref="Parse"/>, <see cref="TryParse"/>の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        bool IsPossibleParse(char c);
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/ITextParser.cs b/HmLib/Parsers/ITextParser.cs
new file mode 100644 (file)
index 0000000..6c9c36f
--- /dev/null
@@ -0,0 +1,54 @@
+// ================================================================================================
+// <summary>
+//      テキスト全体のような文字列の解析処理用パーサーのインタフェースソース</summary>
+//
+// <copyright file="ITextParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Text.RegularExpressions;
+
+    /// <summary>
+    /// <see cref="TryParseToEndCondition"/>の終了条件を判定するためのデリゲート。
+    /// </summary>
+    /// <param name="s">解析対象の文字列。</param>
+    /// <param name="index">処理インデックス。</param>
+    /// <returns>終了条件を満たす場合<c>true</c>。</returns>
+    public delegate bool IsEndCondition(string s, int index);
+
+    /// <summary>
+    /// テキスト全体のような文字列の解析処理用パーサーのインタフェースです。
+    /// </summary>
+    /// <remarks>特定の要素だけを解析するのではなく、XMLテキスト全体のような文章を解析するパーサー用の仕組みを定義。</remarks>
+    public interface ITextParser : IParser
+    {
+        #region メソッド
+
+        /// <summary>
+        /// 渡された文字列に対して、指定された文字列に遭遇するまで解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析結果。</param>
+        /// <param name="delimiters">解析を終了する文字列(複数指定可)。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>指定された文字列が出現しない場合、最終位置まで解析を行う。</remarks>
+        bool TryParseToDelimiter(string s, out IElement result, params string[] delimiters);
+
+        /// <summary>
+        /// 渡された文字列に対して、指定された終了条件を満たすまで解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="condition">解析を終了するかの判定を行うデリゲート。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>指定された終了条件を満たさない場合、最終位置まで解析を行う。</remarks>
+        bool TryParseToEndCondition(string s, IsEndCondition condition, out IElement result);
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/ListElement.cs b/HmLib/Parsers/ListElement.cs
new file mode 100644 (file)
index 0000000..91f89e3
--- /dev/null
@@ -0,0 +1,75 @@
+// ================================================================================================
+// <summary>
+//      ページ要素を複数格納する要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="ListElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// ページ要素を複数格納する要素をあらわすモデルクラスです。
+    /// </summary>
+    public class ListElement : List<IElement>, IElement
+    {
+        #region インタフェース実装プロパティ
+        
+        /// <summary>
+        /// 要素がParse等により生成された場合の解析元文字列。
+        /// </summary>
+        /// <remarks>
+        /// <see cref="ToString"/>に生成した値を返して欲しい場合、この値を明示的に<c>null</c>にすべき。
+        /// 元の文字列と完全に同じ文字列を生成できるクラスであれば、常に未設定でも問題ない。
+        /// </remarks>
+        public virtual string ParsedString
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 各要素を書式化したテキスト形式で返す。
+        /// </summary>
+        /// <returns>書式化したテキスト。<c>null</c>は返さない。</returns>
+        /// <remarks>
+        /// <see cref="ParsedString"/>が設定されている場合は、その値を返す。
+        /// </remarks>
+        public override string ToString()
+        {
+            return this.ParsedString != null ? this.ParsedString : StringUtils.DefaultString(this.ToStringImpl());
+        }
+
+        #endregion
+
+        #region 内部実装メソッド
+
+        /// <summary>
+        /// この要素に格納されている要素のToStringを連結して返す。
+        /// </summary>
+        /// <returns>この要素に格納されている要素のテキスト。</returns>
+        protected virtual string ToStringImpl()
+        {
+            StringBuilder b = new StringBuilder();
+            foreach (IElement element in this)
+            {
+                b.Append(element.ToString());
+            }
+
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/TextElement.cs b/HmLib/Parsers/TextElement.cs
new file mode 100644 (file)
index 0000000..733b85d
--- /dev/null
@@ -0,0 +1,60 @@
+// ================================================================================================
+// <summary>
+//      ページや文字列のテキスト要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="TextElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+
+    /// <summary>
+    /// ページや文字列のテキスト要素をあらわすモデルクラスです。
+    /// </summary>
+    /// <remarks>テキストを扱うだけの単純な要素。</remarks>
+    public class TextElement : AbstractElement
+    {
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたテキストを用いて要素を作成する。
+        /// </summary>
+        /// <param name="text">テキスト文字列、未指定時は<c>null</c>。</param>
+        public TextElement(string text = null)
+        {
+            this.Text = text;
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// このテキスト要素のテキスト。
+        /// </summary>
+        public virtual string Text
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// このテキスト要素のテキストを返す。
+        /// </summary>
+        /// <returns>このテキスト要素のテキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            return this.Text;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlCommentElement.cs b/HmLib/Parsers/XmlCommentElement.cs
new file mode 100644 (file)
index 0000000..2af05c6
--- /dev/null
@@ -0,0 +1,61 @@
+// ================================================================================================
+// <summary>
+//      XMLのコメント要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="XmlCommentElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// XML/HTMLのコメント要素をあらわすモデルクラスです。
+    /// </summary>
+    public class XmlCommentElement : XmlTextElement
+    {
+        #region 定数
+
+        /// <summary>
+        /// コメントの開始タグ。
+        /// </summary>
+        public static readonly string DelimiterStart = "<!--";
+
+        /// <summary>
+        /// コメントの閉じタグ。
+        /// </summary>
+        public static readonly string DelimiterEnd = "-->";
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたコメントを用いて要素を作成する。
+        /// </summary>
+        /// <param name="comment">コメント文字列、未指定時は<c>null</c>。</param>
+        public XmlCommentElement(string comment = null)
+            : base(comment)
+        {
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// このコメント要素の書式化して返す。
+        /// </summary>
+        /// <returns>このコメント要素のテキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            return XmlCommentElement.DelimiterStart + base.ToStringImpl() + XmlCommentElement.DelimiterEnd;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlCommentElementParser.cs b/HmLib/Parsers/XmlCommentElementParser.cs
new file mode 100644 (file)
index 0000000..a29a427
--- /dev/null
@@ -0,0 +1,76 @@
+// ================================================================================================
+// <summary>
+//      XMLのコメント要素を解析するためのクラスソース</summary>
+//
+// <copyright file="XmlCommentElementParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+
+    /// <summary>
+    /// XMLのコメント要素を解析するためのクラスです。
+    /// </summary>
+    public class XmlCommentElementParser : AbstractParser
+    {
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたテキストをXMLコメントとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したタグ。</param>
+        /// <returns>タグの場合<c>true</c>。</returns>
+        /// <remarks>
+        /// XML/HTMLタグと判定するには、1文字目が開始タグである必要がある。
+        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
+        /// </remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 入力値確認
+            result = null;
+            if (String.IsNullOrEmpty(s) || !s.StartsWith(XmlCommentElement.DelimiterStart))
+            {
+                return false;
+            }
+
+            // コメント終了まで取得
+            XmlCommentElement comment = new XmlCommentElement();
+            int index = s.IndexOf(XmlCommentElement.DelimiterEnd, XmlCommentElement.DelimiterStart.Length);
+            if (index < 0)
+            {
+                // 閉じタグが存在しない場合、最後までコメントと判定
+                comment.Raw = s.Substring(XmlCommentElement.DelimiterStart.Length);
+                comment.ParsedString = s;
+            }
+            else
+            {
+                // 閉じタグがあった場合、閉じタグまでを返す
+                comment.Raw = s.Substring(
+                    XmlCommentElement.DelimiterStart.Length,
+                    index - XmlCommentElement.DelimiterStart.Length);
+                comment.ParsedString = s.Substring(0, index + XmlCommentElement.DelimiterEnd.Length);
+            }
+
+            result = comment;
+            return true;
+        }
+
+        /// <summary>
+        /// 渡された文字が<see cref="Parse"/>, <see cref="TryParse"/>の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public override bool IsPossibleParse(char c)
+        {
+            return XmlCommentElement.DelimiterStart[0] == c;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlElement.cs b/HmLib/Parsers/XmlElement.cs
new file mode 100644 (file)
index 0000000..d03a497
--- /dev/null
@@ -0,0 +1,149 @@
+// ================================================================================================
+// <summary>
+//      ページのXML要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="XmlElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using System.Xml;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// ページのXML要素をあらわすモデルクラスです。
+    /// </summary>
+    /// <remarks>解析処理は複雑なため、<see cref="XmlParser"/>として別途実装。</remarks>
+    public class XmlElement : ListElement
+    {
+        #region private変数
+
+        /// <summary>
+        /// タグ名。
+        /// </summary>
+        private string name;
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたタグ名・属性・値からXML要素を生成する。
+        /// </summary>
+        /// <param name="name">タグ名。</param>
+        /// <param name="attributes">属性。</param>
+        /// <param name="innerElements">値。</param>
+        /// <param name="parsedString">Parse解析時の元の文字列。</param>
+        public XmlElement(
+            string name,
+            IDictionary<string, string> attributes,
+            ICollection<IElement> innerElements,
+            string parsedString = null)
+        {
+            this.Name = name;
+            this.ParsedString = parsedString;
+            if (attributes != null)
+            {
+                this.Attributes = new Dictionary<string, string>(attributes);
+            }
+            else
+            {
+                this.Attributes = new Dictionary<string, string>();
+            }
+
+            if (innerElements != null)
+            {
+                this.AddRange(innerElements);
+            }
+        }
+
+        /// <summary>
+        /// 指定されたタグ名・値からXML要素を生成する。
+        /// </summary>
+        /// <param name="name">タグ名。</param>
+        /// <param name="value">値。未指定時は<c>null</c>。</param>
+        public XmlElement(string name, string value = null)
+        {
+            this.Name = name;
+            this.Attributes = new Dictionary<string, string>();
+            if (!String.IsNullOrEmpty(value))
+            {
+                this.Add(new TextElement(value));
+            }
+        }
+
+        #endregion
+        
+        #region プロパティ
+
+        /// <summary>
+        /// タグ名。
+        /// </summary>
+        /// <exception cref="ArgumentNullException">タグ名がnullの場合。</exception>
+        /// <exception cref="ArgumentException">タグ名が空の場合。</exception>
+        public virtual string Name
+        {
+            get
+            {
+                return this.name;
+            }
+
+            set
+            {
+                this.name = Validate.NotBlank(value);
+            }
+        }
+
+        /// <summary>
+        /// タグに含まれる属性情報。
+        /// </summary>
+        public virtual IDictionary<string, string> Attributes
+        {
+            get;
+            protected set;
+        }
+
+        #endregion
+
+        #region 内部実装メソッド
+
+        /// <summary>
+        /// このXML要素を表す文字列を返す。
+        /// </summary>
+        /// <returns>このXML要素を表す文字列。</returns>
+        protected override string ToStringImpl()
+        {
+            StringBuilder b = new StringBuilder();
+            XmlWriterSettings s = new XmlWriterSettings();
+            s.CheckCharacters = false;
+            s.ConformanceLevel = ConformanceLevel.Fragment;
+            using (XmlWriter w = XmlWriter.Create(b, s))
+            {
+                w.WriteStartElement(this.Name);
+                foreach (KeyValuePair<string, string> attr in this.Attributes)
+                {
+                    w.WriteAttributeString(attr.Key, attr.Value);
+                }
+
+                foreach (IElement element in this)
+                {
+                    // エンコードする/しないは中身の責任として、ここではエンコードしない
+                    // ※ エンコードするテキストを用意したい場合はXmlTextElementを使うなど
+                    w.WriteRaw(element.ToString());
+                }
+
+                w.WriteEndElement();
+            }
+
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlElementParser.cs b/HmLib/Parsers/XmlElementParser.cs
new file mode 100644 (file)
index 0000000..1e1467d
--- /dev/null
@@ -0,0 +1,440 @@
+// ================================================================================================
+// <summary>
+//      XML/HTML要素を解析するためのクラスソース</summary>
+//
+// <copyright file="XmlElementParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Text;
+    using System.Text.RegularExpressions;
+    using System.Xml;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// XML/HTML要素を解析するためのクラスです。
+    /// </summary>
+    public class XmlElementParser : AbstractParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="XmlParser"/>。
+        /// </summary>
+        private XmlParser parser;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定された<see cref="XmlParser"/>を元にXML/HTML要素を解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="XmlParser"/>。</param>
+        /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
+        public XmlElementParser(XmlParser parser)
+        {
+            this.Parser = parser;
+        }
+
+        /// <summary>
+        /// <see cref="XmlParser"/>を指定しないでXML/HTML要素を解析するためのパーサーを作成する。
+        /// </summary>
+        /// <remarks>拡張用。<see cref="XmlParser"/>は処理に必須なため、別途設定する必要がある。</remarks>
+        protected XmlElementParser()
+        {
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="XmlParser"/>。
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
+        protected XmlParser Parser
+        {
+            get
+            {
+                return this.parser;
+            }
+
+            set
+            {
+                this.parser = Validate.NotNull(value);
+            }
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたテキストをXML/HTMLタグとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したタグ。</param>
+        /// <returns>タグの場合<c>true</c>。</returns>
+        /// <remarks>
+        /// XML/HTMLタグと判定するには、1文字目が開始タグである必要がある。
+        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
+        /// </remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 入力値確認。タグでない場合は即終了
+            result = null;
+            if (String.IsNullOrEmpty(s) || s[0] != '<')
+            {
+                return false;
+            }
+
+            // タグ名取得、タグ名の次に出現しうる文字を探索
+            int index = s.IndexOfAny(new char[] { ' ', '>', '/' }, 1);
+            if (index < 0)
+            {
+                return false;
+            }
+
+            // タグ名確認、コメント(<!--)やちゃんと始まっていないもの(< tag>とか)もここで除外
+            string name = s.Substring(1, index - 1);
+            if (!this.ValidateName(name))
+            {
+                return false;
+            }
+
+            // 開始タグの終端に到達するまで属性を探索
+            IDictionary<string, string> attribute;
+            int endIndex;
+            if (!this.TryParseAttribute(s.Substring(index), out attribute, out endIndex))
+            {
+                return false;
+            }
+
+            index += endIndex;
+
+            // "/>" で終わった場合は、ここでタグ終了
+            if (s[index - 1] == '/')
+            {
+                if (this.parser.IsHtml)
+                {
+                    result = new HtmlElement(name, attribute, null, s.Substring(0, index + 1));
+                }
+                else
+                {
+                    result = new XmlElement(name, attribute, null, s.Substring(0, index + 1));
+                }
+
+                return true;
+            }
+
+            // 閉じタグまでを解析
+            IElement innerElement;
+            if (!this.TryParseContent(s.Substring(index + 1), name, out innerElement, out endIndex))
+            {
+                return false;
+            }
+
+            if (endIndex < 0)
+            {
+                // 閉じタグが無い場合
+                if (this.parser.IsHtml)
+                {
+                    // HTMLの場合、閉じタグが無いのは開始タグだけのパターンと判定
+                    result = new HtmlElement(name, attribute, null, s.Substring(0, index + 1));
+                    return true;
+                }
+                else
+                {
+                    // それ以外は不正な構文だが値の最終文字までを設定
+                    index += innerElement.ToString().Length;
+                }
+            }
+            else
+            {
+                index += endIndex;
+            }
+
+            // 内部要素がリストの場合そのまま、それ以外はリストに入れて親のElementに代入
+            ICollection<IElement> collection;
+            if (innerElement.GetType() == typeof(ListElement))
+            {
+                collection = (ListElement)innerElement;
+            }
+            else
+            {
+                collection = new List<IElement>();
+                collection.Add(innerElement);
+            }
+
+            if (this.parser.IsHtml)
+            {
+                result = new HtmlElement(name, attribute, collection, s.Substring(0, index + 1));
+            }
+            else
+            {
+                result = new XmlElement(name, attribute, collection, s.Substring(0, index + 1));
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 渡された文字が<see cref="Parse"/>, <see cref="TryParse"/>の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public override bool IsPossibleParse(char c)
+        {
+            return '<' == c;
+        }
+
+        #endregion
+
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// 渡されたタグ名/属性名がXML的に正しいかを判定。
+        /// </summary>
+        /// <param name="name">XMLタグ名/属性名。</param>
+        /// <returns>正しい場合 <c>true</c>。</returns>
+        /// <remarks>
+        /// デコード前の値を渡すこと。
+        /// HTMLについては、HTML用のチェックではないが、基本的に呼んで問題ないはず。
+        /// </remarks>
+        private bool ValidateName(string name)
+        {
+            if (String.IsNullOrWhiteSpace(name))
+            {
+                return false;
+            }
+
+            try
+            {
+                XmlConvert.VerifyName(name);
+            }
+            catch (XmlException)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// タグの属性情報部分のテキストを受け取り、属性情報を解析する。
+        /// </summary>
+        /// <param name="s">解析する属性情報文字列(" key1="value1" key2="value2"&gt;~" のような文字列)。</param>
+        /// <param name="attribute">解析した属性。</param>
+        /// <param name="endIndex">タグ終了箇所('&gt;')のインデックス。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        private bool TryParseAttribute(string s, out IDictionary<string, string> attribute, out int endIndex)
+        {
+            // ※ 不正な構文も通すために、強引な汚いソースになってしまっている。
+            //    このメソッドにその辺りの処理を隔離している。
+
+            // 出力値初期化
+            attribute = null;
+            endIndex = -1;
+
+            // 開始タグの終端に到達するまで属性を探索
+            IDictionary<string, string> a = new Dictionary<string, string>();
+            string key = null;
+            bool existedEqual = false;
+            bool existedSpace = false;
+            char[] separators = null;
+            int i;
+            for (i = 0; i < s.Length; i++)
+            {
+                char c = s[i];
+                if (key == null)
+                {
+                    // 属性名の解析
+                    if (c == ' ')
+                    {
+                        // 属性名の前にあるスペースは無視
+                        continue;
+                    }
+
+                    // 属性名の次に出現しうる文字を探索
+                    int index = s.IndexOfAny(new char[] { '=', ' ', '>', '/' }, i);
+                    if (index < 0)
+                    {
+                        // どれも出現しない場合、構文エラー
+                        return false;
+                    }
+
+                    // 属性名を確認
+                    // ※ 属性が無い場合0文字となる
+                    key = s.Substring(i, index - i);
+                    if (!String.IsNullOrEmpty(key) && !this.ValidateName(key))
+                    {
+                        // 属性名の位置に出現し得ない記号が含まれているなど構文エラーも弾く
+                        return false;
+                    }
+
+                    // ループを次回「次に出現しうる文字」になるよう更新
+                    i = index - 1;
+                }
+                else if (!existedEqual)
+                {
+                    // 属性名は解析済みでも値が始まっていない場合
+                    if (c == '=')
+                    {
+                        // イコール後の処理に切り替え
+                        existedEqual = true;
+                        existedSpace = false;
+                    }
+                    else if (c == '>' || c == '/')
+                    {
+                        // ループ終了
+                        if (!String.IsNullOrEmpty(key))
+                        {
+                            // 属性名だけで値が無いパターン(<div disable>とか)
+                            a[this.parser.Decode(key)] = String.Empty;
+                            key = null;
+                        }
+
+                        break;
+                    }
+                    else if (c == ' ')
+                    {
+                        // 属性名の後にあるスペースは記録して無視
+                        existedSpace = true;
+                    }
+                    else if (existedSpace)
+                    {
+                        // 既にスペースが出現した状態で新たに普通の文字が出現した場合、
+                        // キーだけで値が無いパターンだったと判定
+                        a[this.parser.Decode(key)] = String.Empty;
+                        key = null;
+                        existedSpace = false;
+                    }
+                }
+                else
+                {
+                    // 属性値の解析
+                    if (separators == null)
+                    {
+                        if (c == ' ')
+                        {
+                            // イコールの後の、値に入る前のスペースは無視
+                            continue;
+                        }
+
+                        // イコール後の最初の文字の場合、区切り文字かを確認
+                        if (c == '\'' || c == '"')
+                        {
+                            separators = new char[] { c };
+                            continue;
+                        }
+
+                        // いきなり値が始まっている場合は、次の文字までを値と判定
+                        separators = new char[] { ' ', '>', '/' };
+                    }
+
+                    // 属性値の終了文字を探索
+                    int index = s.IndexOfAny(separators, i);
+                    if (index < 0)
+                    {
+                        // どれも出現しない場合、閉じてないということで構文エラー
+                        return false;
+                    }
+
+                    // 区切り文字に到達
+                    a[this.parser.Decode(key)] = this.parser.Decode(s.Substring(i, index - i));
+                    key = null;
+                    existedEqual = false;
+                    separators = null;
+
+                    if (s[index] == '>' || s[index] == '/')
+                    {
+                        // 区切り文字がループ終了の文字だったらここで終了
+                        break;
+                    }
+
+                    // ループを次回「区切り文字の次の文字」になるよう更新
+                    i = index;
+                }
+            }
+
+            // '/' で抜けた場合は、閉じ括弧があるはず位置にインデックスを移動
+            if (s.ElementAtOrDefault(i) == '/')
+            {
+                ++i;
+            }
+
+            // 最後が閉じ括弧で無い場合、閉じていないのでNG
+            // ※ ループが閉じ括弧で終わらなかった場合もここに引っかかる
+            if (s.ElementAtOrDefault(i) != '>')
+            {
+                return false;
+            }
+
+            // 出力値の設定
+            attribute = a;
+            endIndex = i;
+
+            return true;
+        }
+
+        /// <summary>
+        /// タグの開始タグ以降の部分のテキストを受け取り、値と閉じタグを解析する。
+        /// </summary>
+        /// <param name="s">解析する部分文字列("~&lt;/tag&gt;" のような文字列)。</param>
+        /// <param name="tag">解析するタグ名。</param>
+        /// <param name="innerElement">解析したコンテンツ部分。</param>
+        /// <param name="endIndex">閉じタグ終了箇所('&gt;')のインデックス。閉じていない場合は-1。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        private bool TryParseContent(string s, string tag, out IElement innerElement, out int endIndex)
+        {
+            // 検索条件作成
+            RegexOptions options = RegexOptions.Singleline;
+            if (this.parser.IgnoreCase)
+            {
+                // 大文字小文字を区別しない
+                options = options | RegexOptions.IgnoreCase;
+            }
+
+            // 終了条件をデリゲートで作成し、XMLテキストのパーサーを呼び出し
+            // 閉じタグに遭遇するまで、内部要素を再帰的に解析
+            // TODO: 閉じないタグ(<br>とか)だと延々無駄に処理をしてしまうので、可能であれば改善する
+            Regex regex = new Regex("^</" + Regex.Escape(tag) + "\\s*>", options);
+            int end = -1;
+            bool success = this.parser.TryParseToEndCondition(
+                s,
+                (string str, int index)
+                    =>
+                {
+                    // 毎回Substringと正規表現をすると遅いのでチェックが必要かをチェック
+                    if (str[index] != '<')
+                    {
+                        return false;
+                    }
+
+                    Match match = regex.Match(str.Substring(index));
+                    if (!match.Success)
+                    {
+                        return false;
+                    }
+
+                    end = index + match.Length;
+                    return true;
+                },
+                out innerElement);
+
+            endIndex = end;
+            return success;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlParser.cs b/HmLib/Parsers/XmlParser.cs
new file mode 100644 (file)
index 0000000..9d0d83a
--- /dev/null
@@ -0,0 +1,148 @@
+// ================================================================================================
+// <summary>
+//      XML/HTMLテキストを解析するためのクラスソース</summary>
+//
+// <copyright file="XmlParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Net;
+    using System.Text;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// XML/HTMLテキストを解析するためのクラスです。
+    /// </summary>
+    /// <remarks>HTMLについては、解析はできるもののほぼXml用のElementで結果が返されます。</remarks>
+    public class XmlParser : AbstractTextParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// パーサー内で使用する各要素のパーサー。
+        /// </summary>
+        private IParser[] parsers;
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// XML/HTMLテキストを解析するためのパーサーを作成する。
+        /// </summary>
+        public XmlParser()
+        {
+            this.IgnoreCase = true;
+            this.parsers = new IParser[]
+            {
+                new XmlCommentElementParser(),
+                new XmlElementParser(this)
+            };
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// パーサー内で使用する各要素のパーサー。
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
+        public IParser[] Parsers
+        {
+            get
+            {
+                return this.parsers;
+            }
+
+            set
+            {
+                this.parsers = Validate.NotNull(value);
+            }
+        }
+
+        /// <summary>
+        /// 大文字小文字を無視するか?
+        /// </summary>
+        public bool IgnoreCase
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// タグはHTMLの書式か?
+        /// </summary>
+        public bool IsHtml
+        {
+            get;
+            set;
+        }
+
+        #endregion
+        
+        #region XmlParser, XmlElementPaser共通メソッド
+
+        /// <summary>
+        /// 文字列をXML/HTML読み込み用にデコードする。
+        /// </summary>
+        /// <param name="s">デコードする文字列。</param>
+        /// <returns>デコードされた文字列。<c>null</c>の場合、空文字列を返す。</returns>
+        internal string Decode(string s)
+        {
+            if (s == null)
+            {
+                return String.Empty;
+            }
+            else if (this.IsHtml)
+            {
+                return WebUtility.HtmlDecode(s);
+            }
+            else
+            {
+                return XmlUtils.XmlDecode(s);
+            }
+        }
+
+        #endregion
+
+        #region 実装支援用メソッド拡張
+
+        /// <summary>
+        /// 渡されたテキストを各種解析処理で解析する。
+        /// </summary>
+        /// <param name="s">解析するテキスト。</param>
+        /// <param name="index">処理インデックス。</param>
+        /// <param name="result">解析した結果要素。</param>
+        /// <returns>解析できた場合<c>true</c>。</returns>
+        protected override bool TryParseElementAt(string s, int index, out IElement result)
+        {
+            return this.TryParseAt(s, index, out result, this.parsers);
+        }
+
+        /// <summary>
+        /// 文字列が空でない場合、リストにTextエレメントを追加して、文字列をリセットする。
+        /// </summary>
+        /// <param name="list">追加されるリスト。</param>
+        /// <param name="b">追加する文字列。</param>
+        protected override void FlashText(ref ListElement list, ref StringBuilder b)
+        {
+            if (b.Length > 0)
+            {
+                string s = b.ToString();
+                XmlTextElement e = new XmlTextElement(this.Decode(s));
+                e.ParsedString = s;
+                list.Add(e);
+                b.Clear();
+            }
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Parsers/XmlTextElement.cs b/HmLib/Parsers/XmlTextElement.cs
new file mode 100644 (file)
index 0000000..441bdbd
--- /dev/null
@@ -0,0 +1,94 @@
+// ================================================================================================
+// <summary>
+//      XML中のテキスト要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="XmlTextElement.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.IO;
+    using System.Text;
+    using System.Xml;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// XML中のテキスト要素をあらわすモデルクラスです。
+    /// </summary>
+    public class XmlTextElement : TextElement
+    {
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたXMLエンコードされていないテキストを用いて要素を作成する。
+        /// </summary>
+        /// <param name="text">テキスト文字列、未指定時は<c>null</c>。</param>
+        public XmlTextElement(string text = null)
+            : base(text)
+        {
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// このテキスト要素のテキスト。
+        /// </summary>
+        /// <remarks>実際のデータは<see cref="Raw"/>に格納。</remarks>
+        public override string Text
+        {
+            get
+            {
+                if (this.Raw == null)
+                {
+                    return this.Raw;
+                }
+                else
+                {
+                    return XmlUtils.XmlDecode(this.Raw);
+                }
+            }
+
+            set
+            {
+                if (value == null)
+                {
+                    this.Raw = null;
+                }
+                else
+                {
+                    this.Raw = XmlUtils.XmlEncode(value);
+                }
+            }
+        }
+
+        /// <summary>
+        /// このテキスト要素のエンコードされていない生のテキスト。
+        /// </summary>
+        public virtual string Raw
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// このテキスト要素のXMLエンコードしたテキストを返す。
+        /// </summary>
+        /// <returns>このテキスト要素のテキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            return this.Raw;
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLib/Properties/AssemblyInfo.cs b/HmLib/Properties/AssemblyInfo.cs
new file mode 100644 (file)
index 0000000..a35a83b
--- /dev/null
@@ -0,0 +1,45 @@
+// ================================================================================================
+// <summary>
+//      HmLibのアセンブリソース</summary>
+//
+// <copyright file="AssemblyInfo.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
+// アセンブリに関連付けられている情報を変更するには、
+// これらの属性値を変更してください。
+[assembly: AssemblyTitle("HmLib")]
+[assembly: AssemblyDescription("汎用的なクラス等を抽出したライブラリ")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HmLib")]
+[assembly: AssemblyCopyright("Copyright (C) Honeplus 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 
+// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
+// その型の ComVisible 属性を true に設定してください。
+[assembly: ComVisible(false)]
+
+// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
+[assembly: Guid("cc5bd67d-7ef6-4b45-a8a6-b2b0df85a931")]
+
+// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 
+// 既定値にすることができます:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.1.*")]
similarity index 98%
rename from Wptscs/Utilities/ObjectUtils.cs
rename to HmLib/Utilities/ObjectUtils.cs
index 449b4b1..d57a284 100644 (file)
@@ -3,7 +3,7 @@
 //      Apache Commons Lang の ObjectUtilsを参考にしたユーティリティクラスソース。</summary>
 //
 // <copyright file="ObjectUtils.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
similarity index 52%
rename from Wptscs/Utilities/StringUtils.cs
rename to HmLib/Utilities/StringUtils.cs
index 467ec3f..9e45f7c 100644 (file)
@@ -3,7 +3,7 @@
 //      文字列処理に関するユーティリティクラスソース。</summary>
 //
 // <copyright file="StringUtils.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -11,6 +11,7 @@
 namespace Honememo.Utilities
 {
     using System;
+    using System.Text.RegularExpressions;
 
     /// <summary>
     /// 文字列処理に関するユーティリティクラスです。
@@ -18,6 +19,15 @@ namespace Honememo.Utilities
     /// <remarks>一部メソッドは、Apache Commons Lang の StringUtils やJava標準の String を参考にしています。</remarks>
     public static class StringUtils
     {
+        #region 定数
+
+        /// <summary>
+        /// <see cref="FormatDollarVariable"/>で使用する正規表現。
+        /// </summary>
+        private static readonly Regex DollarVariableRegex = new Regex("\\$([0-9]+)");
+
+        #endregion
+
         #region 初期化メソッド
 
         /// <summary>
@@ -50,6 +60,50 @@ namespace Honememo.Utilities
 
         #endregion
 
+        #region 切り出しメソッド
+
+        /// <summary>
+        /// 指定された文字列の部分文字列を例外を発生させることなく取得します。
+        /// </summary>
+        /// <param name="str">部分文字列の取得対象となる文字列。</param>
+        /// <param name="startIndex">部分文字列の開始位置。</param>
+        /// <returns>開始位置からの部分文字列。</returns>
+        public static string Substring(string str, int startIndex)
+        {
+            return StringUtils.Substring(str, startIndex, Int32.MaxValue);
+        }
+
+        /// <summary>
+        /// 指定された文字列の部分文字列を例外を発生させることなく取得します。
+        /// </summary>
+        /// <param name="str">部分文字列の取得対象となる文字列。</param>
+        /// <param name="startIndex">部分文字列の開始位置。</param>
+        /// <param name="length">部分文字列の文字数。</param>
+        /// <returns>開始位置から指定された文字数の部分文字列。文字数が足りない場合、最後まで。</returns>
+        public static string Substring(string str, int startIndex, int length)
+        {
+            if (str == null)
+            {
+                return null;
+            }
+
+            int i = startIndex > 0 ? startIndex : 0;
+            if (i > str.Length)
+            {
+                return String.Empty;
+            }
+
+            int l = length > 0 ? length : 0;
+            if (l > str.Length - i)
+            {
+                l = str.Length - i;
+            }
+
+            return str.Substring(i, l);
+        }
+
+        #endregion
+
         #region 文字列チェック
 
         /// <summary>
@@ -93,7 +147,37 @@ namespace Honememo.Utilities
             // 後は普通のStartWithで処理
             return str.Substring(toffset).StartsWith(prefix);
         }
-        
+
+        #endregion
+
+        #region 書式化メソッド
+
+        /// <summary>
+        /// 指定した文字列の書式項目を、指定した配列内の対応するオブジェクトの文字列形式に置換します。
+        /// </summary>
+        /// <param name="format">$1~$数値の形式でパラメータを指定する書式指定文字列。</param>
+        /// <param name="args">書式設定対象オブジェクト。</param>
+        /// <returns>書式項目が <para>args</para> の対応するオブジェクトの文字列形式に置換された <para>format</para> のコピー。</returns>
+        /// <exception cref="ArgumentNullException"><para>format</para>または<para>args</para>が<c>null</c>の場合。</exception>
+        /// <remarks>.netではなくPerl等で見かける$~形式のフォーマットを行う。</remarks>
+        public static string FormatDollarVariable(string format, params object[] args)
+        {
+            // nullチェック
+            Validate.NotNull(format);
+            Validate.NotNull(args);
+
+            // 正規表現で$1~$数値のパラメータ部分を抜き出し、対応するパラメータに置き換える
+            // 対応するパラメータが存在しない場合、空文字列となる
+            return DollarVariableRegex.Replace(
+                format,
+                (Match match)
+                =>
+                {
+                    int index = Int32.Parse(match.Groups[1].Value) - 1;
+                    return args.Length > index ? ObjectUtils.ToString(args[index]) : String.Empty;
+                });
+        }
+
         #endregion
     }
 }
similarity index 98%
rename from Wptscs/Utilities/Validate.cs
rename to HmLib/Utilities/Validate.cs
index e70392e..0888157 100644 (file)
@@ -3,7 +3,7 @@
 //      Apache Commons Lang の Validateを参考にしたユーティリティクラスソース。</summary>
 //
 // <copyright file="Validate.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
similarity index 72%
rename from Wptscs/Utilities/XmlUtils.cs
rename to HmLib/Utilities/XmlUtils.cs
index d51f84d..8b96bc8 100644 (file)
@@ -3,7 +3,7 @@
 //      Xmlの処理に関するユーティリティクラスソース。</summary>
 //
 // <copyright file="XmlUtils.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -11,6 +11,7 @@
 namespace Honememo.Utilities
 {
     using System;
+    using System.Text;
     using System.Xml;
 
     /// <summary>
@@ -99,5 +100,42 @@ namespace Honememo.Utilities
         }
 
         #endregion
+
+        #region エンコード/デコード
+
+        /// <summary>
+        /// 指定された文字列をXMLエンコードする。
+        /// </summary>
+        /// <param name="s">エンコードする文字列。</param>
+        /// <returns>エンコードした文字列。</returns>
+        /// <exception cref="ArgumentNullException">文字列が<c>null</c>。</exception>
+        /// <remarks>
+        /// 使う場所によってはエンコードが必要ない文字もあるが、汎用のため常時
+        /// &lt;, &gt;, &quot;, &apos;, &amp; の5文字を変換する。
+        /// </remarks>
+        public static string XmlEncode(string s)
+        {
+            Validate.NotNull(s);
+            return s.Replace("&", "&amp;").Replace("<", "&lt;")
+                .Replace(">", "&gt;").Replace("\"", "&quot;").Replace("\'", "&apos;");
+        }
+
+        /// <summary>
+        /// 指定された文字列をXMLデコードする。
+        /// </summary>
+        /// <param name="s">エンコードされた文字列。</param>
+        /// <returns>エンコードを解除した文字列。</returns>
+        /// <exception cref="ArgumentNullException">文字列が<c>null</c>。</exception>
+        /// <remarks>
+        /// &lt;, &gt;, &quot;, &apos;, &amp; の5文字を変換する。
+        /// </remarks>
+        public static string XmlDecode(string s)
+        {
+            Validate.NotNull(s);
+            return s.Replace("&lt;", "<").Replace("&gt;", ">")
+                .Replace("&quot;", "\"").Replace("&apos;", "\'").Replace("&amp;", "&");
+        }
+
+        #endregion
     }
 }
diff --git a/HmLibTest/HmLibTest.csproj b/HmLibTest/HmLibTest.csproj
new file mode 100644 (file)
index 0000000..af43fb8
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{77218F38-BD1C-469B-B975-68AE3E9EE14E}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Honememo</RootNamespace>
+    <AssemblyName>HmLibTest</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <StartAction>Program</StartAction>
+    <StartProgram>$(ProgramFiles)\NUnit 2.5.5\bin\net-2.0\nunit.exe</StartProgram>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="nunit.framework, Version=2.5.5.10112, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL" />
+    <Reference Include="System" />
+    <Reference Include="System.XML" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Models\IgnoreCaseDictionaryTest.cs" />
+    <Compile Include="Models\TreeNodeTest.cs" />
+    <Compile Include="Parsers\XmlCommentElementTest.cs" />
+    <Compile Include="Parsers\ListElementTest.cs" />
+    <Compile Include="Parsers\TextElementTest.cs" />
+    <Compile Include="Parsers\XmlElementTest.cs" />
+    <Compile Include="Parsers\HtmlElementTest.cs" />
+    <Compile Include="Parsers\XmlTextElementTest.cs" />
+    <Compile Include="Parsers\XmlElementParserTest.cs" />
+    <Compile Include="Parsers\XmlCommentElementParserTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Parsers\XmlParserTest.cs" />
+    <Compile Include="Utilities\ObjectUtilsTest.cs" />
+    <Compile Include="Utilities\StringUtilsTest.cs" />
+    <Compile Include="Utilities\ValidateTest.cs" />
+    <Compile Include="Utilities\XmlUtilsTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\HmLib\HmLib.csproj">
+      <Project>{CC7F6106-0C00-427B-9BF2-1EE65448907B}</Project>
+      <Name>HmLib</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
similarity index 99%
rename from WptscsTest/Models/IgnoreCaseDictionaryTest.cs
rename to HmLibTest/Models/IgnoreCaseDictionaryTest.cs
index 19f121a..9680f0e 100644 (file)
@@ -3,7 +3,7 @@
 //      IgnoreCaseDictionaryのテストクラスソース。</summary>
 //
 // <copyright file="IgnoreCaseDictionaryTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/HmLibTest/Models/TreeNodeTest.cs b/HmLibTest/Models/TreeNodeTest.cs
new file mode 100644 (file)
index 0000000..d59aa30
--- /dev/null
@@ -0,0 +1,251 @@
+// ================================================================================================
+// <summary>
+//      TreeNodeのテストクラスソース。</summary>
+//
+// <copyright file="TreeNodeTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Models
+{
+    using System;
+    using System.Collections.Generic;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// TreeNodeのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class TreeNodeTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            // 引数無しの場合null
+            TreeNode<string> node = new TreeNode<string>();
+            Assert.IsNull(node.Value);
+
+            // 引数有りの場合その値
+            node = new TreeNode<string>("test string");
+            Assert.AreEqual("test string", node.Value);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Valueプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestValue()
+        {
+            // 初期状態ではnull
+            TreeNode<string> node1 = new TreeNode<string>();
+            Assert.IsNull(node1.Value);
+
+            // 値を設定するとその値となる
+            node1.Value = "test string";
+            Assert.AreEqual("test string", node1.Value);
+
+            // 別の型でも同様
+            TreeNode<DateTime> node2 = new TreeNode<DateTime>();
+            node2.Value = new DateTime(100L);
+            Assert.AreEqual(new DateTime(100L), node2.Value);
+        }
+
+        /// <summary>
+        /// Parentプロパティテストケース。
+        /// </summary>
+        /// <remarks>値についての確認はAdd()メソッド等の試験で実施。</remarks>
+        [Test]
+        public void TestParent()
+        {
+            // 初期状態ではnull
+            TreeNode<string> node = new TreeNode<string>();
+            Assert.IsNull(node.Parent);
+        }
+
+        /// <summary>
+        /// Childrenプロパティテストケース。
+        /// </summary>
+        /// <remarks>値についての確認はAdd()メソッド等の試験で実施。</remarks>
+        [Test]
+        public void TestChildren()
+        {
+            // 初期状態では空のリスト
+            TreeNode<string> node = new TreeNode<string>();
+            Assert.IsNotNull(node.Children);
+            Assert.AreEqual(0, node.Children.Count);
+        }
+
+        #endregion
+
+        #region 公開メソッドテストケース
+
+        /// <summary>
+        /// Addメソッドテストケース正常系。
+        /// </summary>
+        [Test]
+        public void TestAdd()
+        {
+            // 子ノードを1件登録
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            TreeNode<string> child1 = new TreeNode<string>("child1 string");
+            parent.Add(child1);
+            Assert.IsNull(parent.Parent);
+            Assert.AreEqual(1, parent.Children.Count);
+            Assert.AreSame(parent, child1.Parent);
+            Assert.AreEqual(0, child1.Children.Count);
+            Assert.AreSame(child1, parent.Children[0]);
+
+            // 2件目を登録
+            TreeNode<string> child2 = new TreeNode<string>("child2 string");
+            parent.Add(child2);
+            Assert.AreEqual(2, parent.Children.Count);
+            Assert.AreSame(child1, parent.Children[0]);
+            Assert.AreSame(parent, child2.Parent);
+            Assert.AreEqual(0, child2.Children.Count);
+            Assert.AreSame(child2, parent.Children[1]);
+        }
+
+        /// <summary>
+        /// Addメソッドテストケース 自分を設定。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void TestAddThis()
+        {
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            TreeNode<string> child = new TreeNode<string>("child string");
+            parent.Add(child);
+            child.Add(parent);
+        }
+
+        /// <summary>
+        /// Addメソッドテストケース 親を設定。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void TestAddParent()
+        {
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            TreeNode<string> child = new TreeNode<string>("child string");
+            parent.Add(child);
+            child.Add(parent);
+        }
+
+        /// <summary>
+        /// Addメソッドテストケース 祖先を設定。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void TestAddGrandparent()
+        {
+            TreeNode<string> grandparent = new TreeNode<string>("grandparent string");
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            TreeNode<string> child = new TreeNode<string>("child string");
+            grandparent.Add(parent);
+            parent.Add(child);
+            child.Add(grandparent);
+        }
+
+        /// <summary>
+        /// Removeメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void Remove()
+        {
+            // 子ノードを持つノードを作成
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            TreeNode<string> child1 = new TreeNode<string>("child1 string");
+            TreeNode<string> child2 = new TreeNode<string>("child2 string");
+            TreeNode<string> child3 = new TreeNode<string>("child3 string");
+            parent.Add(child1);
+            parent.Add(child2);
+            parent.Add(child3);
+            Assert.AreEqual(3, parent.Children.Count);
+            Assert.AreSame(parent, child1.Parent);
+            Assert.AreSame(child1, parent.Children[0]);
+
+            // 子ノードを1件削除
+            Assert.IsTrue(parent.Remove(child1));
+            Assert.IsNull(child1.Parent);
+            Assert.AreEqual(2, parent.Children.Count);
+            Assert.AreSame(child2, parent.Children[0]);
+
+            // 削除済みのノードを削除しようとするとfalse
+            Assert.IsFalse(parent.Remove(child1));
+            Assert.AreEqual(2, parent.Children.Count);
+
+            // 子ノード2件目を削除
+            Assert.IsTrue(parent.Remove(child3));
+            Assert.IsNull(child3.Parent);
+            Assert.AreEqual(1, parent.Children.Count);
+            Assert.AreSame(child2, parent.Children[0]);
+        }
+
+        /// <summary>
+        /// GetEnumeratorメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestGetEnumerator()
+        {
+            // 親ノードのみ
+            TreeNode<string> parent = new TreeNode<string>("parent string");
+            this.AreSameIEnumerator<TreeNode<string>>(parent.GetEnumerator(), parent);
+
+            // 子ノードまで
+            TreeNode<string> child1 = new TreeNode<string>("child1 string");
+            TreeNode<string> child2 = new TreeNode<string>("child2 string");
+            parent.Add(child1);
+            parent.Add(child2);
+            this.AreSameIEnumerator<TreeNode<string>>(parent.GetEnumerator(), parent, child1, child2);
+
+            // 孫ノードまで
+            TreeNode<string> grandchild1 = new TreeNode<string>("grandchild1 string");
+            child1.Add(grandchild1);
+            this.AreSameIEnumerator<TreeNode<string>>(parent.GetEnumerator(), parent, child1, grandchild1, child2);
+        }
+
+        #endregion
+
+        #region テスト補助メソッド
+
+        /// <summary>
+        /// IEnumeratorが指定された値を返すかを検証する。
+        /// </summary>
+        /// <typeparam name="T">IEnumeratorが返す型。</typeparam>
+        /// <param name="actual">検証するIEnumerator。</param>
+        /// <param name="expected">期待値。</param>
+        /// <remarks>値の数も含めて検証する。不一致は試験失敗。</remarks>
+        private void AreSameIEnumerator<T>(IEnumerator<T> actual, params T[] expected)
+        {
+            foreach (T obj in expected)
+            {
+                if (actual.MoveNext())
+                {
+                    Assert.AreSame(obj, actual.Current);
+                }
+                else
+                {
+                    Assert.Fail("actual < expected");
+                }
+            }
+
+            if (actual.MoveNext())
+            {
+                Assert.Fail("actual > expected");
+            }
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/HtmlElementTest.cs b/HmLibTest/Parsers/HtmlElementTest.cs
new file mode 100644 (file)
index 0000000..15beab8
--- /dev/null
@@ -0,0 +1,79 @@
+// ================================================================================================
+// <summary>
+//      HtmlElementのテストクラスソース。</summary>
+//
+// <copyright file="HtmlElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// HtmlElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class HtmlElementTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            HtmlElement element = new HtmlElement("testname1");
+            Assert.AreEqual("testname1", element.Name);
+            Assert.AreEqual(0, element.Attributes.Count);
+            Assert.AreEqual(0, element.Count);
+
+            element = new HtmlElement("testname2", "testvalue");
+            Assert.AreEqual("testname2", element.Name);
+            Assert.AreEqual(0, element.Attributes.Count);
+            Assert.AreEqual(1, element.Count);
+            Assert.IsInstanceOf(typeof(TextElement), element[0]);
+            Assert.AreEqual("testvalue", element[0].ToString());
+
+            IDictionary<string, string> attribute = new Dictionary<string, string>();
+            attribute.Add("testattr1", "testattrvalue1");
+            ICollection<IElement> collection = new List<IElement>();
+            collection.Add(new XmlCommentElement("testcomment"));
+            element = new HtmlElement("testname3", attribute, collection);
+            Assert.AreEqual("testname3", element.Name);
+            Assert.AreEqual("testattrvalue1", element.Attributes["testattr1"]);
+            Assert.AreEqual("testcomment", ((XmlCommentElement)element[0]).Text);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            HtmlElement element = new HtmlElement("form");
+            Assert.AreEqual("<form>", element.ToString());
+            element.Attributes.Add("action", "/test.html");
+            Assert.AreEqual("<form action=\"/test.html\">", element.ToString());
+            element.Attributes.Add("disabled", "");
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\">", element.ToString());
+            element.Add(new TextElement("フォーム内のテキスト"));
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\">フォーム内のテキスト</form>", element.ToString());
+            element.Add(new XmlCommentElement("コメント"));
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\">フォーム内のテキスト<!--コメント--></form>", element.ToString());
+            element.Attributes.Add("test_attr", "&<>\"");
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\" test_attr=\"&amp;&lt;&gt;&quot;\">フォーム内のテキスト<!--コメント--></form>", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/ListElementTest.cs b/HmLibTest/Parsers/ListElementTest.cs
new file mode 100644 (file)
index 0000000..9e076bf
--- /dev/null
@@ -0,0 +1,67 @@
+// ================================================================================================
+// <summary>
+//      ListElementのテストクラスソース。</summary>
+//
+// <copyright file="ListElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// ListElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class ListElementTest
+    {
+        #region インタフェース実装プロパティテストケース
+
+        /// <summary>
+        /// ParsedStringプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestParsedString()
+        {
+            ListElement element = new ListElement();
+
+            Assert.IsNull(element.ParsedString);
+            element.ParsedString = "test";
+            Assert.AreEqual("test", element.ParsedString);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            ListElement element = new ListElement();
+
+            Assert.IsEmpty(element.ToString());
+
+            element.Add(new TextElement { Text = "test1" });
+            Assert.AreEqual("test1", element.ToString());
+
+            element.Add(new TextElement { Text = "test2" });
+            Assert.AreEqual("test1test2", element.ToString());
+
+            element.Add(new TextElement { Text = "test3" });
+            element.RemoveAt(1);
+            Assert.AreEqual("test1test3", element.ToString());
+
+            element.ParsedString = "parsed test";
+            Assert.AreEqual("parsed test", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/TextElementTest.cs b/HmLibTest/Parsers/TextElementTest.cs
new file mode 100644 (file)
index 0000000..f8267ab
--- /dev/null
@@ -0,0 +1,73 @@
+// ================================================================================================
+// <summary>
+//      TextElementのテストクラスソース。</summary>
+//
+// <copyright file="TextElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// TextElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class TextElementTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            TextElement element = new TextElement();
+            Assert.IsNull(element.Text);
+
+            element = new TextElement("test");
+            Assert.AreEqual("test", element.Text);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Textプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestText()
+        {
+            TextElement element = new TextElement();
+
+            Assert.IsNull(element.Text);
+            element.Text = "test";
+            Assert.AreEqual("test", element.Text);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            TextElement element = new TextElement();
+
+            Assert.IsEmpty(element.ToString());
+            element.Text = "test";
+            Assert.AreEqual("test", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlCommentElementParserTest.cs b/HmLibTest/Parsers/XmlCommentElementParserTest.cs
new file mode 100644 (file)
index 0000000..3d4eb80
--- /dev/null
@@ -0,0 +1,70 @@
+// ================================================================================================
+// <summary>
+//      XmlCommentElementParserのテストクラスソース。</summary>
+//
+// <copyright file="XmlCommentElementParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlCommentElementParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlCommentElementParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            XmlCommentElementParser parser = new XmlCommentElementParser();
+            IElement comment;
+            Assert.IsTrue(parser.TryParse("<!--test-->", out comment));
+            Assert.AreEqual("<!--test-->", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!-- test -->", out comment));
+            Assert.AreEqual("<!-- test -->", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!--test-->-->", out comment));
+            Assert.AreEqual("<!--test-->", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!--test--", out comment));
+            Assert.AreEqual("<!--test--", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!--->", out comment));
+            Assert.AreEqual("<!--->", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!--->-->", out comment));
+            Assert.AreEqual("<!--->-->", comment.ToString());
+            Assert.IsTrue(parser.TryParse("<!--\n\ntest\r\n-->", out comment));
+            Assert.AreEqual("<!--\n\ntest\r\n-->", comment.ToString());
+            Assert.IsFalse(parser.TryParse("<--test-->", out comment));
+            Assert.IsNull(comment);
+            Assert.IsFalse(parser.TryParse("<%--test--%>", out comment));
+            Assert.IsNull(comment);
+            Assert.IsFalse(parser.TryParse("<! --test-->", out comment));
+            Assert.IsNull(comment);
+        }
+
+        /// <summary>
+        /// IsPossibleParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestIsPossibleParse()
+        {
+            XmlCommentElementParser parser = new XmlCommentElementParser();
+            Assert.IsTrue(parser.IsPossibleParse('<'));
+            Assert.IsFalse(parser.IsPossibleParse('['));
+            Assert.IsFalse(parser.IsPossibleParse('-'));
+            Assert.IsFalse(parser.IsPossibleParse('/'));
+            Assert.IsFalse(parser.IsPossibleParse('#'));
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlCommentElementTest.cs b/HmLibTest/Parsers/XmlCommentElementTest.cs
new file mode 100644 (file)
index 0000000..2920baa
--- /dev/null
@@ -0,0 +1,59 @@
+// ================================================================================================
+// <summary>
+//      XmlCommentElementのテストクラスソース。</summary>
+//
+// <copyright file="XmlCommentElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlCommentElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlCommentElementTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            XmlCommentElement comment = new XmlCommentElement();
+            Assert.IsNull(comment.Text);
+
+            comment = new XmlCommentElement("test");
+            Assert.AreEqual("test", comment.Text);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            XmlCommentElement comment = new XmlCommentElement();
+            Assert.AreEqual("<!---->", comment.ToString());
+
+            comment.Text = "test";
+            Assert.AreEqual("<!--test-->", comment.ToString());
+
+            comment.ParsedString = "<!--test--";
+            Assert.AreEqual("<!--test--", comment.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlElementParserTest.cs b/HmLibTest/Parsers/XmlElementParserTest.cs
new file mode 100644 (file)
index 0000000..887fb69
--- /dev/null
@@ -0,0 +1,349 @@
+// ================================================================================================
+// <summary>
+//      XmlElementParserのテストクラスソース。</summary>
+//
+// <copyright file="XmlElementParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlElementParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlElementParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース(実例)。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            IElement element;
+            XmlElement xmlElement;
+            XmlParser xmlParser = new XmlParser();
+            XmlElementParser parser = new XmlElementParser(xmlParser);
+
+            Assert.IsTrue(parser.TryParse("<h1>test</h1>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<h1>test</h1>", xmlElement.ToString());
+            Assert.AreEqual("h1", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<br /><br />test<br />", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<br />", xmlElement.ToString());
+            Assert.AreEqual("br", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<div id=\"testid\" name=\"testname\"><!--<div id=\"indiv\">test</div>--></div><br />", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<div id=\"testid\" name=\"testname\"><!--<div id=\"indiv\">test</div>--></div>", xmlElement.ToString());
+            Assert.AreEqual("div", xmlElement.Name);
+            Assert.AreEqual(2, xmlElement.Attributes.Count);
+            Assert.AreEqual("testid", xmlElement.Attributes["id"]);
+            Assert.AreEqual("testname", xmlElement.Attributes["name"]);
+
+            xmlParser.IsHtml = true;
+            Assert.IsTrue(parser.TryParse("<p>段落1<p>段落2", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<p>", xmlElement.ToString());
+            Assert.AreEqual("p", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<input type=\"checkbox\" name=\"param\" value=\"test\" checked><label for=\"param\">チェック</label>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<input type=\"checkbox\" name=\"param\" value=\"test\" checked>", xmlElement.ToString());
+            Assert.AreEqual("input", xmlElement.Name);
+            Assert.AreEqual(4, xmlElement.Attributes.Count);
+            Assert.AreEqual("checkbox", xmlElement.Attributes["type"]);
+            Assert.AreEqual("param", xmlElement.Attributes["name"]);
+            Assert.AreEqual("test", xmlElement.Attributes["value"]);
+            Assert.IsEmpty(xmlElement.Attributes["checked"]);
+
+            Assert.IsTrue(parser.TryParse("<div id=\"outer\">outertext<div id=\"inner\">innertext</div></div>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<div id=\"outer\">outertext<div id=\"inner\">innertext</div></div>", xmlElement.ToString());
+            Assert.AreEqual("div", xmlElement.Name);
+            Assert.AreEqual(1, xmlElement.Attributes.Count);
+            Assert.AreEqual("outer", xmlElement.Attributes["id"]);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(基本形)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNormal()
+        {
+            IElement element;
+            XmlElement xmlElement;
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+
+            Assert.IsTrue(parser.TryParse("<testtag></testtag>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag></testtag>", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag2>test value</testtag2>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag2>test value</testtag2>", xmlElement.ToString());
+            Assert.AreEqual("testtag2", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag3> test value2 </testtag3>testend", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag3> test value2 </testtag3>", xmlElement.ToString());
+            Assert.AreEqual("testtag3", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag4 testattr=\"testvalue\"> test<!-- </testtag4> --> value3 </testtag4>testend", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag4 testattr=\"testvalue\"> test<!-- </testtag4> --> value3 </testtag4>", xmlElement.ToString());
+            Assert.AreEqual("testtag4", xmlElement.Name);
+            Assert.AreEqual(1, xmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue", xmlElement.Attributes["testattr"]);
+
+            Assert.IsTrue(parser.TryParse("<testtag5 testattr2='testvalue2'>testbody</testtag5 >testend", out element));
+            xmlElement = (XmlElement)element;
+            Assert.IsInstanceOf(typeof(XmlTextElement), xmlElement[0]);
+            Assert.AreEqual("<testtag5 testattr2='testvalue2'>testbody</testtag5 >", xmlElement.ToString());
+            Assert.AreEqual("testtag5", xmlElement.Name);
+            Assert.AreEqual(1, xmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue2", xmlElement.Attributes["testattr2"]);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(普通でNGパターン)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNormalNg()
+        {
+            IElement element;
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+
+            Assert.IsFalse(parser.TryParse(" <testtag></testtag>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<!-- comment -->", out element));
+            Assert.IsNull(element);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(単一のパターン)。
+        /// </summary>
+        [Test]
+        public void TestTryParseSingle()
+        {
+            IElement element;
+            XmlElement xmlElement;
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+
+            Assert.IsTrue(parser.TryParse("<testtag />", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag />", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag2/>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag2/>", xmlElement.ToString());
+            Assert.AreEqual("testtag2", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag3   />testtag4 />", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag3   />", xmlElement.ToString());
+            Assert.AreEqual("testtag3", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag5 testattr=\"testvalue\" />/>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag5 testattr=\"testvalue\" />", xmlElement.ToString());
+            Assert.AreEqual("testtag5", xmlElement.Name);
+            Assert.AreEqual(1, xmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue", xmlElement.Attributes["testattr"]);
+
+            Assert.IsTrue(parser.TryParse("<testtag6 testattr1=\"testvalue1\" testattr2=\"testvalue2\"/>/>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag6 testattr1=\"testvalue1\" testattr2=\"testvalue2\"/>", xmlElement.ToString());
+            Assert.AreEqual("testtag6", xmlElement.Name);
+            Assert.AreEqual(2, xmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue1", xmlElement.Attributes["testattr1"]);
+            Assert.AreEqual("testvalue2", xmlElement.Attributes["testattr2"]);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(不正な構文)。
+        /// </summary>
+        [Test]
+        public void TestTryParseLazy()
+        {
+            IElement element;
+            XmlElement xmlElement;
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+
+            Assert.IsTrue(parser.TryParse("<p>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<p>", xmlElement.ToString());
+            Assert.AreEqual("p", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag>test value", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag>test value", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag2 testattr=test value>test value</testtag2>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag2 testattr=test value>test value</testtag2>", xmlElement.ToString());
+            Assert.AreEqual("testtag2", xmlElement.Name);
+            Assert.AreEqual(2, xmlElement.Attributes.Count);
+            Assert.AreEqual("test", xmlElement.Attributes["testattr"]);
+            Assert.IsEmpty(xmlElement.Attributes["value"]);
+
+            Assert.IsTrue(parser.TryParse("<testtag3>test value2</ testtag3>testend", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag3>test value2</ testtag3>testend", xmlElement.ToString());
+            Assert.AreEqual("testtag3", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+        }
+
+
+        /// <summary>
+        /// TryParseメソッドテストケース(不正でNG)。
+        /// </summary>
+        [Test]
+        public void TestTryParseLazyNg()
+        {
+            IElement element;
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+
+            Assert.IsFalse(parser.TryParse("< testtag></testtag>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<testtag", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<testtag ", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<testtag /", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<testtag </testtag>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<testtag testattr=\"testvalue'></testtag>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<sub((>4</sub>", out element));
+            Assert.IsNull(element);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(HTML)。
+        /// </summary>
+        [Test]
+        public void TestTryParseHtml()
+        {
+            IElement element;
+            HtmlElement htmlElement;
+            XmlElementParser parser = new XmlElementParser(new XmlParser { IsHtml = true });
+
+            Assert.IsTrue(parser.TryParse("<testtag />", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<testtag />", htmlElement.ToString());
+            Assert.AreEqual("testtag", htmlElement.Name);
+            Assert.AreEqual(0, htmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<p>", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<p>", htmlElement.ToString());
+            Assert.AreEqual("p", htmlElement.Name);
+            Assert.AreEqual(0, htmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag2 />testtag3 />", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<testtag2 />", htmlElement.ToString());
+            Assert.AreEqual("testtag2", htmlElement.Name);
+            Assert.AreEqual(0, htmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag4></testtag5>", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<testtag4>", htmlElement.ToString());
+            Assert.AreEqual("testtag4", htmlElement.Name);
+            Assert.AreEqual(0, htmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag6 testattr=\"testvalue\">>", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<testtag6 testattr=\"testvalue\">", htmlElement.ToString());
+            Assert.AreEqual("testtag6", htmlElement.Name);
+            Assert.AreEqual(1, htmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue", htmlElement.Attributes["testattr"]);
+
+            Assert.IsTrue(parser.TryParse("<testtag7 testattr1=\"testvalue1\" testattr2=\"testvalue2\">test</testtag7>", out element));
+            htmlElement = (HtmlElement)element;
+            Assert.AreEqual("<testtag7 testattr1=\"testvalue1\" testattr2=\"testvalue2\">test</testtag7>", htmlElement.ToString());
+            Assert.AreEqual("testtag7", htmlElement.Name);
+            Assert.AreEqual(2, htmlElement.Attributes.Count);
+            Assert.AreEqual("testvalue1", htmlElement.Attributes["testattr1"]);
+            Assert.AreEqual("testvalue2", htmlElement.Attributes["testattr2"]);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(大文字小文字)。
+        /// </summary>
+        [Test]
+        public void TestTryParseIgnoreCase()
+        {
+            IElement element;
+            XmlElement xmlElement;
+            XmlParser xmlParser = new XmlParser { IgnoreCase = false };
+            XmlElementParser parser = new XmlElementParser(xmlParser);
+
+            Assert.IsTrue(parser.TryParse("<testtag></testtag></Testtag>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag></testtag>", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            Assert.IsTrue(parser.TryParse("<testtag></Testtag></testtag>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag></Testtag></testtag>", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+
+            xmlParser.IgnoreCase = true;
+            Assert.IsTrue(parser.TryParse("<testtag></Testtag></testtag>", out element));
+            xmlElement = (XmlElement)element;
+            Assert.AreEqual("<testtag></Testtag>", xmlElement.ToString());
+            Assert.AreEqual("testtag", xmlElement.Name);
+            Assert.AreEqual(0, xmlElement.Attributes.Count);
+        }
+
+        #endregion
+
+        #region 公開メソッドテストケース
+
+        /// <summary>
+        /// IsElementPossibleメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestIsElementPossible()
+        {
+            XmlElementParser parser = new XmlElementParser(new XmlParser());
+            Assert.IsTrue(parser.IsPossibleParse('<'));
+            Assert.IsFalse(parser.IsPossibleParse('['));
+            Assert.IsFalse(parser.IsPossibleParse('-'));
+            Assert.IsFalse(parser.IsPossibleParse('/'));
+            Assert.IsFalse(parser.IsPossibleParse('#'));
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlElementTest.cs b/HmLibTest/Parsers/XmlElementTest.cs
new file mode 100644 (file)
index 0000000..5a535d6
--- /dev/null
@@ -0,0 +1,135 @@
+// ================================================================================================
+// <summary>
+//      XmlElementのテストクラスソース。</summary>
+//
+// <copyright file="XmlElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlElementTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            XmlElement element = new XmlElement("testname1");
+            Assert.AreEqual("testname1", element.Name);
+            Assert.AreEqual(0, element.Attributes.Count);
+            Assert.AreEqual(0, element.Count);
+
+            element = new XmlElement("testname2", "testvalue");
+            Assert.AreEqual("testname2", element.Name);
+            Assert.AreEqual(0, element.Attributes.Count);
+            Assert.AreEqual(1, element.Count);
+            Assert.IsInstanceOf(typeof(TextElement), element[0]);
+            Assert.AreEqual("testvalue", element[0].ToString());
+
+            IDictionary<string, string> attribute = new Dictionary<string, string>();
+            attribute.Add("testattr1", "testattrvalue1");
+            ICollection<IElement> collection = new List<IElement>();
+            collection.Add(new XmlCommentElement("testcomment"));
+            element = new XmlElement("testname3", attribute, collection);
+            Assert.AreEqual("testname3", element.Name);
+            Assert.AreEqual("testattrvalue1", element.Attributes["testattr1"]);
+            Assert.AreEqual("testcomment", ((XmlCommentElement)element[0]).Text);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Nameプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestName()
+        {
+            XmlElement element = new XmlElement("testname1");
+            Assert.AreEqual("testname1", element.Name);
+            element.Name = "testname2";
+            Assert.AreEqual("testname2", element.Name);
+        }
+
+        /// <summary>
+        /// Nameプロパティテストケース(null)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestNameNull()
+        {
+            XmlElement element = new XmlElement(null);
+        }
+
+        /// <summary>
+        /// Nameプロパティテストケース(空文字列)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentException))]
+        public void TestNameEmpty()
+        {
+            XmlElement element = new XmlElement(String.Empty);
+        }
+
+        /// <summary>
+        /// Nameプロパティテストケース(空白文字列)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentException))]
+        public void TestNameBlank()
+        {
+            XmlElement element = new XmlElement(" ");
+        }
+
+        /// <summary>
+        /// Attributesプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestAttributes()
+        {
+            XmlElement element = new XmlElement("testname");
+            Assert.IsNotNull(element.Attributes);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            XmlElement element = new XmlElement("form");
+            Assert.AreEqual("<form />", element.ToString());
+            element.Attributes.Add("action", "/test.html");
+            Assert.AreEqual("<form action=\"/test.html\" />", element.ToString());
+            element.Attributes.Add("disabled", "");
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\" />", element.ToString());
+            element.Add(new TextElement("フォーム内のテキスト"));
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\">フォーム内のテキスト</form>", element.ToString());
+            element.Add(new XmlCommentElement("コメント"));
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\">フォーム内のテキスト<!--コメント--></form>", element.ToString());
+            element.Attributes.Add("test_attr", "&<>\"");
+            Assert.AreEqual("<form action=\"/test.html\" disabled=\"\" test_attr=\"&amp;&lt;&gt;&quot;\">フォーム内のテキスト<!--コメント--></form>", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlParserTest.cs b/HmLibTest/Parsers/XmlParserTest.cs
new file mode 100644 (file)
index 0000000..972bd5b
--- /dev/null
@@ -0,0 +1,77 @@
+// ================================================================================================
+// <summary>
+//      XmlParserのテストクラスソース。</summary>
+//
+// <copyright file="XmlParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// Parseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestParse()
+        {
+            // ※ 現状解析が失敗するパターンは無い
+            XmlParser parser = new XmlParser();
+
+            Assert.AreEqual("test", parser.Parse("test").ToString());
+
+            IElement element = parser.Parse("testbefore<p>testinner</p><!--comment-->testafter");
+            Assert.IsInstanceOf(typeof(ICollection<IElement>), element);
+            ICollection<IElement> collection = (ICollection<IElement>)element;
+            Assert.AreEqual(4, collection.Count);
+            Assert.AreEqual("testbefore", collection.ElementAt(0).ToString());
+            Assert.IsInstanceOf(typeof(XmlElement), collection.ElementAt(1));
+            Assert.AreEqual("<p>testinner</p>", collection.ElementAt(1).ToString());
+            Assert.IsInstanceOf(typeof(XmlCommentElement), collection.ElementAt(2));
+            Assert.AreEqual("<!--comment-->", collection.ElementAt(2).ToString());
+            Assert.AreEqual("testafter", collection.ElementAt(3).ToString());
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            // ※ 現状解析が失敗するパターンは無い
+            IElement element;
+            XmlParser parser = new XmlParser();
+
+            Assert.IsTrue(parser.TryParse("test", out element));
+            Assert.IsInstanceOf(typeof(TextElement), element);
+            Assert.AreEqual("test", element.ToString());
+
+            Assert.IsTrue(parser.TryParse("testbefore<p>testinner</p><!--comment-->testafter", out element));
+            Assert.IsInstanceOf(typeof(ICollection<IElement>), element);
+            ICollection<IElement> collection = (ICollection<IElement>)element;
+            Assert.AreEqual(4, collection.Count);
+            Assert.AreEqual("testbefore", collection.ElementAt(0).ToString());
+            Assert.IsInstanceOf(typeof(XmlElement), collection.ElementAt(1));
+            Assert.AreEqual("<p>testinner</p>", collection.ElementAt(1).ToString());
+            Assert.IsInstanceOf(typeof(XmlCommentElement), collection.ElementAt(2));
+            Assert.AreEqual("<!--comment-->", collection.ElementAt(2).ToString());
+            Assert.AreEqual("testafter", collection.ElementAt(3).ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Parsers/XmlTextElementTest.cs b/HmLibTest/Parsers/XmlTextElementTest.cs
new file mode 100644 (file)
index 0000000..f4636f0
--- /dev/null
@@ -0,0 +1,98 @@
+// ================================================================================================
+// <summary>
+//      XmlTextElementのテストクラスソース。</summary>
+//
+// <copyright file="XmlTextElementTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Parsers
+{
+    using System;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// XmlTextElementのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class XmlTextElementTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            XmlTextElement element = new XmlTextElement();
+            Assert.IsNull(element.Text);
+
+            element = new XmlTextElement("test");
+            Assert.AreEqual("test", element.Text);
+
+            element = new XmlTextElement("<test>");
+            Assert.AreEqual("<test>", element.Text);
+            Assert.AreEqual("&lt;test&gt;", element.Raw);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Textプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestText()
+        {
+            XmlTextElement element = new XmlTextElement();
+
+            element.Text = "test";
+            Assert.AreEqual("test", element.Text);
+
+            element.Text = "<test>";
+            Assert.AreEqual("<test>", element.Text);
+            Assert.AreEqual("&lt;test&gt;", element.Raw);
+        }
+
+        /// <summary>
+        /// Rawプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestRaw()
+        {
+            XmlTextElement element = new XmlTextElement();
+
+            element.Raw = "test";
+            Assert.AreEqual("test", element.Raw);
+
+            element.Raw = "&lt;test&gt;";
+            Assert.AreEqual("&lt;test&gt;", element.Raw);
+            Assert.AreEqual("<test>", element.Text);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            XmlTextElement element = new XmlTextElement();
+
+            Assert.IsEmpty(element.ToString());
+            element.Text = "test";
+            Assert.AreEqual("test", element.ToString());
+            element.Text = "<test> & \"test'";
+            Assert.AreEqual("&lt;test&gt; &amp; &quot;test&apos;", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/HmLibTest/Properties/AssemblyInfo.cs b/HmLibTest/Properties/AssemblyInfo.cs
new file mode 100644 (file)
index 0000000..52bcf0f
--- /dev/null
@@ -0,0 +1,45 @@
+// ================================================================================================
+// <summary>
+//      HmLib(テスト)のアセンブリソース</summary>
+//
+// <copyright file="AssemblyInfo.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
+// アセンブリに関連付けられている情報を変更するには、
+// これらの属性値を変更してください。
+[assembly: AssemblyTitle("HmLib(テスト)")]
+[assembly: AssemblyDescription("汎用的なクラス等を抽出したライブラリ(テスト)")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HmLib")]
+[assembly: AssemblyCopyright("Copyright (C) Honeplus 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 
+// 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
+// その型の ComVisible 属性を true に設定してください。
+[assembly: ComVisible(false)]
+
+// 次の GUID は、このプロジェクトが COM に公開される場合の、typelib の ID です
+[assembly: Guid("35530aab-ce56-49e6-8c75-c836de705e9f")]
+
+// アセンブリのバージョン情報は、以下の 4 つの値で構成されています:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 
+// 既定値にすることができます:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.1.*")]
similarity index 97%
rename from WptscsTest/Utilities/ObjectUtilsTest.cs
rename to HmLibTest/Utilities/ObjectUtilsTest.cs
index bfcb90d..85c3411 100644 (file)
@@ -3,7 +3,7 @@
 //      ObjectUtilsのテストクラスソース。</summary>
 //
 // <copyright file="ObjectUtilsTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/HmLibTest/Utilities/StringUtilsTest.cs b/HmLibTest/Utilities/StringUtilsTest.cs
new file mode 100644 (file)
index 0000000..5005226
--- /dev/null
@@ -0,0 +1,175 @@
+// ================================================================================================
+// <summary>
+//      StringUtilsのテストクラスソース。</summary>
+//
+// <copyright file="StringUtilsTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Utilities
+{
+    using System;
+    using System.Text;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// StringUtilsのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class StringUtilsTest
+    {
+        #region 初期化メソッドテストケース
+
+        /// <summary>
+        /// DefaultStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestDefaultString()
+        {
+            // 引数一つ
+            Assert.IsEmpty(StringUtils.DefaultString(null));
+            Assert.IsEmpty(StringUtils.DefaultString(String.Empty));
+            Assert.AreEqual(" ", StringUtils.DefaultString(" "));
+            Assert.AreEqual("null以外の文字列", StringUtils.DefaultString("null以外の文字列"));
+
+            // 引数二つ
+            Assert.AreEqual("初期値", StringUtils.DefaultString(null, "初期値"));
+            Assert.IsEmpty(StringUtils.DefaultString(String.Empty, "初期値"));
+            Assert.AreEqual(" ", StringUtils.DefaultString(" ", "初期値"));
+            Assert.AreEqual("null以外の文字列", StringUtils.DefaultString("null以外の文字列", "初期値"));
+        }
+
+        #endregion
+
+        #region 切り出しメソッドテストケース
+
+        /// <summary>
+        /// Substringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestSubstring()
+        {
+            // 引数一つ
+            Assert.IsNull(StringUtils.Substring(null, 0));
+            Assert.AreEqual("abc", StringUtils.Substring("abc", 0));
+            Assert.AreEqual("c", StringUtils.Substring("abc", 2));
+            Assert.IsEmpty(StringUtils.Substring("abc", 4));
+            Assert.AreEqual("abc", StringUtils.Substring("abc", -2));
+            Assert.AreEqual("abc", StringUtils.Substring("abc", -4));
+            Assert.AreEqual("3", StringUtils.Substring("0123", 3));
+            Assert.IsEmpty(StringUtils.Substring("0123", 4));
+
+            // 引数二つ
+            Assert.IsNull(StringUtils.Substring(null, 0, 0));
+            Assert.IsEmpty(StringUtils.Substring(String.Empty, 0, 1));
+            Assert.AreEqual("ab", StringUtils.Substring("abc", 0, 2));
+            Assert.IsEmpty(StringUtils.Substring("abc", 2, 0));
+            Assert.AreEqual("c", StringUtils.Substring("abc", 2, 2));
+            Assert.IsEmpty(StringUtils.Substring("abc", 4, 2));
+            Assert.IsEmpty(StringUtils.Substring("abc", -2, -1));
+            Assert.AreEqual("ab", StringUtils.Substring("abc", -4, 2));
+            Assert.AreEqual("3", StringUtils.Substring("0123", 3, 1));
+            Assert.AreEqual("3", StringUtils.Substring("0123", 3, 2));
+            Assert.IsEmpty(StringUtils.Substring("0123", 4, 1));
+        }
+
+        #endregion
+
+        #region 文字列チェックテストケース
+
+        /// <summary>
+        /// StartsWithメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestStartsWith()
+        {
+            // null
+            Assert.IsTrue(StringUtils.StartsWith(null, null, 3));
+            Assert.IsFalse(StringUtils.StartsWith(null, "", 2));
+            Assert.IsFalse(StringUtils.StartsWith("", null, 5));
+
+            // 空、文字数
+            Assert.IsFalse(StringUtils.StartsWith("", "", 0));
+            Assert.IsTrue(StringUtils.StartsWith("a", "", 0));
+            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "", 14));
+            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "", 15));
+            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "", -1));
+
+            // 通常
+            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "bc", 1));
+            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "ab", 1));
+            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "あいうえお", 10));
+            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "あいうえおか", 10));
+        }
+
+        /// <summary>
+        /// StartsWithメソッドテストケース(性能試験)。
+        /// </summary>
+        [Test, Timeout(1500)]
+        public void TestStartsWithResponse()
+        {
+            // テストデータとして適当な、ただしある文字が定期的に出現する長い文字列を生成
+            StringBuilder b = new StringBuilder();
+            int span = 0x7D - 0x20;
+            for (int i = 0; i < 100000; i++)
+            {
+                b.Append(Char.ConvertFromUtf32(i % span + 0x20));
+            }
+
+            // 先頭から最後までひたすら実行して時間がかかりすぎないかをチェック
+            string s = b.ToString();
+            for (int i = 0; i < s.Length; i++)
+            {
+                StringUtils.StartsWith(s, "a", i);
+            }
+        }
+
+        #endregion
+
+        #region 書式化メソッドテストケース
+
+        /// <summary>
+        /// FormatDollarVariableメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestFormatDollarVariable()
+        {
+            // 空文字列
+            Assert.IsEmpty(StringUtils.FormatDollarVariable(String.Empty));
+            Assert.IsEmpty(StringUtils.FormatDollarVariable(String.Empty, String.Empty));
+
+            // 通常
+            Assert.AreEqual("test", StringUtils.FormatDollarVariable("test"));
+            Assert.AreEqual("testtest", StringUtils.FormatDollarVariable("test$1test"));
+            Assert.AreEqual("test15test", StringUtils.FormatDollarVariable("test$1test", 15));
+            Assert.AreEqual("testtest", StringUtils.FormatDollarVariable("test$2test", 15));
+            Assert.AreEqual(
+                "int[] value = {30, {0}, 10000};\nstring[] = {\"文字列$1\", \"12.345\"};\n",
+                StringUtils.FormatDollarVariable("int[] value = {$1, $2, $3};\nstring[] = {\"$4\", \"$5\"};\n", 30, "{0}", 10000, "文字列$1", 12.345));
+        }
+
+        /// <summary>
+        /// FormatDollarVariableメソッドテストケース(書式がnull)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestFormatDollarVariableFormatNull()
+        {
+            StringUtils.FormatDollarVariable(null);
+        }
+
+        /// <summary>
+        /// FormatDollarVariableメソッドテストケース(パラメータがnull)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestFormatDollarVariableArgsNull()
+        {
+            StringUtils.FormatDollarVariable(String.Empty, null);
+        }
+
+        #endregion
+    }
+}
similarity index 99%
rename from WptscsTest/Utilities/ValidateTest.cs
rename to HmLibTest/Utilities/ValidateTest.cs
index b72fe8a..63eb50b 100644 (file)
@@ -3,7 +3,7 @@
 //      Validateのテストクラスソース。</summary>
 //
 // <copyright file="ValidateTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
similarity index 59%
rename from WptscsTest/Utilities/XmlUtilsTest.cs
rename to HmLibTest/Utilities/XmlUtilsTest.cs
index e513fda..b2d84cb 100644 (file)
@@ -3,7 +3,7 @@
 //      XmlUtilsのテストクラスソース。</summary>
 //
 // <copyright file="XmlUtilsTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -71,5 +71,57 @@ namespace Honememo.Utilities
         }
 
         #endregion
+
+        #region エンコード/デコードテストケース
+
+        /// <summary>
+        /// XmlEncodeメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestXmlEncode()
+        {
+            Assert.AreEqual("test", XmlUtils.XmlEncode("test"));
+            Assert.AreEqual("&lt;", XmlUtils.XmlEncode("<"));
+            Assert.AreEqual("&gt;", XmlUtils.XmlEncode(">"));
+            Assert.AreEqual("&amp;", XmlUtils.XmlEncode("&"));
+            Assert.AreEqual("&quot;", XmlUtils.XmlEncode("\""));
+            Assert.AreEqual("&apos;", XmlUtils.XmlEncode("'"));
+        }
+
+        /// <summary>
+        /// XmlEncodeメソッドテストケース(null)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestXmlEncodeNull()
+        {
+            XmlUtils.XmlEncode(null);
+        }
+
+        /// <summary>
+        /// XmlDecodeメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestXmlDecode()
+        {
+            Assert.AreEqual("test", XmlUtils.XmlDecode("test"));
+            Assert.AreEqual("<", XmlUtils.XmlDecode("&lt;"));
+            Assert.AreEqual(">", XmlUtils.XmlDecode("&gt;"));
+            Assert.AreEqual("&", XmlUtils.XmlDecode("&amp;"));
+            Assert.AreEqual("\"", XmlUtils.XmlDecode("&quot;"));
+            Assert.AreEqual("'", XmlUtils.XmlDecode("&apos;"));
+        }
+
+        /// <summary>
+        /// XmlDecodeメソッドテストケース(null)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestXmlDecodeNull()
+        {
+            XmlUtils.XmlDecode(null);
+        }
+
+        #endregion
     }
 }
index ac99a02..57a5c62 100644 (file)
Binary files a/Wikipedia 翻訳支援ツール.asta and b/Wikipedia 翻訳支援ツール.asta differ
index 1ce842b..d2461ec 100644 (file)
@@ -2,9 +2,16 @@
 Microsoft Visual Studio Solution File, Format Version 11.00
 # Visual C# Express 2010
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wptscs", "wptscs\Wptscs.csproj", "{4E20E523-0230-474C-B8F1-1E991360F5F5}"
+       ProjectSection(ProjectDependencies) = postProject
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B} = {CC7F6106-0C00-427B-9BF2-1EE65448907B}
+       EndProjectSection
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WptscsTest", "WptscsTest\WptscsTest.csproj", "{FDBDFBB8-2975-4521-87C6-D75CF9144DED}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HmLib", "HmLib\HmLib.csproj", "{CC7F6106-0C00-427B-9BF2-1EE65448907B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HmLibTest", "HmLibTest\HmLibTest.csproj", "{77218F38-BD1C-469B-B975-68AE3E9EE14E}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -25,6 +32,18 @@ Global
                {FDBDFBB8-2975-4521-87C6-D75CF9144DED}.Release|Any CPU.ActiveCfg = Release|Any CPU
                {FDBDFBB8-2975-4521-87C6-D75CF9144DED}.Release|Any CPU.Build.0 = Release|Any CPU
                {FDBDFBB8-2975-4521-87C6-D75CF9144DED}.Release|x86.ActiveCfg = Release|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Release|Any CPU.Build.0 = Release|Any CPU
+               {CC7F6106-0C00-427B-9BF2-1EE65448907B}.Release|x86.ActiveCfg = Release|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Debug|x86.ActiveCfg = Debug|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Release|Any CPU.Build.0 = Release|Any CPU
+               {77218F38-BD1C-469B-B975-68AE3E9EE14E}.Release|x86.ActiveCfg = Release|Any CPU
        EndGlobalSection
        GlobalSection(SolutionProperties) = preSolution
                HideSolutionNode = FALSE
index f31290a..9ead15c 100644 (file)
             this.tabControl = new System.Windows.Forms.TabControl();
             this.tabPageItems = new System.Windows.Forms.TabPage();
             this.dataGridViewItems = new System.Windows.Forms.DataGridView();
+            this.ColumnFromCode = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnFromTitle = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnAlias = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnArrow = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnToCode = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnToTitle = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.ColumnTimestamp = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.tabPageHeadings = new System.Windows.Forms.TabPage();
             this.dataGridViewHeading = new System.Windows.Forms.DataGridView();
             this.tabPageServer = new System.Windows.Forms.TabPage();
@@ -47,6 +54,8 @@
             this.textBoxBracket = new System.Windows.Forms.TextBox();
             this.labelBracket = new System.Windows.Forms.Label();
             this.groupBoxServer = new System.Windows.Forms.GroupBox();
+            this.textBoxLinkInterwikiFormat = new System.Windows.Forms.TextBox();
+            this.labelLinkInterwikiFormat = new System.Windows.Forms.Label();
             this.textBoxDocumentationTemplateDefaultPage = new System.Windows.Forms.TextBox();
             this.labelDocumentationTemplateDefaultPage = new System.Windows.Forms.Label();
             this.textBoxDocumentationTemplate = new System.Windows.Forms.TextBox();
             this.labelUserAgent = new System.Windows.Forms.Label();
             this.errorProvider = new System.Windows.Forms.ErrorProvider(this.components);
             this.toolTip = new System.Windows.Forms.ToolTip(this.components);
-            this.ColumnFromCode = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnFromTitle = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnAlias = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnArrow = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnToCode = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnToTitle = new System.Windows.Forms.DataGridViewTextBoxColumn();
-            this.ColumnTimestamp = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.tabControl.SuspendLayout();
             this.tabPageItems.SuspendLayout();
             ((System.ComponentModel.ISupportInitialize)(this.dataGridViewItems)).BeginInit();
             this.dataGridViewItems.RowValidated += new System.Windows.Forms.DataGridViewCellEventHandler(this.ResetErrorText_RowValidated);
             this.dataGridViewItems.RowValidating += new System.Windows.Forms.DataGridViewCellCancelEventHandler(this.DataGridViewItems_RowValidating);
             // 
+            // ColumnFromCode
+            // 
+            resources.ApplyResources(this.ColumnFromCode, "ColumnFromCode");
+            this.ColumnFromCode.MaxInputLength = 16;
+            this.ColumnFromCode.Name = "ColumnFromCode";
+            // 
+            // ColumnFromTitle
+            // 
+            resources.ApplyResources(this.ColumnFromTitle, "ColumnFromTitle");
+            this.ColumnFromTitle.MaxInputLength = 255;
+            this.ColumnFromTitle.Name = "ColumnFromTitle";
+            // 
+            // ColumnAlias
+            // 
+            resources.ApplyResources(this.ColumnAlias, "ColumnAlias");
+            this.ColumnAlias.MaxInputLength = 255;
+            this.ColumnAlias.Name = "ColumnAlias";
+            // 
+            // ColumnArrow
+            // 
+            resources.ApplyResources(this.ColumnArrow, "ColumnArrow");
+            this.ColumnArrow.Name = "ColumnArrow";
+            this.ColumnArrow.ReadOnly = true;
+            this.ColumnArrow.Resizable = System.Windows.Forms.DataGridViewTriState.False;
+            this.ColumnArrow.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
+            // 
+            // ColumnToCode
+            // 
+            resources.ApplyResources(this.ColumnToCode, "ColumnToCode");
+            this.ColumnToCode.MaxInputLength = 16;
+            this.ColumnToCode.Name = "ColumnToCode";
+            // 
+            // ColumnToTitle
+            // 
+            resources.ApplyResources(this.ColumnToTitle, "ColumnToTitle");
+            this.ColumnToTitle.MaxInputLength = 255;
+            this.ColumnToTitle.Name = "ColumnToTitle";
+            // 
+            // ColumnTimestamp
+            // 
+            resources.ApplyResources(this.ColumnTimestamp, "ColumnTimestamp");
+            this.ColumnTimestamp.Name = "ColumnTimestamp";
+            // 
             // tabPageHeadings
             // 
             this.tabPageHeadings.Controls.Add(this.dataGridViewHeading);
             // groupBoxServer
             // 
             resources.ApplyResources(this.groupBoxServer, "groupBoxServer");
+            this.groupBoxServer.Controls.Add(this.textBoxLinkInterwikiFormat);
+            this.groupBoxServer.Controls.Add(this.labelLinkInterwikiFormat);
             this.groupBoxServer.Controls.Add(this.textBoxDocumentationTemplateDefaultPage);
             this.groupBoxServer.Controls.Add(this.labelDocumentationTemplateDefaultPage);
             this.groupBoxServer.Controls.Add(this.textBoxDocumentationTemplate);
             this.groupBoxServer.Name = "groupBoxServer";
             this.groupBoxServer.TabStop = false;
             // 
+            // textBoxLinkInterwikiFormat
+            // 
+            resources.ApplyResources(this.textBoxLinkInterwikiFormat, "textBoxLinkInterwikiFormat");
+            this.textBoxLinkInterwikiFormat.Name = "textBoxLinkInterwikiFormat";
+            this.toolTip.SetToolTip(this.textBoxLinkInterwikiFormat, resources.GetString("textBoxLinkInterwikiFormat.ToolTip"));
+            // 
+            // labelLinkInterwikiFormat
+            // 
+            resources.ApplyResources(this.labelLinkInterwikiFormat, "labelLinkInterwikiFormat");
+            this.labelLinkInterwikiFormat.Name = "labelLinkInterwikiFormat";
+            this.toolTip.SetToolTip(this.labelLinkInterwikiFormat, resources.GetString("labelLinkInterwikiFormat.ToolTip"));
+            // 
             // textBoxDocumentationTemplateDefaultPage
             // 
             resources.ApplyResources(this.textBoxDocumentationTemplateDefaultPage, "textBoxDocumentationTemplateDefaultPage");
             // 
             // textBoxDocumentationTemplate
             // 
+            this.textBoxDocumentationTemplate.AcceptsReturn = true;
             resources.ApplyResources(this.textBoxDocumentationTemplate, "textBoxDocumentationTemplate");
             this.textBoxDocumentationTemplate.Name = "textBoxDocumentationTemplate";
             this.toolTip.SetToolTip(this.textBoxDocumentationTemplate, resources.GetString("textBoxDocumentationTemplate.ToolTip"));
             this.toolTip.InitialDelay = 500;
             this.toolTip.ReshowDelay = 100;
             // 
-            // ColumnFromCode
-            // 
-            resources.ApplyResources(this.ColumnFromCode, "ColumnFromCode");
-            this.ColumnFromCode.MaxInputLength = 16;
-            this.ColumnFromCode.Name = "ColumnFromCode";
-            // 
-            // ColumnFromTitle
-            // 
-            resources.ApplyResources(this.ColumnFromTitle, "ColumnFromTitle");
-            this.ColumnFromTitle.MaxInputLength = 255;
-            this.ColumnFromTitle.Name = "ColumnFromTitle";
-            // 
-            // ColumnAlias
-            // 
-            resources.ApplyResources(this.ColumnAlias, "ColumnAlias");
-            this.ColumnAlias.MaxInputLength = 255;
-            this.ColumnAlias.Name = "ColumnAlias";
-            // 
-            // ColumnArrow
-            // 
-            resources.ApplyResources(this.ColumnArrow, "ColumnArrow");
-            this.ColumnArrow.Name = "ColumnArrow";
-            this.ColumnArrow.ReadOnly = true;
-            this.ColumnArrow.Resizable = System.Windows.Forms.DataGridViewTriState.False;
-            this.ColumnArrow.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
-            // 
-            // ColumnToCode
-            // 
-            resources.ApplyResources(this.ColumnToCode, "ColumnToCode");
-            this.ColumnToCode.MaxInputLength = 16;
-            this.ColumnToCode.Name = "ColumnToCode";
-            // 
-            // ColumnToTitle
-            // 
-            resources.ApplyResources(this.ColumnToTitle, "ColumnToTitle");
-            this.ColumnToTitle.MaxInputLength = 255;
-            this.ColumnToTitle.Name = "ColumnToTitle";
-            // 
-            // ColumnTimestamp
-            // 
-            resources.ApplyResources(this.ColumnTimestamp, "ColumnTimestamp");
-            this.ColumnTimestamp.Name = "ColumnTimestamp";
-            // 
             // ConfigForm
             // 
             this.AcceptButton = this.buttonOk;
         private System.Windows.Forms.DataGridViewTextBoxColumn ColumnToCode;
         private System.Windows.Forms.DataGridViewTextBoxColumn ColumnToTitle;
         private System.Windows.Forms.DataGridViewTextBoxColumn ColumnTimestamp;
+        private System.Windows.Forms.Label labelLinkInterwikiFormat;
+        private System.Windows.Forms.TextBox textBoxLinkInterwikiFormat;
     }
 }
\ No newline at end of file
index 91b136d..8861124 100644 (file)
@@ -3,7 +3,7 @@
 //      Wikipedia翻訳支援ツール設定画面クラスソース</summary>
 //
 // <copyright file="ConfigForm.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -15,12 +15,15 @@ namespace Honememo.Wptscs
     using System.ComponentModel;
     using System.Data;
     using System.Drawing;
+    using System.Linq;
     using System.Reflection;
     using System.Text;
     using System.Windows.Forms;
     using Honememo.Utilities;
     using Honememo.Wptscs.Models;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// Wikipedia翻訳支援ツール設定画面のクラスです。
@@ -54,7 +57,7 @@ namespace Honememo.Wptscs
             this.InitializeComponent();
 
             // 設定対象のConfigを受け取る
-            this.config = Utilities.Validate.NotNull(config, "config");
+            this.config = Honememo.Utilities.Validate.NotNull(config, "config");
         }
 
         #endregion
@@ -726,8 +729,17 @@ namespace Honememo.Wptscs
             this.textBoxCategoryNamespace.Text = site.CategoryNamespace.ToString();
             this.textBoxFileNamespace.Text = site.FileNamespace.ToString();
             this.textBoxRedirect.Text = StringUtils.DefaultString(site.Redirect);
-            this.textBoxDocumentationTemplate.Text = StringUtils.DefaultString(site.DocumentationTemplate);
+
+            // Template:Documentionは改行区切りのマルチテキストとして扱う
+            StringBuilder b = new StringBuilder();
+            foreach (string s in site.DocumentationTemplates)
+            {
+                b.Append(s).Append(Environment.NewLine);
+            }
+
+            this.textBoxDocumentationTemplate.Text = b.ToString();
             this.textBoxDocumentationTemplateDefaultPage.Text = StringUtils.DefaultString(site.DocumentationTemplateDefaultPage);
+            this.textBoxLinkInterwikiFormat.Text = StringUtils.DefaultString(site.LinkInterwikiFormat);
         }
 
         /// <summary>
@@ -787,7 +799,7 @@ namespace Honememo.Wptscs
             this.SaveChangedValue((Website)site);
 
             // 初期値を持つパラメータがあるため、全て変更された場合のみ格納する。
-            // â\80» ã\82\82ã\81\86ã\81¡ã\82\87ã\81£ã\81¨ç¶ºéº\97ã\81«æ\9b¸ã\81\8dã\81\9fã\81\8bã\81£ã\81\9fã\81\8cã\80\81ã\83ªã\83\95ã\83¬ã\82¯ã\82·ã\83§ã\83³ã\82\92使ã\82\8fã\81ªã\81\84ã\81¨å\85±é\80\9aå\8c\96ã\81§ã\81\8dã\81ªã\81\95ã\81\9dã\81\86ã\81 ったので力技
+            // â\80» ã\82\82ã\81\86ã\81¡ã\82\87ã\81£ã\81¨ç¶ºéº\97ã\81«æ\9b¸ã\81\8dã\81\9fã\81\8bã\81£ã\81\9fã\81\8cã\80\81ã\81\86ã\81¾ã\81\84æ\89\8bã\81\8cæ\80\9dã\81\84ã\81¤ã\81\8bã\81ªã\81\8bったので力技
             //    MediaWikiクラス側で行わないのは、場合によっては意図的に初期値と同じ値を設定すること
             //    もありえるから(初期値が変わる可能性がある場合など)。
             string str = StringUtils.DefaultString(this.textBoxExportPath.Text).Trim();
@@ -808,10 +820,15 @@ namespace Honememo.Wptscs
                 site.Redirect = str;
             }
 
-            str = StringUtils.DefaultString(this.textBoxDocumentationTemplate.Text).Trim();
-            if (str != site.DocumentationTemplate)
+            // Template:Documentionの設定は行ごとに格納
+            // ※ この値は初期値を持たないパラメータ
+            site.DocumentationTemplates.Clear();
+            foreach (string s in StringUtils.DefaultString(this.textBoxDocumentationTemplate.Text).Split('\n'))
             {
-                site.DocumentationTemplate = str;
+                if (!String.IsNullOrWhiteSpace(s))
+                {
+                    site.DocumentationTemplates.Add(s.Trim());
+                }
             }
 
             str = StringUtils.DefaultString(this.textBoxDocumentationTemplateDefaultPage.Text).Trim();
@@ -820,6 +837,12 @@ namespace Honememo.Wptscs
                 site.DocumentationTemplateDefaultPage = str;
             }
 
+            str = StringUtils.DefaultString(this.textBoxLinkInterwikiFormat.Text).Trim();
+            if (str != site.LinkInterwikiFormat)
+            {
+                site.LinkInterwikiFormat = str;
+            }
+
             // 以下、数値へのparseは事前にチェックしてあるので、ここではチェックしない
             if (!String.IsNullOrWhiteSpace(this.textBoxTemplateNamespace.Text))
             {
index c7d43d0..be96e7d 100644 (file)
   </data>
   <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
   <data name="buttonOk.Location" type="System.Drawing.Point, System.Drawing">
-    <value>215, 411</value>
+    <value>215, 441</value>
   </data>
   <data name="buttonOk.Size" type="System.Drawing.Size, System.Drawing">
     <value>75, 23</value>
     <value>Bottom</value>
   </data>
   <data name="buttonCancel.Location" type="System.Drawing.Point, System.Drawing">
-    <value>335, 411</value>
+    <value>335, 441</value>
   </data>
   <data name="buttonCancel.Size" type="System.Drawing.Size, System.Drawing">
     <value>75, 23</value>
     <value>6, 6</value>
   </data>
   <data name="dataGridViewItems.Size" type="System.Drawing.Size, System.Drawing">
-    <value>580, 355</value>
+    <value>580, 395</value>
   </data>
   <data name="dataGridViewItems.TabIndex" type="System.Int32, mscorlib">
     <value>0</value>
     <value>3, 3, 3, 3</value>
   </data>
   <data name="tabPageItems.Size" type="System.Drawing.Size, System.Drawing">
-    <value>592, 367</value>
+    <value>592, 397</value>
   </data>
   <data name="tabPageItems.TabIndex" type="System.Int32, mscorlib">
     <value>0</value>
   <data name="&gt;&gt;tabPageItems.ZOrder" xml:space="preserve">
     <value>0</value>
   </data>
+  <data name="dataGridViewHeading.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left, Right</value>
+  </data>
+  <data name="dataGridViewHeading.Location" type="System.Drawing.Point, System.Drawing">
+    <value>6, 6</value>
+  </data>
+  <data name="dataGridViewHeading.Size" type="System.Drawing.Size, System.Drawing">
+    <value>580, 395</value>
+  </data>
+  <data name="dataGridViewHeading.TabIndex" type="System.Int32, mscorlib">
+    <value>0</value>
+  </data>
   <data name="&gt;&gt;dataGridViewHeading.Name" xml:space="preserve">
     <value>dataGridViewHeading</value>
   </data>
     <value>3, 3, 3, 3</value>
   </data>
   <data name="tabPageHeadings.Size" type="System.Drawing.Size, System.Drawing">
-    <value>592, 367</value>
+    <value>592, 397</value>
   </data>
   <data name="tabPageHeadings.TabIndex" type="System.Int32, mscorlib">
     <value>1</value>
   <data name="&gt;&gt;tabPageHeadings.ZOrder" xml:space="preserve">
     <value>1</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguage.Name" xml:space="preserve">
-    <value>groupBoxLanguage</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguage.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguage.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.Name" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="&gt;&gt;comboBoxLanguage.Name" xml:space="preserve">
-    <value>comboBoxLanguage</value>
-  </data>
-  <data name="&gt;&gt;comboBoxLanguage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;comboBoxLanguage.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;comboBoxLanguage.ZOrder" xml:space="preserve">
-    <value>2</value>
-  </data>
-  <data name="&gt;&gt;labelLanguage.Name" xml:space="preserve">
-    <value>labelLanguage</value>
-  </data>
-  <data name="&gt;&gt;labelLanguage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelLanguage.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;labelLanguage.ZOrder" xml:space="preserve">
-    <value>3</value>
-  </data>
-  <data name="tabPageServer.Location" type="System.Drawing.Point, System.Drawing">
-    <value>4, 22</value>
-  </data>
-  <data name="tabPageServer.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
-    <value>3, 3, 3, 3</value>
-  </data>
-  <data name="tabPageServer.Size" type="System.Drawing.Size, System.Drawing">
-    <value>592, 367</value>
-  </data>
-  <data name="tabPageServer.TabIndex" type="System.Int32, mscorlib">
-    <value>2</value>
-  </data>
-  <data name="tabPageServer.Text" xml:space="preserve">
-    <value>サーバー/言語</value>
-  </data>
-  <data name="tabPageServer.ToolTipText" xml:space="preserve">
-    <value>各言語ごとのサーバー/書式等の設定です。</value>
-  </data>
-  <data name="&gt;&gt;tabPageServer.Name" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;tabPageServer.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;tabPageServer.Parent" xml:space="preserve">
-    <value>tabControl</value>
-  </data>
-  <data name="&gt;&gt;tabPageServer.ZOrder" xml:space="preserve">
-    <value>2</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Name" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Parent" xml:space="preserve">
-    <value>tabPageApplication</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Name" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
-  </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Parent" xml:space="preserve">
-    <value>tabPageApplication</value>
-  </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="tabPageApplication.Location" type="System.Drawing.Point, System.Drawing">
-    <value>4, 22</value>
-  </data>
-  <data name="tabPageApplication.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
-    <value>3, 3, 3, 3</value>
-  </data>
-  <data name="tabPageApplication.Size" type="System.Drawing.Size, System.Drawing">
-    <value>592, 367</value>
-  </data>
-  <data name="tabPageApplication.TabIndex" type="System.Int32, mscorlib">
-    <value>3</value>
-  </data>
-  <data name="tabPageApplication.Text" xml:space="preserve">
-    <value>その他</value>
-  </data>
-  <data name="&gt;&gt;tabPageApplication.Name" xml:space="preserve">
-    <value>tabPageApplication</value>
-  </data>
-  <data name="&gt;&gt;tabPageApplication.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;tabPageApplication.Parent" xml:space="preserve">
-    <value>tabControl</value>
-  </data>
-  <data name="&gt;&gt;tabPageApplication.ZOrder" xml:space="preserve">
-    <value>3</value>
-  </data>
-  <data name="tabControl.Location" type="System.Drawing.Point, System.Drawing">
-    <value>12, 12</value>
-  </data>
-  <data name="tabControl.ShowToolTips" type="System.Boolean, mscorlib">
-    <value>True</value>
-  </data>
-  <data name="tabControl.Size" type="System.Drawing.Size, System.Drawing">
-    <value>600, 393</value>
-  </data>
-  <data name="tabControl.TabIndex" type="System.Int32, mscorlib">
-    <value>1</value>
-  </data>
-  <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
-    <value>148, 17</value>
-  </metadata>
-  <data name="tabControl.ToolTip" xml:space="preserve">
-    <value>その他の設定・情報です。</value>
-  </data>
-  <data name="&gt;&gt;tabControl.Name" xml:space="preserve">
-    <value>tabControl</value>
-  </data>
-  <data name="&gt;&gt;tabControl.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TabControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;tabControl.Parent" xml:space="preserve">
-    <value>$this</value>
-  </data>
-  <data name="&gt;&gt;tabControl.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="dataGridViewHeading.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
-  <data name="dataGridViewHeading.Location" type="System.Drawing.Point, System.Drawing">
-    <value>6, 6</value>
-  </data>
-  <data name="dataGridViewHeading.Size" type="System.Drawing.Size, System.Drawing">
-    <value>580, 355</value>
-  </data>
-  <data name="dataGridViewHeading.TabIndex" type="System.Int32, mscorlib">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewHeading.Name" xml:space="preserve">
-    <value>dataGridViewHeading</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewHeading.Type" xml:space="preserve">
-    <value>System.Windows.Forms.DataGridView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewHeading.Parent" xml:space="preserve">
-    <value>tabPageHeadings</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewHeading.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
   <data name="groupBoxLanguage.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
     <value>Top, Bottom, Left, Right</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguageName.Name" xml:space="preserve">
-    <value>groupBoxLanguageName</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguageName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguageName.Parent" xml:space="preserve">
-    <value>groupBoxLanguage</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguageName.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;textBoxBracket.Name" xml:space="preserve">
-    <value>textBoxBracket</value>
-  </data>
-  <data name="&gt;&gt;textBoxBracket.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxBracket.Parent" xml:space="preserve">
-    <value>groupBoxLanguage</value>
-  </data>
-  <data name="&gt;&gt;textBoxBracket.ZOrder" xml:space="preserve">
-    <value>1</value>
+  <data name="groupBoxLanguageName.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left, Right</value>
   </data>
-  <data name="&gt;&gt;labelBracket.Name" xml:space="preserve">
-    <value>labelBracket</value>
+  <data name="dataGridViewLanguageName.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left, Right</value>
   </data>
-  <data name="&gt;&gt;labelBracket.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <metadata name="ColumnCode.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <data name="ColumnCode.HeaderText" xml:space="preserve">
+    <value>コード</value>
   </data>
-  <data name="&gt;&gt;labelBracket.Parent" xml:space="preserve">
-    <value>groupBoxLanguage</value>
+  <data name="ColumnCode.ToolTipText" xml:space="preserve">
+    <value>表記先の言語です。</value>
   </data>
-  <data name="&gt;&gt;labelBracket.ZOrder" xml:space="preserve">
-    <value>2</value>
+  <data name="ColumnCode.Width" type="System.Int32, mscorlib">
+    <value>53</value>
   </data>
-  <data name="groupBoxLanguage.Enabled" type="System.Boolean, mscorlib">
-    <value>False</value>
+  <metadata name="ColumnName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <data name="ColumnName.HeaderText" xml:space="preserve">
+    <value>名称(記事名)</value>
   </data>
-  <data name="groupBoxLanguage.Location" type="System.Drawing.Point, System.Drawing">
-    <value>300, 39</value>
+  <data name="ColumnName.ToolTipText" xml:space="preserve">
+    <value>表記先の言語での名称です。Wikipedia記事名が望ましいです。</value>
   </data>
-  <data name="groupBoxLanguage.Size" type="System.Drawing.Size, System.Drawing">
-    <value>286, 322</value>
+  <data name="ColumnName.Width" type="System.Int32, mscorlib">
+    <value>78</value>
   </data>
-  <data name="groupBoxLanguage.TabIndex" type="System.Int32, mscorlib">
-    <value>3</value>
+  <metadata name="ColumnShortName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
+  <data name="ColumnShortName.HeaderText" xml:space="preserve">
+    <value>略称</value>
   </data>
-  <data name="groupBoxLanguage.Text" xml:space="preserve">
-    <value>è¨\80èª\9eã\81®è¨­å®\9a</value>
+  <data name="ColumnShortName.ToolTipText" xml:space="preserve">
+    <value>表è¨\98å\85\88ã\81®è¨\80èª\9eã\81§ã\81®ç\95¥ç§°ã\81§ã\81\99ã\80\82ç\89¹ã\81«ä¸\8dè¦\81ã\81ªå ´å\90\88空æ¬\84ã\81§ã\81\99ã\80\82</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguage.Name" xml:space="preserve">
-    <value>groupBoxLanguage</value>
+  <data name="ColumnShortName.Width" type="System.Int32, mscorlib">
+    <value>51</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="dataGridViewLanguageName.Location" type="System.Drawing.Point, System.Drawing">
+    <value>6, 18</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguage.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
+  <data name="dataGridViewLanguageName.Size" type="System.Drawing.Size, System.Drawing">
+    <value>260, 279</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguage.ZOrder" xml:space="preserve">
+  <data name="dataGridViewLanguageName.TabIndex" type="System.Int32, mscorlib">
     <value>0</value>
   </data>
-  <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
-    <value>148, 17</value>
-  </metadata>
-  <data name="groupBoxLanguageName.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
   <data name="&gt;&gt;dataGridViewLanguageName.Name" xml:space="preserve">
     <value>dataGridViewLanguageName</value>
   </data>
     <value>8, 43</value>
   </data>
   <data name="groupBoxLanguageName.Size" type="System.Drawing.Size, System.Drawing">
-    <value>272, 273</value>
+    <value>272, 303</value>
   </data>
   <data name="groupBoxLanguageName.TabIndex" type="System.Int32, mscorlib">
     <value>2</value>
   <data name="groupBoxLanguageName.Text" xml:space="preserve">
     <value>各言語での言語名</value>
   </data>
+  <metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>148, 17</value>
+  </metadata>
   <data name="groupBoxLanguageName.ToolTip" xml:space="preserve">
     <value>その言語が、各言語でなんと表記されるかを登録します。
 (例 日本語→英語では Japanese と表記)</value>
   <data name="&gt;&gt;groupBoxLanguageName.Type" xml:space="preserve">
     <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;groupBoxLanguageName.Parent" xml:space="preserve">
-    <value>groupBoxLanguage</value>
-  </data>
-  <data name="&gt;&gt;groupBoxLanguageName.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <metadata name="ColumnCode.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="ColumnName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <metadata name="ColumnShortName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <data name="dataGridViewLanguageName.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
-  <data name="dataGridViewLanguageName.Location" type="System.Drawing.Point, System.Drawing">
-    <value>6, 18</value>
-  </data>
-  <data name="dataGridViewLanguageName.Size" type="System.Drawing.Size, System.Drawing">
-    <value>260, 249</value>
-  </data>
-  <data name="dataGridViewLanguageName.TabIndex" type="System.Int32, mscorlib">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewLanguageName.Name" xml:space="preserve">
-    <value>dataGridViewLanguageName</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewLanguageName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.DataGridView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewLanguageName.Parent" xml:space="preserve">
-    <value>groupBoxLanguageName</value>
-  </data>
-  <data name="&gt;&gt;dataGridViewLanguageName.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <metadata name="ColumnCode.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <data name="ColumnCode.HeaderText" xml:space="preserve">
-    <value>コード</value>
-  </data>
-  <data name="ColumnCode.ToolTipText" xml:space="preserve">
-    <value>表記先の言語です。</value>
-  </data>
-  <data name="ColumnCode.Width" type="System.Int32, mscorlib">
-    <value>53</value>
-  </data>
-  <metadata name="ColumnName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <data name="ColumnName.HeaderText" xml:space="preserve">
-    <value>名称(記事名)</value>
-  </data>
-  <data name="ColumnName.ToolTipText" xml:space="preserve">
-    <value>表記先の言語での名称です。Wikipedia記事名が望ましいです。</value>
-  </data>
-  <data name="ColumnName.Width" type="System.Int32, mscorlib">
-    <value>78</value>
-  </data>
-  <metadata name="ColumnShortName.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
-    <value>True</value>
-  </metadata>
-  <data name="ColumnShortName.HeaderText" xml:space="preserve">
-    <value>略称</value>
-  </data>
-  <data name="ColumnShortName.ToolTipText" xml:space="preserve">
-    <value>表記先の言語での略称です。特に不要な場合空欄です。</value>
-  </data>
-  <data name="ColumnShortName.Width" type="System.Int32, mscorlib">
-    <value>51</value>
+  <data name="&gt;&gt;groupBoxLanguageName.Parent" xml:space="preserve">
+    <value>groupBoxLanguage</value>
+  </data>
+  <data name="&gt;&gt;groupBoxLanguageName.ZOrder" xml:space="preserve">
+    <value>0</value>
   </data>
   <data name="textBoxBracket.Location" type="System.Drawing.Point, System.Drawing">
     <value>89, 18</value>
   </data>
   <data name="textBoxBracket.ToolTip" xml:space="preserve">
     <value>各言語での括弧書きのスタイルを入力します。
-括弧の中身部分を {0} としてください。</value>
+括弧の中身部分を $1 としてください。</value>
   </data>
   <data name="&gt;&gt;textBoxBracket.Name" xml:space="preserve">
     <value>textBoxBracket</value>
   </data>
   <data name="labelBracket.ToolTip" xml:space="preserve">
     <value>各言語での括弧書きのスタイルを入力します。
-括弧の中身部分を {0} としてください。</value>
+括弧の中身部分を $1 としてください。</value>
   </data>
   <data name="&gt;&gt;labelBracket.Name" xml:space="preserve">
     <value>labelBracket</value>
   <data name="&gt;&gt;labelBracket.ZOrder" xml:space="preserve">
     <value>2</value>
   </data>
-  <data name="groupBoxServer.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplateDefaultPage.Name" xml:space="preserve">
-    <value>textBoxDocumentationTemplateDefaultPage</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplateDefaultPage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplateDefaultPage.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplateDefaultPage.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;labelDocumentationTemplateDefaultPage.Name" xml:space="preserve">
-    <value>labelDocumentationTemplateDefaultPage</value>
-  </data>
-  <data name="&gt;&gt;labelDocumentationTemplateDefaultPage.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelDocumentationTemplateDefaultPage.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;labelDocumentationTemplateDefaultPage.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplate.Name" xml:space="preserve">
-    <value>textBoxDocumentationTemplate</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplate.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplate.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxDocumentationTemplate.ZOrder" xml:space="preserve">
-    <value>2</value>
-  </data>
-  <data name="&gt;&gt;labelDocumentationTemplate.Name" xml:space="preserve">
-    <value>labelDocumentationTemplate</value>
+  <data name="groupBoxLanguage.Enabled" type="System.Boolean, mscorlib">
+    <value>False</value>
   </data>
-  <data name="&gt;&gt;labelDocumentationTemplate.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="groupBoxLanguage.Location" type="System.Drawing.Point, System.Drawing">
+    <value>300, 39</value>
   </data>
-  <data name="&gt;&gt;labelDocumentationTemplate.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="groupBoxLanguage.Size" type="System.Drawing.Size, System.Drawing">
+    <value>286, 352</value>
   </data>
-  <data name="&gt;&gt;labelDocumentationTemplate.ZOrder" xml:space="preserve">
+  <data name="groupBoxLanguage.TabIndex" type="System.Int32, mscorlib">
     <value>3</value>
   </data>
-  <data name="&gt;&gt;textBoxFileNamespace.Name" xml:space="preserve">
-    <value>textBoxFileNamespace</value>
-  </data>
-  <data name="&gt;&gt;textBoxFileNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxFileNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxFileNamespace.ZOrder" xml:space="preserve">
-    <value>4</value>
-  </data>
-  <data name="&gt;&gt;labelFileNamespace.Name" xml:space="preserve">
-    <value>labelFileNamespace</value>
-  </data>
-  <data name="&gt;&gt;labelFileNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelFileNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;labelFileNamespace.ZOrder" xml:space="preserve">
-    <value>5</value>
-  </data>
-  <data name="&gt;&gt;textBoxCategoryNamespace.Name" xml:space="preserve">
-    <value>textBoxCategoryNamespace</value>
-  </data>
-  <data name="&gt;&gt;textBoxCategoryNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxCategoryNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxCategoryNamespace.ZOrder" xml:space="preserve">
-    <value>6</value>
-  </data>
-  <data name="&gt;&gt;labelCategoryNamespace.Name" xml:space="preserve">
-    <value>labelCategoryNamespace</value>
-  </data>
-  <data name="&gt;&gt;labelCategoryNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelCategoryNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;labelCategoryNamespace.ZOrder" xml:space="preserve">
-    <value>7</value>
-  </data>
-  <data name="&gt;&gt;textBoxTemplateNamespace.Name" xml:space="preserve">
-    <value>textBoxTemplateNamespace</value>
-  </data>
-  <data name="&gt;&gt;textBoxTemplateNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxTemplateNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxTemplateNamespace.ZOrder" xml:space="preserve">
-    <value>8</value>
-  </data>
-  <data name="&gt;&gt;labelTemplateNamespace.Name" xml:space="preserve">
-    <value>labelTemplateNamespace</value>
-  </data>
-  <data name="&gt;&gt;labelTemplateNamespace.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelTemplateNamespace.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;labelTemplateNamespace.ZOrder" xml:space="preserve">
-    <value>9</value>
-  </data>
-  <data name="&gt;&gt;textBoxRedirect.Name" xml:space="preserve">
-    <value>textBoxRedirect</value>
-  </data>
-  <data name="&gt;&gt;textBoxRedirect.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;textBoxRedirect.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;textBoxRedirect.ZOrder" xml:space="preserve">
-    <value>10</value>
-  </data>
-  <data name="&gt;&gt;labelRedirect.Name" xml:space="preserve">
-    <value>labelRedirect</value>
-  </data>
-  <data name="&gt;&gt;labelRedirect.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="groupBoxLanguage.Text" xml:space="preserve">
+    <value>言語の設定</value>
   </data>
-  <data name="&gt;&gt;labelRedirect.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="&gt;&gt;groupBoxLanguage.Name" xml:space="preserve">
+    <value>groupBoxLanguage</value>
   </data>
-  <data name="&gt;&gt;labelRedirect.ZOrder" xml:space="preserve">
-    <value>11</value>
+  <data name="&gt;&gt;groupBoxLanguage.Type" xml:space="preserve">
+    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;textBoxExportPath.Name" xml:space="preserve">
-    <value>textBoxExportPath</value>
+  <data name="&gt;&gt;groupBoxLanguage.Parent" xml:space="preserve">
+    <value>tabPageServer</value>
   </data>
-  <data name="&gt;&gt;textBoxExportPath.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="&gt;&gt;groupBoxLanguage.ZOrder" xml:space="preserve">
+    <value>0</value>
   </data>
-  <data name="&gt;&gt;textBoxExportPath.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="groupBoxServer.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left</value>
   </data>
-  <data name="&gt;&gt;textBoxExportPath.ZOrder" xml:space="preserve">
-    <value>12</value>
+  <data name="textBoxLinkInterwikiFormat.Location" type="System.Drawing.Point, System.Drawing">
+    <value>115, 316</value>
   </data>
-  <data name="&gt;&gt;labelExportPath.Name" xml:space="preserve">
-    <value>labelExportPath</value>
+  <data name="textBoxLinkInterwikiFormat.MaxLength" type="System.Int32, mscorlib">
+    <value>255</value>
   </data>
-  <data name="&gt;&gt;labelExportPath.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="textBoxLinkInterwikiFormat.Size" type="System.Drawing.Size, System.Drawing">
+    <value>153, 19</value>
   </data>
-  <data name="&gt;&gt;labelExportPath.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="textBoxLinkInterwikiFormat.TabIndex" type="System.Int32, mscorlib">
+    <value>19</value>
   </data>
-  <data name="&gt;&gt;labelExportPath.ZOrder" xml:space="preserve">
-    <value>13</value>
+  <data name="textBoxLinkInterwikiFormat.ToolTip" xml:space="preserve">
+    <value>言語間リンクが見つからない場合に置き換えるフォーマットを入力します。
+記事名, 言語, 他言語版記事名, 表示名 に相当する部分を $1, $2, $3, $4 としてください。
+空欄の場合、[[:記事名:言語|表示名]] での置き換えを行います。</value>
   </data>
-  <data name="&gt;&gt;textBoxNamespacePath.Name" xml:space="preserve">
-    <value>textBoxNamespacePath</value>
+  <data name="&gt;&gt;textBoxLinkInterwikiFormat.Name" xml:space="preserve">
+    <value>textBoxLinkInterwikiFormat</value>
   </data>
-  <data name="&gt;&gt;textBoxNamespacePath.Type" xml:space="preserve">
+  <data name="&gt;&gt;textBoxLinkInterwikiFormat.Type" xml:space="preserve">
     <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;textBoxNamespacePath.Parent" xml:space="preserve">
+  <data name="&gt;&gt;textBoxLinkInterwikiFormat.Parent" xml:space="preserve">
     <value>groupBoxServer</value>
   </data>
-  <data name="&gt;&gt;textBoxNamespacePath.ZOrder" xml:space="preserve">
-    <value>14</value>
-  </data>
-  <data name="&gt;&gt;labelNamespacePath.Name" xml:space="preserve">
-    <value>labelNamespacePath</value>
-  </data>
-  <data name="&gt;&gt;labelNamespacePath.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="&gt;&gt;textBoxLinkInterwikiFormat.ZOrder" xml:space="preserve">
+    <value>0</value>
   </data>
-  <data name="&gt;&gt;labelNamespacePath.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="labelLinkInterwikiFormat.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
   </data>
-  <data name="&gt;&gt;labelNamespacePath.ZOrder" xml:space="preserve">
-    <value>15</value>
+  <data name="labelLinkInterwikiFormat.Location" type="System.Drawing.Point, System.Drawing">
+    <value>6, 319</value>
   </data>
-  <data name="&gt;&gt;textBoxLocation.Name" xml:space="preserve">
-    <value>textBoxLocation</value>
+  <data name="labelLinkInterwikiFormat.Size" type="System.Drawing.Size, System.Drawing">
+    <value>105, 12</value>
   </data>
-  <data name="&gt;&gt;textBoxLocation.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="labelLinkInterwikiFormat.TabIndex" type="System.Int32, mscorlib">
+    <value>18</value>
   </data>
-  <data name="&gt;&gt;textBoxLocation.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
+  <data name="labelLinkInterwikiFormat.Text" xml:space="preserve">
+    <value>仮リンク用フォーマット:</value>
   </data>
-  <data name="&gt;&gt;textBoxLocation.ZOrder" xml:space="preserve">
-    <value>16</value>
+  <data name="labelLinkInterwikiFormat.ToolTip" xml:space="preserve">
+    <value>言語間リンクが見つからない場合に置き換えるフォーマットを入力します。
+記事名, 言語, 他言語版記事名, 表示名 に相当する部分を $1, $2, $3, $4 としてください。
+空欄の場合、[[:記事名:言語|表示名]] での置き換えを行います。</value>
   </data>
-  <data name="&gt;&gt;labelLocation.Name" xml:space="preserve">
-    <value>labelLocation</value>
+  <data name="&gt;&gt;labelLinkInterwikiFormat.Name" xml:space="preserve">
+    <value>labelLinkInterwikiFormat</value>
   </data>
-  <data name="&gt;&gt;labelLocation.Type" xml:space="preserve">
+  <data name="&gt;&gt;labelLinkInterwikiFormat.Type" xml:space="preserve">
     <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;labelLocation.Parent" xml:space="preserve">
-    <value>groupBoxServer</value>
-  </data>
-  <data name="&gt;&gt;labelLocation.ZOrder" xml:space="preserve">
-    <value>17</value>
-  </data>
-  <data name="groupBoxServer.Enabled" type="System.Boolean, mscorlib">
-    <value>False</value>
-  </data>
-  <data name="groupBoxServer.Location" type="System.Drawing.Point, System.Drawing">
-    <value>6, 39</value>
-  </data>
-  <data name="groupBoxServer.Size" type="System.Drawing.Size, System.Drawing">
-    <value>288, 322</value>
-  </data>
-  <data name="groupBoxServer.TabIndex" type="System.Int32, mscorlib">
-    <value>2</value>
-  </data>
-  <data name="groupBoxServer.Text" xml:space="preserve">
-    <value>MediaWikiの設定</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.Name" xml:space="preserve">
+  <data name="&gt;&gt;labelLinkInterwikiFormat.Parent" xml:space="preserve">
     <value>groupBoxServer</value>
   </data>
-  <data name="&gt;&gt;groupBoxServer.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.Parent" xml:space="preserve">
-    <value>tabPageServer</value>
-  </data>
-  <data name="&gt;&gt;groupBoxServer.ZOrder" xml:space="preserve">
+  <data name="&gt;&gt;labelLinkInterwikiFormat.ZOrder" xml:space="preserve">
     <value>1</value>
   </data>
   <data name="textBoxDocumentationTemplateDefaultPage.Location" type="System.Drawing.Point, System.Drawing">
-    <value>115, 266</value>
+    <value>115, 288</value>
   </data>
   <data name="textBoxDocumentationTemplateDefaultPage.MaxLength" type="System.Int32, mscorlib">
     <value>255</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxDocumentationTemplateDefaultPage.ZOrder" xml:space="preserve">
-    <value>0</value>
+    <value>2</value>
   </data>
   <data name="labelDocumentationTemplateDefaultPage.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
   </data>
   <data name="labelDocumentationTemplateDefaultPage.Location" type="System.Drawing.Point, System.Drawing">
-    <value>6, 269</value>
+    <value>6, 291</value>
   </data>
   <data name="labelDocumentationTemplateDefaultPage.Size" type="System.Drawing.Size, System.Drawing">
     <value>93, 12</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelDocumentationTemplateDefaultPage.ZOrder" xml:space="preserve">
-    <value>1</value>
+    <value>3</value>
   </data>
   <data name="textBoxDocumentationTemplate.Location" type="System.Drawing.Point, System.Drawing">
     <value>115, 241</value>
   </data>
   <data name="textBoxDocumentationTemplate.MaxLength" type="System.Int32, mscorlib">
-    <value>255</value>
+    <value>2000</value>
+  </data>
+  <data name="textBoxDocumentationTemplate.Multiline" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <data name="textBoxDocumentationTemplate.ScrollBars" type="System.Windows.Forms.ScrollBars, System.Windows.Forms">
+    <value>Vertical</value>
   </data>
   <data name="textBoxDocumentationTemplate.Size" type="System.Drawing.Size, System.Drawing">
-    <value>140, 19</value>
+    <value>160, 39</value>
   </data>
   <data name="textBoxDocumentationTemplate.TabIndex" type="System.Int32, mscorlib">
     <value>9</value>
   </data>
   <data name="textBoxDocumentationTemplate.ToolTip" xml:space="preserve">
-    <value>その言語での [[Template:Documentation]] に相当するテンプレート名を入力します。</value>
+    <value>その言語での [[Template:Documentation]] に相当するテンプレート名を入力します。
+行ごとに複数のテンプレートが指定可能です(リダイレクト等で複数存在する場合のため)。</value>
   </data>
   <data name="&gt;&gt;textBoxDocumentationTemplate.Name" xml:space="preserve">
     <value>textBoxDocumentationTemplate</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxDocumentationTemplate.ZOrder" xml:space="preserve">
-    <value>2</value>
+    <value>4</value>
   </data>
   <data name="labelDocumentationTemplate.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
     <value>解説用テンプレート:</value>
   </data>
   <data name="labelDocumentationTemplate.ToolTip" xml:space="preserve">
-    <value>その言語での [[Template:Documentation]] に相当するテンプレート名を入力します。</value>
+    <value>その言語での [[Template:Documentation]] に相当するテンプレート名を入力します。
+行ごとに複数のテンプレートが指定可能です(リダイレクト等で複数存在する場合のため)。</value>
   </data>
   <data name="&gt;&gt;labelDocumentationTemplate.Name" xml:space="preserve">
     <value>labelDocumentationTemplate</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelDocumentationTemplate.ZOrder" xml:space="preserve">
-    <value>3</value>
+    <value>5</value>
   </data>
   <data name="textBoxFileNamespace.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
     <value>Disable</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxFileNamespace.ZOrder" xml:space="preserve">
-    <value>4</value>
+    <value>6</value>
   </data>
   <data name="labelFileNamespace.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelFileNamespace.ZOrder" xml:space="preserve">
-    <value>5</value>
+    <value>7</value>
   </data>
   <data name="textBoxCategoryNamespace.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
     <value>Disable</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxCategoryNamespace.ZOrder" xml:space="preserve">
-    <value>6</value>
+    <value>8</value>
   </data>
   <data name="labelCategoryNamespace.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelCategoryNamespace.ZOrder" xml:space="preserve">
-    <value>7</value>
+    <value>9</value>
   </data>
   <data name="textBoxTemplateNamespace.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
     <value>Disable</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxTemplateNamespace.ZOrder" xml:space="preserve">
-    <value>8</value>
+    <value>10</value>
   </data>
   <data name="labelTemplateNamespace.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelTemplateNamespace.ZOrder" xml:space="preserve">
-    <value>9</value>
+    <value>11</value>
   </data>
   <data name="textBoxRedirect.Location" type="System.Drawing.Point, System.Drawing">
     <value>115, 214</value>
     <value>100</value>
   </data>
   <data name="textBoxRedirect.Size" type="System.Drawing.Size, System.Drawing">
-    <value>140, 19</value>
+    <value>153, 19</value>
   </data>
   <data name="textBoxRedirect.TabIndex" type="System.Int32, mscorlib">
     <value>8</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxRedirect.ZOrder" xml:space="preserve">
-    <value>10</value>
+    <value>12</value>
   </data>
   <data name="labelRedirect.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelRedirect.ZOrder" xml:space="preserve">
-    <value>11</value>
+    <value>13</value>
   </data>
   <data name="textBoxExportPath.Location" type="System.Drawing.Point, System.Drawing">
     <value>81, 66</value>
     <value>2083</value>
   </data>
   <data name="textBoxExportPath.Size" type="System.Drawing.Size, System.Drawing">
-    <value>174, 19</value>
+    <value>187, 19</value>
   </data>
   <data name="textBoxExportPath.TabIndex" type="System.Int32, mscorlib">
     <value>2</value>
   </data>
   <data name="textBoxExportPath.ToolTip" xml:space="preserve">
     <value>サーバーから記事をXMLでダウンロードするためのパスを入力します。
-記事名を代入する部分を {0} としてください。
+記事名を代入する部分を $1 としてください。
 
 どのようなパスかについては、日本語版Wikipediaの
 特別ページ → ページの書き出し
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxExportPath.ZOrder" xml:space="preserve">
-    <value>12</value>
+    <value>14</value>
   </data>
   <data name="labelExportPath.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
   </data>
   <data name="labelExportPath.ToolTip" xml:space="preserve">
     <value>サーバーから記事をXMLでダウンロードするためのパスを入力します。
-記事名を代入する部分を {0} としてください。
+記事名を代入する部分を $1 としてください。
 
 どのようなパスかについては、日本語版Wikipediaの
 特別ページ → ページの書き出し
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelExportPath.ZOrder" xml:space="preserve">
-    <value>13</value>
+    <value>15</value>
   </data>
   <data name="textBoxNamespacePath.Location" type="System.Drawing.Point, System.Drawing">
     <value>81, 109</value>
     <value>2083</value>
   </data>
   <data name="textBoxNamespacePath.Size" type="System.Drawing.Size, System.Drawing">
-    <value>174, 19</value>
+    <value>187, 19</value>
   </data>
   <data name="textBoxNamespacePath.TabIndex" type="System.Int32, mscorlib">
     <value>4</value>
@@ -1478,7 +1076,7 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxNamespacePath.ZOrder" xml:space="preserve">
-    <value>14</value>
+    <value>16</value>
   </data>
   <data name="labelNamespacePath.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
@@ -1511,7 +1109,7 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelNamespacePath.ZOrder" xml:space="preserve">
-    <value>15</value>
+    <value>17</value>
   </data>
   <data name="textBoxLocation.Location" type="System.Drawing.Point, System.Drawing">
     <value>81, 18</value>
@@ -1520,7 +1118,7 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>300</value>
   </data>
   <data name="textBoxLocation.Size" type="System.Drawing.Size, System.Drawing">
-    <value>174, 19</value>
+    <value>187, 19</value>
   </data>
   <data name="textBoxLocation.TabIndex" type="System.Int32, mscorlib">
     <value>1</value>
@@ -1538,7 +1136,7 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;textBoxLocation.ZOrder" xml:space="preserve">
-    <value>16</value>
+    <value>18</value>
   </data>
   <data name="labelLocation.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
@@ -1568,7 +1166,34 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>groupBoxServer</value>
   </data>
   <data name="&gt;&gt;labelLocation.ZOrder" xml:space="preserve">
-    <value>17</value>
+    <value>19</value>
+  </data>
+  <data name="groupBoxServer.Enabled" type="System.Boolean, mscorlib">
+    <value>False</value>
+  </data>
+  <data name="groupBoxServer.Location" type="System.Drawing.Point, System.Drawing">
+    <value>6, 39</value>
+  </data>
+  <data name="groupBoxServer.Size" type="System.Drawing.Size, System.Drawing">
+    <value>288, 352</value>
+  </data>
+  <data name="groupBoxServer.TabIndex" type="System.Int32, mscorlib">
+    <value>2</value>
+  </data>
+  <data name="groupBoxServer.Text" xml:space="preserve">
+    <value>MediaWikiの設定</value>
+  </data>
+  <data name="&gt;&gt;groupBoxServer.Name" xml:space="preserve">
+    <value>groupBoxServer</value>
+  </data>
+  <data name="&gt;&gt;groupBoxServer.Type" xml:space="preserve">
+    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;groupBoxServer.Parent" xml:space="preserve">
+    <value>tabPageServer</value>
+  </data>
+  <data name="&gt;&gt;groupBoxServer.ZOrder" xml:space="preserve">
+    <value>1</value>
   </data>
   <data name="comboBoxLanguage.ImeMode" type="System.Windows.Forms.ImeMode, System.Windows.Forms">
     <value>Disable</value>
@@ -1632,80 +1257,38 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
   <data name="&gt;&gt;labelLanguage.ZOrder" xml:space="preserve">
     <value>3</value>
   </data>
-  <data name="groupBoxInformation.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
-  <data name="&gt;&gt;labelWebsite.Name" xml:space="preserve">
-    <value>labelWebsite</value>
-  </data>
-  <data name="&gt;&gt;labelWebsite.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelWebsite.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;labelWebsite.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Name" xml:space="preserve">
-    <value>linkLabelWebsite</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Type" xml:space="preserve">
-    <value>System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="&gt;&gt;labelCopyright.Name" xml:space="preserve">
-    <value>labelCopyright</value>
+  <data name="tabPageServer.Location" type="System.Drawing.Point, System.Drawing">
+    <value>4, 22</value>
   </data>
-  <data name="&gt;&gt;labelCopyright.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="tabPageServer.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
+    <value>3, 3, 3, 3</value>
   </data>
-  <data name="&gt;&gt;labelCopyright.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
+  <data name="tabPageServer.Size" type="System.Drawing.Size, System.Drawing">
+    <value>592, 397</value>
   </data>
-  <data name="&gt;&gt;labelCopyright.ZOrder" xml:space="preserve">
+  <data name="tabPageServer.TabIndex" type="System.Int32, mscorlib">
     <value>2</value>
   </data>
-  <data name="&gt;&gt;labelApplicationName.Name" xml:space="preserve">
-    <value>labelApplicationName</value>
-  </data>
-  <data name="&gt;&gt;labelApplicationName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="tabPageServer.Text" xml:space="preserve">
+    <value>サーバー/言語</value>
   </data>
-  <data name="&gt;&gt;labelApplicationName.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
+  <data name="tabPageServer.ToolTipText" xml:space="preserve">
+    <value>各言語ごとのサーバー/書式等の設定です。</value>
   </data>
-  <data name="&gt;&gt;labelApplicationName.ZOrder" xml:space="preserve">
-    <value>3</value>
+  <data name="&gt;&gt;tabPageServer.Name" xml:space="preserve">
+    <value>tabPageServer</value>
   </data>
-  <data name="groupBoxInformation.Location" type="System.Drawing.Point, System.Drawing">
-    <value>9, 189</value>
+  <data name="&gt;&gt;tabPageServer.Type" xml:space="preserve">
+    <value>System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="groupBoxInformation.Size" type="System.Drawing.Size, System.Drawing">
-    <value>577, 169</value>
+  <data name="&gt;&gt;tabPageServer.Parent" xml:space="preserve">
+    <value>tabControl</value>
   </data>
-  <data name="groupBoxInformation.TabIndex" type="System.Int32, mscorlib">
+  <data name="&gt;&gt;tabPageServer.ZOrder" xml:space="preserve">
     <value>2</value>
   </data>
-  <data name="groupBoxInformation.Text" xml:space="preserve">
-    <value>バージョン情報</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Name" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Type" xml:space="preserve">
-    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.Parent" xml:space="preserve">
-    <value>tabPageApplication</value>
-  </data>
-  <data name="&gt;&gt;groupBoxInformation.ZOrder" xml:space="preserve">
-    <value>0</value>
+  <data name="groupBoxInformation.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left, Right</value>
   </data>
   <data name="labelWebsite.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
@@ -1741,229 +1324,109 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>61, 67</value>
   </data>
   <data name="linkLabelWebsite.Size" type="System.Drawing.Size, System.Drawing">
-    <value>166, 12</value>
-  </data>
-  <data name="linkLabelWebsite.TabIndex" type="System.Int32, mscorlib">
-    <value>1</value>
-  </data>
-  <data name="linkLabelWebsite.Text" xml:space="preserve">
-    <value>http://honeplus.blog50.fc2.com/</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Name" xml:space="preserve">
-    <value>linkLabelWebsite</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Type" xml:space="preserve">
-    <value>System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;linkLabelWebsite.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="labelCopyright.AutoSize" type="System.Boolean, mscorlib">
-    <value>True</value>
-  </data>
-  <data name="labelCopyright.Location" type="System.Drawing.Point, System.Drawing">
-    <value>12, 45</value>
-  </data>
-  <data name="labelCopyright.Size" type="System.Drawing.Size, System.Drawing">
-    <value>252, 12</value>
-  </data>
-  <data name="labelCopyright.TabIndex" type="System.Int32, mscorlib">
-    <value>1</value>
-  </data>
-  <data name="labelCopyright.Text" xml:space="preserve">
-    <value>Copyright (C) Honeplus 2011 ※AssemblyInfo.cs</value>
-  </data>
-  <data name="&gt;&gt;labelCopyright.Name" xml:space="preserve">
-    <value>labelCopyright</value>
-  </data>
-  <data name="&gt;&gt;labelCopyright.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelCopyright.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;labelCopyright.ZOrder" xml:space="preserve">
-    <value>2</value>
-  </data>
-  <data name="labelApplicationName.AutoSize" type="System.Boolean, mscorlib">
-    <value>True</value>
-  </data>
-  <data name="labelApplicationName.Font" type="System.Drawing.Font, System.Drawing">
-    <value>MS UI Gothic, 9pt, style=Bold</value>
-  </data>
-  <data name="labelApplicationName.Location" type="System.Drawing.Point, System.Drawing">
-    <value>12, 24</value>
-  </data>
-  <data name="labelApplicationName.Size" type="System.Drawing.Size, System.Drawing">
-    <value>373, 12</value>
-  </data>
-  <data name="labelApplicationName.TabIndex" type="System.Int32, mscorlib">
-    <value>0</value>
-  </data>
-  <data name="labelApplicationName.Text" xml:space="preserve">
-    <value>Wikipdia翻訳支援ツール Ver1.00 ※FormUtil.ApplicationName()</value>
-  </data>
-  <data name="&gt;&gt;labelApplicationName.Name" xml:space="preserve">
-    <value>labelApplicationName</value>
-  </data>
-  <data name="&gt;&gt;labelApplicationName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelApplicationName.Parent" xml:space="preserve">
-    <value>groupBoxInformation</value>
-  </data>
-  <data name="&gt;&gt;labelApplicationName.ZOrder" xml:space="preserve">
-    <value>3</value>
-  </data>
-  <data name="groupBoxApplicationConfig.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
-    <value>Top, Bottom, Left, Right</value>
-  </data>
-  <data name="&gt;&gt;checkBoxIgnoreError.Name" xml:space="preserve">
-    <value>checkBoxIgnoreError</value>
-  </data>
-  <data name="&gt;&gt;checkBoxIgnoreError.Type" xml:space="preserve">
-    <value>System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;checkBoxIgnoreError.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
-  </data>
-  <data name="&gt;&gt;checkBoxIgnoreError.ZOrder" xml:space="preserve">
-    <value>0</value>
-  </data>
-  <data name="&gt;&gt;labelRefererNote.Name" xml:space="preserve">
-    <value>labelRefererNote</value>
-  </data>
-  <data name="&gt;&gt;labelRefererNote.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelRefererNote.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
-  </data>
-  <data name="&gt;&gt;labelRefererNote.ZOrder" xml:space="preserve">
-    <value>1</value>
-  </data>
-  <data name="&gt;&gt;labelUserAgentNote.Name" xml:space="preserve">
-    <value>labelUserAgentNote</value>
-  </data>
-  <data name="&gt;&gt;labelUserAgentNote.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelUserAgentNote.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
-  </data>
-  <data name="&gt;&gt;labelUserAgentNote.ZOrder" xml:space="preserve">
-    <value>2</value>
-  </data>
-  <data name="&gt;&gt;labelChaceNote.Name" xml:space="preserve">
-    <value>labelChaceNote</value>
-  </data>
-  <data name="&gt;&gt;labelChaceNote.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;labelChaceNote.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
-  </data>
-  <data name="&gt;&gt;labelChaceNote.ZOrder" xml:space="preserve">
-    <value>3</value>
-  </data>
-  <data name="&gt;&gt;textBoxCacheExpire.Name" xml:space="preserve">
-    <value>textBoxCacheExpire</value>
-  </data>
-  <data name="&gt;&gt;textBoxCacheExpire.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>205, 12</value>
   </data>
-  <data name="&gt;&gt;textBoxCacheExpire.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="linkLabelWebsite.TabIndex" type="System.Int32, mscorlib">
+    <value>1</value>
   </data>
-  <data name="&gt;&gt;textBoxCacheExpire.ZOrder" xml:space="preserve">
-    <value>4</value>
+  <data name="linkLabelWebsite.Text" xml:space="preserve">
+    <value>http://sourceforge.jp/projects/wptscs/</value>
   </data>
-  <data name="&gt;&gt;textBoxReferer.Name" xml:space="preserve">
-    <value>textBoxReferer</value>
+  <data name="&gt;&gt;linkLabelWebsite.Name" xml:space="preserve">
+    <value>linkLabelWebsite</value>
   </data>
-  <data name="&gt;&gt;textBoxReferer.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="&gt;&gt;linkLabelWebsite.Type" xml:space="preserve">
+    <value>System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;textBoxReferer.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="&gt;&gt;linkLabelWebsite.Parent" xml:space="preserve">
+    <value>groupBoxInformation</value>
   </data>
-  <data name="&gt;&gt;textBoxReferer.ZOrder" xml:space="preserve">
-    <value>5</value>
+  <data name="&gt;&gt;linkLabelWebsite.ZOrder" xml:space="preserve">
+    <value>1</value>
   </data>
-  <data name="&gt;&gt;labelReferer.Name" xml:space="preserve">
-    <value>labelReferer</value>
+  <data name="labelCopyright.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
   </data>
-  <data name="&gt;&gt;labelReferer.Type" xml:space="preserve">
-    <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="labelCopyright.Location" type="System.Drawing.Point, System.Drawing">
+    <value>12, 45</value>
   </data>
-  <data name="&gt;&gt;labelReferer.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="labelCopyright.Size" type="System.Drawing.Size, System.Drawing">
+    <value>252, 12</value>
   </data>
-  <data name="&gt;&gt;labelReferer.ZOrder" xml:space="preserve">
-    <value>6</value>
+  <data name="labelCopyright.TabIndex" type="System.Int32, mscorlib">
+    <value>1</value>
   </data>
-  <data name="&gt;&gt;labelCacheExpire.Name" xml:space="preserve">
-    <value>labelCacheExpire</value>
+  <data name="labelCopyright.Text" xml:space="preserve">
+    <value>Copyright (C) Honeplus 2011 ※AssemblyInfo.cs</value>
   </data>
-  <data name="&gt;&gt;labelCacheExpire.Type" xml:space="preserve">
+  <data name="&gt;&gt;labelCopyright.Name" xml:space="preserve">
+    <value>labelCopyright</value>
+  </data>
+  <data name="&gt;&gt;labelCopyright.Type" xml:space="preserve">
     <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;labelCacheExpire.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="&gt;&gt;labelCopyright.Parent" xml:space="preserve">
+    <value>groupBoxInformation</value>
   </data>
-  <data name="&gt;&gt;labelCacheExpire.ZOrder" xml:space="preserve">
-    <value>7</value>
+  <data name="&gt;&gt;labelCopyright.ZOrder" xml:space="preserve">
+    <value>2</value>
   </data>
-  <data name="&gt;&gt;textBoxUserAgent.Name" xml:space="preserve">
-    <value>textBoxUserAgent</value>
+  <data name="labelApplicationName.AutoSize" type="System.Boolean, mscorlib">
+    <value>True</value>
   </data>
-  <data name="&gt;&gt;textBoxUserAgent.Type" xml:space="preserve">
-    <value>System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  <data name="labelApplicationName.Font" type="System.Drawing.Font, System.Drawing">
+    <value>MS UI Gothic, 9pt, style=Bold</value>
   </data>
-  <data name="&gt;&gt;textBoxUserAgent.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="labelApplicationName.Location" type="System.Drawing.Point, System.Drawing">
+    <value>12, 24</value>
   </data>
-  <data name="&gt;&gt;textBoxUserAgent.ZOrder" xml:space="preserve">
-    <value>8</value>
+  <data name="labelApplicationName.Size" type="System.Drawing.Size, System.Drawing">
+    <value>373, 12</value>
   </data>
-  <data name="&gt;&gt;labelUserAgent.Name" xml:space="preserve">
-    <value>labelUserAgent</value>
+  <data name="labelApplicationName.TabIndex" type="System.Int32, mscorlib">
+    <value>0</value>
   </data>
-  <data name="&gt;&gt;labelUserAgent.Type" xml:space="preserve">
+  <data name="labelApplicationName.Text" xml:space="preserve">
+    <value>Wikipdia翻訳支援ツール Ver1.00 ※FormUtil.ApplicationName()</value>
+  </data>
+  <data name="&gt;&gt;labelApplicationName.Name" xml:space="preserve">
+    <value>labelApplicationName</value>
+  </data>
+  <data name="&gt;&gt;labelApplicationName.Type" xml:space="preserve">
     <value>System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;labelUserAgent.Parent" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="&gt;&gt;labelApplicationName.Parent" xml:space="preserve">
+    <value>groupBoxInformation</value>
   </data>
-  <data name="&gt;&gt;labelUserAgent.ZOrder" xml:space="preserve">
-    <value>9</value>
+  <data name="&gt;&gt;labelApplicationName.ZOrder" xml:space="preserve">
+    <value>3</value>
   </data>
-  <data name="groupBoxApplicationConfig.Location" type="System.Drawing.Point, System.Drawing">
-    <value>6, 6</value>
+  <data name="groupBoxInformation.Location" type="System.Drawing.Point, System.Drawing">
+    <value>9, 189</value>
   </data>
-  <data name="groupBoxApplicationConfig.Size" type="System.Drawing.Size, System.Drawing">
-    <value>580, 177</value>
+  <data name="groupBoxInformation.Size" type="System.Drawing.Size, System.Drawing">
+    <value>577, 209</value>
   </data>
-  <data name="groupBoxApplicationConfig.TabIndex" type="System.Int32, mscorlib">
-    <value>1</value>
+  <data name="groupBoxInformation.TabIndex" type="System.Int32, mscorlib">
+    <value>2</value>
   </data>
-  <data name="groupBoxApplicationConfig.Text" xml:space="preserve">
-    <value>ã\82¢ã\83\97ã\83ªã\82±ã\83¼ã\82·ã\83§ã\83³ã\81®è¨­å®\9a</value>
+  <data name="groupBoxInformation.Text" xml:space="preserve">
+    <value>ã\83\90ã\83¼ã\82¸ã\83§ã\83³æ\83\85å ±</value>
   </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Name" xml:space="preserve">
-    <value>groupBoxApplicationConfig</value>
+  <data name="&gt;&gt;groupBoxInformation.Name" xml:space="preserve">
+    <value>groupBoxInformation</value>
   </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Type" xml:space="preserve">
+  <data name="&gt;&gt;groupBoxInformation.Type" xml:space="preserve">
     <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.Parent" xml:space="preserve">
+  <data name="&gt;&gt;groupBoxInformation.Parent" xml:space="preserve">
     <value>tabPageApplication</value>
   </data>
-  <data name="&gt;&gt;groupBoxApplicationConfig.ZOrder" xml:space="preserve">
-    <value>1</value>
+  <data name="&gt;&gt;groupBoxInformation.ZOrder" xml:space="preserve">
+    <value>0</value>
+  </data>
+  <data name="groupBoxApplicationConfig.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
+    <value>Top, Bottom, Left, Right</value>
   </data>
   <data name="checkBoxIgnoreError.AutoSize" type="System.Boolean, mscorlib">
     <value>True</value>
@@ -2267,6 +1730,84 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
   <data name="&gt;&gt;labelUserAgent.ZOrder" xml:space="preserve">
     <value>9</value>
   </data>
+  <data name="groupBoxApplicationConfig.Location" type="System.Drawing.Point, System.Drawing">
+    <value>6, 6</value>
+  </data>
+  <data name="groupBoxApplicationConfig.Size" type="System.Drawing.Size, System.Drawing">
+    <value>580, 217</value>
+  </data>
+  <data name="groupBoxApplicationConfig.TabIndex" type="System.Int32, mscorlib">
+    <value>1</value>
+  </data>
+  <data name="groupBoxApplicationConfig.Text" xml:space="preserve">
+    <value>アプリケーションの設定</value>
+  </data>
+  <data name="&gt;&gt;groupBoxApplicationConfig.Name" xml:space="preserve">
+    <value>groupBoxApplicationConfig</value>
+  </data>
+  <data name="&gt;&gt;groupBoxApplicationConfig.Type" xml:space="preserve">
+    <value>System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;groupBoxApplicationConfig.Parent" xml:space="preserve">
+    <value>tabPageApplication</value>
+  </data>
+  <data name="&gt;&gt;groupBoxApplicationConfig.ZOrder" xml:space="preserve">
+    <value>1</value>
+  </data>
+  <data name="tabPageApplication.Location" type="System.Drawing.Point, System.Drawing">
+    <value>4, 22</value>
+  </data>
+  <data name="tabPageApplication.Padding" type="System.Windows.Forms.Padding, System.Windows.Forms">
+    <value>3, 3, 3, 3</value>
+  </data>
+  <data name="tabPageApplication.Size" type="System.Drawing.Size, System.Drawing">
+    <value>592, 397</value>
+  </data>
+  <data name="tabPageApplication.TabIndex" type="System.Int32, mscorlib">
+    <value>3</value>
+  </data>
+  <data name="tabPageApplication.Text" xml:space="preserve">
+    <value>その他</value>
+  </data>
+  <data name="&gt;&gt;tabPageApplication.Name" xml:space="preserve">
+    <value>tabPageApplication</value>
+  </data>
+  <data name="&gt;&gt;tabPageApplication.Type" xml:space="preserve">
+    <value>System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;tabPageApplication.Parent" xml:space="preserve">
+    <value>tabControl</value>
+  </data>
+  <data name="&gt;&gt;tabPageApplication.ZOrder" xml:space="preserve">
+    <value>3</value>
+  </data>
+  <data name="tabControl.Location" type="System.Drawing.Point, System.Drawing">
+    <value>12, 12</value>
+  </data>
+  <data name="tabControl.ShowToolTips" type="System.Boolean, mscorlib">
+    <value>True</value>
+  </data>
+  <data name="tabControl.Size" type="System.Drawing.Size, System.Drawing">
+    <value>600, 423</value>
+  </data>
+  <data name="tabControl.TabIndex" type="System.Int32, mscorlib">
+    <value>1</value>
+  </data>
+  <data name="tabControl.ToolTip" xml:space="preserve">
+    <value>その他の設定・情報です。</value>
+  </data>
+  <data name="&gt;&gt;tabControl.Name" xml:space="preserve">
+    <value>tabControl</value>
+  </data>
+  <data name="&gt;&gt;tabControl.Type" xml:space="preserve">
+    <value>System.Windows.Forms.TabControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;tabControl.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;tabControl.ZOrder" xml:space="preserve">
+    <value>0</value>
+  </data>
   <metadata name="errorProvider.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   </metadata>
@@ -2277,7 +1818,7 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
     <value>6, 12</value>
   </data>
   <data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
-    <value>624, 443</value>
+    <value>624, 473</value>
   </data>
   <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
@@ -2302,41 +1843,11 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
 </value>
   </data>
   <data name="$this.MinimumSize" type="System.Drawing.Size, System.Drawing">
-    <value>640, 480</value>
+    <value>640, 510</value>
   </data>
   <data name="$this.Text" xml:space="preserve">
     <value>設定</value>
   </data>
-  <data name="&gt;&gt;ColumnCode.Name" xml:space="preserve">
-    <value>ColumnCode</value>
-  </data>
-  <data name="&gt;&gt;ColumnCode.Type" xml:space="preserve">
-    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;ColumnName.Name" xml:space="preserve">
-    <value>ColumnName</value>
-  </data>
-  <data name="&gt;&gt;ColumnName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;ColumnShortName.Name" xml:space="preserve">
-    <value>ColumnShortName</value>
-  </data>
-  <data name="&gt;&gt;ColumnShortName.Type" xml:space="preserve">
-    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;errorProvider.Name" xml:space="preserve">
-    <value>errorProvider</value>
-  </data>
-  <data name="&gt;&gt;errorProvider.Type" xml:space="preserve">
-    <value>System.Windows.Forms.ErrorProvider, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
-  <data name="&gt;&gt;toolTip.Name" xml:space="preserve">
-    <value>toolTip</value>
-  </data>
-  <data name="&gt;&gt;toolTip.Type" xml:space="preserve">
-    <value>System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
-  </data>
   <data name="&gt;&gt;ColumnFromCode.Name" xml:space="preserve">
     <value>ColumnFromCode</value>
   </data>
@@ -2379,6 +1890,36 @@ http://www.mediawiki.org/wiki/API:Main_page/ja</value>
   <data name="&gt;&gt;ColumnTimestamp.Type" xml:space="preserve">
     <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
+  <data name="&gt;&gt;ColumnCode.Name" xml:space="preserve">
+    <value>ColumnCode</value>
+  </data>
+  <data name="&gt;&gt;ColumnCode.Type" xml:space="preserve">
+    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;ColumnName.Name" xml:space="preserve">
+    <value>ColumnName</value>
+  </data>
+  <data name="&gt;&gt;ColumnName.Type" xml:space="preserve">
+    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;ColumnShortName.Name" xml:space="preserve">
+    <value>ColumnShortName</value>
+  </data>
+  <data name="&gt;&gt;ColumnShortName.Type" xml:space="preserve">
+    <value>System.Windows.Forms.DataGridViewTextBoxColumn, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;errorProvider.Name" xml:space="preserve">
+    <value>errorProvider</value>
+  </data>
+  <data name="&gt;&gt;errorProvider.Type" xml:space="preserve">
+    <value>System.Windows.Forms.ErrorProvider, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;toolTip.Name" xml:space="preserve">
+    <value>toolTip</value>
+  </data>
+  <data name="&gt;&gt;toolTip.Type" xml:space="preserve">
+    <value>System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
   <data name="&gt;&gt;$this.Name" xml:space="preserve">
     <value>ConfigForm</value>
   </data>
index a05f010..5b9b8f7 100644 (file)
@@ -3,7 +3,7 @@
 //      Wikipedia用の翻訳支援処理実装クラスソース</summary>
 //
 // <copyright file="MediaWikiTranslator.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -13,12 +13,17 @@ namespace Honememo.Wptscs.Logics
     using System;
     using System.Collections.Generic;
     using System.IO;
+    using System.Linq;
     using System.Net;
     using System.Text;
     using System.Windows.Forms;
+    using Honememo.Parsers;
     using Honememo.Utilities;
     using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Parsers;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// Wikipedia用の翻訳支援処理実装クラスです。
@@ -61,62 +66,6 @@ namespace Honememo.Wptscs.Logics
         
         #endregion
 
-        #region 整理予定の静的メソッド
-
-        /// <summary>
-        /// コメント区間のチェック。
-        /// </summary>
-        /// <param name="comment">解析したコメント。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <returns>コメント区間の場合、終了位置のインデックスを返す。それ以外は-1。</returns>
-        public static int ChkComment(out string comment, string text, int index)
-        {
-            // 入力値確認
-            if (String.IsNullOrEmpty(text))
-            {
-                comment = String.Empty;
-                return -1;
-            }
-
-            // 改良版メソッドをコール
-            if (!LazyXmlParser.TryParseComment(text.Substring(index), out comment))
-            {
-                comment = String.Empty;
-                return -1;
-            }
-
-            return index + comment.Length - 1;
-        }
-
-        /// <summary>
-        /// nowiki区間のチェック。
-        /// </summary>
-        /// <param name="nowiki">解析したnowikiブロック。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <returns>nowiki区間の場合、終了位置のインデックスを返す。それ以外は-1。</returns>
-        public static int ChkNowiki(out string nowiki, string text, int index)
-        {
-            // 入力値確認
-            if (String.IsNullOrEmpty(text))
-            {
-                nowiki = String.Empty;
-                return -1;
-            }
-
-            // 改良版メソッドをコール
-            if (!MediaWikiPage.TryParseNowiki(text.Substring(index), out nowiki))
-            {
-                nowiki = String.Empty;
-                return -1;
-            }
-
-            return index + nowiki.Length - 1;
-        }
-
-        #endregion
-
         #region メイン処理メソッド
 
         /// <summary>
@@ -124,81 +73,58 @@ namespace Honememo.Wptscs.Logics
         /// ※継承クラスでは、この関数に処理を実装すること
         /// </summary>
         /// <param name="name">記事名。</param>
-        /// <returns><c>true</c> 処理成功。</returns>
-        protected override bool RunBody(string name)
+        /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Translator.Log"/>に出力される。</exception>
+        protected override void RunBody(string name)
         {
-            System.Diagnostics.Debug.WriteLine("\nMediaWikiTranslator.runBody > " + name);
-
             // 対象記事を取得
-            MediaWikiPage article = this.ChkTargetArticle(name);
+            MediaWikiPage article = this.GetTargetPage(name);
             if (article == null)
             {
-                return false;
+                throw new ApplicationException("article is not found");
             }
 
             // 対象記事に言語間リンクが存在する場合、処理を継続するか確認
-            string interWiki = article.GetInterWiki(this.To.Language.Code);
-            if (interWiki != String.Empty)
+            // ※ 言語間リンク取得中は、処理状態を解析中に変更
+            string interWiki = null;
+            this.ChangeStatusInExecuting(
+                () => interWiki = article.GetInterWiki(this.To.Language.Code),
+                Resources.StatusParsing);
+            if (!String.IsNullOrEmpty(interWiki))
             {
+                // ※ 確認ダイアログの表示中は処理時間をカウントしない
+                this.Stopwatch.Stop();
                 if (MessageBox.Show(
-                        String.Format(Resources.QuestionMessage_ArticleExist, interWiki),
+                        String.Format(Resources.QuestionMessageArticleExisted, interWiki),
                         Resources.QuestionTitle,
                         MessageBoxButtons.YesNo,
                         MessageBoxIcon.Question)
-                   == System.Windows.Forms.DialogResult.No)
+                   == DialogResult.No)
                 {
-                    this.LogLine(ENTER + String.Format(Resources.QuestionMessage_ArticleExist, interWiki));
-                    return false;
+                    this.LogLine(ENTER + String.Format(Resources.QuestionMessageArticleExisted, interWiki));
+                    throw new ApplicationException("user canceled");
                 }
-                else
-                {
-                    this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessage_ArticleExistInterWiki, interWiki));
-                }
-            }
 
-            // 冒頭部を作成
-            this.Text += "'''xxx'''";
-            string bracket = this.To.Language.Bracket;
-            if (bracket.Contains("{0}"))
-            {
-                string originalName = String.Empty;
-                string langTitle = this.GetFullName(this.From, this.To.Language.Code);
-                if (langTitle != String.Empty)
-                {
-                    originalName = "[[" + langTitle + "]]: ";
-                }
-
-                this.Text += String.Format(bracket, originalName + "'''" + name + "'''");
+                // OKが選択された場合、処理続行
+                this.Stopwatch.Start();
+                this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessageTargetArticleHadInterWiki, interWiki));
             }
 
-            this.Text += "\n\n";
-
-            // 言語間リンク・定型句の変換
-            this.LogLine(ENTER + Resources.RightArrow + " " + String.Format(Resources.LogMessage_CheckAndReplaceStart, interWiki));
-            this.Text += this.ReplaceText(article.Text, article.Title);
+            // 冒頭部を作成
+            this.Text += this.CreateOpening(article.Title);
 
-            // ユーザーからの中止要求をチェック
-            if (CancellationPending)
-            {
-                return false;
-            }
+            // 言語間リンク・定型句の変換、実行中は処理状態を解析中に設定
+            this.LogLine(ENTER + Resources.RightArrow + " " + String.Format(Resources.LogMessageStartParseAndReplace, interWiki));
+            this.ChangeStatusInExecuting(
+                () => this.Text += this.ReplaceElement(new MediaWikiParser(this.From).Parse(article.Text), article.Title).ToString(),
+                Resources.StatusParsing);
 
-            // 新しい言語間リンクと、コメントを追記
-            this.Text += "\n\n[[" + this.From.Language.Code + ":" + name + "]]\n";
-            this.Text += String.Format(
-                Resources.ArticleFooter,
-                FormUtils.ApplicationName(),
-                this.From.Language.Code,
-                name,
-                article.Timestamp.HasValue ? article.Timestamp.Value.ToString("U") : String.Empty) + "\n";
+            // 記事の末尾に新しい言語間リンクと、コメントを追記
+            this.Text += this.CreateEnding(article);
 
-            // ダウンロードされるテキストがLFなので、ここで全てCRLFに変換
+            // ダウンロードされるテキストがLFなので、最後に全てCRLFに変換
             // ※ダウンロード時にCRLFにするような仕組みが見つかれば、そちらを使う
             //   その場合、上のように\nをべたに吐いている部分を修正する
             this.Text = this.Text.Replace("\n", ENTER);
-
-            System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.runBody > Success!");
-            return true;
         }
 
         #endregion
@@ -214,65 +140,91 @@ namespace Honememo.Wptscs.Logics
         /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
         protected new MediaWikiPage GetPage(string title, string notFoundMsg)
         {
-            // 親クラスのメソッドを戻り値の型だけ変更
-            return base.GetPage(title, notFoundMsg) as MediaWikiPage;
+            // &amp; &nbsp; 等の特殊文字をデコードして、親クラスのメソッドを呼び出し
+            return base.GetPage(WebUtility.HtmlDecode(title), notFoundMsg) as MediaWikiPage;
         }
 
         #endregion
 
-        #region å\90\84å\87¦ç\90\86ã\81®メソッド
-        
+        #region å\86\92é ­ï¼\8fæ\9c«å°¾ã\83\96ã\83­ã\83\83ã\82¯ã\81®ç\94\9fæ\88\90メソッド
+
         /// <summary>
-        /// 翻訳支援対象のページを取得
+        /// 変換後記事冒頭用の「'''日本語記事名'''([[英語|英]]: '''英語記事名''')」みたいなのを作成する
         /// </summary>
-        /// <param name="title">ページ名。</param>
-        /// <returns>å\8f\96å¾\97ã\81\97ã\81\9fã\83\9aã\83¼ã\82¸ã\80\82å\8f\96å¾\97失æ\95\97æ\99\82ã\81¯<c>null</c>。</returns>
-        protected MediaWikiPage ChkTargetArticle(string title)
+        /// <param name="title">翻訳支援対象の記事名。</param>
+        /// <returns>å\86\92é ­é\83¨ã\81®ã\83\86ã\82­ã\82¹ã\83\88。</returns>
+        protected virtual string CreateOpening(string title)
         {
-            // 指定された記事の生データをWikipediaから取得
-            this.LogLine(String.Format(Resources.LogMessage_GetArticle, this.From.Location, title));
-            MediaWikiPage page = this.GetPage(title, Resources.RightArrow + " " + Resources.LogMessage_ArticleNothing);
-
-            // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
-            if (page != null && page.IsRedirect())
+            StringBuilder b = new StringBuilder("'''xxx'''");
+            string langPart = String.Empty;
+            string langTitle = this.GetFullName(this.From, this.To.Language.Code);
+            if (!String.IsNullOrEmpty(langTitle))
             {
-                this.LogLine(Resources.RightArrow + " " + Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]]");
-                page = this.GetPage(page.Redirect.Title, Resources.RightArrow + " " + Resources.LogMessage_ArticleNothing);
+                langPart = new MediaWikiLink(langTitle).ToString() + ": ";
             }
 
-            return page;
+            b.Append(this.To.Language.FormatBracket(langPart + "'''" + title + "'''"));
+            b.Append("\n\n");
+            return b.ToString();
         }
 
         /// <summary>
-        /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す
+        /// 変換後記事末尾用の新しい言語間リンクとコメントを作成する
         /// </summary>
-        /// <param name="title">記事名。</param>
-        /// <param name="code">言語コード。</param>
-        /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
-        protected string GetInterWiki(string title, string code)
+        /// <param name="page">翻訳支援対象の記事。</param>
+        /// <returns>末尾部のテキスト。</returns>
+        protected virtual string CreateEnding(MediaWikiPage page)
         {
-            MediaWikiPage page = this.GetPage(title, Resources.LogMessage_LinkArticleNothing);
+            MediaWikiLink link = new MediaWikiLink();
+            link.Title = page.Title;
+            link.Code = this.From.Language.Code;
+            return "\n\n" + link.ToString() + "\n" + String.Format(
+                Resources.ArticleFooter,
+                FormUtils.ApplicationName(),
+                this.From.Language.Code,
+                page.Title,
+                page.Timestamp.HasValue ? page.Timestamp.Value.ToString("U") : String.Empty) + "\n";
+        }
 
-            // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
-            if (page != null && page.IsRedirect())
+        #endregion
+
+        #region 言語間リンクの取得メソッド
+
+        /// <summary>
+        /// 指定された記事を取得し、言語間リンクを確認、返す(テンプレート以外)。
+        /// </summary>
+        /// <param name="name">記事名。</param>
+        /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
+        protected string GetInterWiki(string name)
+        {
+            return this.GetInterWiki(name, false);
+        }
+
+        /// <summary>
+        /// 指定された記事を取得し、言語間リンクを確認、返す。
+        /// </summary>
+        /// <param name="title">記事名。</param>
+        /// <param name="template"><c>true</c> テンプレート。</param>
+        /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
+        protected string GetInterWiki(string title, bool template)
+        {
+            MediaWikiLink element;
+            if (template)
             {
-                this.Log += Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]] " + Resources.RightArrow + " ";
-                page = this.GetPage(page.Redirect.Title, Resources.LogMessage_LinkArticleNothing);
+                element = new MediaWikiTemplate(title);
+            }
+            else
+            {
+                element = new MediaWikiLink(title);
             }
 
-            // 記事があればその言語間リンクを取得
-            string interWiki = null;
-            if (page != null)
+            Log += element.ToString() + " " + Resources.RightArrow + " ";
+            string interWiki = this.GetInterWikiUseTable(title, this.To.Language.Code);
+
+            // 改行が出力されていない場合(正常時)、改行
+            if (!Log.EndsWith(ENTER))
             {
-                interWiki = page.GetInterWiki(this.To.Language.Code);
-                if (!String.IsNullOrEmpty(interWiki))
-                {
-                    Log += "[[" + interWiki + "]]";
-                }
-                else
-                {
-                    Log += Resources.LogMessage_InterWikiNothing;
-                }
+                Log += ENTER;
             }
 
             return interWiki;
@@ -296,42 +248,47 @@ namespace Honememo.Wptscs.Logics
             string interWiki = null;
             lock (this.ItemTable)
             {
+                // 対訳表を使用して言語間リンクを確認
+                // ※ 対訳表へのキーとしてはHTMLデコードした記事名を使用する
                 TranslationDictionary.Item item;
-                if (this.ItemTable.TryGetValue(title, out item))
+                if (this.ItemTable.TryGetValue(WebUtility.HtmlDecode(title), out item))
                 {
                     // 対訳表に存在する場合はその値を使用
                     // リダイレクトがあれば、そのメッセージも表示
                     if (!String.IsNullOrWhiteSpace(item.Alias))
                     {
-                        this.Log += Resources.LogMessage_Redirect + " [[" + item.Alias + "]] " + Resources.RightArrow + " ";
+                        this.Log += Resources.LogMessageRedirect + " "
+                            + new MediaWikiLink(item.Alias).ToString() + " " + Resources.RightArrow + " ";
                     }
 
                     if (!String.IsNullOrEmpty(item.Word))
                     {
                         interWiki = item.Word;
-                        Log += "[[" + interWiki + "]]";
+                        Log += new MediaWikiLink(interWiki).ToString();
                     }
                     else
                     {
                         interWiki = String.Empty;
-                        Log += Resources.LogMessage_InterWikiNothing;
+                        Log += Resources.LogMessageInterWikiNotFound;
                     }
 
-                    Log += Resources.LogMessageTranslation;
+                    // ログ上に対訳表を使用した旨通知
+                    Log += Resources.LogMessageNoteTranslation;
                     return interWiki;
                 }
 
                 // 対訳表に存在しない場合は、普通に取得し表に記録
                 // ※ nullも存在しないことの記録として格納
                 item = new TranslationDictionary.Item { Timestamp = DateTime.UtcNow };
-                MediaWikiPage page = this.GetPage(title, Resources.LogMessage_LinkArticleNothing);
+                MediaWikiPage page = this.GetPage(title, Resources.LogMessageLinkArticleNotFound);
 
                 // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
                 if (page != null && page.IsRedirect())
                 {
                     item.Alias = page.Redirect.Title;
-                    this.Log += Resources.LogMessage_Redirect + " [[" + page.Redirect.Title + "]] " + Resources.RightArrow + " ";
-                    page = this.GetPage(page.Redirect.Title, Resources.LogMessage_LinkArticleNothing);
+                    this.Log += Resources.LogMessageRedirect + " "
+                        + new MediaWikiLink(page.Redirect.Title).ToString() + " " + Resources.RightArrow + " ";
+                    page = this.GetPage(page.Redirect.Title, Resources.LogMessageLinkArticleNotFound);
                 }
 
                 // 記事があればその言語間リンクを取得
@@ -340,665 +297,598 @@ namespace Honememo.Wptscs.Logics
                     interWiki = page.GetInterWiki(this.To.Language.Code);
                     if (!String.IsNullOrEmpty(interWiki))
                     {
-                        Log += "[[" + interWiki + "]]";
+                        Log += new MediaWikiLink(interWiki).ToString();
                     }
                     else
                     {
-                        Log += Resources.LogMessage_InterWikiNothing;
+                        Log += Resources.LogMessageInterWikiNotFound;
                     }
 
                     item.Word = interWiki;
-                    this.ItemTable[title] = item;
+                    this.ItemTable[WebUtility.HtmlDecode(title)] = item;
                 }
             }
 
             return interWiki;
         }
-        
+
         /// <summary>
-        /// 指定された記事を取得し、言語間リンクを確認、返す。
+        /// ログメッセージを出力しつつ、指定された記事の指定された言語コードへの言語間リンクを返す。
         /// </summary>
         /// <param name="title">記事名。</param>
-        /// <param name="template"><c>true</c> テンプレート。</param>
-        /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
-        protected string GetInterWiki(string title, bool template)
+        /// <param name="code">言語コード。</param>
+        /// <returns>言語間リンク先の記事名。見つからない場合は空。ページ自体が存在しない場合は<c>null</c>。</returns>
+        protected string GetInterWiki(string title, string code)
         {
-            // 指定された記事の生データをWikipediaから取得
-            // ※記事自体が存在しない場合、NULLを返す
-            if (!template)
-            {
-                Log += "[[" + title + "]] " + Resources.RightArrow + " ";
-            }
-            else
-            {
-                Log += "{{" + title + "}} " + Resources.RightArrow + " ";
-            }
+            MediaWikiPage page = this.GetPage(title, Resources.LogMessageLinkArticleNotFound);
 
             // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
-            string interWiki = this.GetInterWikiUseTable(title, this.To.Language.Code);
+            if (page != null && page.IsRedirect())
+            {
+                this.Log += Resources.LogMessageRedirect + " "
+                    + new MediaWikiLink(page.Redirect.Title).ToString() + " " + Resources.RightArrow + " ";
+                page = this.GetPage(page.Redirect.Title, Resources.LogMessageLinkArticleNotFound);
+            }
 
-            // 改行が出力されていない場合(正常時)、改行
-            if (!Log.EndsWith(ENTER))
+            // 記事があればその言語間リンクを取得
+            string interWiki = null;
+            if (page != null)
             {
-                Log += ENTER;
+                interWiki = page.GetInterWiki(this.To.Language.Code);
+                if (!String.IsNullOrEmpty(interWiki))
+                {
+                    Log += new MediaWikiLink(interWiki).ToString();
+                }
+                else
+                {
+                    Log += Resources.LogMessageInterWikiNotFound;
+                }
             }
 
             return interWiki;
         }
 
+        #endregion
+
+        #region 要素の変換メソッド
+
         /// <summary>
-        /// æ\8c\87å®\9aã\81\95ã\82\8cã\81\9fè¨\98äº\8bã\82\92å\8f\96å¾\97ã\81\97ã\80\81è¨\80èª\9eé\96\93ã\83ªã\83³ã\82¯ã\82\92確èª\8dã\80\81è¿\94ã\81\99ï¼\88ã\83\86ã\83³ã\83\97ã\83¬ã\83¼ã\83\88以å¤\96ï¼\89
+        /// æ¸¡ã\81\95ã\82\8cã\81\9fã\83\9aã\83¼ã\82¸è¦\81ç´ ã\81®å¤\89æ\8f\9bã\82\92è¡\8cã\81\86
         /// </summary>
-        /// <param name="name">記事名。</param>
-        /// <returns>言語間リンク先の記事、存在しない場合 <c>null</c>。</returns>
-        protected string GetInterWiki(string name)
+        /// <param name="element">ページ要素。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換後のページ要素。</returns>
+        protected virtual IElement ReplaceElement(IElement element, string parent)
         {
-            return this.GetInterWiki(name, false);
+            // ユーザーからの中止要求をチェック
+            this.ThrowExceptionIfCanceled();
+
+            // 要素の型に応じて、必要な置き換えを行う
+            if (element is MediaWikiTemplate)
+            {
+                // テンプレート
+                return this.ReplaceTemplate((MediaWikiTemplate)element, parent);
+            }
+            else if (element is MediaWikiLink)
+            {
+                // 内部リンク
+                return this.ReplaceLink((MediaWikiLink)element, parent);
+            }
+            else if (element is MediaWikiHeading)
+            {
+                // 見出し
+                return this.ReplaceHeading((MediaWikiHeading)element, parent);
+            }
+            else if (element is MediaWikiVariable)
+            {
+                // 変数
+                return this.ReplaceVariable((MediaWikiVariable)element, parent);
+            }
+            else if (element is ListElement)
+            {
+                // 値を格納する要素
+                return this.ReplaceListElement((ListElement)element, parent);
+            }
+
+            // それ以外は、特に何もせず元の値を返す
+            return element;
         }
 
         /// <summary>
-        /// 渡されたテキストを解析し、言語間リンク・見出し等の変換を行う
+        /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する
         /// </summary>
-        /// <param name="text">記事テキスト。</param>
-        /// <param name="parent">元記事タイトル。</param>
-        /// <param name="headingEnable">見出しのチェックを行うか?</param>
-        /// <returns>変換後の記事テキスト。</returns>
-        protected string ReplaceText(string text, string parent, bool headingEnable)
+        /// <param name="link">変換元リンク。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換済みリンク。</returns>
+        protected virtual IElement ReplaceLink(MediaWikiLink link, string parent)
         {
-            // 指定された記事の言語間リンク・見出しを探索し、翻訳先言語での名称に変換し、それに置換した文字列を返す
-            StringBuilder b = new StringBuilder();
-            bool enterFlag = true;
-            MediaWikiPage wikiAP = new MediaWikiPage(this.From, "dummy", null);
-            for (int i = 0; i < text.Length; i++)
+            // 記事名が存在しないor自記事内の別セクションへのリンクの場合、記事名絡みの処理を飛ばす
+            if (!this.IsSectionLink(link, parent))
             {
-                // ユーザーからの中止要求をチェック
-                if (CancellationPending == true)
-                {
-                    break;
-                }
-
-                char c = text[i];
+                // 記事名の種類に応じて処理を実施
+                MediaWikiPage article = new MediaWikiPage(this.From, link.Title);
 
-                // 見出しも処理対象の場合
-                if (headingEnable)
+                if (link.IsSubpage)
                 {
-                    // 改行の場合、次のループで見出し行チェックを行う
-                    if (c == '\n')
-                    {
-                        enterFlag = true;
-                        b.Append(c);
-                        continue;
-                    }
-
-                    // 行の始めでは、その行が見出しの行かのチェックを行う
-                    if (enterFlag)
-                    {
-                        string newTitleLine;
-                        int index2 = this.ChkTitleLine(out newTitleLine, text, i);
-                        if (index2 != -1)
-                        {
-                            // 行の終わりまでインデックスを移動
-                            i = index2;
-
-                            // 置き換えられた見出し行を出力
-                            b.Append(newTitleLine);
-                            continue;
-                        }
-                        else
-                        {
-                            enterFlag = false;
-                        }
-                    }
+                    // サブページの場合、記事名を補完
+                    link.Title = parent + link.Title;
                 }
-
-                // コメント(<!--)のチェック
-                string comment;
-                int index = MediaWikiTranslator.ChkComment(out comment, text, i);
-                if (index != -1)
+                else if (!String.IsNullOrEmpty(link.Code))
                 {
-                    i = index;
-                    b.Append(comment);
-                    if (comment.Contains("\n") == true)
-                    {
-                        enterFlag = true;
-                    }
-
-                    continue;
+                    // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
+                    // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
+                    return this.ReplaceLinkInterwiki(link);
                 }
-
-                // nowikiのチェック
-                string nowiki;
-                index = MediaWikiTranslator.ChkNowiki(out nowiki, text, i);
-                if (index != -1)
+                else if (article.IsFile())
                 {
-                    i = index;
-                    b.Append(nowiki);
-                    continue;
+                    // 画像の場合、名前空間を翻訳先言語の書式に変換、パラメータ部を再帰的に処理
+                    return this.ReplaceLinkFile(link, parent);
                 }
-
-                // 変数({{{1}}}とか)のチェック
-                string variable;
-                string value;
-                index = wikiAP.ChkVariable(out variable, out value, text, i);
-                if (index != -1)
+                else if (article.IsCategory() && !link.IsColon)
                 {
-                    i = index;
-
-                    // 変数の | 以降に値が記述されている場合、それに対して再帰的に処理を行う
-                    int valueIndex = variable.IndexOf('|');
-                    if (valueIndex != -1 && !String.IsNullOrEmpty(value))
-                    {
-                        variable = variable.Substring(0, valueIndex + 1) + this.ReplaceText(value, parent) + "}}}";
-                    }
-
-                    b.Append(variable);
-                    continue;
+                    // カテゴリで記事へのリンクでない([[:Category:xxx]]みたいなリンクでない)場合、
+                    // カテゴリ用の変換を実施
+                    return this.ReplaceLinkCategory(link);
                 }
 
-                // 内部リンク・テンプレートのチェック&変換、言語間リンクを取得し出力する
-                string subtext;
-                index = this.ReplaceLink(out subtext, text, i, parent);
-                if (index != -1)
+                // 専用処理の無い内部リンクの場合、言語間リンクによる置き換えを行う
+                string interWiki = this.GetInterWiki(link.Title);
+                if (interWiki == null)
                 {
-                    i = index;
-                    b.Append(subtext);
-                    continue;
+                    // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
                 }
-
-                // 通常はそのままコピー
-                b.Append(text[i]);
-            }
-
-            return b.ToString();
-        }
-
-        /// <summary>
-        /// 渡されたテキストを解析し、言語間リンク・見出し等の変換を行う。
-        /// </summary>
-        /// <param name="text">記事テキスト。</param>
-        /// <param name="parent">元記事タイトル。</param>
-        /// <returns>変換後の記事テキスト。</returns>
-        protected string ReplaceText(string text, string parent)
-        {
-            return this.ReplaceText(text, parent, true);
-        }
-
-        /// <summary>
-        /// リンクの解析・置換を行う。
-        /// </summary>
-        /// <param name="link">解析したリンク。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <param name="parent">元記事タイトル。</param>
-        /// <returns>リンクの場合、終了位置のインデックスを返す。それ以外は-1。</returns>
-        protected int ReplaceLink(out string link, string text, int index, string parent)
-        {
-            // 出力値初期化
-            int lastIndex = -1;
-            link = String.Empty;
-            MediaWikiPage.Link l;
-
-            // 内部リンク・テンプレートの確認と解析
-            MediaWikiPage wikiAP = new MediaWikiPage(this.From, "dummy", null);
-            lastIndex = wikiAP.ChkLinkText(out l, text, index);
-            if (lastIndex != -1)
-            {
-                // 記事名に変数が使われている場合があるので、そのチェックと展開
-                int subindex = l.Title.IndexOf("{{{");
-                if (subindex != -1)
+                else if (interWiki == String.Empty)
                 {
-                    string variable;
-                    string value;
-                    int lastIndex2 = wikiAP.ChkVariable(out variable, out value, l.Title, subindex);
-                    if (lastIndex2 != -1 && !String.IsNullOrEmpty(value))
-                    {
-                        // 変数の | 以降に値が記述されている場合、それに置き換える
-                        string newArticle = l.Title.Substring(0, subindex) + value;
-                        if (lastIndex2 + 1 < l.Title.Length)
-                        {
-                            newArticle += l.Title.Substring(lastIndex2 + 1);
-                        }
-
-                        l.Title = newArticle;
-                    }
-                    else
+                    // 言語間リンクが存在しない場合、可能なら{{仮リンク}}に置き換え
+                    if (!String.IsNullOrEmpty(this.To.LinkInterwikiFormat))
                     {
-                        // 値が設定されていない場合、処理してもしょうがないので、除外
-                        System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceLink > 対象外 : " + l.OriginalText);
-                        return -1;
+                        return this.ReplaceLinkLinkInterwiki(link);
                     }
-                }
 
-                string newText = null;
-
-                // 内部リンクの場合
-                if (text[index] == '[')
-                {
-                    // 内部リンクの変換後文字列を取得
-                    newText = this.ReplaceInnerLink(l, parent);
+                    // 設定が無ければ [[:en:xxx]] みたいな形式に置換
+                    link.Title = this.From.Language.Code + ':' + link.Title;
+                    link.IsColon = true;
                 }
-                else if (text[index] == '{')
+                else if (link.IsSubpage)
                 {
-                    // テンプレートの場合
-                    // テンプレートの変換後文字列を取得
-                    newText = this.ReplaceTemplate(l, parent);
+                    // 言語間リンクが存在してサブページの場合、親ページ部分を消す
+                    link.Title = StringUtils.Substring(interWiki, interWiki.IndexOf('/'));
                 }
                 else
                 {
-                    // 上記以外の場合は、対象外
-                    System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceLink > プログラムミス : " + l.OriginalText);
+                    // 普通に言語間リンクが存在する場合、記事名を置き換え
+                    link.Title = interWiki;
                 }
 
-                // 変換後文字列がNULL以外
-                if (newText != null)
+                if (link.PipeTexts.Count == 0 && interWiki != null)
                 {
-                    link = newText;
-                }
-                else
-                {
-                    lastIndex = -1;
+                    // 表示名が存在しない場合、元の名前を表示名に設定
+                    // 元の名前にはあればセクションも含む
+                    link.PipeTexts.Add(
+                        new TextElement(new MediaWikiLink { Title = article.Title, Section = link.Section }
+                            .GetLinkString()));
                 }
             }
 
-            return lastIndex;
+            // セクション部分([[#関連項目]]とか)を変換
+            if (!String.IsNullOrEmpty(link.Section))
+            {
+                link.Section = this.ReplaceLinkSection(link.Section);
+            }
+
+            link.ParsedString = null;
+            return link;
         }
 
         /// <summary>
-        /// 内部リンクの文字列を変換する。
+        /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
         /// </summary>
-        /// <param name="link">変換元リンク文字列。</param>
-        /// <param name="parent">記事タイトル。</param>
-        /// <returns>å¤\89æ\8f\9bæ¸\88ã\81¿ã\83ªã\83³ã\82¯æ\96\87å­\97å\88\97。</returns>
-        protected string ReplaceInnerLink(MediaWikiPage.Link link, string parent)
+        /// <param name="template">変換元テンプレート。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>å¤\89æ\8f\9bæ¸\88ã\81¿ã\83\86ã\83³ã\83\97ã\83¬ã\83¼ã\83\88。</returns>
+        protected virtual IElement ReplaceTemplate(MediaWikiTemplate template, string parent)
         {
-            // 変数初期設定
-            StringBuilder b = new StringBuilder("[[");
-            string comment = String.Empty;
-            MediaWikiPage.Link l = link;
-
-            // 記事内を指している場合([[#関連項目]]だけとか)以外
-            if (!String.IsNullOrEmpty(l.Title) &&
-               !(l.Title == parent && String.IsNullOrEmpty(l.Code) && !String.IsNullOrEmpty(l.Section)))
+            // システム変数({{PAGENAME}}とか)の場合は対象外
+            if (this.From.IsMagicWord(template.Title))
             {
-                // 変換の対象外とするリンクかをチェック
-                MediaWikiPage article = new MediaWikiPage(this.From, l.Title);
-
-                // サブページの場合、記事名を補填
-                if (l.IsSubpage)
-                {
-                    l.Title = parent + l.Title;
-                }
-                else if (!String.IsNullOrEmpty(l.Code))
-                {
-                    // 言語間リンク・姉妹プロジェクトへのリンクは対象外
-                    // 先頭が : でない、翻訳先言語への言語間リンクの場合
-                    if (!l.IsColon && l.Code == this.To.Language.Code)
-                    {
-                        // 削除する。正常終了で、置換後文字列なしを返す
-                        System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > " + l.OriginalText + " を削除");
-                        return String.Empty;
-                    }
-
-                    // それ以外は対象外
-                    System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > 対象外 : " + l.OriginalText);
-                    return null;
-                }
-                else if (article.IsFile())
-                {
-                    // 画像も対象外だが、名前空間だけ翻訳先言語の書式に変換
-                    return this.ReplaceFileLink(l);
-                }
+                return template;
+            }
 
-                // リンクを辿り、対象記事の言語間リンクを取得
-                string interWiki = this.GetInterWiki(l.Title);
+            // テンプレートは通常名前空間が省略されているので補完する
+            string filledTitle = this.FillTemplateName(template, parent);
 
+            // リンクを辿り、対象記事の言語間リンクを取得
+            string interWiki = this.GetInterWiki(filledTitle, true);
+            if (interWiki == null)
+            {
                 // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
-                if (interWiki == null)
-                {
-                    b.Append(l.Title);
-                }
-                else if (interWiki == String.Empty)
-                {
-                    // 言語間リンクが存在しない場合、[[:en:xxx]]みたいな形式に置換
-                    b.Append(":");
-                    b.Append(this.From.Language.Code);
-                    b.Append(":");
-                    b.Append(l.Title);
-                }
-                else
-                {
-                    // 言語間リンクが存在する場合、そちらを指すように置換
-                    // 前の文字列を復元
-                    if (l.IsSubpage)
-                    {
-                        int index = interWiki.IndexOf('/');
-                        if (index == -1)
-                        {
-                            index = 0;
-                        }
-
-                        b.Append(interWiki.Substring(index));
-                    }
-                    else if (l.IsColon)
-                    {
-                        b.Append(":");
-                        b.Append(interWiki);
-                    }
-                    else
-                    {
-                        b.Append(interWiki);
-                    }
-                }
-
-                // カテゴリーの場合は、コメントで元の文字列を追加する
-                if (article.IsCategory() && !l.IsColon)
-                {
-                    comment = "<!-- " + l.OriginalText + " -->";
+                return template;
+            }
+            else if (interWiki == String.Empty)
+            {
+                // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
+                // おまけで、元のテンプレートの状態をコメントでつける
+                ListElement list = new ListElement();
+                MediaWikiLink link = new MediaWikiLink();
+                link.IsColon = true;
+                link.Title = this.From.Language.Code + ':' + filledTitle;
+                list.Add(link);
+                XmlCommentElement comment = new XmlCommentElement();
+                comment.Raw = ' ' + template.ToString() + ' ';
+                list.Add(comment);
+                return list;
+            }
+            else
+            {
+                // 言語間リンクが存在する場合、そちらを指すように置換
+                // : より前の部分を削除して出力(: が無いときは-1+1で0から)
+                template.Title = interWiki.Substring(interWiki.IndexOf(':') + 1);
 
-                    // カテゴリーで[[:en:xxx]]みたいな形式にした場合、| 以降は不要なので削除
-                    if (interWiki == String.Empty)
-                    {
-                        l.PipeTexts = new List<string>();
-                    }
-                }
-                else if (l.PipeTexts.Count == 0 && interWiki != null)
-                {
-                    // 表示名が存在しない場合、元の名前を表示名に設定
-                    l.PipeTexts.Add(article.Title);
-                }
+                // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
+                template.PipeTexts = this.ReplaceElements(template.PipeTexts, parent);
+                template.ParsedString = null;
+                return template;
             }
+        }
 
-            // 見出し([[#関連項目]]とか)を出力
-            if (!String.IsNullOrEmpty(l.Section))
+        /// <summary>
+        /// 指定された見出しに対して、対訳表による変換を行う。
+        /// </summary>
+        /// <param name="heading">見出し。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換後の見出し。</returns>
+        protected virtual IElement ReplaceHeading(MediaWikiHeading heading, string parent)
+        {
+            // 定型句変換
+            StringBuilder oldText = new StringBuilder();
+            foreach (IElement e in heading)
             {
-                // 見出しは、定型句変換を通す
-                string heading = this.GetHeading(l.Section);
-                b.Append("#");
-                b.Append(heading != null ? heading : l.Section);
+                oldText.Append(e.ToString());
             }
 
-            // 表示名を出力
-            foreach (string text in l.PipeTexts)
+            string oldHeading = heading.ToString();
+            string newText = this.GetHeading(oldText.ToString().Trim());
+            if (newText != null)
             {
-                b.Append("|");
-                if (!String.IsNullOrEmpty(text))
-                {
-                    // 画像の場合、| の後に内部リンクやテンプレートが書かれている場合があるが、
-                    // 画像は処理対象外でありその中のリンクは個別に再度処理されるため、ここでは特に何もしない
-                    b.Append(text);
-                }
+                // 対訳表による変換が行えた場合、そこで処理終了
+                heading.Clear();
+                heading.ParsedString = null;
+                heading.Add(new XmlTextElement(newText));
+                this.LogLine(ENTER + oldHeading + " " + Resources.RightArrow + " " + heading.ToString());
+                return heading;
             }
 
-            // リンクを閉じる
-            b.Append("]]");
+            // 対訳表に存在しない場合、内部要素を通常の変換で再帰的に処理
+            this.LogLine(ENTER + heading.ToString());
+            return this.ReplaceListElement(heading, parent);
+        }
 
-            // コメントを付加
-            if (comment != String.Empty)
+        /// <summary>
+        /// 変数要素を再帰的に解析し、変換先言語の記事への要素に変換する。
+        /// </summary>
+        /// <param name="variable">変換元変数要素。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換済み変数要素。</returns>
+        protected virtual IElement ReplaceVariable(MediaWikiVariable variable, string parent)
+        {
+            // 変数、これ自体は処理しないが、再帰的に探索
+            string old = variable.Value.ToString();
+            variable.Value = this.ReplaceElement(variable.Value, parent);
+            if (variable.Value.ToString() != old)
             {
-                b.Append(comment);
+                // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
+                variable.ParsedString = null;
             }
 
-            System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceInnerLink > " + l.OriginalText);
-            return b.ToString();
+            return variable;
         }
 
         /// <summary>
-        /// テンプレートの文字列を変換する。
+        /// 要素を再帰的に解析し、変換先言語の記事への要素に変換する。
         /// </summary>
-        /// <param name="link">変換元テンプレート文字列。</param>
-        /// <param name="parent">記事タイトル。</param>
-        /// <returns>変換済みテンプレート文字列。</returns>
-        protected string ReplaceTemplate(MediaWikiPage.Link link, string parent)
+        /// <param name="listElement">変換元要素。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換済み要素。</returns>
+        protected virtual IElement ReplaceListElement(ListElement listElement, string parent)
         {
-            // 変数初期設定
-            MediaWikiPage.Link l = link;
-
-            // テンプレートは記事名が必須
-            if (String.IsNullOrEmpty(l.Title))
+            // 値を格納する要素、これ自体は処理しないが、再帰的に探索
+            for (int i = 0; i < listElement.Count; i++)
             {
-                System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > 対象外 : " + l.OriginalText);
-                return null;
+                string old = listElement[i].ToString();
+                listElement[i] = this.ReplaceElement(listElement[i], parent);
+                if (listElement[i].ToString() != old)
+                {
+                    // 内部要素が変化した(置き換えが行われた)場合、変換前のテキストを破棄
+                    listElement.ParsedString = null;
+                }
             }
 
-            // システム変数の場合は対象外
-            if (this.From.IsMagicWord(l.Title))
+            return listElement;
+        }
+
+        #endregion
+
+        #region その他内部処理用メソッド
+
+        /// <summary>
+        /// 翻訳支援対象のページを取得。
+        /// </summary>
+        /// <param name="title">翻訳支援対象の記事名。</param>
+        /// <returns>取得したページ。取得失敗時は<c>null</c>。</returns>
+        protected MediaWikiPage GetTargetPage(string title)
+        {
+            // 指定された記事のXMLデータをWikipediaから取得
+            this.LogLine(String.Format(Resources.LogMessageGetTargetArticle, this.From.Location, title));
+            MediaWikiPage page = this.GetPage(title, Resources.RightArrow + " " + Resources.LogMessageTargetArticleNotFound);
+
+            // リダイレクトかをチェックし、リダイレクトであれば、その先の記事を取得
+            if (page != null && page.IsRedirect())
             {
-                System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > システム変数 : " + l.OriginalText);
-                return null;
+                this.LogLine(Resources.RightArrow + " " + Resources.LogMessageRedirect
+                    + " " + new MediaWikiLink(page.Redirect.Title).ToString());
+                page = this.GetPage(
+                    page.Redirect.Title,
+                    Resources.RightArrow + " " + Resources.LogMessageTargetArticleNotFound);
             }
 
-            // テンプレート名前空間か、普通の記事かを判定
-            if (!l.IsColon && !l.IsSubpage)
-            {
-                string prefix = null;
-                IList<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
-                if (prefixes != null && prefixes.Count > 0)
-                {
-                    prefix = prefixes[0];
-                }
+            return page;
+        }
 
-                if (!String.IsNullOrEmpty(prefix) && !l.Title.StartsWith(prefix + ":"))
-                {
-                    // 頭にTemplate:を付けた記事名でアクセスし、テンプレートが存在するかをチェック
-                    string title = prefix + ":" + l.Title;
-                    MediaWikiPage page = null;
-                    try
-                    {
-                        page = this.From.GetPage(title) as MediaWikiPage;
-                    }
-                    catch (WebException e)
-                    {
-                        if (e.Status == WebExceptionStatus.ProtocolError
-                            && (e.Response as HttpWebResponse).StatusCode != HttpStatusCode.NotFound)
-                        {
-                            // 記事が取得できない場合も、404でない場合は存在するとして処理
-                            this.LogLine(String.Format(Resources.LogMessage_TemplateUnknown, l.Title, prefix, e.Message));
-                            l.Title = title;
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.ReplaceTemplate > " + e.Message);
-                    }
+        /// <summary>
+        /// 同記事内の別のセクションを指すリンク([[#関連項目]]とか[[自記事#関連項目]]とか)か?
+        /// </summary>
+        /// <param name="link">判定する内部リンク。</param>
+        /// <param name="parent">内部リンクがあった記事。</param>
+        /// <returns>セクション部分のみ変換済みリンク。</returns>
+        private bool IsSectionLink(MediaWikiLink link, string parent)
+        {
+            // 記事名が指定されていない、または記事名が自分の記事名で
+            // 言語コード等も特に無く、かつセクションが指定されている場合
+            // (記事名もセクションも指定されていない・・・というケースもありえるが、
+            //   その場合他に指定できるものも思いつかないので通す)
+            return String.IsNullOrEmpty(link.Title)
+                || (link.Title == parent && String.IsNullOrEmpty(link.Code) && !String.IsNullOrEmpty(link.Section));
+        }
 
-                    if (page != null)
-                    {
-                        // 記事が存在する場合、テンプレートをつけた名前を使用
-                        l.Title = title;
-                    }
-                }
-            }
-            else if (l.IsSubpage)
+        /// <summary>
+        /// 内部リンクのセクション部分([[#関連項目]]とか)の定型句変換を行う。
+        /// </summary>
+        /// <param name="section">セクション文字列。</param>
+        /// <returns>セクション部分のみ変換済みリンク。</returns>
+        private string ReplaceLinkSection(string section)
+        {
+            // セクションが指定されている場合、定型句変換を通す
+            string heading = this.GetHeading(section);
+            return heading != null ? heading : section;
+        }
+
+        /// <summary>
+        /// 言語間リンク指定の内部リンクを解析し、不要であれば削除する。
+        /// </summary>
+        /// <param name="link">変換元言語間リンク。</param>
+        /// <returns>変換済み言語間リンク。</returns>
+        private IElement ReplaceLinkInterwiki(MediaWikiLink link)
+        {
+            // 言語間リンク・姉妹プロジェクトへのリンクの場合、変換対象外とする
+            // ただし、先頭が : でない、翻訳先言語への言語間リンクだけは削除
+            if (!link.IsColon && link.Code == this.To.Language.Code)
             {
-                // サブページの場合、記事名を補填
-                l.Title = parent + l.Title;
+                return new TextElement();
             }
 
-            // リンクを辿り、対象記事の言語間リンクを取得
-            string interWiki = this.GetInterWiki(l.Title, true);
+            return link;
+        }
 
-            // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
-            StringBuilder b = new StringBuilder();
+        /// <summary>
+        /// カテゴリ指定の内部リンクを解析し、変換先言語のカテゴリへのリンクに変換する。
+        /// </summary>
+        /// <param name="link">変換元カテゴリ。</param>
+        /// <returns>変換済みカテゴリ。</returns>
+        private IElement ReplaceLinkCategory(MediaWikiLink link)
+        {
+            // リンクを辿り、対象記事の言語間リンクを取得
+            string interWiki = this.GetInterWiki(link.Title);
             if (interWiki == null)
             {
-                b.Append(l.OriginalText);
+                // 記事自体が存在しない(赤リンク)場合、リンクはそのまま
+                return link;
             }
             else if (interWiki == String.Empty)
             {
-                // 言語間リンクが存在しない場合、[[:en:Template:xxx]]みたいな普通のリンクに置換
-                // おまけで、元のテンプレートの状態をコメントでつける
-                b.Append("[[:");
-                b.Append(this.From.Language.Code);
-                b.Append(":");
-                b.Append(l.Title);
-                b.Append("]]<!-- ");
-                b.Append(l.OriginalText);
-                b.Append(" -->");
+                // 言語間リンクが存在しない場合、コメントで元の文字列を保存した後
+                // [[:en:xxx]]みたいな形式に置換。また | 以降は削除する
+                XmlCommentElement comment = new XmlCommentElement();
+                comment.Raw = ' ' + link.ToString() + ' ';
+
+                link.Title = this.From.Language.Code + ':' + link.Title;
+                link.IsColon = true;
+                link.PipeTexts.Clear();
+                link.ParsedString = null;
+
+                ListElement list = new ListElement();
+                list.Add(link);
+                list.Add(comment);
+                return list;
             }
             else
             {
-                // 言語間リンクが存在する場合、そちらを指すように置換
-                b.Append("{{");
-
-                // 前の文字列を復元
-                if (l.IsColon)
-                {
-                    b.Append(":");
-                }
-
-                if (l.IsMsgnw)
-                {
-                    b.Append(MediaWikiPage.Msgnw);
-                }
-
-                // : より前の部分を削除して出力(: が無いときは-1+1で0から)
-                b.Append(interWiki.Substring(interWiki.IndexOf(':') + 1));
-
-                // 改行を復元
-                if (l.Enter)
-                {
-                    b.Append("\n");
-                }
+                // 普通に言語間リンクが存在する場合、記事名を置き換え
+                link.Title = interWiki;
+                link.ParsedString = null;
+                return link;
+            }
+        }
 
-                // | の後を付加
-                foreach (string text in l.PipeTexts)
-                {
-                    b.Append("|");
-                    if (!String.IsNullOrEmpty(text))
-                    {
-                        // | の後に内部リンクやテンプレートが書かれている場合があるので、再帰的に処理する
-                        b.Append(this.ReplaceText(text, parent));
-                    }
-                }
+        /// <summary>
+        /// ファイル指定の内部リンクを解析し、変換先言語で参照可能なファイルへのリンクに変換する。
+        /// </summary>
+        /// <param name="link">変換元リンク。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換済みリンク。</returns>
+        private IElement ReplaceLinkFile(MediaWikiLink link, string parent)
+        {
+            // 名前空間を翻訳先言語の書式に変換、またパラメータ部を再帰的に処理
+            link.Title = this.ReplaceLinkNamespace(link.Title, this.To.FileNamespace);
+            link.PipeTexts = this.ReplaceElements(link.PipeTexts, parent);
+            link.ParsedString = null;
+            return link;
+        }
 
-                // リンクを閉じる
-                b.Append("}}");
+        /// <summary>
+        /// 記事名のうち名前空間部分の変換先言語への変換を行う。
+        /// </summary>
+        /// <param name="title">変換元記事名。</param>
+        /// <param name="id">名前空間のID。</param>
+        /// <returns>変換済み記事名。</returns>
+        private string ReplaceLinkNamespace(string title, int id)
+        {
+            // 名前空間だけ翻訳先言語の書式に変換
+            IList<string> names;
+            if (!this.To.Namespaces.TryGetValue(id, out names))
+            {
+                // 翻訳先言語に相当する名前空間が無い場合、何もしない
+                return title;
             }
 
-            System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.replaceTemplate > " + l.OriginalText);
-            return b.ToString();
+            // 記事名の名前空間部分を置き換えて返す
+            return names[0] + title.Substring(title.IndexOf(':'));
         }
 
         /// <summary>
-        /// 指定されたインデックスの位置に存在する見出し(==関連項目==みたいなの)を解析し、可能であれば変換して返す
+        /// 内部リンクを他言語版への{{仮リンク}}等に変換する。
         /// </summary>
-        /// <param name="heading">変換後の見出し。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <returns>見出しの場合、見出し終了位置のインデックスを返す。それ以外は-1。</returns>
-        protected virtual int ChkTitleLine(out string heading, string text, int index)
+        /// <param name="link">変換元言語間リンク。</param>
+        /// <returns>変換済み言語間リンク。</returns>
+        private IElement ReplaceLinkLinkInterwiki(MediaWikiLink link)
         {
-            // 初期化
-            // ※見出しではない、構文がおかしいなどの場合、-1を返す
-            int lastIndex = -1;
+            // 仮リンクにはセクションの指定が可能なので、存在する場合付加する
+            // ※ 渡されたlinkをそのまま使わないのは、余計なゴミが含まれる可能性があるため
+            MediaWikiLink title = new MediaWikiLink { Title = link.Title, Section = link.Section };
+            string langTitle = title.GetLinkString();
+            if (!String.IsNullOrEmpty(title.Section))
+            {
+                // 変換先言語版のセクションは、セクションの変換を通したものにする
+                title.Section = this.ReplaceLinkSection(title.Section);
+            }
             
-            // 構文を解析して、1行の文字列と、=の個数を取得
-            // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
-            // ※Wikipediaでは <!--test-.=<!--test-.=関連項目<!--test-.==<!--test-. みたいなのでも
-            //   正常に認識するので、できるだけ対応する
-            // ※変換が正常に行われた場合、コメントは削除される
-            bool startFlag = true;
-            int startSignCounter = 0;
-            string nonCommentLine = String.Empty;
-            StringBuilder b = new StringBuilder();
-            for (lastIndex = index; lastIndex < text.Length; lastIndex++)
+            // 表示名は、設定されていればその値を、なければ変換元言語の記事名を使用
+            string label = langTitle;
+            if (link.PipeTexts.Count > 0)
             {
-                char c = text[lastIndex];
-
-                // 改行まで
-                if (c == '\n')
-                {
-                    break;
-                }
+                label = link.PipeTexts.Last().ToString();
+            }
 
-                // コメントは無視する
-                string comment;
-                int subindex = MediaWikiTranslator.ChkComment(out comment, text, lastIndex);
-                if (subindex != -1)
-                {
-                    b.Append(comment);
-                    lastIndex = subindex;
-                    continue;
-                }
-                else if (startFlag)
-                {
-                    // 先頭部の場合、=の数を数える
-                    if (c == '=')
-                    {
-                        ++startSignCounter;
-                    }
-                    else
-                    {
-                        startFlag = false;
-                    }
-                }
+            // 書式化した文字列を返す
+            // ※ {{仮リンク}}を想定しているが、やろうと思えば何でもできるのでテキストで処理
+            return new TextElement(this.To.FormatLinkInterwiki(title.GetLinkString(), this.From.Language.Code, langTitle, label));
+        }
 
-                nonCommentLine += c;
-                b.Append(c);
+        /// <summary>
+        /// 渡された要素リストに対して<see cref="ReplaceElement"/>による変換を行う。
+        /// </summary>
+        /// <param name="elements">変換元要素リスト。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>変換済み要素リスト。</returns>
+        private IList<IElement> ReplaceElements(IList<IElement> elements, string parent)
+        {
+            if (elements == null)
+            {
+                return null;
             }
 
-            heading = b.ToString();
+            IList<IElement> result = new List<IElement>();
+            foreach (IElement e in elements)
+            {
+                result.Add(this.ReplaceElement(e, parent));
+            }
 
-            // 改行文字、または文章の最後+1になっているはずなので、1文字戻す
-            --lastIndex;
+            return result;
+        }
 
-            // = で始まる行ではない場合、処理対象外
-            if (startSignCounter < 1)
+        /// <summary>
+        /// テンプレート名に必要に応じて名前空間を補完する。
+        /// </summary>
+        /// <param name="template">テンプレート。</param>
+        /// <param name="parent">サブページ用の親記事タイトル。</param>
+        /// <returns>補完済みのテンプレート名。</returns>
+        private string FillTemplateName(MediaWikiTemplate template, string parent)
+        {
+            if (template.IsColon || !new MediaWikiPage(this.From, template.Title).IsMain())
             {
-                heading = String.Empty;
-                return -1;
+                // 標準名前空間が指定されている(先頭にコロンが無い)
+                // または何かしらの名前空間が指定されている場合、補完不要
+                return template.Title;
             }
-
-            // 終わりの = の数を確認
-            // ※↓の処理だと中身の無い行(====とか)は弾かれてしまうが、どうせ処理できないので許容する
-            int endSignCounter = 0;
-            for (int i = nonCommentLine.Length - 1; i >= startSignCounter; i--)
+            else if (template.IsSubpage)
             {
-                if (nonCommentLine[i] == '=')
-                {
-                    ++endSignCounter;
-                }
-                else
-                {
-                    break;
-                }
+                // サブページの場合、親記事名での補完のみ
+                return parent + template.Title;
             }
 
-            // = で終わる行ではない場合、処理対象外
-            if (endSignCounter < 1)
+            // 補完する必要がある場合、名前空間のプレフィックス(Template等)を取得
+            string prefix = this.GetTemplatePrefix();
+            if (String.IsNullOrEmpty(prefix))
             {
-                heading = String.Empty;
-                return -1;
+                // 名前空間の設定が存在しない場合、何も出来ないため終了
+                return template.Title;
             }
 
-            // 始まりと終わり、=の少ないほうにあわせる(==test===とか用の処理)
-            int signCounter = startSignCounter;
-            if (startSignCounter > endSignCounter)
+            // 頭にプレフィックスを付けた記事名で実在するかをチェック
+            string filledTitle = prefix + ":" + template.Title;
+
+            // 既に対訳表にプレフィックス付きの記事名が確認されているか?
+            // ※ 対訳表へのキーとしてはHTMLデコードした記事名を使用する
+            if (this.ItemTable != null && this.ItemTable.ContainsKey(WebUtility.HtmlDecode(filledTitle)))
             {
-                signCounter = endSignCounter;
+                // 記事が存在する場合、プレフィックスをつけた名前を使用
+                return filledTitle;
             }
 
-            // 定型句変換
-            string oldText = nonCommentLine.Substring(signCounter, nonCommentLine.Length - (signCounter * 2)).Trim();
-            string newText = this.GetHeading(oldText);
-            if (newText != null)
+            // 未確認の場合、実際に頭にプレフィックスを付けた記事名でアクセスし、存在するかをチェック
+            // TODO: GetInterWikiの方とあわせ、テンプレートでは2度GetPageが呼ばれている。可能であれば共通化する
+            MediaWikiPage page = null;
+            try
+            {
+                page = this.From.GetPage(filledTitle) as MediaWikiPage;
+            }
+            catch (WebException e)
             {
-                string sign = "=";
-                for (int i = 1; i < signCounter; i++)
+                if (e.Status == WebExceptionStatus.ProtocolError
+                    && (e.Response as HttpWebResponse).StatusCode != HttpStatusCode.NotFound)
                 {
-                    sign += "=";
+                    // 記事が取得できない場合も、404でない場合は存在するものとして処理
+                    this.LogLine(String.Format(Resources.LogMessageTemplateNameUnidentified, template.Title, prefix, e.Message));
+                    return filledTitle;
                 }
+            }
+            catch (Exception e)
+            {
+                // それ以外のエラー(Webではなくfileでのエラーとか)は存在しないものと扱う
+                System.Diagnostics.Debug.WriteLine("MediaWikiTranslator.FillTemplateName > " + e.Message);
+            }
 
-                string newHeading = sign + newText + sign;
-                this.LogLine(ENTER + heading + " " + Resources.RightArrow + " " + newHeading);
-                heading = newHeading;
+            if (page != null)
+            {
+                // 記事が存在する場合、プレフィックスをつけた名前を使用
+                return filledTitle;
             }
-            else
+
+            return template.Title;
+        }
+
+        /// <summary>
+        /// テンプレート名前空間のプレフィックスを取得。
+        /// </summary>
+        /// <returns>プレフィックス。取得できない場合<c>null</c></returns>
+        private string GetTemplatePrefix()
+        {
+            IList<string> prefixes = this.From.Namespaces[this.From.TemplateNamespace];
+            if (prefixes != null)
             {
-                this.LogLine(ENTER + heading);
+                return prefixes.FirstOrDefault();
             }
 
-            return lastIndex;
+            return null;
         }
 
         /// <summary>
@@ -1006,7 +896,7 @@ namespace Honememo.Wptscs.Logics
         /// </summary>
         /// <param name="heading">翻訳元言語での見出し。</param>
         /// <returns>翻訳先言語での見出し。値が存在しない場合は<c>null</c>。</returns>
-        protected string GetHeading(string heading)
+        private string GetHeading(string heading)
         {
             return this.HeadingTable.GetWord(heading);
         }
@@ -1017,7 +907,7 @@ namespace Honememo.Wptscs.Logics
         /// <param name="site">サイト。</param>
         /// <param name="code">言語のコード。</param>
         /// <returns>ページ名|略称形式の言語名称。</returns>
-        protected string GetFullName(Website site, string code)
+        private string GetFullName(Website site, string code)
         {
             if (site.Language.Names.ContainsKey(code))
             {
@@ -1035,26 +925,6 @@ namespace Honememo.Wptscs.Logics
             return String.Empty;
         }
 
-        /// <summary>
-        /// 画像などのファイルへの内部リンクの置き換えを行う。
-        /// </summary>
-        /// <param name="link">内部リンク。</param>
-        /// <returns>置き換え後のリンク文字列、置き換えを行わない場合<c>null</c>。</returns>
-        private string ReplaceFileLink(MediaWikiPage.Link link)
-        {
-            // 名前空間だけ翻訳先言語の書式に変換
-            IList<string> names;
-            if (!this.To.Namespaces.TryGetValue(this.To.FileNamespace, out names))
-            {
-                // 翻訳先言語に相当する名前空間が無い場合、何もしない
-                return null;
-            }
-
-            // 記事名の名前空間部分を置き換えて返す
-            link.Title = names[0] + link.Title.Substring(link.Title.IndexOf(':'));
-            return link.Text;
-        }
-
         #endregion
     }
 }
index d1a2a64..d6ee4dc 100644 (file)
@@ -3,7 +3,7 @@
 //      翻訳支援処理を実装するための共通クラスソース</summary>
 //
 // <copyright file="Translator.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -11,6 +11,7 @@
 namespace Honememo.Wptscs.Logics
 {
     using System;
+    using System.Diagnostics;
     using System.IO;
     using System.Net;
     using System.Net.NetworkInformation;
@@ -18,6 +19,7 @@ namespace Honememo.Wptscs.Logics
     using Honememo.Utilities;
     using Honememo.Wptscs.Models;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// 翻訳支援処理を実装するための共通クラスです。
@@ -34,15 +36,41 @@ namespace Honememo.Wptscs.Logics
         /// <summary>
         /// ログメッセージ。
         /// </summary>
-        private string log;
+        private string log = String.Empty;
+
+        /// <summary>
+        /// 処理状態メッセージ。
+        /// </summary>
+        private string status = String.Empty;
 
         /// <summary>
         /// 変換後テキスト。
         /// </summary>
-        private string text;
+        private string text = String.Empty;
 
         #endregion
-        
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// インスタンスを生成する。
+        /// </summary>
+        public Translator()
+        {
+            this.Stopwatch = new Stopwatch();
+        }
+
+        #endregion
+
+        #region デリゲート
+
+        /// <summary>
+        /// <see cref="ChangeStatusInExecuting"/> で実行する処理のためのデリゲート。
+        /// </summary>
+        protected delegate void MethodWithChangeStatus();
+
+        #endregion
+
         #region イベント
 
         /// <summary>
@@ -50,6 +78,11 @@ namespace Honememo.Wptscs.Logics
         /// </summary>
         public event EventHandler LogUpdate;
 
+        /// <summary>
+        /// 処理状態更新伝達イベント。
+        /// </summary>
+        public event EventHandler StatusUpdate;
+
         #endregion
 
         #region プロパティ
@@ -87,7 +120,7 @@ namespace Honememo.Wptscs.Logics
 
             protected set
             {
-                this.log = (value != null) ? value : String.Empty;
+                this.log = StringUtils.DefaultString(value);
                 if (this.LogUpdate != null)
                 {
                     this.LogUpdate(this, EventArgs.Empty);
@@ -96,6 +129,35 @@ namespace Honememo.Wptscs.Logics
         }
 
         /// <summary>
+        /// 処理状態メッセージ。
+        /// </summary>
+        public string Status
+        {
+            get
+            {
+                return this.status;
+            }
+
+            protected set
+            {
+                this.status = StringUtils.DefaultString(value);
+                if (this.StatusUpdate != null)
+                {
+                    this.StatusUpdate(this, EventArgs.Empty);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 処理時間ストップウォッチ。
+        /// </summary>
+        public Stopwatch Stopwatch
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
         /// 変換後テキスト。
         /// </summary>
         public string Text
@@ -188,8 +250,9 @@ namespace Honememo.Wptscs.Logics
         /// 翻訳支援処理実行。
         /// </summary>
         /// <param name="name">記事名。</param>
-        /// <returns><c>true</c> 処理成功</returns>
-        public virtual bool Run(string name)
+        /// <exception cref="ApplicationException">処理が中断された場合。中断の理由は<see cref="Log"/>に出力される。</exception>
+        /// <exception cref="InvalidOperationException"><see cref="From"/>, <see cref="To"/>が設定されていない場合。</exception>
+        public virtual void Run(string name)
         {
             // ※必須な情報が設定されていない場合、InvalidOperationExceptionを返す
             if (this.From == null || this.To == null)
@@ -197,8 +260,9 @@ namespace Honememo.Wptscs.Logics
                 throw new InvalidOperationException("From or To is null");
             }
 
-            // 変数を初期化
+            // 変数を初期化、処理時間を測定開始
             this.Initialize();
+            this.Stopwatch.Start();
 
             // サーバー接続チェック
             string host = new Uri(this.From.Location).Host;
@@ -206,13 +270,25 @@ namespace Honememo.Wptscs.Logics
             {
                 if (!this.Ping(host))
                 {
-                    return false;
+                    throw new ApplicationException("ping failed");
                 }
             }
 
+            // ここまでの間に終了条件が出ているかを確認
+            this.ThrowExceptionIfCanceled();
+
             // 翻訳支援処理実行部の本体を実行
             // ※以降の処理は、継承クラスにて定義
-            return this.RunBody(name);
+            try
+            {
+                this.RunBody(name);
+            }
+            finally
+            {
+                // 終了後は処理状態をクリア、処理時間を測定終了
+                this.Status = String.Empty;
+                this.Stopwatch.Stop();
+            }
         }
         
         #endregion
@@ -223,9 +299,9 @@ namespace Honememo.Wptscs.Logics
         /// 翻訳支援処理実行部の本体。
         /// </summary>
         /// <param name="name">記事名。</param>
-        /// <returns><c>true</c> 処理成功</returns>
+        /// <exception cref="ApplicationException">処理を中断する場合。中断の理由は<see cref="Log"/>に出力する。</exception>
         /// <remarks>テンプレートメソッド的な構造になっています。</remarks>
-        protected abstract bool RunBody(string name);
+        protected abstract void RunBody(string name);
 
         /// <summary>
         /// ログメッセージを1行追加出力。
@@ -261,46 +337,52 @@ namespace Honememo.Wptscs.Logics
         /// <param name="title">ページタイトル。</param>
         /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
         /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
-        /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
+        /// <remarks>
+        /// 通信エラーなど例外が発生した場合は、別途エラーログを出力する。
+        /// また、実行中は処理状態をサーバー接続中に更新する。
+        /// </remarks>
         protected Page GetPage(string title, string notFoundMsg)
         {
-            try
-            {
-                // 取得できた場合はここで終了
-                return this.From.GetPage(title);
-            }
-            catch (WebException e)
+            // ページ取得処理、実行中は処理状態を変更
+            Page result = null;
+            this.ChangeStatusInExecuting(
+                () => result = this.GetPageAtNotChangeStatus(title, notFoundMsg),
+                Resources.StatusDownloading);
+            return result;
+        }
+
+        /// <summary>
+        /// 終了要求が出ている場合、例外を投げる。
+        /// </summary>
+        /// <exception cref="ApplicationException"><see cref="CancellationPending"/>が<c>true</c>の場合。</exception>
+        protected void ThrowExceptionIfCanceled()
+        {
+            if (this.CancellationPending)
             {
-                // 通信エラー
-                if (e.Status == WebExceptionStatus.ProtocolError
-                    && (e.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotFound)
-                {
-                    // 404
-                    this.Log += notFoundMsg;
-                }
-                else
-                {
-                    // それ以外のエラー
-                    this.LogLine(Resources.RightArrow + " " + e.Message);
-                    if (e.Response != null)
-                    {
-                        this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessage_ErrorURL, e.Response.ResponseUri));
-                    }
-                }
+                throw new ApplicationException("CancellationPending is true");
             }
-            catch (FileNotFoundException)
+        }
+
+        /// <summary>
+        /// 指定された処理を実行する間、処理状態を渡された値に更新する。
+        /// 処理終了後は以前の処理状態に戻す。
+        /// </summary>
+        /// <param name="method">実行する処理。</param>
+        /// <param name="status">処理状態。</param>
+        protected void ChangeStatusInExecuting(MethodWithChangeStatus method, string status)
+        {
+            // 現在の処理状態を保存、新しい処理状態をセットし、処理を実行する
+            string oldStatus = this.Status;
+            this.Status = status;
+            try
             {
-                // ファイル無し
-                this.Log += notFoundMsg;
+                method();
             }
-            catch (Exception e)
+            finally
             {
-                // その他の想定外のエラー
-                this.LogLine(Resources.RightArrow + " " + e.Message);
+                // 処理状態を以前の状態に戻す
+                this.Status = oldStatus;
             }
-
-            // 取得失敗時いずれの場合もnull
-            return null;
         }
 
         #endregion
@@ -313,7 +395,9 @@ namespace Honememo.Wptscs.Logics
         private void Initialize()
         {
             // 変数を初期化
-            this.log = String.Empty;
+            this.Log = String.Empty;
+            this.Status = String.Empty;
+            this.Stopwatch.Reset();
             this.Text = String.Empty;
             this.CancellationPending = false;
         }
@@ -323,8 +407,24 @@ namespace Honememo.Wptscs.Logics
         /// </summary>
         /// <param name="server">サーバー名。</param>
         /// <returns><c>true</c> 接続成功。</returns>
+        /// <remarks>実行中は処理状態をサーバー接続中に更新する。</remarks>
         private bool Ping(string server)
         {
+            // サーバー接続チェック、実行中は処理状態を変更
+            bool result = false;
+            this.ChangeStatusInExecuting(
+                () => result = this.PingAtNotChangeStatus(server),
+                Resources.StatusPinging);
+            return result;
+        }
+
+        /// <summary>
+        /// サーバー接続チェック(処理状態更新無し)。
+        /// </summary>
+        /// <param name="server">サーバー名。</param>
+        /// <returns><c>true</c> 接続成功。</returns>
+        private bool PingAtNotChangeStatus(string server)
+        {
             // サーバー接続チェック
             Ping ping = new Ping();
             try
@@ -345,6 +445,54 @@ namespace Honememo.Wptscs.Logics
             return true;
         }
 
+        /// <summary>
+        /// ログメッセージを出力しつつページを取得(処理状態更新無し)。
+        /// </summary>
+        /// <param name="title">ページタイトル。</param>
+        /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
+        /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
+        /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
+        private Page GetPageAtNotChangeStatus(string title, string notFoundMsg)
+        {
+            try
+            {
+                // 取得できた場合はここで終了
+                return this.From.GetPage(title);
+            }
+            catch (WebException e)
+            {
+                // 通信エラー
+                if (e.Status == WebExceptionStatus.ProtocolError
+                    && (e.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotFound)
+                {
+                    // 404
+                    this.Log += notFoundMsg;
+                }
+                else
+                {
+                    // それ以外のエラー
+                    this.LogLine(Resources.RightArrow + " " + e.Message);
+                    if (e.Response != null)
+                    {
+                        this.LogLine(Resources.RightArrow + " " + String.Format(Resources.LogMessageErrorURL, e.Response.ResponseUri));
+                    }
+                }
+            }
+            catch (FileNotFoundException)
+            {
+                // ファイル無し
+                this.Log += notFoundMsg;
+            }
+            catch (Exception e)
+            {
+                // その他の想定外のエラー
+                this.LogLine(Resources.RightArrow + " " + e.Message);
+            }
+
+            // 取得失敗時いずれの場合もnull
+            return null;
+        }
+
         #endregion
     }
 }
index effd1ce..936dae9 100644 (file)
@@ -63,9 +63,14 @@ namespace Honememo.Wptscs
             this.folderBrowserDialogSaveDirectory = new System.Windows.Forms.FolderBrowserDialog();
             this.backgroundWorkerRun = new System.ComponentModel.BackgroundWorker();
             this.toolTip = new System.Windows.Forms.ToolTip(this.components);
+            this.statusStrip = new System.Windows.Forms.StatusStrip();
+            this.toolStripStatusLabelStatus = new System.Windows.Forms.ToolStripStatusLabel();
+            this.toolStripStatusLabelStopwatch = new System.Windows.Forms.ToolStripStatusLabel();
+            this.timerStatusStopwatch = new System.Windows.Forms.Timer(this.components);
             this.groupBoxTransfer.SuspendLayout();
             this.groupBoxSaveDirectory.SuspendLayout();
             this.groupBoxRun.SuspendLayout();
+            this.statusStrip.SuspendLayout();
             this.SuspendLayout();
             // 
             // groupBoxTransfer
@@ -228,10 +233,35 @@ namespace Honememo.Wptscs
             this.toolTip.InitialDelay = 500;
             this.toolTip.ReshowDelay = 100;
             // 
+            // statusStrip
+            // 
+            this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.toolStripStatusLabelStatus,
+            this.toolStripStatusLabelStopwatch});
+            resources.ApplyResources(this.statusStrip, "statusStrip");
+            this.statusStrip.Name = "statusStrip";
+            // 
+            // toolStripStatusLabelStatus
+            // 
+            this.toolStripStatusLabelStatus.Name = "toolStripStatusLabelStatus";
+            resources.ApplyResources(this.toolStripStatusLabelStatus, "toolStripStatusLabelStatus");
+            this.toolStripStatusLabelStatus.Spring = true;
+            // 
+            // toolStripStatusLabelStopwatch
+            // 
+            this.toolStripStatusLabelStopwatch.Name = "toolStripStatusLabelStopwatch";
+            resources.ApplyResources(this.toolStripStatusLabelStopwatch, "toolStripStatusLabelStopwatch");
+            // 
+            // timerStatusStopwatch
+            // 
+            this.timerStatusStopwatch.Interval = 1000;
+            this.timerStatusStopwatch.Tick += new System.EventHandler(this.TimerStatusStopwatch_Tick);
+            // 
             // MainForm
             // 
             resources.ApplyResources(this, "$this");
             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.Controls.Add(this.statusStrip);
             this.Controls.Add(this.groupBoxRun);
             this.Controls.Add(this.groupBoxSaveDirectory);
             this.Controls.Add(this.groupBoxTransfer);
@@ -244,7 +274,10 @@ namespace Honememo.Wptscs
             this.groupBoxSaveDirectory.PerformLayout();
             this.groupBoxRun.ResumeLayout(false);
             this.groupBoxRun.PerformLayout();
+            this.statusStrip.ResumeLayout(false);
+            this.statusStrip.PerformLayout();
             this.ResumeLayout(false);
+            this.PerformLayout();
 
         }
 
@@ -270,6 +303,10 @@ namespace Honememo.Wptscs
         private System.Windows.Forms.FolderBrowserDialog folderBrowserDialogSaveDirectory;
         private System.ComponentModel.BackgroundWorker backgroundWorkerRun;
         private System.Windows.Forms.ToolTip toolTip;
+        private System.Windows.Forms.StatusStrip statusStrip;
+        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelStatus;
+        private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabelStopwatch;
+        private System.Windows.Forms.Timer timerStatusStopwatch;
     }
 }
 
index 07c6c2e..8662a13 100644 (file)
@@ -3,7 +3,7 @@
 //      Wikipedia翻訳支援ツール主画面クラスソース</summary>
 //
 // <copyright file="MainForm.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -22,6 +22,8 @@ namespace Honememo.Wptscs
     using Honememo.Wptscs.Logics;
     using Honememo.Wptscs.Models;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// Wikipedia翻訳支援ツール主画面のクラスです。
@@ -38,12 +40,12 @@ namespace Honememo.Wptscs
         /// <summary>
         /// 検索支援処理クラスのオブジェクト。
         /// </summary>
-        private Translator translate;
+        private Translator translator;
 
         /// <summary>
         /// 表示済みログ文字列長。
         /// </summary>
-        private int logLastLength;
+        private int logLength;
 
         #endregion
 
@@ -76,7 +78,7 @@ namespace Honememo.Wptscs
                 this.Close();
             }
 
-            this.translate = null;
+            this.translator = null;
             Control.CheckForIllegalCrossThreadCalls = false;
 
             // コンボボックス設定
@@ -320,9 +322,9 @@ namespace Honememo.Wptscs
             {
                 System.Diagnostics.Debug.WriteLine("MainForm.-Stop_Click > 処理中断");
                 this.backgroundWorkerRun.CancelAsync();
-                if (this.translate != null)
+                if (this.translator != null)
                 {
-                    this.translate.CancellationPending = true;
+                    this.translator.CancellationPending = true;
                 }
             }
         }
@@ -336,98 +338,54 @@ namespace Honememo.Wptscs
         {
             try
             {
-                // 翻訳支援処理の前処理
+                // 初期化と開始メッセージ
                 this.textBoxLog.Clear();
-                this.logLastLength = 0;
-                this.textBoxLog.AppendText(
-                    String.Format(
-                        Resources.LogMessage_Start,
-                        FormUtils.ApplicationName(),
-                        DateTime.Now.ToString("F")));
-
-                // 処理結果とログのための出力ファイル名を作成
-                string fileName;
-                string logName;
-                this.MakeFileName(out fileName, out logName, this.textBoxArticle.Text.Trim(), this.textBoxSaveDirectory.Text);
+                this.logLength = 0;
+                this.textBoxLog.AppendText(String.Format(Resources.LogMessageStart, FormUtils.ApplicationName(), DateTime.Now.ToString("F")));
 
-                // ç¿»è¨³æ\94¯æ\8f´å\87¦ç\90\86ã\82\92å®\9fè¡\8cã\81\97ã\80\81çµ\90æ\9e\9cã\81¨ã\83­ã\82°ã\82\92ã\83\95ã\82¡ã\82¤ã\83«ã\81«å\87ºå\8a\9b
+                // ç¿»è¨³æ\94¯æ\8f´å\87¦ç\90\86ã\83­ã\82¸ã\83\83ã\82¯ã\81®ã\82ªã\83\96ã\82¸ã\82§ã\82¯ã\83\88ã\82\92ç\94\9fæ\88\90
                 try
                 {
-                    this.translate = Translator.Create(this.config, this.comboBoxSource.Text, this.comboBoxTarget.Text);
+                    this.translator = Translator.Create(this.config, this.comboBoxSource.Text, this.comboBoxTarget.Text);
                 }
                 catch (NotImplementedException)
                 {
-                    // 将来の拡張用
-                    this.textBoxLog.AppendText(String.Format(Resources.InformationMessage_DevelopingMethod, "Wikipedia以外の処理"));
-                    FormUtils.InformationDialog(Resources.InformationMessage_DevelopingMethod, "Wikipedia以外の処理");
+                    // 設定ファイルに対応していないパターンが書かれている場合の例外、将来の拡張用
+                    this.textBoxLog.AppendText(String.Format(Resources.InformationMessageDevelopingMethod, "Wikipedia以外の処理"));
+                    FormUtils.InformationDialog(Resources.InformationMessageDevelopingMethod, "Wikipedia以外の処理");
                     return;
                 }
 
-                this.translate.LogUpdate += new EventHandler(this.GetLogUpdate);
+                // ログ・処理状態更新通知を受け取るためのイベント登録
+                // 処理時間更新用にタイマーを起動
+                this.translator.LogUpdate += new EventHandler(this.GetLogUpdate);
+                this.translator.StatusUpdate += new EventHandler(this.GetStatusUpdate);
+                this.Invoke((MethodInvoker)delegate { this.timerStatusStopwatch.Start(); });
 
-                // 実行前に、ユーザーから中止要求がされているかをチェック
-                if (this.backgroundWorkerRun.CancellationPending)
+                // 翻訳支援処理を実行
+                bool success = true;
+                try
                 {
-                    this.textBoxLog.AppendText(String.Format(Resources.LogMessage_Stop, logName));
+                    this.translator.Run(this.textBoxArticle.Text.Trim());
                 }
-                else
+                catch (ApplicationException)
                 {
-                    // 翻訳支援処理を実行
-                    bool successFlag = this.translate.Run(this.textBoxArticle.Text.Trim());
-
-                    // 処理に時間がかかるため、出力ファイル名を再確認
-                    this.MakeFileName(out fileName, out logName, this.textBoxArticle.Text.Trim(), this.textBoxSaveDirectory.Text);
-                    if (successFlag)
-                    {
-                        // 処理結果を出力
-                        try
-                        {
-                            StreamWriter sw = new StreamWriter(Path.Combine(this.textBoxSaveDirectory.Text, fileName));
-                            try
-                            {
-                                sw.Write(this.translate.Text);
-                                this.textBoxLog.AppendText(String.Format(Resources.LogMessage_End, fileName, logName));
-                            }
-                            finally
-                            {
-                                sw.Close();
-                            }
-                        }
-                        catch (Exception ex)
-                        {
-                            this.textBoxLog.AppendText(String.Format(Resources.LogMessage_ErrorFileSave, Path.Combine(this.textBoxSaveDirectory.Text, fileName), ex.Message));
-                            this.textBoxLog.AppendText(String.Format(Resources.LogMessage_Stop, logName));
-                        }
-                    }
-                    else
-                    {
-                        this.textBoxLog.AppendText(String.Format(Resources.LogMessage_Stop, logName));
-                    }
+                    // 中止要求で停止した場合、その旨イベントに格納する
+                    e.Cancel = this.backgroundWorkerRun.CancellationPending;
+                    success = false;
                 }
-
-                // ログを出力
-                try
+                finally
                 {
-                    StreamWriter sw = new StreamWriter(Path.Combine(this.textBoxSaveDirectory.Text, logName));
-                    try
-                    {
-                        sw.Write(this.textBoxLog.Text);
-                    }
-                    finally
-                    {
-                        sw.Close();
-                    }
-                }
-                catch (Exception ex)
-                {
-                    this.textBoxLog.AppendText(String.Format(Resources.LogMessage_ErrorFileSave, Path.Combine(this.textBoxSaveDirectory.Text, logName), ex.Message));
+                    // 処理時間更新用のタイマーを終了
+                    this.Invoke((MethodInvoker)delegate { this.timerStatusStopwatch.Stop(); });
                 }
+
+                // 実行結果から、ログと変換後テキストをファイル出力
+                this.WriteResult(success);
             }
             catch (Exception ex)
             {
                 this.textBoxLog.AppendText("\r\n" + String.Format(Resources.ErrorMessageDevelopmentError, ex.Message, ex.StackTrace) + "\r\n");
-                System.Diagnostics.Debug.WriteLine("MainForm.backgroundWorkerRun_DoWork > 想定外のエラー : " + ex.Message);
-                System.Diagnostics.Debug.WriteLine(ex.StackTrace);
             }
         }
 
@@ -439,9 +397,18 @@ namespace Honememo.Wptscs
         private void BackgroundWorkerRun_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
         {
             // 設定ファイルのキャッシュ情報を更新
+            // ※ 微妙に時間がかかるので、ステータスバーに通知
             try
             {
-                this.config.Save(Settings.Default.ConfigurationFile);
+                this.toolStripStatusLabelStatus.Text = Resources.StatusCacheUpdating;
+                try
+                {
+                    this.config.Save(Settings.Default.ConfigurationFile);
+                }
+                finally
+                {
+                    this.toolStripStatusLabelStatus.Text = String.Empty;
+                }
             }
             catch (Exception ex)
             {
@@ -454,6 +421,17 @@ namespace Honememo.Wptscs
             this.Release();
         }
 
+        /// <summary>
+        /// ステータスバー処理時間更新タイマー処理。
+        /// </summary>
+        /// <param name="sender">イベント発生オブジェクト。</param>
+        /// <param name="e">発生したイベント。</param>
+        private void TimerStatusStopwatch_Tick(object sender, EventArgs e)
+        {
+            // 処理時間をステータスバーに反映
+            this.toolStripStatusLabelStopwatch.Text = String.Format(Resources.ElapsedTime, this.translator.Stopwatch.Elapsed);
+        }
+
         #endregion
 
         #region それ以外のメソッド
@@ -482,9 +460,18 @@ namespace Honememo.Wptscs
         private bool LoadConfig()
         {
             // 設定ファイルの読み込み
+            // ※ 微妙に時間がかかるので、ステータスバーに通知
             try
             {
-                this.config = Config.GetInstance(Settings.Default.ConfigurationFile);
+                this.toolStripStatusLabelStatus.Text = Resources.StatusConfigReading;
+                try
+                {
+                    this.config = Config.GetInstance(Settings.Default.ConfigurationFile);
+                }
+                finally
+                {
+                    this.toolStripStatusLabelStatus.Text = String.Empty;
+                }
             }
             catch (FileNotFoundException ex)
             {
@@ -542,6 +529,57 @@ namespace Honememo.Wptscs
         }
 
         /// <summary>
+        /// 翻訳支援処理のログ・変換後テキストをファイル出力。
+        /// </summary>
+        /// <param name="success">翻訳支援処理が成功した場合<c>true</c>。</param>
+        private void WriteResult(bool success)
+        {
+            // 若干時間がかかるのでステータスバーに通知
+            this.toolStripStatusLabelStatus.Text = Resources.StatusFileWriting;
+            try
+            {
+                // 使用可能な出力ファイル名を生成
+                string fileName;
+                string logName;
+                this.MakeFileName(out fileName, out logName, this.textBoxArticle.Text.Trim(), this.textBoxSaveDirectory.Text);
+
+                if (success)
+                {
+                    // 翻訳支援処理成功時は変換後テキストを出力
+                    try
+                    {
+                        File.WriteAllText(Path.Combine(this.textBoxSaveDirectory.Text, fileName), this.translator.Text);
+                        this.textBoxLog.AppendText(String.Format(Resources.LogMessageEnd, fileName, logName));
+                    }
+                    catch (Exception ex)
+                    {
+                        this.textBoxLog.AppendText(String.Format(Resources.LogMessageFileSaveFailed, Path.Combine(this.textBoxSaveDirectory.Text, fileName), ex.Message));
+                        this.textBoxLog.AppendText(String.Format(Resources.LogMessageStop, logName));
+                    }
+                }
+                else
+                {
+                    this.textBoxLog.AppendText(String.Format(Resources.LogMessageStop, logName));
+                }
+
+                // ログを出力
+                try
+                {
+                    File.WriteAllText(Path.Combine(this.textBoxSaveDirectory.Text, logName), this.textBoxLog.Text);
+                }
+                catch (Exception ex)
+                {
+                    this.textBoxLog.AppendText(String.Format(Resources.LogMessageFileSaveFailed, Path.Combine(this.textBoxSaveDirectory.Text, logName), ex.Message));
+                }
+            }
+            finally
+            {
+                // ステータスバーをクリア
+                this.toolStripStatusLabelStatus.Text = String.Empty;
+            }
+        }
+
+        /// <summary>
         /// 渡された文字列から.txtと.logの重複していないファイル名を作成。
         /// </summary>
         /// <param name="fileName">出力結果ファイル名。</param>
@@ -577,20 +615,31 @@ namespace Honememo.Wptscs
         }
 
         /// <summary>
-        /// 翻訳支援処理クラスのイベント用。
+        /// ç¿»è¨³æ\94¯æ\8f´å\87¦ç\90\86ã\82¯ã\83©ã\82¹ã\81®ã\83­ã\82°æ\9b´æ\96°ã\82¤ã\83\99ã\83³ã\83\88ç\94¨ã\80\82
         /// </summary>
         /// <param name="sender">イベント発生オブジェクト。</param>
         /// <param name="e">発生したイベント。</param>
-        private void GetLogUpdate(object sender, System.EventArgs e)
+        private void GetLogUpdate(object sender, EventArgs e)
         {
             // 前回以降に追加されたログをテキストボックスに出力
-            int length = this.translate.Log.Length;
-            if (length > this.logLastLength)
+            int length = this.translator.Log.Length;
+            if (length > this.logLength)
             {
-                this.textBoxLog.AppendText(this.translate.Log.Substring(this.logLastLength, length - this.logLastLength));
+                this.textBoxLog.AppendText(this.translator.Log.Substring(this.logLength, length - this.logLength));
             }
 
-            this.logLastLength = length;
+            this.logLength = length;
+        }
+
+        /// <summary>
+        /// 翻訳支援処理クラスの処理状態更新イベント用。
+        /// </summary>
+        /// <param name="sender">イベント発生オブジェクト。</param>
+        /// <param name="e">発生したイベント。</param>
+        private void GetStatusUpdate(object sender, EventArgs e)
+        {
+            // 処理状態をステータスバーに通知
+            this.toolStripStatusLabelStatus.Text = this.translator.Status;
         }
 
         #endregion
index 90f94f4..cb1c485 100644 (file)
     <value>$this</value>
   </data>
   <data name="&gt;&gt;groupBoxTransfer.ZOrder" xml:space="preserve">
-    <value>2</value>
+    <value>3</value>
   </data>
   <data name="textBoxSaveDirectory.Location" type="System.Drawing.Point, System.Drawing">
     <value>60, 18</value>
     <value>$this</value>
   </data>
   <data name="&gt;&gt;groupBoxSaveDirectory.ZOrder" xml:space="preserve">
-    <value>1</value>
+    <value>2</value>
   </data>
   <data name="groupBoxRun.Anchor" type="System.Windows.Forms.AnchorStyles, System.Windows.Forms">
     <value>Top, Bottom, Left, Right</value>
     <value>Vertical</value>
   </data>
   <data name="textBoxLog.Size" type="System.Drawing.Size, System.Drawing">
-    <value>416, 193</value>
+    <value>416, 207</value>
   </data>
   <data name="textBoxLog.TabIndex" type="System.Int32, mscorlib">
     <value>4</value>
     <value>12, 169</value>
   </data>
   <data name="groupBoxRun.Size" type="System.Drawing.Size, System.Drawing">
-    <value>440, 262</value>
+    <value>440, 276</value>
   </data>
   <data name="groupBoxRun.TabIndex" type="System.Int32, mscorlib">
     <value>2</value>
     <value>$this</value>
   </data>
   <data name="&gt;&gt;groupBoxRun.ZOrder" xml:space="preserve">
-    <value>0</value>
+    <value>1</value>
   </data>
   <metadata name="folderBrowserDialogSaveDirectory.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>17, 17</value>
   <metadata name="backgroundWorkerRun.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
     <value>274, 17</value>
   </metadata>
+  <metadata name="statusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>557, 17</value>
+  </metadata>
+  <data name="toolStripStatusLabelStatus.Size" type="System.Drawing.Size, System.Drawing">
+    <value>363, 18</value>
+  </data>
+  <data name="toolStripStatusLabelStatus.TextAlign" type="System.Drawing.ContentAlignment, System.Drawing">
+    <value>MiddleLeft</value>
+  </data>
+  <data name="toolStripStatusLabelStopwatch.Size" type="System.Drawing.Size, System.Drawing">
+    <value>86, 18</value>
+  </data>
+  <data name="toolStripStatusLabelStopwatch.Text" xml:space="preserve">
+    <value>実行時間 0:00</value>
+  </data>
+  <data name="statusStrip.Location" type="System.Drawing.Point, System.Drawing">
+    <value>0, 450</value>
+  </data>
+  <data name="statusStrip.Size" type="System.Drawing.Size, System.Drawing">
+    <value>464, 23</value>
+  </data>
+  <data name="statusStrip.TabIndex" type="System.Int32, mscorlib">
+    <value>3</value>
+  </data>
+  <data name="&gt;&gt;statusStrip.Name" xml:space="preserve">
+    <value>statusStrip</value>
+  </data>
+  <data name="&gt;&gt;statusStrip.Type" xml:space="preserve">
+    <value>System.Windows.Forms.StatusStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;statusStrip.Parent" xml:space="preserve">
+    <value>$this</value>
+  </data>
+  <data name="&gt;&gt;statusStrip.ZOrder" xml:space="preserve">
+    <value>0</value>
+  </data>
+  <metadata name="timerStatusStopwatch.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>673, 17</value>
+  </metadata>
   <metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
     <value>6, 12</value>
   </data>
   <data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
-    <value>464, 443</value>
+    <value>464, 473</value>
   </data>
   <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
     <value>
-        AAABAAIAICAQAAAABADoAgAAJgAAABAQEAAAAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC
-        AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/
-        AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-        AAC7sAAAAAAAAAAAAAAAAAAAu7sAAAAAAAAAAAAAAAAAAAC7AAAAAAAAAAAAABEAAAAAuwAAu7sLsAAA
-        AAAREAAAALsAC7u7u7AAAAAAAREAAAC7AAuwAAuwAREREREREAAAuwALsAALsAERERERERAAALsAALsA
-        C7AAAAAAAREAAAC7AAALu7uwAAAAABEQAAAAuwAAsAALsAAAAAARAAAAu7sAALu7u7AAAAAAAAAAALu7
-        AAC7u7sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7AAAAAAAAAAAAAAAA
-        AAAAuwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu7uwALsAALsAAAAAAAAAC7
-        u7uwC7AAC7AAAAAAAAALuwAAsAuwAAuwAAAAAAAAC7AAAAALsAALsAAAAAAAAAu7u7uwC7AAC7AAAAAA
-        AAALsAALsAuwAAuwAAAAAAAAC7sAC7ALsAALsAAAAAAAAAC7u7sAC7u7u7AAAAAAAAAAC7uwAAuwu7sA
-        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
-        /////x////8P////z///P88J/x/OAf+PznmAB855gAfPOf+Pz4H/H895/z8PAf//DwP/////////////
-        z////8/////////////g55//wGef/49nn/+f55//gGef/55nn/+OZ5//wOAf/+HkP///////////////
-        //8oAAAAEAAAACAAAAABAAQAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAA
-        AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAB3d3d3d3
-        d3dERERERERER0////////hHT///////+EdP///////4R0////////hHT///////+EdP///////4R0//
-        //////hHT///////+EdIiIiIiIiIR0zMzMzMzMxHxERERERERMAAAAAAAAAAAAAAAAAAAAAA//8AAIAA
-        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAD//wAA//8AAA==
+        AAABAAEAMkYAAAEAIAAIOQAAFgAAACgAAAAyAAAAjAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANyYmnDcmJ3g3JifENyYn8DcmJ9A3JidANyYmHDcmJGwAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzJi7ENyYn/DcmJ/w3J
+        if8NyYn/DcmJ/w3Jif8NyYnzDcmJXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAAfwQAAH8IAAB/BQAA
+        fwIAAAAAC86UcA3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn+DcmJWQAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAA
+        fwEAAH8CAAB/BAAAfwYAAH8HAAB/AwAAfwEAAAAAAAAAAAAAAAANyYkHDcmJMw3JibgNyYn/DcmJ/w3J
+        if8NyYnyDcmJFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAH8BAAB/AQAAfwIAAH8DAAB/AQAAfwEAAAAAAAAAAAAAfwEAAAAAAAAAAAAA
+        AAAAAAAADcmJBg3JidkNyYn/DcmJ/w3Jif8NyYmEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAAfwIAAAAAAAB/AQAAfwIAAH8BAAAAAAAA
+        AAAAAAAAAAB/AQAAAAAAAAAAAAAAAAAAAAAAAAAADcmJbg3Jif8NyYn/DcmJ/w3JidgAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAA
+        fwFQUHIDSEhzCktLchJISGsVUFByCVBQcgMAAAAAAAB/AQAAAAAAAAAAAAAAAAAAAAANyYkfDcmJ/w3J
+        if8NyYn/DcmJ/w3JiRkAAAAAAAAAAAAAAAANyYk2DcmJsw3Jie4NyYn9DcmJ8w3JidcNyYmgDcmJMwAA
+        AAANyYniDcmJ/w3Jif8NyYn/DcmJUQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQcg5KSm0rQ0NnUD4+Y2E/P2Q/Pj5kEwAAAAAAAH8BAAB/AgAA
+        AAAAAAAAAAAAAAAAAAANyYngDcmJ/w3Jif8NyYn/DcmJUgAAAAAAAAAADcmJQg3JifoNyYn/DcmJ/w3J
+        if8NyYn/DcmJ/w3Jif8NyYn+DcmJnQ3JibQNyYn/DcmJ/w3Jif8NyYmNAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAAAAAAAAAAAAAAAAAAAAAAAAAAS0tuJD8/ZH85OV/BMzNb1TU1
+        XLc4OF5mNjZhEgAAfwIAAH8CAAAAAAAAAAAAAAAAAAAAAA3JiaYNyYn/DcmJ/w3Jif8NyYmLAAAAAAAA
+        AAANyYnLDcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3J
+        icgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/AQAAfwEAAAAAAAB/AQAAfwEAAH8BAAB/ASAg
+        eQpAQGhBODhfvywsVfMmJlD6KSlS8zExWcI2Nl5QLy9fCQAAfwEAAH8BAAAAAAAAAAAAAAAADcmJbA3J
+        if8NyYn/DcmJ/w3JicUAAAAAAAAAAA3JifkNyYn/DcmJ/w3Jif8NyYmRDcmJFg3JiQMNyYkRDcmJOQ3J
+        iXoNyYnXDcmJ/w3Jif8NyYn/DcmJ+g3JiQkAAAAAAAAAAAAAAAAAAAAAAAB/AQAAfwEAAH8BAAB/AQAA
+        AAAAAH8BAAB/AQAAfwMAAH8EDw91GTg4Z2I0NFvXJiZP+yAgS/4iIk3+KytU8DMzWqEwMFkvHx9aBQAA
+        AAAAAAAAAAAAAAAAAAANyYkzDcmJ/w3Jif8NyYn/DcmJ+A3JiQYAAAAADcmJ7g3Jif8NyYn/DcmJ/w3J
+        iQkAAAAAAAAAAAAAAAAAAAAAAAAAAA3JiQYNyYn3DcmJ/w3Jif8NyYn/DcmJPwAAAAAAAAAAAAAAAAAA
+        AAAAAH8CAAAAAAAAfwEAAH8BAAAAAAAAAAAAAH8BAAB/AwAAfwEAAH8OMzNqUDQ0XMEpKVL5ISFM/iEh
+        TP4mJk/7Li5W3TAwWXwrK1QZAAAAAAAAAAAAAAAAAAAAAA3JiQQNyYn0DcmJ/w3Jif8NyYn/DcmJOAAA
+        AAANyYmuDcmJ/w3Jif8NyYn/DcmJMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3JicMNyYn/DcmJ/w3J
+        if8NyYl7AAAAAAAAAAAAAAAAAAAAAAAAfwEAAAAAAAB/AgAAfwIAAH8BAAAAAAAAAAAAAH8CAAB/AgAA
+        fwc2NmkeOzthhjMzWuIpKVL7JCRO/iQkTv0nJ1H3LCxVzSwsVGQdHUgQAAAAAAAAAAAAAAAAAAAAAA3J
+        ib8NyYn/DcmJ/w3Jif8NyYlxAAAAAA3JiTANyYn5DcmJ/w3Jif8NyYnaDcmJNAAAAAAAAAAAAAAAAAAA
+        AAAAAAAADcmJiA3Jif8NyYn/DcmJ/w3JibcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8DNzdzFz8/
+        biE+PmsmOjpmIjg4Zh41NWgZOjpyGEFBbRRDQ2lBPj5kpzY2Xe4rK1T8IyNN/iEhTP0kJE70KChRtCIi
+        TDwaGkYCAAAAAAAAAAAAAAAADcmJhg3Jif8NyYn/DcmJ/w3JiasAAAAAAAAAAA3JiUoNyYnvDcmJ/w3J
+        if8NyYn/DcmJyQ3JiYgNyYlYDcmJNg3JiRsNyYlUDcmJ/w3Jif8NyYn/DcmJ8A3JiQIAAAAAAAAAAAAA
+        AAAAAAAAPT17Ezo6c1Y4OGugNTVkxzMzYNMyMl/QMjJgxzIyZL40NGa4NTVnsDk5abE7O2jBNzdf6i0t
+        VvwiIkz+Hh5J/x4eSf4kJE/tKipViSMjVBcAAAAAAAAAAAAAAAANyYlMDcmJ/w3Jif8NyYn/DcmJ5AAA
+        AAAAAAAAAAAAAA3JiRcNyYmHDcmJ4g3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3J
+        if8NyYn/DcmJLgAAAAAAAAAAAAAAAEVF3AI9PX9ANzdsti8vXfEoKFP8JCRQ/SQkT/0lJVH8JiZU+igo
+        VvkpKVf5LCxZ+C0tWfkqKlX8JCRO/h0dSf8cHEf/HBxI/yEhTfssLFnALCxkLQAAAAAAAAAAAAAAAA3J
+        iRMNyYn/DcmJ/w3Jif8NyYn/DcmJHgAAAAAAAAAAAAAAAAAAAAAAAAAADcmJJw3JiV0NyYmIDcmJqw3J
+        icgNyYnfDcmJ8Q3Jif4NyYn/DcmJ/w3Jif8NyYlpAAAAAAAAAAAAAAAAOjpzDjw8dmk0NGXhKChU/SAg
+        S/8dHUn/Hh5J/x4eSv8fH0v/ICBM/yAgTP8iIk7/IiJO/yAgTP8dHUj/GxtH/xoaRv8bG0f/ISFM/i4u
+        W9g1NWw/GhpGBAAAAAAAAAAAAAAAAA3JidkNyYn/DcmJ/w3Jif8NyYlXAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcmJgQ3Jif8NyYn/DcmJ/w3JiZ8AAAAAAAAAAAAA
+        AABGRooKOzt2YjY2adopKVb8ISFN/x8fSv8eHkr/ICBM/yAgTP4gIE3/ICBN/yAgTf8gIEz/Hh5K/xsb
+        R/8aGkb/GhpG/xsbR/8jI07+MTFg0jg4cDMAAAAAAAAAAAAAAAAAAAAADcmJnw3Jif8NyYn/DcmJ/w3J
+        iZEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANyYmGDcmJ/w3J
+        if8NyYn/DcmJvwAAAAAAAAAAAAAAAAAAAAA/P341OTlxoTExZOYqKlj4JydU+ygoVvgoKFf2KSlZ9Sgo
+        WPYnJ1b3JiZW9iUlVPggIE39HBxI/xsbRv8bG0b/Hh5J/ykpVPg3N2emODh3HAAAAAAAAAAAAAAAAAAA
+        AAANyYllDcmJ/w3Jif8NyYn/DcmJygAAAAAAAAAAAAAAAA3JicgNyYmlDcmJbA3JiTsNyYkXDcmJBQ3J
+        iQYNyYkjDcmJbw3JifUNyYn/DcmJ/w3Jif8NyYm2AAAAAAAAAAAAAAAAAAAAAEVFkA09PX9COjp1fzY2
+        bqQzM2qtNDRqpjMza5kxMWqXMDBony8vZ6IsLGGyJiZX0yEhTfQdHUn9HBxI/x4eSf8lJU/8MDBd2jo6
+        bV4/P4sIAAAAAAAAAAANyYnnDcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn6DcmJCQAAAAAAAAAADcmJog3J
+        if8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3JiWcAAAAAAAAAAAAA
+        AAAAAAAARUXcAkVF3AdFRboWPj6rGjs7oRs/P6kWPz+0EDw8og89PZURMDB/GScnWG0jI0/UHh5K+R0d
+        SP4eHkr+JCRP/DAwW+Y4OGaLOjp0HQAAAAAAAAAAAAAAAA3JibQNyYn/DcmJ/w3Jif8NyYn/DcmJ/w3J
+        if8NyYk9AAAAAAAAAAANyYljDcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3J
+        if8NyYmbDcmJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEVF
+        3AEaGkYWJiZRlSAgTO8cHEj+HR1I/yEhTP4uLlfyOTllrUBAbzo/P20EAAAAAAAAAAAAAAAADcmJgQ3J
+        if8NyYn/DcmJ/w3Jif8NyYn/DcmJ/w3JiXcAAAAAAAAAAA3JiQ8NyYlhDcmJkA3JibgNyYnZDcmJ8Q3J
+        ifwNyYn3DcmJ6A3JiccNyYmPDcmJNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAACEhTB8oKFSgISFN8h4eSf0fH0v+KSlU+Dc3Y80/P29YRkZ4CwAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKChVFCwsWH4oKFTbJiZS9Soq
+        VfQ1NWHYQUFyekZGfRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAaGkYHMzNiOjMzYoU0NGK0ODhosD4+cHtFRXwsRkaKAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAA3JieQNyYn/DcmJ/w3Jif8NyYmjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKSn8JPj51Ij8/dTRISIQsR0eFG0ZGigcAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcmJrA3Jif8NyYn/DcmJ/w3JidoAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARkaKAkZG
+        igIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANyYl0DcmJ/w3J
+        if8NyYn/DcmJ/w3JiRIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAA3JiTwNyYn/DcmJ/w3Jif8NyYn/DcmJSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGAWC7BgbAuwYK4LsGDaC7Bg8Auw
+        YPwLsGD1C7Bg2AuwYKoLsGBsC7BgIQAAAAAAAAAAC7Bg4wuwYP8LsGD/C7Bg/wuwYFQAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAALsGDjC7Bg/wuwYP8LsGD/C7BgXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGADC7Bgfwuw
+        YPYLsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7BgQAAAAAALsGCpC7Bg/wuw
+        YP8LsGD/C7BgjQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYKoLsGD/C7Bg/wuwYP8LsGCZAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAuwYKcLsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuw
+        YP8LsGB4AAAAAAuwYG8LsGD/C7Bg/wuwYP8LsGDGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgcAuw
+        YP8LsGD/C7Bg/wuwYNUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGBWC7Bg/wuwYP8LsGD/C7Bg/wuwYN4LsGBoC7BgJguw
+        YAgLsGAEC7BgIwuwYGMLsGDDC7Bg/wuwYK4AAAAAC7BgNAuwYP8LsGD/C7Bg/wuwYPgLsGAGAAAAAAAA
+        AAAAAAAAAAAAAAAAAAALsGA3C7Bg/wuwYP8LsGD/C7Bg/guwYBIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYLwLsGD/C7Bg/wuw
+        YP8LsGDRC7BgDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGA+C7BgsgAAAAALsGAEC7Bg9Quw
+        YP8LsGD/C7Bg/wuwYDgAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYAULsGD3C7Bg/wuwYP8LsGD/C7BgTQAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAC7Bg7wuwYP8LsGD/C7Bg/wuwYEsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAALsGDAC7Bg/wuwYP8LsGD/C7BgcQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuw
+        YMMLsGD/C7Bg/wuwYP8LsGCJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGD9C7Bg/wuwYP8LsGD/C7BgDgAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYIYLsGD/C7Bg/wuwYP8LsGCqAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAC7BgiguwYP8LsGD/C7Bg/wuwYMUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYPELsGD/C7Bg/wuw
+        YP8LsGAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgSwuw
+        YP8LsGD/C7Bg/wuwYOMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGBQC7Bg/wuwYP8LsGD/C7Bg+Quw
+        YAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAC7BgzQuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuw
+        YP8LsGD/C7BgqAAAAAALsGASC7Bg/guwYP8LsGD/C7Bg/wuwYBwAAAAAAAAAAAAAAAAAAAAAAAAAAAuw
+        YBgLsGD/C7Bg/wuwYP8LsGD/C7BgPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALsGCPC7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuw
+        YP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGDaAAAAAAAAAAALsGDXC7Bg/wuwYP8LsGD/C7BgVQAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYOYLsGD/C7Bg/wuwYP8LsGB3AAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYDILsGD/C7Bg/wuw
+        YP8LsGCtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgCwuwYP8LsGD/C7Bg/wuwYPgAAAAAAAAAAAuw
+        YJ0LsGD/C7Bg/wuwYP8LsGCOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgwwuwYP8LsGD/C7Bg/wuw
+        YKUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAuwYK8LsGD/C7Bg/wuwYP8LsGBbAAAAAAAAAAAAAAAAAAAAAAAAAAALsGAaC7Bg/wuw
+        YP8LsGD/C7Bg8QAAAAAAAAAAC7BgYguwYP8LsGD/C7Bg/wuwYMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAALsGDHC7Bg/wuwYP8LsGD/C7BgugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgGAuwYOYLsGD/C7Bg/wuwYP4LsGCbC7BgNguw
+        YAkLsGAGC7BgMguwYL0LsGD/C7Bg/wuwYP8LsGDEAAAAAAAAAAALsGAoC7Bg/wuwYP8LsGD/C7Bg/wuw
+        YMYLsGBvC7BgLwuwYAkLsGALC7BgYQuwYP4LsGD/C7Bg/wuwYP8LsGCrAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgLQuw
+        YOgLsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYFwAAAAAAAAAAAuw
+        YAELsGDsC7Bg/wuwYP8LsGD/C7Bg+QuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuw
+        YGkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAC7BgGwuwYLULsGD/C7Bg/wuwYP8LsGD/C7Bg/wuwYP8LsGD/C7Bg/wuw
+        YP8LsGCOAAAAAAAAAAAAAAAAAAAAAAuwYLQLsGD/C7Bg/wuwYP8LsGCMC7BgkAuwYPwLsGD/C7Bg/wuw
+        YP8LsGD/C7Bg/wuwYP8LsGDMC7BgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuwYDgLsGCTC7Bg0Auw
+        YPILsGD8C7Bg8guwYNQLsGCcC7BgOQAAAAAAAAAAAAAAAAAAAAAAAAAAC7BgeQuwYP8LsGD/C7Bg/wuw
+        YL4AAAAAC7BgKguwYJwLsGDZC7Bg9AuwYPsLsGDfC7BgjAuwYA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////8AA////////wAD////////AAP///////8AA////////
+        wAD////////AAP///////8AA////////wAD////////AAP///////8AA//8B////wAD//wD////AAP//
+        gH///8AA///4P///wAD///wf///AAP///h///8AA///+HwMPwAD///4eAAfAAP/x/gwAB8AA/+D/DB8H
+        wAD/4H8MP4fAAP/gfww/h8AA/+A/Dh+DwAD/8B8HA8PAAOAAD4eAA8AAwAAPh/gDwADAAA+H/8HAAMAA
+        D4P/wcAAwAAPw5/BwADwAB4DgAPAAP/wHgPAA8AA/+A+A+APwAD/4H/////AAP/w/////8AA//H/4P//
+        wAD////g///AAP////D//8AA////8P//wAD////////AAP///////8AA/AeH8P//wADwAYPwf//AAMAB
+        w/h//8AAwfjD+H//wACD/sP4f//AAIf/w/g//8AAh//B+D//wACH/+H8P//AAIAAYfw//8AAgABh/D//
+        wADD+GD8H//AAMP4cPwf/8AA4PBwfB//wADwAPAAP//AAPgA8AA//8AA/gP4YH//wAD////////AAP//
+        /////8AA////////wAD////////AAP///////8AA////////wAD////////AAP///////8AA////////
+        wAD////////AAP///////8AA////////wAD////////AAP///////8AA
 </value>
   </data>
   <data name="$this.MinimumSize" type="System.Drawing.Size, System.Drawing">
     <value>480, 480</value>
   </data>
   <data name="$this.Text" xml:space="preserve">
-    <value>Wikipedia 翻訳支援ツール C#</value>
+    <value>Wikipedia 翻訳支援ツール</value>
   </data>
   <data name="&gt;&gt;folderBrowserDialogSaveDirectory.Name" xml:space="preserve">
     <value>folderBrowserDialogSaveDirectory</value>
   <data name="&gt;&gt;toolTip.Type" xml:space="preserve">
     <value>System.Windows.Forms.ToolTip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
+  <data name="&gt;&gt;toolStripStatusLabelStatus.Name" xml:space="preserve">
+    <value>toolStripStatusLabelStatus</value>
+  </data>
+  <data name="&gt;&gt;toolStripStatusLabelStatus.Type" xml:space="preserve">
+    <value>System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;toolStripStatusLabelStopwatch.Name" xml:space="preserve">
+    <value>toolStripStatusLabelStopwatch</value>
+  </data>
+  <data name="&gt;&gt;toolStripStatusLabelStopwatch.Type" xml:space="preserve">
+    <value>System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+  <data name="&gt;&gt;timerStatusStopwatch.Name" xml:space="preserve">
+    <value>timerStatusStopwatch</value>
+  </data>
+  <data name="&gt;&gt;timerStatusStopwatch.Type" xml:space="preserve">
+    <value>System.Windows.Forms.Timer, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
   <data name="&gt;&gt;$this.Name" xml:space="preserve">
     <value>MainForm</value>
   </data>
index 35ec57a..f1f6a06 100644 (file)
@@ -3,7 +3,7 @@
 //      アプリケーションの設定を保持するクラスソース</summary>
 //
 // <copyright file="Config.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -19,6 +19,8 @@ namespace Honememo.Wptscs.Models
     using Honememo.Utilities;
     using Honememo.Wptscs.Logics;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// アプリケーションの設定を保持するクラスです。
index f8984c2..77441a0 100644 (file)
@@ -3,7 +3,7 @@
 //      言語に関する情報をあらわすモデルクラスソース</summary>
 //
 // <copyright file="Language.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -123,6 +123,20 @@ namespace Honememo.Wptscs.Models
 
         #endregion
 
+        #region 公開メソッド
+
+        /// <summary>
+        /// <see cref="Bracket"/> を渡された値で書式化した文字列を返す。
+        /// </summary>
+        /// <param name="value">記事名。</param>
+        /// <returns>書式化した文字列。<see cref="Bracket"/>が未設定の場合<c>null</c>。</returns>
+        public string FormatBracket(string value)
+        {
+            return StringUtils.FormatDollarVariable(this.Bracket, value);
+        }
+
+        #endregion
+
         #region XMLシリアライズ用メソッド
 
         /// <summary>
diff --git a/Wptscs/Models/MediaWikiPage.cs b/Wptscs/Models/MediaWikiPage.cs
deleted file mode 100644 (file)
index daa8c7d..0000000
+++ /dev/null
@@ -1,1157 +0,0 @@
-// ================================================================================================
-// <summary>
-//      MediaWikiのページをあらわすモデルクラスソース</summary>
-//
-// <copyright file="MediaWikiPage.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
-// <author>
-//      Honeplus</author>
-// ================================================================================================
-
-namespace Honememo.Wptscs.Models
-{
-    using System;
-    using System.Collections.Generic;
-    using System.Linq;
-    using System.Text;
-    using Honememo.Utilities;
-
-    /// <summary>
-    /// MediaWikiのページをあらわすモデルクラスです。
-    /// </summary>
-    public class MediaWikiPage : Page
-    {
-        #region 定数宣言
-
-        /// <summary>
-        /// nowikiタグ。
-        /// </summary>
-        public static readonly string NowikiTag = "nowiki";
-
-        /// <summary>
-        /// msgnwの書式。
-        /// </summary>
-        public static readonly string Msgnw = "msgnw:";
-
-        #endregion
-
-        #region private変数
-
-        /// <summary>
-        /// リダイレクト先のページ名。
-        /// </summary>
-        private Link redirect;
-
-        #endregion
-
-        #region コンストラクタ
-
-        /// <summary>
-        /// コンストラクタ。
-        /// </summary>
-        /// <param name="website">ページが所属するウェブサイト。</param>
-        /// <param name="title">ページタイトル。</param>
-        /// <param name="text">ページの本文。</param>
-        /// <param name="timestamp">ページのタイムスタンプ。</param>
-        public MediaWikiPage(MediaWiki website, string title, string text, DateTime? timestamp)
-            : base(website, title, text, timestamp)
-        {
-        }
-
-        /// <summary>
-        /// コンストラクタ。
-        /// ページのタイムスタンプには<c>null</c>を設定。
-        /// </summary>
-        /// <param name="website">ページが所属するウェブサイト。</param>
-        /// <param name="title">ページタイトル。</param>
-        /// <param name="text">ページの本文。</param>
-        public MediaWikiPage(MediaWiki website, string title, string text)
-            : base(website, title, text)
-        {
-        }
-
-        /// <summary>
-        /// コンストラクタ。
-        /// ページの本文, タイムスタンプには<c>null</c>を設定。
-        /// </summary>
-        /// <param name="website">ページが所属するウェブサイト。</param>
-        /// <param name="title">ページタイトル。</param>
-        public MediaWikiPage(MediaWiki website, string title)
-            : base(website, title)
-        {
-        }
-
-        #endregion
-
-        #region プロパティ
-
-        /// <summary>
-        /// ページが所属するウェブサイト。
-        /// </summary>
-        public new MediaWiki Website
-        {
-            get
-            {
-                return base.Website as MediaWiki;
-            }
-
-            protected set
-            {
-                base.Website = value;
-            }
-        }
-
-        /// <summary>
-        /// ページの本文。
-        /// </summary>
-        public override string Text
-        {
-            get
-            {
-                return base.Text;
-            }
-
-            protected set
-            {
-                // 本文は普通に格納
-                base.Text = value;
-
-                // 本文格納のタイミングでリダイレクトページ(#REDIRECT等)かを判定
-                if (!String.IsNullOrEmpty(base.Text))
-                {
-                    this.TryParseRedirect();
-                }
-            }
-        }
-
-        /// <summary>
-        /// リダイレクト先へのリンク。
-        /// </summary>
-        public Link Redirect
-        {
-            get
-            {
-                // Textが設定されている場合のみ有効
-                this.ValidateIncomplete();
-                return this.redirect;
-            }
-
-            protected set
-            {
-                this.redirect = value;
-            }
-        }
-
-        #endregion
-
-        #region 公開静的メソッド
-
-        /// <summary>
-        /// 渡されたテキストがnowikiブロックかを解析する。
-        /// </summary>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="nowiki">解析したnowikiブロック。</param>
-        /// <returns>nowikiブロックの場合<c>true</c>。</returns>
-        /// <remarks>
-        /// nowikiブロックと判定するには、1文字目が開始タグである必要がある。
-        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
-        /// また、入れ子は考慮しない。
-        /// </remarks>
-        public static bool TryParseNowiki(string text, out string nowiki)
-        {
-            nowiki = null;
-            LazyXmlParser parser = new LazyXmlParser();
-            LazyXmlParser.SimpleElement element;
-            if (parser.TryParse(text, out element))
-            {
-                if (element.Name.ToLower() == MediaWikiPage.NowikiTag)
-                {
-                    nowiki = element.OuterXml;
-                    return true;
-                }
-            }
-
-            return false;
-        }
-
-        #endregion
-
-        #region 公開インスタンスメソッド
-
-        /// <summary>
-        /// 指定された言語コードへの言語間リンクを返す。
-        /// </summary>
-        /// <param name="code">言語コード。</param>
-        /// <returns>言語間リンク先の記事名。見つからない場合は空。</returns>
-        /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
-        public string GetInterWiki(string code)
-        {
-            // Textが設定されている場合のみ有効
-            this.ValidateIncomplete();
-
-            // 記事に存在する指定言語への言語間リンクを取得
-            for (int i = 0; i < this.Text.Length; i++)
-            {
-                char c = this.Text[i];
-                Link link;
-                switch (c)
-                {
-                    case '<':
-                        // コメント(<!--)またはnowiki区間の場合飛ばす
-                        string subtext = this.Text.Substring(i);
-                        string value;
-                        if (LazyXmlParser.TryParseComment(subtext, out value))
-                        {
-                            i += value.Length - 1;
-                        }
-                        else if (MediaWikiPage.TryParseNowiki(subtext, out value))
-                        {
-                            i += value.Length - 1;
-                        }
-
-                        break;
-
-                    case '{':
-                        // テンプレート
-                        if (this.TryParseTemplate(this.Text.Substring(i), out link))
-                        {
-                            i += link.OriginalText.Length - 1;
-
-                            // Documentationテンプレートがある場合は、その中を探索
-                            string interWiki = this.GetDocumentationInterWiki(link, code);
-                            if (!String.IsNullOrEmpty(interWiki))
-                            {
-                                return interWiki;
-                            }
-                        }
-
-                        break;
-
-                    case '[':
-                        // リンク
-                        if (this.TryParseLink(this.Text.Substring(i), out link))
-                        {
-                            i += link.OriginalText.Length - 1;
-
-                            // 指定言語への言語間リンクの場合、内容を取得し、処理終了
-                            if (link.Code == code && !link.IsColon)
-                            {
-                                return link.Title;
-                            }
-                        }
-
-                        break;
-                }
-            }
-
-            // 未発見の場合、空文字列
-            return String.Empty;
-        }
-
-        /// <summary>
-        /// ページがリダイレクトかをチェック。
-        /// </summary>
-        /// <returns><c>true</c> リダイレクト。</returns>
-        public bool IsRedirect()
-        {
-            // Textが設定されている場合のみ有効
-            return this.Redirect != null;
-        }
-
-        /// <summary>
-        /// ページがテンプレートかをチェック。
-        /// </summary>
-        /// <returns><c>true</c> テンプレート。</returns>
-        public bool IsTemplate()
-        {
-            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
-            return this.IsNamespacePage(this.Website.TemplateNamespace);
-        }
-
-        /// <summary>
-        /// ページがカテゴリーかをチェック。
-        /// </summary>
-        /// <returns><c>true</c> カテゴリー。</returns>
-        public bool IsCategory()
-        {
-            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
-            return this.IsNamespacePage(this.Website.CategoryNamespace);
-        }
-
-        /// <summary>
-        /// ページが画像かをチェック。
-        /// </summary>
-        /// <returns><c>true</c> 画像。</returns>
-        public bool IsFile()
-        {
-            // 指定されたページ名がファイル(Image:等で始まる)かをチェック
-            return this.IsNamespacePage(this.Website.FileNamespace);
-        }
-
-        /// <summary>
-        /// ページが標準名前空間かをチェック。
-        /// </summary>
-        /// <returns><c>true</c> 標準名前空間。</returns>
-        public bool IsMain()
-        {
-            // 指定されたページ名が標準名前空間以外の名前空間(Wikipedia:等で始まる)かをチェック
-            string title = this.Title.ToLower();
-            foreach (IList<string> prefixes in this.Website.Namespaces.Values)
-            {
-                foreach (string prefix in prefixes)
-                {
-                    if (title.StartsWith(prefix.ToLower() + ":"))
-                    {
-                        return false;
-                    }
-                }
-            }
-
-            return true;
-        }
-
-        #region Linkクラスに移動したいメソッド
-
-        // TODO: 以下の各メソッドのうち、リンクに関するものはLinkクラスに移したい。
-        //       また、余計な依存関係を持っているものを整理したい。
-
-        /// <summary>
-        /// 渡されたWikipediaの内部リンクを解析。
-        /// </summary>
-        /// <param name="text">[[で始まる文字列。</param>
-        /// <param name="link">解析したリンク。</param>
-        /// <returns>解析に成功した場合<c>true</c>。</returns>
-        public bool TryParseLink(string text, out Link link)
-        {
-            // 出力値初期化
-            link = null;
-
-            // 入力値確認
-            if (!text.StartsWith("[["))
-            {
-                return false;
-            }
-
-            // 構文を解析して、[[]]内部の文字列を取得
-            // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
-            string article = String.Empty;
-            string section = String.Empty;
-            IList<string> pipeTexts = new List<string>();
-            int lastIndex = -1;
-            int pipeCounter = 0;
-            bool sharpFlag = false;
-            for (int i = 2; i < text.Length; i++)
-            {
-                char c = text[i];
-
-                // ]]が見つかったら、処理正常終了
-                if (StringUtils.StartsWith(text, "]]", i))
-                {
-                    lastIndex = ++i;
-                    break;
-                }
-
-                // | が含まれている場合、以降の文字列は表示名などとして扱う
-                if (c == '|')
-                {
-                    ++pipeCounter;
-                    pipeTexts.Add(String.Empty);
-                    continue;
-                }
-
-                // 変数([[{{{1}}}]]とか)の再帰チェック
-                string dummy;
-                string variable;
-                int index = this.ChkVariable(out variable, out dummy, text, i);
-                if (index != -1)
-                {
-                    i = index;
-                    if (pipeCounter > 0)
-                    {
-                        pipeTexts[pipeCounter - 1] += variable;
-                    }
-                    else if (sharpFlag)
-                    {
-                        section += variable;
-                    }
-                    else
-                    {
-                        article += variable;
-                    }
-
-                    continue;
-                }
-
-                // | の前のとき
-                if (pipeCounter <= 0)
-                {
-                    // 変数以外で { } または < > [ ] \n が含まれている場合、リンクは無効
-                    if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}') || (c == '\n'))
-                    {
-                        break;
-                    }
-
-                    // # の前のとき
-                    if (!sharpFlag)
-                    {
-                        // #が含まれている場合、以降の文字列は見出しへのリンクとして扱う(1つめの#のみ有効)
-                        if (c == '#')
-                        {
-                            sharpFlag = true;
-                        }
-                        else
-                        {
-                            article += c;
-                        }
-                    }
-                    else
-                    {
-                        // # の後のとき
-                        section += c;
-                    }
-                }
-                else
-                {
-                    // | の後のとき
-                    if (c == '<')
-                    {
-                        string subtext = text.Substring(i);
-                        string value;
-                        if (LazyXmlParser.TryParseComment(subtext, out value))
-                        {
-                            // コメント(<!--)が含まれている場合、リンクは無効
-                            break;
-                        }
-                        else if (MediaWikiPage.TryParseNowiki(subtext, out value))
-                        {
-                            // nowikiブロック
-                            i += value.Length - 1;
-                            pipeTexts[pipeCounter - 1] += value;
-                            continue;
-                        }
-                    }
-
-                    // リンク [[ {{ ([[image:xx|[[test]]の画像]]とか)の再帰チェック
-                    Link l;
-                    index = this.ChkLinkText(out l, text, i);
-                    if (index != -1)
-                    {
-                        i = index;
-                        pipeTexts[pipeCounter - 1] += l.OriginalText;
-                        continue;
-                    }
-
-                    pipeTexts[pipeCounter - 1] += c;
-                }
-            }
-
-            // 解析失敗
-            if (lastIndex < 0)
-            {
-                return false;
-            }
-
-            // 解析に成功した場合、結果を出力値に設定
-            link = new Link();
-
-            // 変数ブロックの文字列をリンクのテキストに設定
-            link.OriginalText = text.Substring(0, lastIndex + 1);
-
-            // 前後のスペースは削除(見出しは後ろのみ)
-            link.Title = article.Trim();
-            link.Section = section.TrimEnd();
-
-            // | 以降はそのまま設定
-            link.PipeTexts = pipeTexts;
-
-            // 記事名から情報を抽出
-            // サブページ
-            if (link.Title.StartsWith("/"))
-            {
-                link.IsSubpage = true;
-            }
-            else if (link.Title.StartsWith(":"))
-            {
-                // 先頭が :
-                link.IsColon = true;
-                link.Title = link.Title.TrimStart(':').TrimStart();
-            }
-
-            // 標準名前空間以外で[[xxx:yyy]]のようになっている場合、言語コード
-            if (link.Title.Contains(":") && new MediaWikiPage(this.Website, link.Title).IsMain())
-            {
-                // ※本当は、言語コード等の一覧を作り、其処と一致するものを・・・とすべきだろうが、
-                //   メンテしきれないので : を含む名前空間以外を全て言語コード等と判定
-                link.Code = link.Title.Substring(0, link.Title.IndexOf(':')).TrimEnd();
-                link.Title = link.Title.Substring(link.Title.IndexOf(':') + 1).TrimStart();
-            }
-
-            return true;
-        }
-
-        /// <summary>
-        /// 渡されたWikipediaのテンプレートを解析。
-        /// </summary>
-        /// <param name="text">{{で始まる文字列。</param>
-        /// <param name="link">解析したテンプレートのリンク。</param>
-        /// <returns>解析に成功した場合<c>true</c>。</returns>
-        public bool TryParseTemplate(string text, out Link link)
-        {
-            // 出力値初期化
-            link = null;
-
-            // 入力値確認
-            if (!text.StartsWith("{{"))
-            {
-                return false;
-            }
-
-            // 構文を解析して、{{}}内部の文字列を取得
-            // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
-            string article = String.Empty;
-            IList<string> pipeTexts = new List<string>();
-            int lastIndex = -1;
-            int pipeCounter = 0;
-            for (int i = 2; i < text.Length; i++)
-            {
-                char c = text[i];
-
-                // }}が見つかったら、処理正常終了
-                if (StringUtils.StartsWith(text, "}}", i))
-                {
-                    lastIndex = ++i;
-                    break;
-                }
-
-                // | が含まれている場合、以降の文字列は引数などとして扱う
-                if (c == '|')
-                {
-                    ++pipeCounter;
-                    pipeTexts.Add(String.Empty);
-                    continue;
-                }
-
-                // 変数([[{{{1}}}]]とか)の再帰チェック
-                string dummy;
-                string variable;
-                int index = this.ChkVariable(out variable, out dummy, text, i);
-                if (index != -1)
-                {
-                    i = index;
-                    if (pipeCounter > 0)
-                    {
-                        pipeTexts[pipeCounter - 1] += variable;
-                    }
-                    else
-                    {
-                        article += variable;
-                    }
-
-                    continue;
-                }
-
-                // | の前のとき
-                if (pipeCounter <= 0)
-                {
-                    // 変数以外で < > [ ] { } が含まれている場合、リンクは無効
-                    if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}'))
-                    {
-                        break;
-                    }
-
-                    article += c;
-                }
-                else
-                {
-                    // | の後のとき
-                    if (c == '<')
-                    {
-                        string subtext = text.Substring(i);
-                        string value;
-                        if (LazyXmlParser.TryParseComment(subtext, out value))
-                        {
-                            // コメント(<!--)が含まれている場合、リンクは無効
-                            break;
-                        }
-                        else if (MediaWikiPage.TryParseNowiki(subtext, out value))
-                        {
-                            // nowikiブロック
-                            i += value.Length - 1;
-                            pipeTexts[pipeCounter - 1] += value;
-                            continue;
-                        }
-                    }
-
-                    // リンク [[ {{ ({{test|[[例]]}}とか)の再帰チェック
-                    Link l;
-                    index = this.ChkLinkText(out l, text, i);
-                    if (index != -1)
-                    {
-                        i = index;
-                        pipeTexts[pipeCounter - 1] += l.OriginalText;
-                        continue;
-                    }
-
-                    pipeTexts[pipeCounter - 1] += c;
-                }
-            }
-
-            // 解析失敗
-            if (lastIndex < 0)
-            {
-                return false;
-            }
-
-            // 解析に成功した場合、結果を出力値に設定
-            link = new Link();
-            link.IsTemplateTag = true;
-
-            // 変数ブロックの文字列をリンクのテキストに設定
-            link.OriginalText = text.Substring(0, lastIndex + 1);
-
-            // 前後のスペース・改行は削除(見出しは後ろのみ)
-            link.Title = article.Trim();
-
-            // | 以降はそのまま設定
-            link.PipeTexts = pipeTexts;
-
-            // 記事名から情報を抽出
-            // サブページ
-            if (link.Title.StartsWith("/") == true)
-            {
-                link.IsSubpage = true;
-            }
-            else if (link.Title.StartsWith(":"))
-            {
-                // 先頭が :
-                link.IsColon = true;
-                link.Title = link.Title.TrimStart(':').TrimStart();
-            }
-
-            // 先頭が msgnw:
-            link.IsMsgnw = link.Title.ToLower().StartsWith(Msgnw.ToLower());
-            if (link.IsMsgnw)
-            {
-                link.Title = link.Title.Substring(Msgnw.Length);
-            }
-
-            // 記事名直後の改行の有無
-            if (article.TrimEnd(' ').EndsWith("\n"))
-            {
-                link.Enter = true;
-            }
-
-            return true;
-        }
-
-        #endregion
-
-        /// <summary>
-        /// 渡されたテキストの指定された位置に存在するWikipediaの内部リンク・テンプレートをチェック。
-        /// </summary>
-        /// <param name="link">解析したリンク。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <returns>正常時の戻り値には、]]の後ろの]の位置のインデックスを返す。異常時は-1。</returns>
-        public int ChkLinkText(out Link link, string text, int index)
-        {
-            // 入力値に応じて、処理を振り分け
-            if (StringUtils.StartsWith(text, "[[", index))
-            {
-                // 内部リンク
-                if (this.TryParseLink(text.Substring(index), out link))
-                {
-                    return index + link.OriginalText.Length - 1;
-                }
-            }
-            else if (StringUtils.StartsWith(text, "{{", index))
-            {
-                // テンプレート
-                if (this.TryParseTemplate(text.Substring(index), out link))
-                {
-                    return index + link.OriginalText.Length - 1;
-                }
-            }
-
-            // 出力値初期化。リンク以外の場合、nullを返す
-            link = null;
-            return -1;
-        }
-
-        /// <summary>
-        /// 渡されたテキストの指定された位置に存在する変数を解析。
-        /// </summary>
-        /// <param name="variable">解析した変数。</param>
-        /// <param name="value">変数のパラメータ値。</param>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="index">解析開始インデックス。</param>
-        /// <returns>正常時の戻り値には、変数の終了位置のインデックスを返す。異常時は-1。</returns>
-        public int ChkVariable(out string variable, out string value, string text, int index)
-        {
-            // 出力値初期化
-            int lastIndex = -1;
-            variable = String.Empty;
-            value = String.Empty;
-
-            // 入力値確認
-            if (!StringUtils.StartsWith(text, "{{{", index))
-            {
-                return lastIndex;
-            }
-
-            // ブロック終了まで取得
-            bool pipeFlag = false;
-            for (int i = index + 3; i < text.Length; i++)
-            {
-                // 終了条件のチェック
-                if (StringUtils.StartsWith(text, "}}}", i))
-                {
-                    lastIndex = i + 2;
-                    break;
-                }
-
-                if (text[i] == '<')
-                {
-                    string comment;
-                    if (LazyXmlParser.TryParseComment(text.Substring(i), out comment))
-                    {
-                        // コメント(<!--)ブロック
-                        i += comment.Length - 1;
-                        continue;
-                    }
-                }
-
-                // | が含まれている場合、以降の文字列は代入された値として扱う
-                if (text[i] == '|')
-                {
-                    pipeFlag = true;
-                }
-                else if (!pipeFlag)
-                {
-                    // | の前のとき
-                    // ※Wikipediaの仕様上は、{{{1{|表示}}} のように変数名の欄に { を
-                    //   含めることができるようだが、判別しきれないので、エラーとする
-                    //   (どうせ意図してそんなことする人は居ないだろうし・・・)
-                    if (text[i] == '{')
-                    {
-                        break;
-                    }
-                }
-                else
-                {
-                    // | の後のとき
-                    if (text[i] == '<')
-                    {
-                        string nowiki;
-                        if (MediaWikiPage.TryParseNowiki(text.Substring(i), out nowiki))
-                        {
-                            // nowikiブロック
-                            i += nowiki.Length - 1;
-                            value += nowiki;
-                            continue;
-                        }
-                    }
-
-                    // 変数({{{1|{{{2}}}}}}とか)の再帰チェック
-                    string var;
-                    string dummy;
-                    int subindex = this.ChkVariable(out var, out dummy, text, i);
-                    if (subindex != -1)
-                    {
-                        i = subindex;
-                        value += var;
-                        continue;
-                    }
-
-                    // リンク [[ {{ ({{{1|[[test]]}}}とか)の再帰チェック
-                    Link link;
-                    subindex = this.ChkLinkText(out link, text, i);
-                    if (subindex != -1)
-                    {
-                        i = subindex;
-                        value += link.OriginalText;
-                        continue;
-                    }
-
-                    value += text[i];
-                }
-            }
-
-            // 変数ブロックの文字列を出力値に設定
-            if (lastIndex != -1)
-            {
-                variable = text.Substring(index, lastIndex - index + 1);
-            }
-            else
-            {
-                // 正常な構文ではなかった場合、出力値をクリア
-                variable = String.Empty;
-                value = String.Empty;
-            }
-
-            return lastIndex;
-        }
-
-        #endregion
-
-        #region 内部処理用インスタンスメソッド
-
-        /// <summary>
-        /// ページが指定された番号の名前空間に所属するかをチェック。
-        /// </summary>
-        /// <param name="id">名前空間のID。</param>
-        /// <returns><c>true</c> 所属する。</returns>
-        protected bool IsNamespacePage(int id)
-        {
-            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
-            IList<string> prefixes = this.Website.Namespaces[id];
-            if (prefixes != null)
-            {
-                string title = this.Title.ToLower();
-                foreach (string prefix in prefixes)
-                {
-                    if (title.StartsWith(prefix.ToLower() + ":"))
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// オブジェクトがメソッドの実行に不完全な状態でないか検証する。
-        /// 不完全な場合、例外をスローする。
-        /// </summary>
-        /// <exception cref="InvalidOperationException">オブジェクトは不完全。</exception>
-        protected void ValidateIncomplete()
-        {
-            if (String.IsNullOrEmpty(this.Text))
-            {
-                // ページ本文が設定されていない場合不完全と判定
-                throw new InvalidOperationException("Text is unset");
-            }
-        }
-
-        /// <summary>
-        /// 現在のページをリダイレクトとして解析する。
-        /// </summary>
-        /// <returns>リダイレクトの場合<c>true</c>。</returns>
-        /// <remarks>リダイレクトの場合、転送先ページ名をプロパティに格納。</remarks>
-        private bool TryParseRedirect()
-        {
-            // 日本語版みたいに、#REDIRECTと言語固有の#転送みたいなのがあると思われるので、
-            // 翻訳元言語とデフォルトの設定でチェック
-            this.Redirect = null;
-            for (int i = 0; i < 2; i++)
-            {
-                string format = this.Website.Redirect;
-                if (i == 1)
-                {
-                    format = Properties.Settings.Default.MediaWikiRedirect;
-                }
-
-                if (!String.IsNullOrEmpty(format)
-                    && this.Text.ToLower().StartsWith(format.ToLower()))
-                {
-                    Link link;
-                    if (this.TryParseLink(this.Text.Substring(format.Length).TrimStart(), out link))
-                    {
-                        this.Redirect = link;
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// 渡されたTemplate:Documentationの呼び出しから、指定された言語コードへの言語間リンクを返す。
-        /// </summary>
-        /// <param name="link">テンプレート呼び出しのリンク。</param>
-        /// <param name="code">言語コード。</param>
-        /// <returns>言語間リンク先の記事名。見つからない場合またはパラメータが対象外の場合は空。</returns>
-        /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
-        private string GetDocumentationInterWiki(Link link, string code)
-        {
-            // テンプレートタグか、この言語にTemplate:Documentationの設定がされているかを確認
-            string docTitle = this.Website.DocumentationTemplate;
-            if (!link.IsTemplateTag || String.IsNullOrEmpty(docTitle))
-            {
-                return String.Empty;
-            }
-
-            // Documentationテンプレートのリンクかを確認
-            if (link.Title.ToLower() != docTitle.ToLower())
-            {
-                // 名前空間で一致していない可能性があるので、名前空間を取ってもう一度判定
-                int index = docTitle.IndexOf(':');
-                if (new MediaWikiPage(this.Website, docTitle).IsTemplate()
-                    && index >= 0 && index + 1 < docTitle.Length)
-                {
-                    docTitle = docTitle.Substring(docTitle.IndexOf(':') + 1);
-                }
-
-                if (link.Title.ToLower() != docTitle.ToLower())
-                {
-                    // どちらでも一致しない場合は別のテンプレートなりなので無視
-                    return String.Empty;
-                }
-            }
-
-            // 解説記事名を確認
-            string subtitle = link.PipeTexts.ElementAtOrDefault(0);
-            if (String.IsNullOrWhiteSpace(subtitle) || subtitle.Contains('='))
-            {
-                // 指定されていない場合はデフォルトのページを探索
-                subtitle = this.Website.DocumentationTemplateDefaultPage;
-            }
-
-            if (String.IsNullOrEmpty(subtitle))
-            {
-                return String.Empty;
-            }
-
-            // サブページの場合、親ページのページ名を付加
-            // TODO: サブページの仕組みについては要再検討
-            if (subtitle.StartsWith("/"))
-            {
-                subtitle = this.Title + subtitle;
-            }
-
-            // 解説ページから言語間リンクを取得
-            MediaWikiPage subpage = null;
-            try
-            {
-                // ※ 本当はここでの取得状況も画面に見せたいが、今のつくりで
-                //    そうするとややこしくなるので隠蔽する。
-                subpage = this.Website.GetPage(subtitle) as MediaWikiPage;
-            }
-            catch (Exception ex)
-            {
-                System.Diagnostics.Debug.WriteLine(ex.StackTrace);
-            }
-
-            if (subpage != null)
-            {
-                string interWiki = subpage.GetInterWiki(code);
-                if (!String.IsNullOrEmpty(interWiki))
-                {
-                    return interWiki;
-                }
-            }
-
-            // 未発見の場合、空文字列
-            return String.Empty;
-        }
-
-        #endregion
-
-        #region 内部クラス
-
-        /// <summary>
-        /// Wikipediaのリンクの要素を格納するための構造体。
-        /// </summary>
-        public class Link
-        {
-            #region プロパティ
-
-            /// <summary>
-            /// リンクのオブジェクト作成時の元テキスト([[~]])。
-            /// </summary>
-            public string OriginalText
-            {
-                get;
-                //// TODO: このクラスにParseを移動完了したら、protectedにする
-                set;
-            }
-
-            /// <summary>
-            /// リンクの記事名。
-            /// </summary>
-            /// <remarks>リンクに記載されていた記事名であり、名前空間の情報などは含まない可能性があるため注意。</remarks>
-            public string Title
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// リンクのセクション名(#)。
-            /// </summary>
-            public string Section
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// リンクのパイプ後の文字列(|)。
-            /// </summary>
-            public IList<string> PipeTexts
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// 言語間または他プロジェクトへのリンクの場合、コード。
-            /// </summary>
-            public string Code
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// テンプレートタグで書かれたリンク({{~}})か?
-            /// </summary>
-            /// <remarks>
-            /// 必ずしもリンク先がテンプレートであることを意味しない。
-            /// 普通のページをこの書式でテンプレートのように使用することも可能である。
-            /// </remarks>
-            public bool IsTemplateTag
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// 記事名の先頭がサブページを示す / で始まるか?
-            /// </summary>
-            /// <remarks>※ 2010年9月現在、この処理には不足あり。</remarks>
-            public bool IsSubpage
-            {
-                // TODO: サブページには相対パスで[[../~]]や[[../../~]]というような書き方もある模様。
-                //       この辺りの処理は[[Help:サブページ]]を元に全面的に見直す必要あり
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// リンクの先頭が : で始まるかを示すフラグ。
-            /// </summary>
-            public bool IsColon
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// テンプレートの場合に、テンプレートのソースをそのまま出力することを示す msgnw: が付加されているか?
-            /// </summary>
-            public bool IsMsgnw
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// テンプレートの場合に、記事名の後で改行が入るか?
-            /// </summary>
-            public bool Enter
-            {
-                get;
-                set;
-            }
-
-            /// <summary>
-            /// リンクが表すテキスト([[~]])。
-            /// </summary>
-            public string Text
-            {
-                get
-                {
-                    // 戻り値初期化
-                    StringBuilder b = new StringBuilder();
-
-                    // 枠の設定
-                    string startSign = "[[";
-                    string endSign = "]]";
-                    if (this.IsTemplateTag)
-                    {
-                        startSign = "{{";
-                        endSign = "}}";
-                    }
-
-                    // 先頭の枠の付加
-                    b.Append(startSign);
-
-                    // 先頭の : の付加
-                    if (this.IsColon)
-                    {
-                        b.Append(':');
-                    }
-
-                    // msgnw: (テンプレートを<nowiki>タグで挟む)の付加
-                    if (this.IsTemplateTag && this.IsMsgnw)
-                    {
-                        b.Append(MediaWikiPage.Msgnw);
-                    }
-
-                    // 言語コード・他プロジェクトコードの付加
-                    if (!String.IsNullOrEmpty(this.Code))
-                    {
-                        b.Append(this.Code);
-                    }
-
-                    // リンクの付加
-                    if (!String.IsNullOrEmpty(this.Title))
-                    {
-                        b.Append(this.Title);
-                    }
-
-                    // セクション名の付加
-                    if (!String.IsNullOrEmpty(this.Section))
-                    {
-                        b.Append('#');
-                        b.Append(this.Section);
-                    }
-
-                    // 改行の付加
-                    if (this.Enter)
-                    {
-                        b.Append('\n');
-                    }
-
-                    // パイプ後の文字列の付加
-                    if (this.PipeTexts != null)
-                    {
-                        foreach (string s in this.PipeTexts)
-                        {
-                            b.Append('|');
-                            if (!String.IsNullOrEmpty(s))
-                            {
-                                b.Append(s);
-                            }
-                        }
-                    }
-
-                    // 終わりの枠の付加
-                    b.Append(endSign);
-                    return b.ToString();
-                }
-            }
-
-            #endregion
-
-            #region 公開メソッド
-
-            /// <summary>
-            /// このオブジェクトを表すリンク文字列を返す。
-            /// </summary>
-            /// <returns>オブジェクトを表すリンク文字列。</returns>
-            public override string ToString()
-            {
-                // リンクを表すテキスト、ならびに元テキストを返す
-                return this.Text + "<!-- " + this.OriginalText + " -->";
-            }
-
-            #endregion
-        }
-
-        #endregion
-    }
-}
index d0e343b..d024390 100644 (file)
@@ -3,7 +3,7 @@
 //      言語間の翻訳パターンをあらわすモデルクラスソース</summary>
 //
 // <copyright file="TranslationDictionary.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -201,7 +201,7 @@ namespace Honememo.Wptscs.Models
                 {
                     writer.WriteAttributeString(
                         "Timestamp",
-                        item.Value.Timestamp.Value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"));
+                        XmlConvert.ToString(item.Value.Timestamp.Value, XmlDateTimeSerializationMode.Utc));
                 }
 
                 writer.WriteEndElement();
index 31c8947..3617bb9 100644 (file)
@@ -3,7 +3,7 @@
 //      言語間の対訳表をあらわすモデルクラスソース</summary>
 //
 // <copyright file="TranslationTable.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/Wptscs/Parsers/MediaWikiHeading.cs b/Wptscs/Parsers/MediaWikiHeading.cs
new file mode 100644 (file)
index 0000000..11c9234
--- /dev/null
@@ -0,0 +1,82 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiページの見出し要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="MediaWikiHeading.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiページの見出し要素をあらわすモデルクラスです。
+    /// </summary>
+    public class MediaWikiHeading : ListElement
+    {
+        #region 定数
+
+        /// <summary>
+        /// 見出しの開始文字。
+        /// </summary>
+        public static readonly char DelimiterStart = '=';
+
+        /// <summary>
+        /// 見出しの閉じ文字。
+        /// </summary>
+        public static readonly char DelimiterEnd = '=';
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// 見出し階層。
+        /// </summary>
+        public int Level
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// この要素を書式化した見出し文字列を返す。
+        /// </summary>
+        /// <returns>見出し文字列。</returns>
+        protected override string ToStringImpl()
+        {
+            // 戻り値初期化
+            StringBuilder b = new StringBuilder();
+
+            // 開始文字の付加
+            for (int i = 0; i < this.Level; i++)
+            {
+                b.Append(MediaWikiHeading.DelimiterStart);
+            }
+
+            // 見出し文字列の設定
+            b.Append(base.ToStringImpl());
+
+            // 閉じ文字の付加
+            for (int i = 0; i < this.Level; i++)
+            {
+                b.Append(MediaWikiHeading.DelimiterEnd);
+            }
+
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiHeadingParser.cs b/Wptscs/Parsers/MediaWikiHeadingParser.cs
new file mode 100644 (file)
index 0000000..91c8363
--- /dev/null
@@ -0,0 +1,171 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiの見出しを解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiHeadingParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiの見出しを解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiHeadingParser : AbstractParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="MediaWikiParser"/>。
+        /// </summary>
+        private MediaWikiParser parser;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定された<see cref="MediaWikiParser"/>を元に見出しを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="MediaWikiParser"/>。</param>
+        public MediaWikiHeadingParser(MediaWikiParser parser)
+        {
+            this.parser = parser;
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたテキストをMediaWikiの見出し(==関連項目==みたいなの)として解析する。
+        /// </summary>
+        /// <param name="s">行頭からの文字列。</param>
+        /// <param name="result">解析した見出し。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>
+        /// 見出しは行単位で有効になるため、行頭からの文字列を渡す必要がある。
+        /// (ただし、--&lt;==見出し== みたいな事もできるので、その場合は=の開始部分から。)
+        /// </remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 出力値初期化
+            result = null;
+
+            // 始まりの = の数を数える
+            // ※ 構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
+            // TODO: Wikipediaでは <!--test-->=<!--test-->=関連項目<!--test-->==<!--test--> みたいなのでも認識するが、2012年1月現在未対応
+            //      (昔は対応していたが、その過程でコメントが失われれるつくりになっており、
+            //        Parser周りを整理した際に情報を取りこぼさないことを最優先としたため取り止め。)
+            int startCount = 0;
+            for (int i = 0; i < s.Length; i++)
+            {
+                if (s[i] == MediaWikiHeading.DelimiterStart)
+                {
+                    ++startCount;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            // = で始まる行ではない場合、処理対象外
+            if (startCount < 1)
+            {
+                return false;
+            }
+
+            // 始まりの = の次の文字から、行の終わりまでを解析
+            // (=={{lang\n|ja|見出し}}== みたいに何かの中にある改行はOK。Wikipediaでも認識された)
+            IElement element;
+            this.parser.TryParseToDelimiter(StringUtils.Substring(s, startCount), out element, "\r", "\n");
+
+            // 終わりの = の数を確認
+            // ※ この処理だと中身の無い行(====とか)は弾かれてしまうが、どうせ処理できないので許容する
+            string substr = element.ToString().TrimEnd();
+            int endCount = 0;
+            for (int i = substr.Length - 1; i >= 0; i--)
+            {
+                if (substr[i] == MediaWikiHeading.DelimiterEnd)
+                {
+                    ++endCount;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            // = で終わる行ではない場合、処理対象外
+            if (endCount < 1)
+            {
+                return false;
+            }
+
+            // 始まりと終わり、=の少ないほうにあわせる(==test===とか用の処理)
+            int level = startCount;
+            if (startCount > endCount)
+            {
+                level = endCount;
+            }
+
+            // 確定した見出しの階層から、見出し内部の文字列を抽出。内部要素を再帰的に探索する
+            // ※ 二重処理になってしまうが、後ろの = を取り除くと微妙にややこしいことになりそうだったので
+            //    見出しは処理件数も少なく、深い再帰もないはずなので、影響ない・・・はず
+            IElement innerElement;
+            if (!this.parser.TryParse(substr.Substring(0, substr.Length - level), out innerElement))
+            {
+                return false;
+            }
+
+            // 解析に成功した場合、結果を出力値に設定
+            result = this.MakeElement(innerElement, level, s.Substring(0, startCount + element.ToString().Length));
+            return true;
+        }
+        
+        #endregion
+
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// 見出しタグを解析した結果から、MediaWiki見出し要素を生成する。
+        /// </summary>
+        /// <param name="innerElement">見出しタグ上の見出し部分の要素。</param>
+        /// <param name="level">見出しの階層。</param>
+        /// <param name="parsedString">解析した見出しタグの文字列。</param>
+        /// <returns>生成した見出し要素。</returns>
+        private MediaWikiHeading MakeElement(IElement innerElement, int level, string parsedString)
+        {
+            MediaWikiHeading heading = new MediaWikiHeading();
+
+            // 解析した見出しの素のテキストを保存
+            heading.ParsedString = parsedString;
+
+            // 見出しの階層を保存
+            heading.Level = level;
+
+            // 内部要素については、結果がリストの場合マージ、それ以外はそのままElementに代入
+            if (innerElement.GetType() == typeof(ListElement))
+            {
+                heading.AddRange((ListElement)innerElement);
+            }
+            else
+            {
+                heading.Add(innerElement);
+            }
+
+            return heading;
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiLink.cs b/Wptscs/Parsers/MediaWikiLink.cs
new file mode 100644 (file)
index 0000000..f027550
--- /dev/null
@@ -0,0 +1,195 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiページの内部リンク要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="MediaWikiLink.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+
+    /// <summary>
+    /// MediaWikiページの内部リンク要素をあらわすモデルクラスです。
+    /// </summary>
+    public class MediaWikiLink : AbstractElement
+    {
+        #region 定数
+
+        /// <summary>
+        /// 内部リンクの開始タグ。
+        /// </summary>
+        public static readonly string DelimiterStart = "[[";
+
+        /// <summary>
+        /// 内部リンクの閉じタグ。
+        /// </summary>
+        public static readonly string DelimiterEnd = "]]";
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたタイトルの内部リンク要素をあらわすインスタンスを生成する。
+        /// </summary>
+        /// <param name="title">記事名。</param>
+        public MediaWikiLink(string title) : this()
+        {
+            this.Title = title;
+        }
+
+        /// <summary>
+        /// 内部リンク要素をあらわす空のインスタンスを生成する。
+        /// </summary>
+        public MediaWikiLink()
+        {
+            this.PipeTexts = new List<IElement>();
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// リンクの記事名。
+        /// </summary>
+        /// <remarks>リンクに記載されていた記事名であり、名前空間の情報などは含まない可能性があるため注意。</remarks>
+        public virtual string Title
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// リンクのセクション名(#)。
+        /// </summary>
+        public virtual string Section
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// リンクのパイプ後の文字列(|)。
+        /// </summary>
+        public virtual IList<IElement> PipeTexts
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// 言語間または他プロジェクトへのリンクの場合、コード。
+        /// </summary>
+        public virtual string Code
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// リンクの先頭が : で始まるかを示すフラグ。
+        /// </summary>
+        public virtual bool IsColon
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// 記事名の先頭がサブページを示す / で始まるか?
+        /// </summary>
+        /// <remarks>※ 2011年5月現在、この処理には不足あり。</remarks>
+        public virtual bool IsSubpage
+        {
+            // TODO: サブページには相対パスで[[../~]]や[[../../~]]というような書き方もある模様。
+            //       この辺りの処理は[[Help:サブページ]]を元に全面的に見直す必要あり
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 公開メソッド
+
+        /// <summary>
+        /// この要素を書式化したリンク先部分のテキスト(先頭の:から言語コード, 記事名, セクションまで)を返す。
+        /// </summary>
+        /// <returns>記事名部分のテキスト。</returns>
+        public string GetLinkString()
+        {
+            StringBuilder b = new StringBuilder();
+
+            // 先頭の : の付加
+            if (this.IsColon)
+            {
+                b.Append(':');
+            }
+
+            // 言語コード・他プロジェクトコードの付加
+            if (!String.IsNullOrEmpty(this.Code))
+            {
+                b.Append(this.Code);
+                b.Append(':');
+            }
+
+            // 記事名の付加
+            if (!String.IsNullOrEmpty(this.Title))
+            {
+                b.Append(this.Title);
+            }
+
+            // セクション名の付加
+            if (this.Section != null)
+            {
+                b.Append('#');
+                b.Append(this.Section);
+            }
+
+            return b.ToString();
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// この要素を書式化した内部リンクテキストを返す。
+        /// </summary>
+        /// <returns>内部リンクテキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            // 戻り値初期化
+            StringBuilder b = new StringBuilder();
+            
+            // 開始タグの付加
+            b.Append(MediaWikiLink.DelimiterStart);
+
+            // リンク先部分のテキスト(先頭の:から言語コード, 記事名, セクションまで)を設定
+            b.Append(this.GetLinkString());
+
+            // パイプ後の文字列の付加
+            if (this.PipeTexts != null)
+            {
+                foreach (IElement p in this.PipeTexts)
+                {
+                    b.Append('|');
+                    b.Append(p.ToString());
+                }
+            }
+
+            // 閉じタグの付加
+            b.Append(MediaWikiLink.DelimiterEnd);
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiLinkParser.cs b/Wptscs/Parsers/MediaWikiLinkParser.cs
new file mode 100644 (file)
index 0000000..f9ec57e
--- /dev/null
@@ -0,0 +1,226 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiの内部リンク要素を解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiLinkParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiの内部リンク要素を解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiLinkParser : AbstractParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="MediaWikiParser"/>。
+        /// </summary>
+        private MediaWikiParser parser;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定された<see cref="MediaWikiParser"/>を元に見出しを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="MediaWikiParser"/>。</param>
+        public MediaWikiLinkParser(MediaWikiParser parser)
+        {
+            this.parser = parser;
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッド
+        
+        /// <summary>
+        /// 渡されたテキストをMediaWikiの内部リンクとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したリンク。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 出力値初期化
+            result = null;
+
+            // 開始条件 [[ のチェック
+            if (!s.StartsWith(MediaWikiLink.DelimiterStart))
+            {
+                return false;
+            }
+
+            // 構文を解析して、[[]]内部の文字列を取得
+            // ※ 構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
+            StringBuilder article = new StringBuilder();
+            StringBuilder section = null;
+            IList<IElement> pipeTexts = new List<IElement>();
+            int lastIndex = -1;
+            for (int i = MediaWikiLink.DelimiterStart.Length; i < s.Length; i++)
+            {
+                char c = s[i];
+
+                // 終了条件 ]] のチェック
+                if (StringUtils.StartsWith(s, MediaWikiLink.DelimiterEnd, i))
+                {
+                    lastIndex = ++i;
+                    break;
+                }
+
+                // [[記事名#セクション|表示名]], [[File:ファイル名|パラメータ1|パラメータ2]]
+                // といったフォーマットのため、| の前後で処理を変更
+                if (c == '|')
+                {
+                    // | の後(表示名やパラメータ)には何でもありえるので親のパーサーで再帰的に解析
+                    IElement element;
+                    if (!this.parser.TryParseToDelimiter(StringUtils.Substring(s, i + 1), out element, MediaWikiLink.DelimiterEnd, "|"))
+                    {
+                        // 平文でも解析するメソッドのため、基本的に失敗することは無い
+                        // 万が一の場合は解析失敗とする
+                        break;
+                    }
+
+                    i += element.ToString().Length;
+                    pipeTexts.Add(element);
+                    continue;
+                }
+                else
+                {
+                    // | の前(記事名などの部分)のとき、変数・コメントの再帰チェック
+                    IElement element;
+                    if (this.TryParseAt(s, i, out element, this.parser.CommentParser, this.parser.VariableParser))
+                    {
+                        // 変数・コメントなら、解析したブロック単位で記事名orセクションに追加
+                        i += element.ToString().Length - 1;
+                        if (section != null)
+                        {
+                            section.Append(element.ToString());
+                        }
+                        else
+                        {
+                            article.Append(element.ToString());
+                        }
+
+                        continue;
+                    }
+
+                    // 変数・コメント以外で { } または < > [ ] \n が含まれている場合、リンクは無効
+                    // TODO: <noinclude>も含まれていてOKだが、2012年1月現在未対応
+                    if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}') || (c == '\n'))
+                    {
+                        break;
+                    }
+
+                    if (section == null)
+                    {
+                        // [[記事名#セクション]] のため、記事名解析中に # が登場したら格納先変更
+                        if (c == '#')
+                        {
+                            section = new StringBuilder();
+                        }
+                        else
+                        {
+                            // それ以外の普通の文字なら1文字ずつ記事名に追加
+                            article.Append(c);
+                        }
+                    }
+                    else
+                    {
+                        // セクション解析中の場合、普通の文字なら1文字ずつセクションに追加
+                        section.Append(c);
+                    }
+                }
+            }
+
+            // 終了条件でループを抜けていない場合、解析失敗
+            if (lastIndex < 0)
+            {
+                return false;
+            }
+
+            // 解析に成功した場合、結果を出力値に設定
+            result = this.MakeElement(
+                article.ToString(),
+                section != null ? section.ToString() : null,
+                pipeTexts,
+                s.Substring(0, lastIndex + 1));
+            return true;
+        }
+
+        /// <summary>
+        /// 渡された文字が<see cref="TryParse"/>等の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public override bool IsPossibleParse(char c)
+        {
+            return MediaWikiLink.DelimiterStart[0] == c;
+        }
+        
+        #endregion
+        
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// 内部リンクタグを解析した結果から、MediaWikiリンク要素を生成する。
+        /// </summary>
+        /// <param name="article">内部リンクタグ上の記事名部分の文字列。</param>
+        /// <param name="section">内部リンクタグ上のセクション部分の文字列。</param>
+        /// <param name="pipeTexts">内部リンクタグ上のパイプ後の文字列。</param>
+        /// <param name="parsedString">解析した内部リンクタグの文字列。</param>
+        /// <returns>生成したリンク要素。</returns>
+        private MediaWikiLink MakeElement(string article, string section, IList<IElement> pipeTexts, string parsedString)
+        {
+            MediaWikiLink link = new MediaWikiLink();
+
+            // 解析した内部リンクの素のテキストを保存
+            link.ParsedString = parsedString;
+
+            // 前後のスペースは削除(見出しは後ろのみ)
+            link.Title = article.Trim();
+            link.Section = section != null ? section.TrimEnd() : null;
+
+            // | 以降は再帰的に解析した値を設定
+            link.PipeTexts = pipeTexts;
+
+            // 記事名から情報を抽出
+            if (link.Title.StartsWith("/"))
+            {
+                // サブページ([[/サブページ]]みたいなの)
+                link.IsSubpage = true;
+            }
+            else if (link.Title.StartsWith(":"))
+            {
+                // 先頭が :([[:en:他言語版記事名]]みたいなの)
+                link.IsColon = true;
+                link.Title = link.Title.TrimStart(':').TrimStart();
+            }
+
+            // 標準名前空間以外で[[xxx:yyy]]のようになっている場合、言語コード
+            // ※ : を含む名前空間以外を全て言語コードと判定
+            if (link.Title.Contains(":") && new MediaWikiPage(this.parser.Website, link.Title).IsMain())
+            {
+                link.Code = link.Title.Substring(0, link.Title.IndexOf(':')).TrimEnd();
+                link.Title = link.Title.Substring(link.Title.IndexOf(':') + 1).TrimStart();
+            }
+
+            return link;
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiNowikiParser.cs b/Wptscs/Parsers/MediaWikiNowikiParser.cs
new file mode 100644 (file)
index 0000000..6b9d193
--- /dev/null
@@ -0,0 +1,96 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiのnowikiブロックを解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiNowikiParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiのnowikiブロックを解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiNowikiParser : XmlElementParser
+    {
+        #region 定数宣言
+
+        /// <summary>
+        /// nowikiタグ。
+        /// </summary>
+        private static readonly string nowikiTag = "nowiki";
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// MediaWikiのnowikiブロックを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="MediaWikiParser"/>。</param>
+        public MediaWikiNowikiParser(MediaWikiParser parser)
+        {
+            // nowikiブロックではMediaWikiの各種構文やコメントも含むhtmlタグも全て無効なため、
+            // 親クラスにそうした処理を含まない空のXMLParserを指定する。
+            // ※ HTML/XMLの扱い等に関する設定はMediaWiki全体のものを引き継ぐ
+            this.Parser = new XmlParser();
+            this.Parser.Parsers = new IParser[0];
+            this.Parser.IgnoreCase = parser.IgnoreCase;
+            this.Parser.IsHtml = parser.IsHtml;
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッド
+        
+        /// <summary>
+        /// 渡されたテキストをMediaWikiのnowikiブロックとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したnowikiブロック。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>
+        /// nowikiブロックと判定するには、1文字目が開始タグである必要がある。
+        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
+        /// </remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            result = null;
+            IElement element;
+            if (base.TryParse(s, out element))
+            {
+                XmlElement xmlElement = (XmlElement)element;
+                if (xmlElement.Name.ToLower() == MediaWikiNowikiParser.nowikiTag)
+                {
+                    // nowiki区間は内部要素を全てテキストとして扱う
+                    XmlTextElement innerElement = new XmlTextElement();
+                    StringBuilder b = new StringBuilder();
+                    foreach (IElement e in xmlElement)
+                    {
+                        b.Append(e.ToString());
+                    }
+
+                    innerElement.Raw = b.ToString();
+                    innerElement.ParsedString = b.ToString();
+                    xmlElement.Clear();
+                    xmlElement.Add(innerElement);
+                    result = xmlElement;
+                    return true;
+                }
+            }
+
+            return false;
+        }
+        
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiParser.cs b/Wptscs/Parsers/MediaWikiParser.cs
new file mode 100644 (file)
index 0000000..9497f80
--- /dev/null
@@ -0,0 +1,243 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiのページを解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiのページを解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiParser : XmlParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが対応するMediaWiki。
+        /// </summary>
+        private MediaWiki website;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたMediaWikiサーバーのページを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="site">このパーサーが対応するMediaWiki</param>
+        public MediaWikiParser(MediaWiki site)
+        {
+            this.Website = site;
+            this.CommentParser = new XmlCommentElementParser();
+            this.NowikiParser = new MediaWikiNowikiParser(this);
+            this.LinkParser = new MediaWikiLinkParser(this);
+            this.TemplateParser = new MediaWikiTemplateParser(this);
+            this.VariableParser = new MediaWikiVariableParser(this);
+            this.HeadingParser = new MediaWikiHeadingParser(this);
+        }
+
+        #endregion
+
+        #region 公開プロパティ
+
+        /// <summary>
+        /// このパーサーが対応するMediaWiki。
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><c>null</c>が指定された場合。</exception>
+        public MediaWiki Website
+        {
+            get
+            {
+                return this.website;
+            }
+
+            set
+            {
+                this.website = Validate.NotNull(value);
+            }
+        }
+
+        #endregion
+
+        #region 関連クラス公開プロパティ
+
+        // ※ 各要素のパーサーについては相互参照しているものが多々あり、
+        //    個別のクラスでnewされると危険なことから、ここで生成して公開する。
+
+        /// <summary>
+        /// パーサー内で使用するXMLコメント要素のパーサー。
+        /// </summary>
+        internal IParser CommentParser
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// パーサー内で使用するnowikiブロックのパーサー。
+        /// </summary>
+        internal IParser NowikiParser
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// パーサー内で使用するMediaWiki内部リンクのパーサー。
+        /// </summary>
+        internal IParser LinkParser
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// パーサー内で使用するMediaWikiテンプレートのパーサー。
+        /// </summary>
+        internal IParser TemplateParser
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// パーサー内で使用するMediaWiki変数のパーサー。
+        /// </summary>
+        internal IParser VariableParser
+        {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// パーサー内で使用するMediaWiki変数のパーサー。
+        /// </summary>
+        internal IParser HeadingParser
+        {
+            get;
+            private set;
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたMediaWikiページに対して、指定された終了条件を満たすまで解析を行う。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="condition">解析を終了するかの判定を行うデリゲート。</param>
+        /// <param name="result">解析結果。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>指定された終了条件を満たさない場合、最終位置まで解析を行う。</remarks>
+        public override bool TryParseToEndCondition(string s, IsEndCondition condition, out IElement result)
+        {
+            // 文字列を1文字ずつチェックし、その内容に応じた要素のリストを作成する
+            ListElement list = new ListElement();
+            StringBuilder b = new StringBuilder();
+            bool newLine = false;
+            for (int i = 0; i < s.Length; i++)
+            {
+                // 終了条件のチェック、未指定時は条件なし
+                if (condition != null && condition(s, i))
+                {
+                    break;
+                }
+
+                IElement innerElement;
+
+                if (s[i] == '\n')
+                {
+                    // 改行の場合、次回に見出しの解析が必要なため記録
+                    b.Append(s[i]);
+                    newLine = true;
+                    continue;
+                }
+                else if (newLine)
+                {
+                    // 見出しの解析
+                    newLine = false;
+                    if (this.TryParseAt(s, i, out innerElement, this.HeadingParser))
+                    {
+                        // それまでに解析済みのテキストを吐き出し、
+                        // その後に解析した要素を追加
+                        this.FlashText(ref list, ref b);
+                        list.Add(innerElement);
+                        i += innerElement.ToString().Length - 1;
+                        continue;
+                    }
+                }
+
+                // コメントの解析
+                if (this.TryParseAt(s, i, out innerElement, this.CommentParser))
+                {
+                    // それまでに解析済みのテキストを吐き出し、
+                    // その後に解析した要素を追加
+                    this.FlashText(ref list, ref b);
+                    list.Add(innerElement);
+                    i += innerElement.ToString().Length - 1;
+
+                    // コメント中に改行が含まれた場合も、見出しの処理を有効化する
+                    if (innerElement.ToString().Contains("\n"))
+                    {
+                        newLine = true;
+                    }
+
+                    continue;
+                }
+
+                // それ以外のnowiki, 変数, 内部リンク, テンプレートの各要素のTryParse処理を呼び出し
+                if (this.TryParseAt(
+                    s,
+                    i,
+                    out innerElement,
+                    this.NowikiParser,
+                    this.VariableParser,
+                    this.LinkParser,
+                    this.TemplateParser))
+                {
+                    // それまでに解析済みのテキストを吐き出し、
+                    // その後に解析した要素を追加
+                    this.FlashText(ref list, ref b);
+                    list.Add(innerElement);
+                    i += innerElement.ToString().Length - 1;
+                    continue;
+                }
+
+                // 通常の文字列はテキスト要素として積み上げる
+                b.Append(s[i]);
+            }
+
+            // 残っていれば最後に解析済みのテキストを吐き出し
+            this.FlashText(ref list, ref b);
+
+            result = list;
+            if (list.Count == 1)
+            {
+                // リストが1件であれば、その要素を直に返す
+                result = list[0];
+            }
+            else if (list.Count == 0)
+            {
+                // 何もなければ、空文字列だったものとして空のテキスト要素を返す
+                result = new TextElement();
+            }
+
+            return true;
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiRedirectParser.cs b/Wptscs/Parsers/MediaWikiRedirectParser.cs
new file mode 100644 (file)
index 0000000..75218d5
--- /dev/null
@@ -0,0 +1,74 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiのリダイレクトページを解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiRedirectParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiのリダイレクトページを解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiRedirectParser : MediaWikiParser
+    {
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたMediaWikiサーバーのページを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="site">このパーサーが対応するMediaWiki</param>
+        public MediaWikiRedirectParser(MediaWiki site)
+            : base(site)
+        {
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたテキストをMediaWikiのリダイレクトページとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したリダイレクトリンク。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        /// <remarks>MediaWikiのページ全体を渡す必要がある。</remarks>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 日本語版みたいに、#REDIRECTと言語固有の#転送みたいなのがあると思われるので、
+            // 翻訳元言語とデフォルトの設定でチェック
+            result = null;
+            for (int i = 0; i < 2; i++)
+            {
+                string format = this.Website.Redirect;
+                if (i == 1)
+                {
+                    format = Settings.Default.MediaWikiRedirect;
+                }
+
+                if (!String.IsNullOrEmpty(format)
+                    && s.ToLower().StartsWith(format.ToLower()))
+                {
+                    if (this.LinkParser.TryParse(s.Substring(format.Length).TrimStart(), out result))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+        
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiTemplate.cs b/Wptscs/Parsers/MediaWikiTemplate.cs
new file mode 100644 (file)
index 0000000..f2c1ba2
--- /dev/null
@@ -0,0 +1,174 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiページのテンプレート要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="MediaWikiTemplate.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiページのテンプレート要素をあらわすモデルクラスです。
+    /// </summary>
+    public class MediaWikiTemplate : MediaWikiLink
+    {
+        #region 定数
+
+        /// <summary>
+        /// テンプレートの開始タグ。
+        /// </summary>
+        public static readonly new string DelimiterStart = "{{";
+
+        /// <summary>
+        /// テンプレートの閉じタグ。
+        /// </summary>
+        public static readonly new string DelimiterEnd = "}}";
+
+        /// <summary>
+        /// msgnwの書式。
+        /// </summary>
+        public static readonly string Msgnw = "msgnw:";
+
+        #endregion
+
+        #region private変数
+
+        /// <summary>
+        /// テンプレートの記事名。
+        /// </summary>
+        private string title;
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定されたタイトルのテンプレート要素をあらわすインスタンスを生成する。
+        /// </summary>
+        /// <param name="title">テンプレート名。</param>
+        public MediaWikiTemplate(string title) : base(title)
+        {
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// テンプレートの記事名。
+        /// </summary>
+        /// <exception cref="ArgumentNullException">記事名がnullの場合。</exception>
+        /// <exception cref="ArgumentException">記事名が空の場合。</exception>
+        /// <remarks>テンプレートに記載されていた記事名であり、名前空間の情報などは含まない可能性があるため注意。</remarks>
+        public override string Title
+        {
+            get
+            {
+                return this.title;
+            }
+
+            set
+            {
+                this.title = Validate.NotBlank(value);
+            }
+        }
+
+        /// <summary>
+        /// テンプレートのソースをそのまま出力することを示す msgnw: が付加されているか?
+        /// </summary>
+        public virtual bool IsMsgnw
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// 記事名の後で改行が入るか?
+        /// </summary>
+        public virtual bool NewLine
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// この要素を書式化したテンプレートテキストを返す。
+        /// </summary>
+        /// <returns>テンプレートテキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            // 戻り値初期化
+            StringBuilder b = new StringBuilder();
+            
+            // 開始タグの付加
+            b.Append(MediaWikiTemplate.DelimiterStart);
+
+            // 先頭の : の付加(テンプレート名前空間ではなく標準名前空間となる)
+            if (this.IsColon)
+            {
+                b.Append(':');
+            }
+
+            // msgnw: (テンプレートを<nowiki>タグで挟む)の付加
+            if (this.IsMsgnw)
+            {
+                b.Append(MediaWikiTemplate.Msgnw);
+            }
+
+            // 言語コード・他プロジェクトコードの付加
+            if (!String.IsNullOrEmpty(this.Code))
+            {
+                b.Append(this.Code);
+                b.Append(':');
+            }
+
+            // テンプレート名の付加
+            if (!String.IsNullOrEmpty(this.Title))
+            {
+                b.Append(this.Title);
+            }
+
+            // セクション名の付加
+            if (this.Section != null)
+            {
+                b.Append('#');
+                b.Append(this.Section);
+            }
+
+            // 改行の付加
+            if (this.NewLine)
+            {
+                b.Append('\n');
+            }
+
+            // パイプ後の文字列の付加
+            if (this.PipeTexts != null)
+            {
+                foreach (IElement p in this.PipeTexts)
+                {
+                    b.Append('|');
+                    b.Append(p.ToString());
+                }
+            }
+
+            // 閉じタグの付加
+            b.Append(MediaWikiTemplate.DelimiterEnd);
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiTemplateParser.cs b/Wptscs/Parsers/MediaWikiTemplateParser.cs
new file mode 100644 (file)
index 0000000..4e7b97c
--- /dev/null
@@ -0,0 +1,193 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiのテンプレート要素を解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiTemplateParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiのテンプレート要素を解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiTemplateParser : AbstractParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="MediaWikiParser"/>。
+        /// </summary>
+        private MediaWikiParser parser;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定された<see cref="MediaWikiParser"/>を元に見出しを解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="MediaWikiParser"/>。</param>
+        public MediaWikiTemplateParser(MediaWikiParser parser)
+        {
+            this.parser = parser;
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッド
+        
+        /// <summary>
+        /// 渡されたテキストをMediaWikiのテンプレートとして解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析したリンク。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 出力値初期化
+            result = null;
+
+            // 開始条件 {{ のチェック
+            if (!s.StartsWith(MediaWikiTemplate.DelimiterStart))
+            {
+                return false;
+            }
+
+            // 構文を解析して、{{}}内部の文字列を取得
+            // ※構文はWikipediaのプレビューで色々試して確認、足りなかったり間違ってたりするかも・・・
+            StringBuilder article = new StringBuilder();
+            IList<IElement> pipeTexts = new List<IElement>();
+            int lastIndex = -1;
+            for (int i = MediaWikiTemplate.DelimiterStart.Length; i < s.Length; i++)
+            {
+                char c = s[i];
+
+                // 終了条件 }} のチェック
+                if (StringUtils.StartsWith(s, MediaWikiTemplate.DelimiterEnd, i))
+                {
+                    lastIndex = ++i;
+                    break;
+                }
+
+                // {{テンプレート名|パラメータ1|パラメータ2}} といったフォーマットのため、| の前後で処理を変更
+                if (c == '|')
+                {
+                    // | の後(パラメータなど)には何でもありえるので親のパーサーで再帰的に解析
+                    IElement element;
+                    if (!this.parser.TryParseToDelimiter(StringUtils.Substring(s, i + 1), out element, MediaWikiTemplate.DelimiterEnd, "|"))
+                    {
+                        // 平文でも解析するメソッドのため、基本的に失敗することは無い
+                        // 万が一の場合は解析失敗とする
+                        break;
+                    }
+
+                    i += element.ToString().Length;
+                    pipeTexts.Add(element);
+                    continue;
+                }
+                else
+                {
+                    // | の前(記事名などの部分)のとき、変数・コメントの再帰チェック
+                    IElement element;
+                    if (this.TryParseAt(s, i, out element, this.parser.CommentParser, this.parser.VariableParser))
+                    {
+                        // 変数・コメントなら、解析したブロック単位でテンプレート名に追加
+                        i += element.ToString().Length - 1;
+                        article.Append(element.ToString());
+                        continue;
+                    }
+
+                    // 変数・コメント以外で < > [ ] { } が含まれている場合、リンクは無効
+                    // TODO: <noinclude>も含まれていてOKだが、2012年1月現在未対応
+                    if ((c == '<') || (c == '>') || (c == '[') || (c == ']') || (c == '{') || (c == '}'))
+                    {
+                        break;
+                    }
+
+                    // それ以外の普通の文字なら1文字ずつテンプレート名に追加
+                    article.Append(c);
+                }
+            }
+
+            // 終了条件でループを抜けていない場合、解析失敗
+            if (lastIndex < 0)
+            {
+                return false;
+            }
+
+            // 解析に成功した場合、結果を出力値に設定
+            result = this.MakeElement(article.ToString(), pipeTexts, s.Substring(0, lastIndex + 1));
+            return true;
+        }
+
+        /// <summary>
+        /// 渡された文字が<see cref="TryParse"/>等の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public override bool IsPossibleParse(char c)
+        {
+            return MediaWikiTemplate.DelimiterStart[0] == c;
+        }
+
+        #endregion
+
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// テンプレートタグを解析した結果から、MediaWikiテンプレート要素を生成する。
+        /// </summary>
+        /// <param name="article">テンプレートタグ上のテンプレート名部分の文字列。</param>
+        /// <param name="pipeTexts">テンプレートタグ上のパイプ後の文字列。</param>
+        /// <param name="parsedString">解析したテンプレートタグの文字列。</param>
+        /// <returns>生成したテンプレート要素。</returns>
+        private MediaWikiTemplate MakeElement(string article, IList<IElement> pipeTexts, string parsedString)
+        {
+            // 解析結果を各種属性に格納
+            // テンプレート名には、前後のスペース・改行を削除した値を設定
+            MediaWikiTemplate template = new MediaWikiTemplate(article.Trim());
+            template.ParsedString = parsedString;
+            template.PipeTexts = pipeTexts;
+
+            // 記事名から情報を抽出
+            if (template.Title.StartsWith("/") == true)
+            {
+                // サブページ({{/サブページ}}みたいなの)
+                template.IsSubpage = true;
+            }
+            else if (template.Title.StartsWith(":"))
+            {
+                // 先頭が :(テンプレート名前空間ではなく標準名前空間となる)
+                template.IsColon = true;
+                template.Title = template.Title.TrimStart(':').TrimStart();
+            }
+
+            // 先頭が msgnw:
+            template.IsMsgnw = template.Title.ToLower().StartsWith(MediaWikiTemplate.Msgnw.ToLower());
+            if (template.IsMsgnw)
+            {
+                template.Title = template.Title.Substring(MediaWikiTemplate.Msgnw.Length);
+            }
+
+            // 記事名直後の改行の有無を記録
+            if (article.TrimEnd(' ').EndsWith("\n"))
+            {
+                template.NewLine = true;
+            }
+
+            return template;
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiVariable.cs b/Wptscs/Parsers/MediaWikiVariable.cs
new file mode 100644 (file)
index 0000000..f71187f
--- /dev/null
@@ -0,0 +1,109 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiページの変数要素をあらわすモデルクラスソース</summary>
+//
+// <copyright file="MediaWikiVariable.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiページの変数要素をあらわすモデルクラスです。
+    /// </summary>
+    public class MediaWikiVariable : AbstractElement
+    {
+        #region 定数
+
+        /// <summary>
+        /// 内部リンクの開始タグ。
+        /// </summary>
+        public static readonly string DelimiterStart = "{{{";
+
+        /// <summary>
+        /// 内部リンクの閉じタグ。
+        /// </summary>
+        public static readonly string DelimiterEnd = "}}}";
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// 変数要素をあらわす空のインスタンスを生成する。
+        /// </summary>
+        /// <param name="variable">変数。</param>
+        /// <param name="value">値。</param>
+        public MediaWikiVariable(string variable, IElement value = null)
+        {
+            this.Variable = variable;
+            this.Value = value;
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// 変数。
+        /// </summary>
+        public virtual string Variable
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// 値。
+        /// </summary>
+        public virtual IElement Value
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region 実装支援用抽象メソッド実装
+
+        /// <summary>
+        /// この要素を書式化した変数テキストを返す。
+        /// </summary>
+        /// <returns>変数テキスト。</returns>
+        protected override string ToStringImpl()
+        {
+            // 戻り値初期化
+            StringBuilder b = new StringBuilder();
+            
+            // 開始タグの付加
+            b.Append(MediaWikiVariable.DelimiterStart);
+
+            // 変数
+            if (!String.IsNullOrEmpty(this.Variable))
+            {
+                b.Append(this.Variable);
+            }
+
+            // 値
+            if (this.Value != null)
+            {
+                b.Append('|');
+                b.Append(this.Value.ToString());
+            }
+
+            // 閉じタグの付加
+            b.Append(MediaWikiVariable.DelimiterEnd);
+            return b.ToString();
+        }
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Parsers/MediaWikiVariableParser.cs b/Wptscs/Parsers/MediaWikiVariableParser.cs
new file mode 100644 (file)
index 0000000..bb2dbd3
--- /dev/null
@@ -0,0 +1,134 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiの変数を解析するパーサークラスソース</summary>
+//
+// <copyright file="MediaWikiVariableParser.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Text;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+
+    /// <summary>
+    /// MediaWikiの変数を解析するパーサークラスです。
+    /// </summary>
+    public class MediaWikiVariableParser : AbstractParser
+    {
+        #region private変数
+
+        /// <summary>
+        /// このパーサーが参照する<see cref="MediaWikiParser"/>。
+        /// </summary>
+        private MediaWikiParser parser;
+
+        #endregion
+        
+        #region コンストラクタ
+
+        /// <summary>
+        /// 指定された<see cref="MediaWikiParser"/>を元に変数を解析するためのパーサーを作成する。
+        /// </summary>
+        /// <param name="parser">このパーサーが参照する<see cref="MediaWikiParser"/>。</param>
+        public MediaWikiVariableParser(MediaWikiParser parser)
+        {
+            this.parser = parser;
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 渡されたテキストをMediaWikiの変数として解析する。
+        /// </summary>
+        /// <param name="s">解析対象の文字列。</param>
+        /// <param name="result">解析した変数。</param>
+        /// <returns>解析に成功した場合<c>true</c>。</returns>
+        public override bool TryParse(string s, out IElement result)
+        {
+            // 出力値初期化
+            result = null;
+
+            // 開始条件 {{{ のチェック
+            if (!s.StartsWith(MediaWikiVariable.DelimiterStart))
+            {
+                return false;
+            }
+
+            // ブロック終了まで取得
+            StringBuilder variable = new StringBuilder();
+            IElement value = null;
+            int lastIndex = -1;
+            for (int i = MediaWikiVariable.DelimiterStart.Length; i < s.Length; i++)
+            {
+                // 終了条件 }}} のチェック
+                if (StringUtils.StartsWith(s, MediaWikiVariable.DelimiterEnd, i))
+                {
+                    lastIndex = i + MediaWikiVariable.DelimiterEnd.Length - 1;
+                    break;
+                }
+
+                // {{{変数名|デフォルト値}}} といったフォーマットのため、| の前後で処理を変更
+                if (s[i] == '|')
+                {
+                    // | の後(変数のデフォルト値など)は何でもありえるので親のパーサーで再帰的に解析
+                    if (!this.parser.TryParseToDelimiter(StringUtils.Substring(s, i + 1), out value, MediaWikiVariable.DelimiterEnd))
+                    {
+                        // 平文でも解析するメソッドのため、基本的に失敗することは無い
+                        // 万が一の場合は解析失敗とする
+                        break;
+                    }
+
+                    i += value.ToString().Length;
+                }
+                else
+                {
+                    // | の前(変数名の部分)のとき、変数・コメントの再帰チェック
+                    IElement element;
+                    if (this.TryParseAt(s, i, out element, this.parser.CommentParser, this.parser.VariableParser))
+                    {
+                        // 変数・コメントなら、解析したブロック単位で変数名に追加
+                        i += element.ToString().Length - 1;
+                        variable.Append(element.ToString());
+                        continue;
+                    }
+
+                    // それ以外の普通の文字なら1文字ずつ変数名に追加
+                    variable.Append(s[i]);
+                }
+            }
+
+            // 終了条件でループを抜けていない場合、解析失敗
+            if (lastIndex < 0)
+            {
+                return false;
+            }
+
+            // 変数名・値と、解析した素の文字列を結果に格納して終了
+            result = new MediaWikiVariable(variable.ToString(), value);
+            result.ParsedString = s.Substring(0, lastIndex + 1);
+
+            return true;
+        }
+
+        /// <summary>
+        /// 渡された文字が<see cref="TryParse"/>等の候補となる先頭文字かを判定する。
+        /// </summary>
+        /// <param name="c">解析文字列の先頭文字。</param>
+        /// <returns>候補となる場合<c>true</c>。このクラスでは常に<c>true</c>を返す。</returns>
+        /// <remarks>性能対策などで<see cref="TryParse"/>を呼ぶ前に目処を付けたい場合用。</remarks>
+        public override bool IsPossibleParse(char c)
+        {
+            return MediaWikiVariable.DelimiterStart[0] == c;
+        }
+
+        #endregion
+    }
+}
index 6baef7c..ca89e7e 100644 (file)
@@ -3,7 +3,7 @@
 //      アプリケーション起動用クラスソース</summary>
 //
 // <copyright file="Program.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
index f0d4ed2..eb72785 100644 (file)
@@ -3,7 +3,7 @@
 //      Wikipedia翻訳支援ツールのアセンブリソース</summary>
 //
 // <copyright file="AssemblyInfo.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyProduct("Wikipedia 翻訳支援ツール")]
-[assembly: AssemblyCopyright("Copyright (C) Honeplus 2011")]
+[assembly: AssemblyCopyright("Copyright (C) Honeplus 2012")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 
@@ -37,4 +37,4 @@ using System.Runtime.InteropServices;
 //      Minor Version 
 //      Build Number
 //      Revision
-[assembly: AssemblyVersion("1.01.*")]
+[assembly: AssemblyVersion("1.10.*")]
index 2960417..e264852 100644 (file)
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // <auto-generated>
 //     このコードはツールによって生成されました。
-//     ランタイム バージョン:4.0.30319.225
+//     ランタイム バージョン:4.0.30319.239
 //
 //     このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
 //     コードが再生成されるときに損失したりします。
@@ -79,11 +79,11 @@ namespace Honememo.Wptscs.Properties {
         }
         
         /// <summary>
-        ///   一時ファイルの作成に失敗しました。設定ファイルに異常が無いか、また以下のフォルダにファイルが作成可能かを確認してください。 に類似しているローカライズされた文字列を検索します。
+        ///   実行時間: {0:m\:ss} に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string ErrorMessage_TemporayError {
+        internal static string ElapsedTime {
             get {
-                return ResourceManager.GetString("ErrorMessage_TemporayError", resourceCulture);
+                return ResourceManager.GetString("ElapsedTime", resourceCulture);
             }
         }
         
@@ -128,9 +128,9 @@ namespace Honememo.Wptscs.Properties {
         }
         
         /// <summary>
-        ///   想定外のエラーが発生しました。プログラムが不安定な状態になった可能性があります。プログラムを再起動してください。
-        ///問題が再発する場合は、設定ファイルを削除するなどしてから、プログラムを再起動してください。
-        ///また、手順や現在の設定を確認し、開発者にご連絡ください。
+        ///   想定外のエラーが発生しました。正常に処理が続けられない状態になった可能性があります。プログラムを再起動してください。
+        ///
+        ///問題が再発する場合は、設定ファイルを削除するなどしてから、プログラムを再起動してください。また、手順や現在の設定を確認し、開発者にご連絡ください。
         ///
         ///<エラーの内容>
         ///{0}
@@ -163,9 +163,9 @@ namespace Honememo.Wptscs.Properties {
         /// <summary>
         ///   現在、{0}には未対応ですm(__)m に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string InformationMessage_DevelopingMethod {
+        internal static string InformationMessageDevelopingMethod {
             get {
-                return ResourceManager.GetString("InformationMessage_DevelopingMethod", resourceCulture);
+                return ResourceManager.GetString("InformationMessageDevelopingMethod", resourceCulture);
             }
         }
         
@@ -179,107 +179,98 @@ namespace Honememo.Wptscs.Properties {
         }
         
         /// <summary>
-        ///   注意:この記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。 に類似しているローカライズされた文字列を検索します。
-        /// </summary>
-        internal static string LogMessage_ArticleExistInterWiki {
-            get {
-                return ResourceManager.GetString("LogMessage_ArticleExistInterWiki", resourceCulture);
-            }
-        }
-        
-        /// <summary>
-        ///   翻訳元として指定された記事は存在しません。記事名を確認してください。 に類似しているローカライズされた文字列を検索します。
+        ///   
+        ///処理結果を {0} に出力しました。このログは {1} に保存されます。
+        /// に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_ArticleNothing {
+        internal static string LogMessageEnd {
             get {
-                return ResourceManager.GetString("LogMessage_ArticleNothing", resourceCulture);
+                return ResourceManager.GetString("LogMessageEnd", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   è¨\80èª\9eé\96\93ã\83ªã\83³ã\82¯ã\81®æ\8e¢ç´¢ã\80\81è¦\8bå\87ºã\81\97ã\81®å¤\89æ\8f\9bã\82\92è¡\8cã\81\84ã\81¾ã\81\99 に類似しているローカライズされた文字列を検索します。
+        ///   è¦\81æ±\82ã\81\97ã\81\9fURLã\81¯ {0} ã\81§ã\81\99ã\80\82 に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_CheckAndReplaceStart {
+        internal static string LogMessageErrorURL {
             get {
-                return ResourceManager.GetString("LogMessage_CheckAndReplaceStart", resourceCulture);
+                return ResourceManager.GetString("LogMessageErrorURL", resourceCulture);
             }
         }
         
         /// <summary>
         ///   
-        ///処理結果を {0} に出力しました。このログは {1} に保存されます。
+        ///ファイル {0} の保存に失敗しました。({1})
         /// に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_End {
+        internal static string LogMessageFileSaveFailed {
             get {
-                return ResourceManager.GetString("LogMessage_End", resourceCulture);
+                return ResourceManager.GetString("LogMessageFileSaveFailed", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   
-        ///ファイル {0} の保存に失敗しました。({1})
-        /// に類似しているローカライズされた文字列を検索します。
+        ///   {0} より [[{1}]] を取得。 に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_ErrorFileSave {
+        internal static string LogMessageGetTargetArticle {
             get {
-                return ResourceManager.GetString("LogMessage_ErrorFileSave", resourceCulture);
+                return ResourceManager.GetString("LogMessageGetTargetArticle", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   è¦\81æ±\82ã\81\97ã\81\9fURLã\81¯ {0} ã\81§ã\81\99ã\80\82 に類似しているローカライズされた文字列を検索します。
+        ///   è¨\80èª\9eé\96\93ã\83ªã\83³ã\82¯ç\84¡ã\81\97 に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_ErrorURL {
+        internal static string LogMessageInterWikiNotFound {
             get {
-                return ResourceManager.GetString("LogMessage_ErrorURL", resourceCulture);
+                return ResourceManager.GetString("LogMessageInterWikiNotFound", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   {0} より [[{1}]] を取得。 に類似しているローカライズされた文字列を検索します。
+        ///   記事無し に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_GetArticle {
+        internal static string LogMessageLinkArticleNotFound {
             get {
-                return ResourceManager.GetString("LogMessage_GetArticle", resourceCulture);
+                return ResourceManager.GetString("LogMessageLinkArticleNotFound", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   言語間リンク無し に類似しているローカライズされた文字列を検索します。
+        ///    ※キャッシュ に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_InterWikiNothing {
+        internal static string LogMessageNoteTranslation {
             get {
-                return ResourceManager.GetString("LogMessage_InterWikiNothing", resourceCulture);
+                return ResourceManager.GetString("LogMessageNoteTranslation", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   記事無し に類似しているローカライズされた文字列を検索します。
+        ///   リダイレクト に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_LinkArticleNothing {
+        internal static string LogMessageRedirect {
             get {
-                return ResourceManager.GetString("LogMessage_LinkArticleNothing", resourceCulture);
+                return ResourceManager.GetString("LogMessageRedirect", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   リダイレクト に類似しているローカライズされた文字列を検索します。
+        ///   {0}、実行日時 {1}
+        ///
+        /// に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_Redirect {
+        internal static string LogMessageStart {
             get {
-                return ResourceManager.GetString("LogMessage_Redirect", resourceCulture);
+                return ResourceManager.GetString("LogMessageStart", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   {0}、実行日時 {1}
-        ///
-        /// に類似しているローカライズされた文字列を検索します。
+        ///   言語間リンクの探索、見出しの変換を行います に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_Start {
+        internal static string LogMessageStartParseAndReplace {
             get {
-                return ResourceManager.GetString("LogMessage_Start", resourceCulture);
+                return ResourceManager.GetString("LogMessageStartParseAndReplace", resourceCulture);
             }
         }
         
@@ -288,45 +279,45 @@ namespace Honememo.Wptscs.Properties {
         ///処理を中止しました。このログは {0} に保存されます。
         /// に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_Stop {
+        internal static string LogMessageStop {
             get {
-                return ResourceManager.GetString("LogMessage_Stop", resourceCulture);
+                return ResourceManager.GetString("LogMessageStop", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   {{{{0}}}} の識別に失敗しました。{{{{0}}}} は {{1}}名前空間の記事して処理します。({{2}}) に類似しているローカライズされた文字列を検索します。
+        ///   注意:この記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。 に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessage_TemplateUnknown {
+        internal static string LogMessageTargetArticleHadInterWiki {
             get {
-                return ResourceManager.GetString("LogMessage_TemplateUnknown", resourceCulture);
+                return ResourceManager.GetString("LogMessageTargetArticleHadInterWiki", resourceCulture);
             }
         }
         
         /// <summary>
-        ///    ※キャッシュ に類似しているローカライズされた文字列を検索します。
+        ///   翻訳元として指定された記事は存在しません。記事名を確認してください。 に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string LogMessageTranslation {
+        internal static string LogMessageTargetArticleNotFound {
             get {
-                return ResourceManager.GetString("LogMessageTranslation", resourceCulture);
+                return ResourceManager.GetString("LogMessageTargetArticleNotFound", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   対象の記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。処理を続けますか? に類似しているローカライズされた文字列を検索します。
+        ///   {{{{0}}}} の名前空間の確認に失敗しました。{{{{0}}}} は {{1}}名前空間と仮定して処理します。({{2}}) に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string QuestionMessage_ArticleExist {
+        internal static string LogMessageTemplateNameUnidentified {
             get {
-                return ResourceManager.GetString("QuestionMessage_ArticleExist", resourceCulture);
+                return ResourceManager.GetString("LogMessageTemplateNameUnidentified", resourceCulture);
             }
         }
         
         /// <summary>
-        ///   {0}を行います。よろしいですか? に類似しているローカライズされた文字列を検索します。
+        ///   対象の記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。処理を続けますか? に類似しているローカライズされた文字列を検索します。
         /// </summary>
-        internal static string QuestionMessage_CommonWorkQuestion {
+        internal static string QuestionMessageArticleExisted {
             get {
-                return ResourceManager.GetString("QuestionMessage_CommonWorkQuestion", resourceCulture);
+                return ResourceManager.GetString("QuestionMessageArticleExisted", resourceCulture);
             }
         }
         
@@ -349,6 +340,60 @@ namespace Honememo.Wptscs.Properties {
         }
         
         /// <summary>
+        ///   キャッシュ更新中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusCacheUpdating {
+            get {
+                return ResourceManager.GetString("StatusCacheUpdating", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   設定ファイル読込中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusConfigReading {
+            get {
+                return ResourceManager.GetString("StatusConfigReading", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   ページ取得中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusDownloading {
+            get {
+                return ResourceManager.GetString("StatusDownloading", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   ファイル出力中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusFileWriting {
+            get {
+                return ResourceManager.GetString("StatusFileWriting", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   ページ解析中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusParsing {
+            get {
+                return ResourceManager.GetString("StatusParsing", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   サーバー接続中... に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        internal static string StatusPinging {
+            get {
+                return ResourceManager.GetString("StatusPinging", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   キャッシュの保存に失敗しました。今回参照したページの情報は、アプリケーション終了まで有効です。
         ///
         ///{0} に類似しているローカライズされた文字列を検索します。
index b446d12..af08332 100644 (file)
   </data>
   <data name="ArticleFooter" xml:space="preserve">
     <value>&lt;!-- {0}、[[:{1}:{2}]]({3}(UTC))より --&gt;</value>
-  </data>
-  <data name="ErrorMessage_TemporayError" xml:space="preserve">
-    <value>一時ファイルの作成に失敗しました。設定ファイルに異常が無いか、また以下のフォルダにファイルが作成可能かを確認してください。</value>
+    <comment>変換した記事の末尾に付けるフッター</comment>
   </data>
   <data name="ErrorMessageConfigLordFailed" xml:space="preserve">
     <value>設定ファイル読み込み時にエラーが発生しました。ファイルが破損している可能性があります。
     <comment>サーバーへの接続失敗</comment>
   </data>
   <data name="ErrorMessageDevelopmentError" xml:space="preserve">
-    <value>想定外のエラーが発生しました。プログラムが不安定な状態になった可能性があります。プログラムを再起動してください。
-問題が再発する場合は、設定ファイルを削除するなどしてから、プログラムを再起動してください。
-また、手順や現在の設定を確認し、開発者にご連絡ください。
+    <value>想定外のエラーが発生しました。正常に処理が続けられない状態になった可能性があります。プログラムを再起動してください。
+
+問題が再発する場合は、設定ファイルを削除するなどしてから、プログラムを再起動してください。また、手順や現在の設定を確認し、開発者にご連絡ください。
 
 <エラーの内容>
 {0}
     <value>エラー</value>
     <comment>エラーダイアログのタイトル</comment>
   </data>
-  <data name="InformationMessage_DevelopingMethod" xml:space="preserve">
+  <data name="InformationMessageDevelopingMethod" xml:space="preserve">
     <value>現在、{0}には未対応ですm(__)m</value>
+    <comment>未実装の機能が呼ばれた際のメッセージ</comment>
   </data>
   <data name="InformationTitle" xml:space="preserve">
     <value>お知らせ</value>
     <comment>お知らせダイアログのタイトル</comment>
   </data>
-  <data name="LogMessage_ArticleExistInterWiki" xml:space="preserve">
+  <data name="LogMessageTargetArticleHadInterWiki" xml:space="preserve">
     <value>注意:この記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。</value>
+    <comment>翻訳支援対象の記事が言語間リンクを持っている場合の警告メッセージ</comment>
   </data>
-  <data name="LogMessage_ArticleNothing" xml:space="preserve">
+  <data name="LogMessageTargetArticleNotFound" xml:space="preserve">
     <value>翻訳元として指定された記事は存在しません。記事名を確認してください。</value>
+    <comment>翻訳支援対象の記事が存在しない場合のエラーメッセージ</comment>
   </data>
-  <data name="LogMessage_CheckAndReplaceStart" xml:space="preserve">
+  <data name="LogMessageStartParseAndReplace" xml:space="preserve">
     <value>言語間リンクの探索、見出しの変換を行います</value>
+    <comment>翻訳支援処理本体を開始する際のメッセージ</comment>
   </data>
-  <data name="LogMessage_End" xml:space="preserve">
+  <data name="LogMessageEnd" xml:space="preserve">
     <value>
 処理結果を {0} に出力しました。このログは {1} に保存されます。
 </value>
+    <comment>翻訳支援処理処理を完了した際のメッセージ</comment>
   </data>
-  <data name="LogMessage_ErrorFileSave" xml:space="preserve">
+  <data name="LogMessageFileSaveFailed" xml:space="preserve">
     <value>
 ファイル {0} の保存に失敗しました。({1})
 </value>
+    <comment>ファイルの出力に失敗した場合のエラーメッセージ</comment>
   </data>
-  <data name="LogMessage_ErrorURL" xml:space="preserve">
+  <data name="LogMessageErrorURL" xml:space="preserve">
     <value>要求したURLは {0} です。</value>
+    <comment>通信エラー発生時のURLを示すメッセージ</comment>
   </data>
-  <data name="LogMessage_GetArticle" xml:space="preserve">
+  <data name="LogMessageGetTargetArticle" xml:space="preserve">
     <value>{0} より [[{1}]] を取得。</value>
+    <comment>翻訳支援対象の記事を取得する際のメッセージ</comment>
   </data>
-  <data name="LogMessage_InterWikiNothing" xml:space="preserve">
+  <data name="LogMessageInterWikiNotFound" xml:space="preserve">
     <value>言語間リンク無し</value>
+    <comment>記事に言語間リンクが存在しない場合のメッセージ</comment>
   </data>
-  <data name="LogMessage_LinkArticleNothing" xml:space="preserve">
+  <data name="LogMessageLinkArticleNotFound" xml:space="preserve">
     <value>記事無し</value>
+    <comment>内部リンク先の記事が存在しない場合のメッセージ</comment>
   </data>
-  <data name="LogMessage_Redirect" xml:space="preserve">
+  <data name="LogMessageRedirect" xml:space="preserve">
     <value>リダイレクト</value>
+    <comment>記事がリダイレクトの場合のメッセージ</comment>
   </data>
-  <data name="LogMessage_Start" xml:space="preserve">
+  <data name="LogMessageStart" xml:space="preserve">
     <value>{0}、実行日時 {1}
 
 </value>
+    <comment>翻訳支援処理処理を開始した際のメッセージ</comment>
   </data>
-  <data name="LogMessage_Stop" xml:space="preserve">
+  <data name="LogMessageStop" xml:space="preserve">
     <value>
 処理を中止しました。このログは {0} に保存されます。
 </value>
+    <comment>翻訳支援処理処理を中止した際のメッセージ</comment>
   </data>
-  <data name="LogMessage_TemplateUnknown" xml:space="preserve">
-    <value>{{{{0}}}} の識別に失敗しました。{{{{0}}}} は {{1}}名前空間の記事して処理します。({{2}})</value>
+  <data name="LogMessageTemplateNameUnidentified" xml:space="preserve">
+    <value>{{{{0}}}} の名前空間の確認に失敗しました。{{{{0}}}} は {{1}}名前空間と仮定して処理します。({{2}})</value>
+    <comment>テンプレートの名前空間を補完する際に、想定外のエラーとなった場合のメッセージ</comment>
   </data>
-  <data name="LogMessageTranslation" xml:space="preserve">
+  <data name="LogMessageNoteTranslation" xml:space="preserve">
     <value> ※キャッシュ</value>
     <comment>対訳表使用時の注釈</comment>
   </data>
-  <data name="QuestionMessage_ArticleExist" xml:space="preserve">
+  <data name="QuestionMessageArticleExisted" xml:space="preserve">
     <value>対象の記事には、翻訳先言語の記事 [[{0}]] への言語間リンクが存在します。処理を続けますか?</value>
-  </data>
-  <data name="QuestionMessage_CommonWorkQuestion" xml:space="preserve">
-    <value>{0}を行います。よろしいですか?</value>
+    <comment>指定された記事が既に翻訳先に存在する場合の確認メッセージ</comment>
   </data>
   <data name="QuestionTitle" xml:space="preserve">
     <value>確認</value>
   <data name="WarningMessageEmptySaveDirectory" xml:space="preserve">
     <value>出力先フォルダを指定してください。</value>
   </data>
+  <data name="StatusDownloading" xml:space="preserve">
+    <value>ページ取得中...</value>
+    <comment>処理状態がページのダウンロード中</comment>
+  </data>
+  <data name="StatusParsing" xml:space="preserve">
+    <value>ページ解析中...</value>
+    <comment>処理状態がページの解析中</comment>
+  </data>
+  <data name="StatusPinging" xml:space="preserve">
+    <value>サーバー接続中...</value>
+    <comment>処理状態がサーバー接続確認中</comment>
+  </data>
+  <data name="StatusCacheUpdating" xml:space="preserve">
+    <value>キャッシュ更新中...</value>
+    <comment>処理状態がキャッシュ更新中</comment>
+  </data>
+  <data name="StatusConfigReading" xml:space="preserve">
+    <value>設定ファイル読込中...</value>
+    <comment>処理状態が設定ファイル読込中</comment>
+  </data>
+  <data name="StatusFileWriting" xml:space="preserve">
+    <value>ファイル出力中...</value>
+    <comment>処理状態がファイル出力中</comment>
+  </data>
+  <data name="ElapsedTime" xml:space="preserve">
+    <value>実行時間: {0:m\:ss}</value>
+    <comment>ステータスバー処理時間のフォーマット</comment>
+  </data>
 </root>
\ No newline at end of file
index 99f9469..8e5ee1d 100644 (file)
@@ -1,7 +1,7 @@
 //------------------------------------------------------------------------------
 // <auto-generated>
 //     このコードはツールによって生成されました。
-//     ランタイム バージョン:4.0.30319.225
+//     ランタイム バージョン:4.0.30319.239
 //
 //     このファイルへの変更は、以下の状況下で不正な動作の原因になったり、
 //     コードが再生成されるときに損失したりします。
@@ -155,8 +155,11 @@ namespace Honememo.Wptscs.Properties {
             "ng>\r\n  <string>#formatdate</string>\r\n  <string>padleft</string>\r\n  <string>padri" +
             "ght</string>\r\n  <string>plural</string>\r\n  <string>grammar</string>\r\n  <string>i" +
             "nt</string>\r\n  <string>#language</string>\r\n  <string>#special</string>\r\n  <strin" +
-            "g>#tag</string>\r\n  <string>gender</string>\r\n  <string>groupconvert</string>\r\n</A" +
-            "rrayOfString>")]
+            "g>#tag</string>\r\n  <string>gender</string>\r\n  <string>groupconvert</string>\r\n  <" +
+            "string>#expr</string>\r\n  <string>#if</string>\r\n  <string>#ifeq</string>\r\n  <stri" +
+            "ng>#ifexist</string>\r\n  <string>#ifexpr</string>\r\n  <string>#switch</string>\r\n  " +
+            "<string>#time</string>\r\n  <string>#rel2abs</string>\r\n  <string>#titleparts</stri" +
+            "ng>\r\n  <string>#iferror</string>\r\n</ArrayOfString>")]
         public global::System.Collections.Specialized.StringCollection MediaWikiMagicWords {
             get {
                 return ((global::System.Collections.Specialized.StringCollection)(this["MediaWikiMagicWords"]));
@@ -165,7 +168,7 @@ namespace Honememo.Wptscs.Properties {
         
         [global::System.Configuration.ApplicationScopedSettingAttribute()]
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
-        [global::System.Configuration.DefaultSettingValueAttribute("/wiki/Special:Export/{0}")]
+        [global::System.Configuration.DefaultSettingValueAttribute("/wiki/Special:Export/$1")]
         public string MediaWikiExportPath {
             get {
                 return ((string)(this["MediaWikiExportPath"]));
@@ -174,7 +177,7 @@ namespace Honememo.Wptscs.Properties {
         
         [global::System.Configuration.ApplicationScopedSettingAttribute()]
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
-        [global::System.Configuration.DefaultSettingValueAttribute(" ({0}) ")]
+        [global::System.Configuration.DefaultSettingValueAttribute(" ($1) ")]
         public string Bracket {
             get {
                 return ((string)(this["Bracket"]));
@@ -231,7 +234,7 @@ namespace Honememo.Wptscs.Properties {
         
         [global::System.Configuration.ApplicationScopedSettingAttribute()]
         [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
-        [global::System.Configuration.DefaultSettingValueAttribute("1.0.0.0")]
+        [global::System.Configuration.DefaultSettingValueAttribute("1.10.0.0")]
         public string ConfigurationCompatible {
             get {
                 return ((string)(this["ConfigurationCompatible"]));
index a9b0db2..e2e3596 100644 (file)
   &lt;string&gt;#tag&lt;/string&gt;
   &lt;string&gt;gender&lt;/string&gt;
   &lt;string&gt;groupconvert&lt;/string&gt;
+  &lt;string&gt;#expr&lt;/string&gt;
+  &lt;string&gt;#if&lt;/string&gt;
+  &lt;string&gt;#ifeq&lt;/string&gt;
+  &lt;string&gt;#ifexist&lt;/string&gt;
+  &lt;string&gt;#ifexpr&lt;/string&gt;
+  &lt;string&gt;#switch&lt;/string&gt;
+  &lt;string&gt;#time&lt;/string&gt;
+  &lt;string&gt;#rel2abs&lt;/string&gt;
+  &lt;string&gt;#titleparts&lt;/string&gt;
+  &lt;string&gt;#iferror&lt;/string&gt;
 &lt;/ArrayOfString&gt;</Value>
     </Setting>
     <Setting Name="MediaWikiExportPath" Type="System.String" Scope="Application">
-      <Value Profile="(Default)">/wiki/Special:Export/{0}</Value>
+      <Value Profile="(Default)">/wiki/Special:Export/$1</Value>
     </Setting>
     <Setting Name="Bracket" Type="System.String" Scope="Application">
-      <Value Profile="(Default)"> ({0}) </Value>
+      <Value Profile="(Default)"> ($1) </Value>
     </Setting>
     <Setting Name="MediaWikiRedirect" Type="System.String" Scope="Application">
       <Value Profile="(Default)">#REDIRECT</Value>
       <Value Profile="(Default)">15.00:00:00</Value>
     </Setting>
     <Setting Name="ConfigurationCompatible" Type="System.String" Scope="Application">
-      <Value Profile="(Default)">1.0.0.0</Value>
+      <Value Profile="(Default)">1.10.0.0</Value>
     </Setting>
     <Setting Name="MediaWikiNamespacePath" Type="System.String" Scope="Application">
       <Value Profile="(Default)">/w/api.php?format=xml&amp;action=query&amp;meta=siteinfo&amp;siprop=namespaces|namespacealiases</Value>
index ac46eb1..06b76d7 100644 (file)
@@ -1,45 +1,47 @@
-=====================================================================
+=====================================================================
 【タイトル】 Wikipedia 翻訳支援ツール
-【ファイル】 wptscs101.zip
-【作成月日】 2011/10/5
+【ファイル】 wptscs110.zip
+【作成月日】 2012/1/30
 【制 作 者】 Honeplus
-【動作環境】 Windows Vista x64での動作を確認。要.NET Framework 4.0
+【動作環境】 Windows Vista/7 での動作を確認。要.NET Framework 4.0 Client Profile
 【配布形態】 修正BSDライセンス
-【HomePage】 http://honeplus.blog50.fc2.com/
+【HomePage】 http://sourceforge.jp/projects/wptscs/
 =====================================================================
 
 ・概要
 Wikipediaでの言語間翻訳をサポートするためのツールです。
-指定されたWikipediaの記事に存在する内部リンク先を確認し、
-その言語間リンクを取得します。
+指定されたWikipediaの記事に存在する内部リンク先を確認し、その言語間リンクを取得します。
 また、登録されていれば、見出しも変換します。
 
 ※Wikipedia以外でも同じシステムのサイト(例えばWikipedia姉妹サイト)
   なら使えるかもしれませんが、未確認です。
 
 
+
 ・インストール方法
 適当なフォルダに展開してください。
-å±\95é\96\8bã\81\97ã\81¦å\87ºã\81¦ã\81\8fã\82\8b以ä¸\8bã\81®ã\83\95ã\82¡ã\82¤ã\83«ã\81\8cã\80\81æ\9c\80ä½\8eé\99\90å¿\85è¦\81ã\81ªã\83\95ã\82¡ã\82¤ã\83«ã\81§ã\81\99ã\80\82ä»\96ã\81¯æ¶\88ã\81\97ã\81¦ã\82\82å\8f¯
+å±\95é\96\8bã\81\97ã\81¦å\87ºã\81¦ã\81\8fã\82\8b以ä¸\8bã\81®ã\83\95ã\82¡ã\82¤ã\83«ã\81\8cã\80\81æ\9c\80ä½\8eé\99\90å¿\85è¦\81ã\81ªã\83\95ã\82¡ã\82¤ã\83«ã\81§ã\81\99ã\80\82ä»\96ã\81¯æ¶\88ã\81\97ã\81¦ã\82\82å\95\8fé¡\8cã\81\82ã\82\8aã\81¾ã\81\9bã\82\93
 
 wptscs.exe        : 実行ファイル
+hmlib.dll         : 実行に必要なdll
 wptscs.exe.config : デフォルト値やプログラム的な設定ファイル
 config.xml        : Wikipediaの各サーバーに関する設定ファイル
 
 アプリケーション上で設定した内容は、上記設定ファイルには反映されません。
 ユーザーごとのフォルダに出力されます。
-(Microsoftがそうしろっていうから・・・。)
 設定ファイルを直接修正、または削除して初期値に戻したい場合は、
-下記のようなパスにあるファイルを更新してください。
+下記のようなパスにあるファイルを更新してください(以下はVistaの例)
 
 C:\Users\[ユーザー名]\AppData\Roaming\Honememo\Wikipedia 翻訳支援ツール\[バージョン番号]\config.xml
 C:\Users\[ユーザー名]\AppData\Local\Honememo\Wikipedia 翻訳支援ツール\[バージョン番号]\user.config
 
 
+
 ・アンインストール方法
 展開したファイルと必要なら上記設定ファイルを削除してください。レジストリ等は一切いじりません。
 
 
+
 ・使い方
 翻訳元/先言語と、処理結果の出力先フォルダを指定してから、翻訳したい翻訳元記事名を入力して実行してください。
 
@@ -53,8 +55,48 @@ C:\Users\[ユーザー名]\AppData\Local\Honememo\Wikipedia 翻訳支援ツー
 間違っても、そのままWikipediaに投稿するなどしないでください。
 また、当プログラムではWikipediaへの書き込みは一切行いません。
 
-※テンプレート等の複雑な書式の記事で使用した場合、一部が正常に処理されない可能性あります。
-  これらの記事で使用する際は、処理結果に異常が無いか注意しながらご利用ください。
+※ 特にテンプレート等の複雑な書式の記事で使用した場合、一部が正常に処理されない可能性あります。
+   これらの記事で使用する際は、処理結果に異常が無いか注意しながらご利用ください。
+
+
+
+・処理結果の詳細
+処理結果として、実行ログと記事テキストの内部リンク等を置き換えたものを出力します。
+
+記事テキストの置き換えについては、下記のようなルールで実行しています。
+(以下、英語版→日本語版で適当な記事を例に)
+
+例)[[Japan]]
+
+言語間リンクがある場合    →  [[日本|Japan]]
+言語間リンクがない場合    →  [[:en:Japan|Japan]]
+ない場合で仮リンクが有効  →  {{仮リンク|Japan|en|Japan|label=Japan}}
+英語版も赤リンクの場合    →  [[Japan]]
+
+例)[[Japan|Nihon]]
+
+言語間リンクがある場合    →  [[日本|Nihon]]
+言語間リンクがない場合    →  [[:en:Japan|Nihon]]
+ない場合で仮リンクが有効  →  {{仮リンク|Japan|en|Japan|label=Nihon}}
+英語版も赤リンクの場合    →  [[Japan|Nihon]]
+
+例)[[Category:Japan|sortkey]]
+
+言語間リンクがある場合    →  [[Category:日本|sortkey]]
+言語間リンクがない場合    →  [[:en:Category:Japan]]<!-- [[Category:Japan|sortkey]] -->
+英語版も赤リンクの場合    →  [[Category:Japan|sortkey]]
+
+例){{Citation needed|date=January 2012}}
+
+言語間リンクがある場合    →  {{要出典|date=January 2012}}
+言語間リンクがない場合    →  [[:en:Template:Citation needed]]<!-- {{Citation needed|date=January 2012}} -->
+英語版も赤リンクの場合    →  {{Citation needed|date=January 2012}}
+
+例)==History==
+
+見出し変換表に登録あり    →  ==歴史==
+見出し変換表に登録なし    →  ==History==
+
 
 
 ・ソースについて
@@ -62,38 +104,66 @@ src.zipにソースファイルをまとめています。開発環境はVisual
 その他外部ツールとしてテスト自動化ツールのnUnitと、コーディングスタイルチェック用のStyleCopを使用しています。
 
 
+
 ・その他
 このプログラムは修正BSDライセンスに基づいたフリーソフトウェアで、無保証です。
 このプログラムを利用して何らかの被害をこうむっても、作者は一切責任を負いません。
 またサポートする保証もありません。
 このプログラムの変更・再配布・流用はご自由にどうぞ。
 
-修正BSDライセンスの文面はこちら(日本語版)
-http://sourceforge.jp/projects/opensource/wiki/licenses%2Fnew_BSD_license
-※ 既存のライセンスを使っていますが、要は「好きに使ってね。責任取らないけど。」ということです。
-
 
 また、ソース中にNUnitのテストデータとしてWikipediaより取得したXMLを同梱しています。
 これらファイル内の著作物の扱いについては、日本語版/英語版の各Wikipediaのライセンスに従ってください。
 
 
+
 ・更新履歴
 Ver0.80  2010/09/18 開発環境/言語をVisual Studio 2005のC++/CLI→Visual C# 2010 Expressに移行。
                     ソースも全面的に見直し(ただし現在は古いコードが多々残った状態)。
-                                       設定画面を全体的に変更。記事の対応パターンを登録できるよう対応。
-                                       キャッシュ状況も見えるように変更。
+                    設定画面を全体的に変更。記事の対応パターンを登録できるよう対応。
+                    キャッシュ状況も見えるように変更。
+
 Ver0.90  2010/09/27 Template:Documentationが使われているページに対応。
                     無駄に遅くなっていた処理を改善。その他細かい部分を改良/修正。
+
 Ver1.00  2011/04/22 Wikipediaのバージョンアップごとに設定ファイルを直さなくて済むよう対処。
                     無駄に遅くなっていた処理を改善。
-                                       設定画面のツールチップや入力値チェックを整備。
-                                       [[ファイル:~]]→[[File:~]]も変換するよう対応。
-                                       [[Apollo&nbsp;17]]のように特殊文字が入っているリンクを処理できるよう修正。
-                                       HTMLタグの解析で正規表現の構文エラーになる可能性があったのを修正。
-                                       config.xmlの日英以外の言語の設定を精査・補填。
+                    設定画面のツールチップや入力値チェックを整備。
+                    [[ファイル:~]]→[[File:~]]も変換するよう対応。
+                    [[Apollo&nbsp;17]]のように特殊文字が入っているリンクを処理できるよう修正。
+                    HTMLタグの解析で正規表現の構文エラーになる可能性があったのを修正。
+                    config.xmlの日英以外の言語の設定を精査・補填。
+
 Ver1.01  2011/10/05 MediaWikiのバージョンアップに伴いAPIの仕様が変わったため対応。
 
+Ver1.10  2012/01/30 ソースの全体的なリファクタリングを実施。
+                    言語間リンクが無い記事の{{仮リンク}}への置き換えに対応。
+                    {{Doc}}→{{Documentation}}のリダイレクトとインラインコンテンツに対応。
+                    .net framework 4 フル版でなく Client Profile で動作するよう改善。
+                    処理状況をステータスバーに表示するよう変更。
+                    各種設定の文字列埋め込みを{0}から$1形式に変更。
+                    [[File:~|]]のコメント部分を処理対象とするよう対応。
+                    キャッシュ済みテンプレートの処理時に余分な通信をしていたのを修正。
+                    [[Help:条件文]]の関数をテンプレートとして処理しようとしてしまっていたのを修正。
+                    初期設定を若干追加。
+                    配布元をSourceForge.JPに移転。
+                    ※ 処理の見直しも行っているため、上記以外の細かい部分の動作にも変更が生じているはずです。
+                    ※ 1.01とは設定ファイルの互換性がありません。
+
+
+
+・既知の不具合
+ピリオドで終わるページが取得できず、存在しないものとして扱われる。
+(例、[[Vulcan Inc.]])
+
+記事名にコロンを含むページが正しく処理できない。
+(例、[[Marathon 2: Durandal]])
+
+階層の深いサブページが正しく処理できない。
+(例、[[../../サブページ]])
+
+キャッシュの一覧で一度に大量の項目を削除すると、フリーズしたかのように時間がかかる。
 
\83»ä»\8aå¾\8cã\81®ç\9b®æ¨\99
-言語間リンクがなかった記事名を{{仮リンク}}とかに置き換えたい。
-汚いソースをがっつり作り直す(エンバグ多数のため挫折中)
\81\9dã\81®ä»\96ã\80\81Wikipediaä¸\8aè¨\80èª\9eé\96\93ã\83ªã\83³ã\82¯ã\81\8cå­\98å\9c¨ã\81\97ã\81¦ã\82\82ã\80\81ç\8f¾è¡\8cã\81®ã\82¢ã\83\97ã\83ªã\81§ã\81¯è¦\8bã\81¤ã\81\91ã\82\89ã\82\8cã\81ªã\81\84ã\83\9aã\83¼ã\82¸ã\82\82ã\81\82ã\82\8aã\81¾ã\81\99ã\80\82
+(例、<noinclude>で埋め込まれている{{Refend}}, どうやってるかよく分からない {{Documentation}})
+これらについては対応できない特殊ケースとして、設定→記事の置き換えに変換パターンを初期設定しています
index c418d1e..1b58448 100644 (file)
@@ -3,7 +3,7 @@
 //      Wikipedia翻訳支援ツールの設定アクセス用ソース</summary>
 //
 // <copyright file="Settings.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/Wptscs/Utilities/AppDefaultWebProxy.cs b/Wptscs/Utilities/AppDefaultWebProxy.cs
new file mode 100644 (file)
index 0000000..9420883
--- /dev/null
@@ -0,0 +1,153 @@
+// ================================================================================================
+// <summary>
+//      アプリケーションデフォルトの値を用いてウェブアクセスするプロキシクラスソース</summary>
+//
+// <copyright file="AppDefaultWebProxy.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Utilities
+{
+    using System;
+    using System.IO;
+    using System.Net;
+    using Honememo.Wptscs.Properties;
+    
+    /// <summary>
+    /// アプリケーションデフォルトの値を用いてウェブアクセスするプロキシクラスです。
+    /// </summary>
+    public class AppDefaultWebProxy : IWebProxy
+    {
+        #region private変数
+
+        /// <summary>
+        /// このプロキシで使用するUserAgent。
+        /// </summary>
+        private string userAgent;
+
+        /// <summary>
+        /// このプロキシで使用するReferer。
+        /// </summary>
+        private string referer;
+
+        #endregion
+
+        #region インタフェース実装プロパティ
+
+        /// <summary>
+        /// このプロキシで使用するUserAgent。
+        /// </summary>
+        /// <returns>UserAgent。</returns>
+        /// <remarks>プロパティ→アプリ設定値→アプリデフォルト値を生成 の順にあるものを返す。</remarks>
+        public string UserAgent
+        {
+            get
+            {
+                // プロパティを確認
+                if (this.userAgent != null)
+                {
+                    return this.userAgent;
+                }
+
+                // アプリ設定値を確認
+                string ua = Settings.Default.UserAgent;
+                if (String.IsNullOrEmpty(ua))
+                {
+                    // 特に設定が無い場合はデフォルトの値を設定
+                    Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
+                    ua = String.Format(
+                        Settings.Default.DefaultUserAgent,
+                        ver.Major,
+                        ver.Minor);
+                }
+
+                return ua;
+            }
+
+            set
+            {
+                this.userAgent = value;
+            }
+        }
+
+        /// <summary>
+        /// このプロキシで使用するReferer。
+        /// </summary>
+        /// <returns>Referer。</returns>
+        /// <remarks>プロパティ→アプリ設定値 の順にあるものを返す。</remarks>
+        public string Referer
+        {
+            get
+            {
+                // プロパティを確認
+                if (this.referer != null)
+                {
+                    return this.referer;
+                }
+
+                // アプリ設定値を確認
+                string r = Settings.Default.Referer;
+                if (String.IsNullOrEmpty(r))
+                {
+                    r = String.Empty;
+                }
+
+                return r;
+            }
+
+            set
+            {
+                this.referer = value;
+            }
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッド
+
+        /// <summary>
+        /// 指定されたURIの情報をストリームで取得。
+        /// </summary>
+        /// <param name="uri">取得対象のURI。</param>
+        /// <returns>取得したストリーム。使用後は必ずクローズすること。</returns>
+        /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
+        public Stream GetStream(Uri uri)
+        {
+            // URIに応じたWebRequestを取得
+            WebRequest req = WebRequest.Create(uri);
+
+            // Requestに応じた設定を行う
+            // ※ HttpWebRequest, FileWebRequestを想定(後者は特に処理無し)
+            //    それ以外については通すが未確認
+            if (req is HttpWebRequest)
+            {
+                // HTTP/HTTPSの場合
+                this.InitializeHttpWebRequest((HttpWebRequest)req);
+            }
+
+            // 応答データを受信するためのStreamを取得し、データを取得
+            return req.GetResponse().GetResponseStream();
+        }
+
+        #endregion
+
+        #region 内部処理用メソッド
+
+        /// <summary>
+        /// HttpWebRequest用の設定を行う。
+        /// </summary>
+        /// <param name="req">設定対象のHttpWebRequest。</param>
+        private void InitializeHttpWebRequest(HttpWebRequest req)
+        {
+            // UserAgent設定
+            req.UserAgent = this.UserAgent;
+
+            // Referer設定
+            req.Referer = this.Referer;
+        }
+        
+        #endregion
+    }
+}
index 0341531..4e29f37 100644 (file)
@@ -3,12 +3,12 @@
 //      Windows処理に関するユーティリティクラスソース。</summary>
 //
 // <copyright file="FormUtils.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Utilities
+namespace Honememo.Wptscs.Utilities
 {
     using System;
     using System.Collections.Generic;
@@ -114,6 +114,7 @@ namespace Honememo.Utilities
 
         /// <summary>
         /// 文字列中のファイル名に使用できない文字を「_」に置換。
+        /// また、&amp;nbsp;由来の半角スペース (u00a0) も普通の半角スペース (u0020) に置換する。
         /// </summary>
         /// <param name="fileName">ファイル名。</param>
         /// <returns>置換後のファイル名。</returns>
@@ -121,12 +122,13 @@ namespace Honememo.Utilities
         {
             // 渡された文字列にファイル名に使えない文字が含まれている場合、_ に置き換える
             string result = fileName;
-            char[] unuseChars = Path.GetInvalidFileNameChars();
-            foreach (char c in unuseChars)
+            foreach (char c in Path.GetInvalidFileNameChars())
             {
                 result = result.Replace(c, '_');
             }
 
+            // &nbsp;由来の半角スペース (u00a0) も普通の半角スペース (u0020) に置き換える
+            result = result.Replace(' ', ' ');
             return result;
         }
 
diff --git a/Wptscs/Utilities/IWebProxy.cs b/Wptscs/Utilities/IWebProxy.cs
new file mode 100644 (file)
index 0000000..2edd364
--- /dev/null
@@ -0,0 +1,55 @@
+// ================================================================================================
+// <summary>
+//      ウェブアクセス処理を隠蔽するプロキシインタフェースソース</summary>
+//
+// <copyright file="IWebProxy.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Utilities
+{
+    using System;
+    using System.IO;
+
+    /// <summary>
+    /// ウェブアクセス処理を隠蔽するプロキシのインタフェースです。
+    /// </summary>
+    public interface IWebProxy
+    {
+        #region プロパティ
+
+        /// <summary>
+        /// このプロキシで使用するUserAgent。
+        /// </summary>
+        string UserAgent
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// このプロキシで使用するReferer。
+        /// </summary>
+        string Referer
+        {
+            get;
+            set;
+        }
+
+        #endregion
+
+        #region メソッド
+
+        /// <summary>
+        /// 指定されたURIの情報をストリームで取得。
+        /// </summary>
+        /// <param name="uri">取得対象のURI。</param>
+        /// <returns>取得したストリーム。使用後は必ずクローズすること。</returns>
+        /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
+        Stream GetStream(Uri uri);
+
+        #endregion
+    }
+}
diff --git a/Wptscs/Utilities/LazyXmlParser.cs b/Wptscs/Utilities/LazyXmlParser.cs
deleted file mode 100644 (file)
index acdbbbc..0000000
+++ /dev/null
@@ -1,617 +0,0 @@
-// ================================================================================================
-// <summary>
-//      曖昧なXML/HTMLタグを解析するためのクラスソース</summary>
-//
-// <copyright file="LazyXmlParser.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
-// <author>
-//      Honeplus</author>
-// ================================================================================================
-
-namespace Honememo.Utilities
-{
-    using System;
-    using System.Collections.Generic;
-    using System.Linq;
-    using System.Text;
-    using System.Text.RegularExpressions;
-
-    /// <summary>
-    /// 曖昧なXML/HTMLタグを解析するためのクラスです。
-    /// </summary>
-    /// <remarks>
-    /// クラス名通り、多少不正な構文であっても解析する。
-    /// </remarks>
-    public class LazyXmlParser
-    {
-        #region 定数宣言
-
-        /// <summary>
-        /// コメントの開始。
-        /// </summary>
-        protected static readonly string CommentStart = "<!--";
-
-        /// <summary>
-        /// コメントの終了。
-        /// </summary>
-        protected static readonly string CommentEnd = "-->";
-
-        #endregion
-
-        #region private変数
-
-        /// <summary>
-        /// 大文字小文字を無視するか?
-        /// </summary>
-        private bool ignoreCase = true;
-
-        #endregion
-
-        #region プロパティ
-
-        /// <summary>
-        /// 大文字小文字を無視するか?
-        /// </summary>
-        public bool IgnoreCase
-        {
-            get
-            {
-                return this.ignoreCase;
-            }
-
-            set
-            {
-                this.ignoreCase = value;
-            }
-        }
-
-        /// <summary>
-        /// タグはHTMLの書式か?
-        /// </summary>
-        public bool IsHtml
-        {
-            get;
-            set;
-        }
-
-        #endregion
-
-        #region 静的メソッド
-
-        /// <summary>
-        /// 渡されたHTMLテキストがコメントかを解析する。
-        /// </summary>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="comment">解析したコメント。</param>
-        /// <returns>コメントの場合<c>true</c>。</returns>
-        /// <remarks>
-        /// コメントと判定するには、1文字目が開始タグである必要がある。
-        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
-        /// </remarks>
-        public static bool TryParseComment(string text, out string comment)
-        {
-            // 入力値確認
-            comment = null;
-            if (String.IsNullOrEmpty(text) || !text.StartsWith(LazyXmlParser.CommentStart))
-            {
-                return false;
-            }
-
-            // コメント終了まで取得
-            int index = text.IndexOf(LazyXmlParser.CommentEnd);
-            if (index < 0)
-            {
-                // 閉じタグが存在しない場合、最後までコメントと判定
-                comment = text;
-                return true;
-            }
-
-            // 閉じタグがあった場合、閉じタグの終わりまでを返す
-            comment = text.Substring(0, index + CommentEnd.Length);
-            return true;
-        }
-
-        #endregion
-        
-        #region 公開メソッド
-
-        /// <summary>
-        /// 渡されたテキストをXML/HTMLタグとして解析する。
-        /// </summary>
-        /// <param name="text">解析するテキスト。</param>
-        /// <param name="element">解析したタグ。</param>
-        /// <returns>タグの場合<c>true</c>。</returns>
-        /// <remarks>
-        /// XML/HTMLタグと判定するには、1文字目が開始タグである必要がある。
-        /// ただし、後ろについては閉じタグが無ければ全て、あればそれ以降は無視する。
-        /// また、本メソッドはあくまで簡易的な構文用であり、入れ子は考慮しない。
-        /// </remarks>
-        public bool TryParse(string text, out SimpleElement element)
-        {
-            // 入力値確認。タグでない場合は即終了
-            element = null;
-            if (String.IsNullOrEmpty(text) || text[0] != '<')
-            {
-                return false;
-            }
-
-            // タグ名取得、タグ名の次に出現しうる文字を探索
-            int index = text.IndexOfAny(new char[] { ' ', '>', '/' }, 1);
-            if (index < 0)
-            {
-                return false;
-            }
-
-            // タグ名確認、コメント(<!--)やちゃんと始まっていないもの(< tag>とか)もここで除外
-            string name = text.Substring(1, index - 1);
-            if (!this.ValidateName(name))
-            {
-                return false;
-            }
-
-            // 開始タグの終端に到達するまで属性を探索
-            IDictionary<string, string> attribute;
-            int endIndex;
-            if (!this.TryParseAttribute(text.Substring(index), out attribute, out endIndex))
-            {
-                return false;
-            }
-
-            index += endIndex;
-
-            // "/>" で終わった場合は、ここでタグ終了
-            if (text[index - 1] == '/')
-            {
-                element = new SimpleElement(name, attribute, String.Empty, text.Substring(0, index + 1));
-                return true;
-            }
-
-            // 閉じタグまでを解析
-            string innerXml;
-            if (!this.TryParseContent(text.Substring(index + 1), name, out innerXml, out endIndex))
-            {
-                return false;
-            }
-
-            if (endIndex < 0)
-            {
-                // 閉じタグが無い場合
-                if (this.IsHtml)
-                {
-                    // HTMLの場合、閉じタグが無いのは開始タグだけのパターンと判定
-                    // ※ indexは開始タグの末尾になっているのでそのまま
-                    innerXml = String.Empty;
-                }
-                else
-                {
-                    // それ以外は不正な構文だが値の最終文字までを設定
-                    index += innerXml.Length;
-                }
-            }
-            else
-            {
-                index += endIndex;
-            }
-
-            element = new SimpleElement(name, attribute, innerXml, text.Substring(0, index + 1));
-            return true;
-        }
-
-        #endregion
-
-        #region 内部処理用メソッド
-
-        /// <summary>
-        /// 文字列をXML/HTML読み込み用にデコードする。
-        /// </summary>
-        /// <param name="str">デコードする文字列。</param>
-        /// <returns>デコードされた文字列。<c>null</c>の場合、空文字列を返す。</returns>
-        private string Decode(string str)
-        {
-            if (str == null)
-            {
-                return String.Empty;
-            }
-            else if (this.IsHtml)
-            {
-                return System.Web.HttpUtility.HtmlDecode(str);
-            }
-            else
-            {
-                return str.Replace("&lt;", "<").Replace("&gt;", ">")
-                    .Replace("&quot;", "\"").Replace("&apos;", "\'").Replace("&amp;", "&");
-            }
-        }
-
-        /// <summary>
-        /// 渡されたタグ名/属性名がXML的に正しいかを判定。
-        /// </summary>
-        /// <param name="name">XMLタグ名/属性名。</param>
-        /// <returns>正しい場合 <c>true</c>。</returns>
-        /// <remarks>
-        /// デコード前の値を渡すこと。
-        /// HTMLについては、HTML用のチェックではないが、基本的に呼んで問題ないはず。
-        /// </remarks>
-        private bool ValidateName(string name)
-        {
-            if (String.IsNullOrEmpty(name))
-            {
-                // 空は無条件でNG
-                return false;
-            }
-
-            if (!this.IsNameFirstCharacter(name[0]))
-            {
-                // 先頭1文字が許可されていない文字の場合NG
-                return false;
-            }
-
-            // 2文字目以降の調査
-            for (int i = 1; i < name.Length; i++)
-            {
-                if (!this.IsNameCharacter(name[i]))
-                {
-                    // 2文字目以降として使えない文字が出現したらNG
-                    return false;
-                }
-            }
-
-            // 全てOK
-            return true;
-        }
-
-        /// <summary>
-        /// XMLタグ名/属性名の先頭文字として使用可能な文字かを判定する。
-        /// </summary>
-        /// <param name="c">文字。</param>
-        /// <returns>使用可能な場合 <c>true</c>。</returns>
-        private bool IsNameFirstCharacter(char c)
-        {
-            // 先頭一文字目はLetterクラスの文字と一部記号のみ許可
-            return Char.IsLetter(c) || c == '_' || c == ':';
-        }
-
-        /// <summary>
-        /// XMLタグ名/属性名の2文字目以降として使用可能な文字かを判定する。
-        /// </summary>
-        /// <param name="c">文字。</param>
-        /// <returns>使用可能な場合 <c>true</c>。</returns>
-        private bool IsNameCharacter(char c)
-        {
-            // 2文字目以降は、一文字目の文字に加えて
-            // Digitクラス、Combining Characterクラス、Extenderクラスの文字とハイフン・ピリオドが可能
-            // ※ 厳密な判定が大変で、Lazyクラスでそこまでやる必要も感じないので、
-            //    処理的に困る制御文字や記号類を除きみんな許可する。
-            return this.IsNameFirstCharacter(c) || c == '-' || c == '.'
-                || (!Char.IsControl(c) && !Char.IsWhiteSpace(c) && !Char.IsSymbol(c) && !Char.IsPunctuation(c));
-        }
-
-        /// <summary>
-        /// タグの属性情報部分のテキストを受け取り、属性情報を解析する。
-        /// </summary>
-        /// <param name="text">解析する属性情報文字列(" key1="value1" key2="value2"&gt;~" のような文字列)。</param>
-        /// <param name="attribute">解析した属性。</param>
-        /// <param name="endIndex">タグ終了箇所('&gt;')のインデックス。</param>
-        /// <returns>解析に成功した場合<c>true</c>。</returns>
-        private bool TryParseAttribute(string text, out IDictionary<string, string> attribute, out int endIndex)
-        {
-            // ※ 不正な構文も通すために、強引な汚いソースになってしまっている。
-            //    このメソッドにその辺りの処理を隔離している。
-
-            // 出力値初期化
-            attribute = null;
-            endIndex = -1;
-
-            // 開始タグの終端に到達するまで属性を探索
-            IDictionary<string, string> a = new Dictionary<string, string>();
-            string key = null;
-            bool existedEqual = false;
-            bool existedSpace = false;
-            char[] separators = null;
-            int i;
-            for (i = 0; i < text.Length; i++)
-            {
-                char c = text[i];
-                if (key == null)
-                {
-                    // 属性名の解析
-                    if (c == ' ')
-                    {
-                        // 属性名の前にあるスペースは無視
-                        continue;
-                    }
-
-                    // 属性名の次に出現しうる文字を探索
-                    int index = text.IndexOfAny(new char[] { '=', ' ', '>', '/' }, i);
-                    if (index < 0)
-                    {
-                        // どれも出現しない場合、構文エラー
-                        return false;
-                    }
-
-                    // 属性名を確認
-                    // ※ 属性が無い場合0文字となる
-                    key = text.Substring(i, index - i);
-                    if (!String.IsNullOrEmpty(key) && !this.ValidateName(key))
-                    {
-                        // 属性名の位置に出現し得ない記号が含まれているなど構文エラーも弾く
-                        return false;
-                    }
-
-                    // ループを次回「次に出現しうる文字」になるよう更新
-                    i = index - 1;
-                }
-                else if (!existedEqual)
-                {
-                    // 属性名は解析済みでも値が始まっていない場合
-                    if (c == '=')
-                    {
-                        // イコール後の処理に切り替え
-                        existedEqual = true;
-                        existedSpace = false;
-                    }
-                    else if (c == '>' || c == '/')
-                    {
-                        // ループ終了
-                        if (!String.IsNullOrEmpty(key))
-                        {
-                            // 属性名だけで値が無いパターン(<div disable>とか)
-                            a[this.Decode(key)] = String.Empty;
-                            key = null;
-                        }
-
-                        break;
-                    }
-                    else if (c == ' ')
-                    {
-                        // 属性名の後にあるスペースは記録して無視
-                        existedSpace = true;
-                    }
-                    else if (existedSpace)
-                    {
-                        // 既にスペースが出現した状態で新たに普通の文字が出現した場合、
-                        // キーだけで値が無いパターンだったと判定
-                        a[this.Decode(key)] = String.Empty;
-                        key = null;
-                        existedSpace = false;
-                    }
-                }
-                else
-                {
-                    // 属性値の解析
-                    if (separators == null)
-                    {
-                        if (c == ' ')
-                        {
-                            // イコールの後の、値に入る前のスペースは無視
-                            continue;
-                        }
-
-                        // イコール後の最初の文字の場合、区切り文字かを確認
-                        if (c == '\'' || c == '"')
-                        {
-                            separators = new char[] { c };
-                            continue;
-                        }
-
-                        // いきなり値が始まっている場合は、次の文字までを値と判定
-                        separators = new char[] { ' ', '>', '/' };
-                    }
-
-                    // 属性値の終了文字を探索
-                    int index = text.IndexOfAny(separators, i);
-                    if (index < 0)
-                    {
-                        // どれも出現しない場合、閉じてないということで構文エラー
-                        return false;
-                    }
-
-                    // 区切り文字に到達
-                    a[this.Decode(key)] = this.Decode(text.Substring(i, index - i));
-                    key = null;
-                    existedEqual = false;
-                    separators = null;
-
-                    if (text[index] == '>' || text[index] == '/')
-                    {
-                        // 区切り文字がループ終了の文字だったらここで終了
-                        break;
-                    }
-
-                    // ループを次回「区切り文字の次の文字」になるよう更新
-                    i = index;
-                }
-            }
-
-            // '/' で抜けた場合は、閉じ括弧があるはず位置にインデックスを移動
-            if (text.ElementAtOrDefault(i) == '/')
-            {
-                ++i;
-            }
-
-            // 最後が閉じ括弧で無い場合、閉じていないのでNG
-            // ※ ループが閉じ括弧で終わらなかった場合もここに引っかかる
-            if (text.ElementAtOrDefault(i) != '>')
-            {
-                return false;
-            }
-
-            // 出力値の設定
-            attribute = a;
-            endIndex = i;
-
-            return true;
-        }
-
-        /// <summary>
-        /// タグの開始タグ以降の部分のテキストを受け取り、値と閉じタグを解析する。
-        /// </summary>
-        /// <param name="text">解析する部分文字列("~&lt;/tag&gt;" のような文字列)。</param>
-        /// <param name="tag">解析するタグ名。</param>
-        /// <param name="innerXml">解析したコンテンツ部分。</param>
-        /// <param name="endIndex">閉じタグ終了箇所('&gt;')のインデックス。閉じていない場合は-1。</param>
-        /// <returns>
-        /// 解析に成功した場合<c>true</c>。
-        /// ※現状では常に<c>true</c>。単に他とパラメータをあわせただけ。
-        /// </returns>
-        private bool TryParseContent(string text, string tag, out string innerXml, out int endIndex)
-        {
-            // 閉じタグまでを取得。終わりが見つからない場合は、全てタグブロックと判断
-            innerXml = text;
-            endIndex = -1;
-
-            // 検索条件作成
-            RegexOptions options = RegexOptions.Singleline;
-            if (this.IgnoreCase)
-            {
-                // 大文字小文字を区別しない
-                options = options | RegexOptions.IgnoreCase;
-            }
-
-            Regex endRegex = new Regex("^</" + Regex.Escape(tag) + "\\s*>", options);
-
-            for (int i = 0; i < text.Length; i++)
-            {
-                // ※ 本当は全て正規表現一発で処理したいが、コメントが含まれている可能性があるのでループ
-                if (text[i] != '<')
-                {
-                    continue;
-                }
-
-                // 終了条件のチェック
-                string s = text.Substring(i);
-                Match match = endRegex.Match(s);
-                if (match.Success)
-                {
-                    innerXml = text.Substring(0, i);
-                    endIndex = i + match.Length;
-                    break;
-                }
-
-                // コメント(<!--)のチェック
-                string comment;
-                if (LazyXmlParser.TryParseComment(s, out comment))
-                {
-                    i += comment.Length - 1;
-                    continue;
-                }
-            }
-
-            return true;
-        }
-
-        #endregion
-
-        #region 内部クラス
-
-        /// <summary>
-        /// XML/HTMLタグをあらわすモデルクラスです。
-        /// </summary>
-        /// <remarks>
-        /// パラメータ等は<c>XmlElement</c>に似せている。
-        /// 本当はそちらを返したいが設定すべき項目が多く簡単に使用できないため。
-        /// </remarks>
-        public class SimpleElement
-        {
-            #region コンストラクタ
-
-            /// <summary>
-            /// 指定された情報を持つタグを作成する。
-            /// </summary>
-            /// <param name="name">タグ名。</param>
-            /// <param name="attributes">属性情報。</param>
-            /// <param name="innerXml">このタグに含まれる値。</param>
-            /// <param name="outerXml">このタグの開始/終了タグを含む文字列。</param>
-            public SimpleElement(string name, IDictionary<string, string> attributes, string innerXml, string outerXml)
-            {
-                this.Name = name;
-                this.Attributes = attributes;
-                this.InnerXml = innerXml;
-                this.OuterXml = outerXml;
-            }
-
-            #endregion
-
-            #region プロパティ
-
-            /// <summary>
-            /// タグ名。
-            /// </summary>
-            public string Name
-            {
-                get;
-                private set;
-            }
-
-            /// <summary>
-            /// このタグに含まれる文字列。
-            /// </summary>
-            public string InnerXml
-            {
-                get;
-                private set;
-            }
-
-            /// <summary>
-            /// このタグの開始/終了タグを含む文字列。
-            /// </summary>
-            public string OuterXml
-            {
-                get;
-                private set;
-            }
-
-            /// <summary>
-            /// タグに含まれる属性情報。
-            /// </summary>
-            public IDictionary<string, string> Attributes
-            {
-                get;
-                private set;
-            }
-
-            #endregion
-
-            #region メソッド
-
-            /// <summary>
-            /// タグに指定した名前の属性があるかどうかを確認する。
-            /// </summary>
-            /// <param name="name">検索する属性の名前。</param>
-            /// <returns>現在のノードに指定した属性がある場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
-            public bool HasAttribute(string name)
-            {
-                return this.Attributes.ContainsKey(name);
-            }
-
-            /// <summary>
-            /// 指定した名前の属性の値を返す。
-            /// </summary>
-            /// <param name="name">取得する属性の名前。</param>
-            /// <returns>指定した属性の値。一致する属性が見つからない場合、属性に指定した値がない場合は、空の文字列を返す。</returns>
-            public string GetAttribute(string name)
-            {
-                string value;
-                if (this.Attributes.TryGetValue(name, out value))
-                {
-                    return StringUtils.DefaultString(value);
-                }
-
-                return String.Empty;
-            }
-
-            /// <summary>
-            /// <c>OuterXml</c> を返す。
-            /// </summary>
-            /// <returns><c>OuterXml</c>。</returns>
-            public override string ToString()
-            {
-                return this.OuterXml;
-            }
-
-            #endregion
-        }
-
-        #endregion
-    }
-}
similarity index 85%
rename from Wptscs/Models/MediaWiki.cs
rename to Wptscs/Websites/MediaWiki.cs
index ecb5e2c..ae1d6e9 100644 (file)
@@ -3,21 +3,22 @@
 //      MediaWikiのウェブサイト(システム)をあらわすモデルクラスソース</summary>
 //
 // <copyright file="MediaWiki.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.Collections.Generic;
     using System.IO;
-    using System.Web;
     using System.Xml;
     using System.Xml.Serialization;
     using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
     using Honememo.Wptscs.Properties;
+    using Honememo.Wptscs.Utilities;
 
     /// <summary>
     /// MediaWikiのウェブサイト(システム)をあらわすモデルクラスです。
@@ -76,7 +77,7 @@ namespace Honememo.Wptscs.Models
         /// </summary>
         /// <param name="language">ウェブサイトの言語。</param>
         /// <param name="location">ウェブサイトの場所。</param>
-        public MediaWiki(Language language, string location)
+        public MediaWiki(Language language, string location) : this()
         {
             // メンバ変数の初期設定
             this.Language = language;
@@ -87,7 +88,7 @@ namespace Honememo.Wptscs.Models
         /// コンストラクタ(Wikipedia用)。
         /// </summary>
         /// <param name="language">ウェブサイトの言語。</param>
-        public MediaWiki(Language language)
+        public MediaWiki(Language language) : this()
         {
             // メンバ変数の初期設定
             // ※ オーバーロードメソッドを呼んでいないのは、languageがnullのときに先にエラーになるから
@@ -100,6 +101,8 @@ namespace Honememo.Wptscs.Models
         /// </summary>
         protected MediaWiki()
         {
+            this.WebProxy = new AppDefaultWebProxy();
+            this.DocumentationTemplates = new List<string>();
         }
 
         #endregion
@@ -272,7 +275,7 @@ namespace Honememo.Wptscs.Models
 
                     // APIのXMLデータをMediaWikiサーバーから取得
                     XmlDocument xml = new XmlDocument();
-                    using (Stream reader = this.GetStream(new Uri(new Uri(this.Location), this.NamespacePath)))
+                    using (Stream reader = this.WebProxy.GetStream(new Uri(new Uri(this.Location), this.NamespacePath)))
                     {
                         xml.Load(reader);
                     }
@@ -379,10 +382,10 @@ namespace Honememo.Wptscs.Models
         /// Template:Documentation(言語間リンク等を別ページに記述するためのテンプレート)に相当するページ名。
         /// </summary>
         /// <remarks>空の場合、その言語版にはこれに相当する機能は無いものとして扱う。</remarks>
-        public string DocumentationTemplate
+        public IList<string> DocumentationTemplates
         {
             get;
-            set;
+            protected set;
         }
 
         /// <summary>
@@ -398,6 +401,26 @@ namespace Honememo.Wptscs.Models
             set;
         }
 
+        /// <summary>
+        /// Template:仮リンク(他言語へのリンク)で書式化するためのフォーマット。
+        /// </summary>
+        /// <remarks>空の場合、その言語版にはこれに相当する機能は無いor使用しないものとして扱う。</remarks>
+        public string LinkInterwikiFormat
+        {
+            get;
+            set;
+        }
+
+        /// <summary>
+        /// このクラスで使用するWebアクセス用Proxyインスタンス。
+        /// </summary>
+        /// <remarks>setterはユニットテスト用に公開。</remarks>
+        public IWebProxy WebProxy
+        {
+            protected get;
+            set;
+        }
+
         #endregion
 
         #region 公開メソッド
@@ -410,13 +433,9 @@ namespace Honememo.Wptscs.Models
         /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
         public override Page GetPage(string title)
         {
-            // &amp; &nbsp; 等の特殊文字をデコード
-            // ※ 本当は呼び元側ですべき処理の気がするが、現状手ごろな場所が無いので
-            string decodeTitle = HttpUtility.HtmlDecode(title);
-
             // fileスキームの場合、記事名からファイルに使えない文字をエスケープ
             // ※ 仕組み的な処理はWebsite側に置きたいが、向こうではタイトルだけを抽出できないので
-            string escapeTitle = decodeTitle;
+            string escapeTitle = title;
             if (new Uri(this.Location).Scheme == "file")
             {
                 escapeTitle = FormUtils.ReplaceInvalidFileNameChars(title);
@@ -424,8 +443,8 @@ namespace Honememo.Wptscs.Models
 
             // ページのXMLデータをMediaWikiサーバーから取得
             XmlDocument xml = new XmlDocument();
-            using (Stream reader = this.GetStream(
-                new Uri(new Uri(this.Location), String.Format(this.ExportPath, escapeTitle))))
+            using (Stream reader = this.WebProxy.GetStream(
+                new Uri(new Uri(this.Location), StringUtils.FormatDollarVariable(this.ExportPath, escapeTitle))))
             {
                 xml.Load(reader);
             }
@@ -486,6 +505,24 @@ namespace Honememo.Wptscs.Models
 
             return false;
         }
+
+        /// <summary>
+        /// <see cref="LinkInterwikiFormat"/> を渡された記事名, 言語, 表示名で書式化した文字列を返す。
+        /// </summary>
+        /// <param name="title">記事名。</param>
+        /// <param name="lang">言語。</param>
+        /// <param name="langTitle">他言語版記事名。</param>
+        /// <param name="label">表示名。</param>
+        /// <returns>書式化した文字列。<see cref="LinkInterwikiFormat"/>が未設定の場合<c>null</c>。</returns>
+        public string FormatLinkInterwiki(string title, string lang, string langTitle, string label)
+        {
+            if (String.IsNullOrEmpty(this.LinkInterwikiFormat))
+            {
+                return null;
+            }
+
+            return StringUtils.FormatDollarVariable(this.LinkInterwikiFormat, title, lang, langTitle, label);
+        }
         
         #endregion
 
@@ -556,12 +593,21 @@ namespace Honememo.Wptscs.Models
             }
 
             // Template:Documentationの設定
-            XmlElement docElement = siteElement.SelectSingleNode("DocumentationTemplate") as XmlElement;
-            if (docElement != null)
+            this.DocumentationTemplates = new List<string>();
+            foreach (XmlNode docNode in siteElement.SelectNodes("DocumentationTemplates/DocumentationTemplate"))
             {
-                this.DocumentationTemplate = docElement.InnerText;
-                this.DocumentationTemplateDefaultPage = docElement.GetAttribute("DefaultPage");
+                this.DocumentationTemplates.Add(docNode.InnerText);
+                XmlElement docElement = docNode as XmlElement;
+                if (docElement != null)
+                {
+                    // ※ XML上DefaultPageはテンプレートごとに異なる値を持てるが、
+                    //    そうした例を見かけたことがないため、代表で一つの値のみ使用
+                    //    (複数値が持てるのも、リダイレクトが存在するためその対策として)
+                    this.DocumentationTemplateDefaultPage = docElement.GetAttribute("DefaultPage");
+                }
             }
+
+            this.LinkInterwikiFormat = XmlUtils.InnerText(siteElement.SelectSingleNode("LinkInterwikiFormat"));
         }
 
         /// <summary>
@@ -598,16 +644,19 @@ namespace Honememo.Wptscs.Models
                 }
             }
 
+            // Template:Documentationの設定
             writer.WriteEndElement();
-
-            // Template:Documentationの設定は一項目で出力
-            if (!String.IsNullOrEmpty(this.DocumentationTemplate))
+            writer.WriteStartElement("DocumentationTemplates");
+            foreach (string doc in this.DocumentationTemplates)
             {
                 writer.WriteStartElement("DocumentationTemplate");
                 writer.WriteAttributeString("DefaultPage", this.DocumentationTemplateDefaultPage);
-                writer.WriteValue(this.DocumentationTemplate);
+                writer.WriteValue(doc);
                 writer.WriteEndElement();
             }
+
+            writer.WriteEndElement();
+            writer.WriteElementString("LinkInterwikiFormat", this.LinkInterwikiFormat);
         }
 
         #endregion
diff --git a/Wptscs/Websites/MediaWikiPage.cs b/Wptscs/Websites/MediaWikiPage.cs
new file mode 100644 (file)
index 0000000..70ee633
--- /dev/null
@@ -0,0 +1,413 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiのページをあらわすモデルクラスソース</summary>
+//
+// <copyright file="MediaWikiPage.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Websites
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using Honememo.Parsers;
+    using Honememo.Utilities;
+    using Honememo.Wptscs.Parsers;
+
+    /// <summary>
+    /// MediaWikiのページをあらわすモデルクラスです。
+    /// </summary>
+    public class MediaWikiPage : Page
+    {
+        #region private変数
+
+        /// <summary>
+        /// リダイレクト先のページ名。
+        /// </summary>
+        private MediaWikiLink redirect;
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// コンストラクタ。
+        /// </summary>
+        /// <param name="website">ページが所属するウェブサイト。</param>
+        /// <param name="title">ページタイトル。</param>
+        /// <param name="text">ページの本文。</param>
+        /// <param name="timestamp">ページのタイムスタンプ。</param>
+        public MediaWikiPage(MediaWiki website, string title, string text, DateTime? timestamp)
+            : base(website, title, text, timestamp)
+        {
+        }
+
+        /// <summary>
+        /// コンストラクタ。
+        /// ページのタイムスタンプには<c>null</c>を設定。
+        /// </summary>
+        /// <param name="website">ページが所属するウェブサイト。</param>
+        /// <param name="title">ページタイトル。</param>
+        /// <param name="text">ページの本文。</param>
+        public MediaWikiPage(MediaWiki website, string title, string text)
+            : base(website, title, text)
+        {
+        }
+
+        /// <summary>
+        /// コンストラクタ。
+        /// ページの本文, タイムスタンプには<c>null</c>を設定。
+        /// </summary>
+        /// <param name="website">ページが所属するウェブサイト。</param>
+        /// <param name="title">ページタイトル。</param>
+        public MediaWikiPage(MediaWiki website, string title)
+            : base(website, title)
+        {
+        }
+
+        #endregion
+
+        #region プロパティ
+
+        /// <summary>
+        /// ページが所属するウェブサイト。
+        /// </summary>
+        public new MediaWiki Website
+        {
+            get
+            {
+                return base.Website as MediaWiki;
+            }
+
+            protected set
+            {
+                base.Website = value;
+            }
+        }
+
+        /// <summary>
+        /// ページの本文。
+        /// </summary>
+        public override string Text
+        {
+            get
+            {
+                return base.Text;
+            }
+
+            protected set
+            {
+                // 本文は普通に格納
+                base.Text = value;
+                this.redirect = null;
+
+                // 本文格納のタイミングでリダイレクトページ(#REDIRECT等)かを判定
+                if (!String.IsNullOrEmpty(base.Text))
+                {
+                    IElement element;
+                    if (new MediaWikiRedirectParser(this.Website).TryParse(base.Text, out element))
+                    {
+                        this.redirect = element as MediaWikiLink;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// リダイレクト先へのリンク。
+        /// </summary>
+        public MediaWikiLink Redirect
+        {
+            get
+            {
+                // Textが設定されている場合のみ有効
+                this.ValidateIncomplete();
+                return this.redirect;
+            }
+
+            protected set
+            {
+                this.redirect = value;
+            }
+        }
+
+        #endregion
+        
+        #region 公開インスタンスメソッド
+
+        /// <summary>
+        /// 指定された言語コードへの言語間リンクを返す。
+        /// </summary>
+        /// <param name="code">言語コード。</param>
+        /// <returns>言語間リンク先の記事名。見つからない場合は空。</returns>
+        /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
+        public string GetInterWiki(string code)
+        {
+            // Textが設定されている場合のみ有効
+            this.ValidateIncomplete();
+
+            // 記事を解析し、その結果から言語間リンクを探索
+            return this.GetInterWiki(code, new MediaWikiParser(this.Website).Parse(this.Text));
+        }
+
+        /// <summary>
+        /// ページがリダイレクトかをチェック。
+        /// </summary>
+        /// <returns><c>true</c> リダイレクト。</returns>
+        public bool IsRedirect()
+        {
+            // Textが設定されている場合のみ有効
+            return this.Redirect != null;
+        }
+
+        /// <summary>
+        /// ページがテンプレートかをチェック。
+        /// </summary>
+        /// <returns><c>true</c> テンプレート。</returns>
+        public bool IsTemplate()
+        {
+            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
+            return this.IsNamespacePage(this.Website.TemplateNamespace);
+        }
+
+        /// <summary>
+        /// ページがカテゴリーかをチェック。
+        /// </summary>
+        /// <returns><c>true</c> カテゴリー。</returns>
+        public bool IsCategory()
+        {
+            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
+            return this.IsNamespacePage(this.Website.CategoryNamespace);
+        }
+
+        /// <summary>
+        /// ページが画像かをチェック。
+        /// </summary>
+        /// <returns><c>true</c> 画像。</returns>
+        public bool IsFile()
+        {
+            // 指定されたページ名がファイル(Image:等で始まる)かをチェック
+            return this.IsNamespacePage(this.Website.FileNamespace);
+        }
+
+        /// <summary>
+        /// ページが標準名前空間かをチェック。
+        /// </summary>
+        /// <returns><c>true</c> 標準名前空間。</returns>
+        public bool IsMain()
+        {
+            // 指定されたページ名が標準名前空間以外の名前空間(Wikipedia:等で始まる)かをチェック
+            string title = this.Title.ToLower();
+            foreach (IList<string> prefixes in this.Website.Namespaces.Values)
+            {
+                foreach (string prefix in prefixes)
+                {
+                    if (title.StartsWith(prefix.ToLower() + ":"))
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        #endregion
+
+        #region 内部処理用インスタンスメソッド
+
+        /// <summary>
+        /// ページが指定された番号の名前空間に所属するかをチェック。
+        /// </summary>
+        /// <param name="id">名前空間のID。</param>
+        /// <returns><c>true</c> 所属する。</returns>
+        protected bool IsNamespacePage(int id)
+        {
+            // 指定された記事名がカテゴリー(Category:等で始まる)かをチェック
+            IList<string> prefixes = this.Website.Namespaces[id];
+            if (prefixes != null)
+            {
+                string title = this.Title.ToLower();
+                foreach (string prefix in prefixes)
+                {
+                    if (title.StartsWith(prefix.ToLower() + ":"))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// オブジェクトがメソッドの実行に不完全な状態でないか検証する。
+        /// 不完全な場合、例外をスローする。
+        /// </summary>
+        /// <exception cref="InvalidOperationException">オブジェクトは不完全。</exception>
+        protected void ValidateIncomplete()
+        {
+            if (String.IsNullOrEmpty(this.Text))
+            {
+                // ページ本文が設定されていない場合不完全と判定
+                throw new InvalidOperationException("Text is unset");
+            }
+        }
+
+        /// <summary>
+        /// 指定されたページ解析結果要素から言語間リンクを取得。
+        /// </summary>
+        /// <param name="code">言語コード。</param>
+        /// <param name="element">要素。</param>
+        /// <returns>言語間リンク先の記事名。見つからない場合は空。</returns>
+        /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
+        private string GetInterWiki(string code, IElement element)
+        {
+            if (element is MediaWikiTemplate)
+            {
+                // Documentationテンプレートがある場合は、その中を探索
+                string interWiki = this.GetDocumentationInterWiki((MediaWikiTemplate)element, code);
+                if (!String.IsNullOrEmpty(interWiki))
+                {
+                    return interWiki;
+                }
+            }
+            else if (element is MediaWikiLink)
+            {
+                // 指定言語への言語間リンクの場合、内容を取得し、処理終了
+                MediaWikiLink link = (MediaWikiLink)element;
+                if (link.Code == code && !link.IsColon)
+                {
+                    return link.Title;
+                }
+            }
+            else if (element is IEnumerable<IElement>)
+            {
+                // 子要素を持つ場合、再帰的に探索
+                foreach (IElement e in (IEnumerable<IElement>)element)
+                {
+                    string interWiki = this.GetInterWiki(code, e);
+                    if (!String.IsNullOrEmpty(interWiki))
+                    {
+                        return interWiki;
+                    }
+                }
+            }
+
+            // 未発見の場合、空文字列
+            return String.Empty;
+        }
+
+        /// <summary>
+        /// 渡されたTemplate:Documentationの呼び出しから、指定された言語コードへの言語間リンクを返す。
+        /// </summary>
+        /// <param name="template">テンプレート呼び出しのリンク。</param>
+        /// <param name="code">言語コード。</param>
+        /// <returns>言語間リンク先の記事名。見つからない場合またはパラメータが対象外の場合は空。</returns>
+        /// <remarks>言語間リンクが複数存在する場合は、先に発見したものを返す。</remarks>
+        private string GetDocumentationInterWiki(MediaWikiTemplate template, string code)
+        {
+            // Documentationテンプレートのリンクかを確認
+            if (!this.IsDocumentationTemplate(template.Title))
+            {
+                return String.Empty;
+            }
+
+            // インライン・コンテンツの可能性があるため、先にパラメータを再帰的に探索
+            foreach (IElement e in template.PipeTexts)
+            {
+                string interWiki = this.GetInterWiki(code, e);
+                if (!String.IsNullOrEmpty(interWiki))
+                {
+                    return interWiki;
+                }
+            }
+
+            // インラインでなさそうな場合、解説記事名を確認
+            string subtitle = ObjectUtils.ToString(template.PipeTexts.ElementAtOrDefault(0));
+            if (String.IsNullOrWhiteSpace(subtitle) || subtitle.Contains('='))
+            {
+                // 指定されていない場合はデフォルトのページを探索
+                subtitle = this.Website.DocumentationTemplateDefaultPage;
+            }
+
+            if (String.IsNullOrEmpty(subtitle))
+            {
+                return String.Empty;
+            }
+
+            // サブページの場合、親ページのページ名を付加
+            // TODO: サブページの仕組みについては要再検討
+            if (subtitle.StartsWith("/"))
+            {
+                subtitle = this.Title + subtitle;
+            }
+
+            // 解説ページから言語間リンクを取得
+            MediaWikiPage subpage = null;
+            try
+            {
+                // ※ 本当はここでの取得状況も画面に見せたいが、今のつくりで
+                //    そうするとややこしくなるので隠蔽する。
+                subpage = this.Website.GetPage(subtitle) as MediaWikiPage;
+            }
+            catch (Exception ex)
+            {
+                System.Diagnostics.Debug.WriteLine(ex.StackTrace);
+            }
+
+            if (subpage != null)
+            {
+                string interWiki = subpage.GetInterWiki(code);
+                if (!String.IsNullOrEmpty(interWiki))
+                {
+                    return interWiki;
+                }
+            }
+
+            // 未発見の場合、空文字列
+            return String.Empty;
+        }
+
+        /// <summary>
+        /// 渡されたテンプレート名がTemplate:Documentationのいずれかに該当するか?
+        /// </summary>
+        /// <param name="title">テンプレート名。</param>
+        /// <returns>該当する場合<c>true</c>。</returns>
+        private bool IsDocumentationTemplate(string title)
+        {
+            // Documentationテンプレートのリンクかを確認
+            string lowerTitle = title.ToLower();
+            foreach (string docTitle in this.Website.DocumentationTemplates)
+            {
+                string lowerDocTitle = docTitle.ToLower();
+
+                // 普通にテンプレート名を比較
+                if (lowerTitle == lowerDocTitle)
+                {
+                    return true;
+                }
+
+                // 名前空間で一致していない可能性があるので、名前空間を取ってもう一度判定
+                int index = lowerDocTitle.IndexOf(':');
+                if (new MediaWikiPage(this.Website, lowerDocTitle).IsTemplate()
+                    && index >= 0 && index + 1 < lowerDocTitle.Length)
+                {
+                    lowerDocTitle = lowerDocTitle.Substring(lowerDocTitle.IndexOf(':') + 1);
+                }
+
+                if (lowerTitle == lowerDocTitle)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        #endregion
+    }
+}
similarity index 99%
rename from Wptscs/Models/Page.cs
rename to Wptscs/Websites/Page.cs
index 38d8ba2..6a370d7 100644 (file)
@@ -8,7 +8,7 @@
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.Linq;
similarity index 59%
rename from Wptscs/Models/Website.cs
rename to Wptscs/Websites/Website.cs
index 4146322..45d56eb 100644 (file)
@@ -3,17 +3,19 @@
 //      ウェブサイトをあらわすモデルクラスソース</summary>
 //
 // <copyright file="Website.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.IO;
     using System.Net;
+    using Honememo.Models;
     using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
     using Honememo.Wptscs.Properties;
 
     /// <summary>
@@ -102,53 +104,6 @@ namespace Honememo.Wptscs.Models
         /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
         public abstract Page GetPage(string title);
 
-        /// <summary>
-        /// 指定されたURIの情報をストリームで取得。
-        /// </summary>
-        /// <param name="uri">取得対象のURI。</param>
-        /// <returns>取得したストリーム。使用後は必ずクローズすること。</returns>
-        /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
-        protected Stream GetStream(Uri uri)
-        {
-            // URIに応じたWebRequestを取得
-            WebRequest req = WebRequest.Create(uri);
-
-            // 設定が必要なRequestの場合は、必要な設定を行う
-            // ※ FileWebRequestであれば特になし、それ以外は通すが未確認
-            if (req as HttpWebRequest != null)
-            {
-                // HTTP/HTTPSの場合
-                HttpWebRequest h = req as HttpWebRequest;
-
-                // UserAgent設定
-                string ua = Settings.Default.UserAgent;
-                if (String.IsNullOrEmpty(ua))
-                {
-                    // 特に設定が無い場合はデフォルトの値を設定
-                    Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
-                    ua = String.Format(
-                        Settings.Default.DefaultUserAgent,
-                        ver.Major,
-                        ver.Minor);
-                }
-
-                h.UserAgent = ua;
-
-                // Referer設定
-                string referer = Settings.Default.Referer;
-                if (String.IsNullOrEmpty(referer))
-                {
-                    referer = String.Empty;
-                }
-
-                h.Referer = referer;
-            }
-
-            // 応答データを受信するためのStreamを取得し、データを取得
-            System.Diagnostics.Debug.WriteLine("Website.GetStream > " + uri.ToString());
-            return req.GetResponse().GetResponseStream();
-        }
-
         #endregion
     }
 }
index 74426cf..4ab4958 100644 (file)
@@ -16,6 +16,7 @@
     </FileUpgradeFlags>
     <OldToolsVersion>2.0</OldToolsVersion>
     <UpgradeBackupLocation />
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -40,7 +41,6 @@
     <Reference Include="System.Data" />
     <Reference Include="System.Deployment" />
     <Reference Include="System.Drawing" />
-    <Reference Include="System.Web" />
     <Reference Include="System.Windows.Forms" />
     <Reference Include="System.Xml" />
   </ItemGroup>
       <ExcludeFromStyleCop>true</ExcludeFromStyleCop>
     </Compile>
     <Compile Include="Models\Config.cs" />
-    <Compile Include="Utilities\LazyXmlParser.cs" />
-    <Compile Include="Models\IgnoreCaseDictionary.cs" />
     <Compile Include="Models\Language.cs" />
-    <Compile Include="Models\MediaWiki.cs" />
-    <Compile Include="Models\MediaWikiPage.cs" />
-    <Compile Include="Models\Page.cs" />
+    <Compile Include="Parsers\MediaWikiHeading.cs" />
+    <Compile Include="Parsers\MediaWikiTemplate.cs" />
+    <Compile Include="Parsers\MediaWikiHeadingParser.cs" />
+    <Compile Include="Parsers\MediaWikiLinkParser.cs" />
+    <Compile Include="Parsers\MediaWikiTemplateParser.cs" />
+    <Compile Include="Parsers\MediaWikiNowikiParser.cs" />
+    <Compile Include="Parsers\MediaWikiVariable.cs" />
+    <Compile Include="Parsers\MediaWikiVariableParser.cs" />
+    <Compile Include="Parsers\MediaWikiRedirectParser.cs" />
+    <Compile Include="Websites\MediaWiki.cs" />
+    <Compile Include="Parsers\MediaWikiLink.cs" />
+    <Compile Include="Websites\MediaWikiPage.cs" />
+    <Compile Include="Websites\Page.cs" />
     <Compile Include="Models\TranslationDictionary.cs" />
     <Compile Include="Models\TranslationTable.cs" />
-    <Compile Include="Models\Website.cs" />
+    <Compile Include="Websites\Website.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Utilities\AppDefaultWebProxy.cs" />
+    <Compile Include="Utilities\IWebProxy.cs" />
+    <Compile Include="Parsers\MediaWikiParser.cs" />
     <EmbeddedResource Include="ConfigForm.resx">
       <SubType>Designer</SubType>
       <DependentUpon>ConfigForm.cs</DependentUpon>
     </Compile>
     <Compile Include="Settings.cs" />
     <Compile Include="Utilities\FormUtils.cs" />
-    <Compile Include="Utilities\ObjectUtils.cs" />
-    <Compile Include="Utilities\StringUtils.cs" />
-    <Compile Include="Utilities\Validate.cs" />
-    <Compile Include="Utilities\XmlUtils.cs" />
   </ItemGroup>
   <ItemGroup>
     <Content Include="App.ico" />
       <SubType>Designer</SubType>
     </Content>
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\HmLib\HmLib.csproj">
+      <Project>{CC7F6106-0C00-427B-9BF2-1EE65448907B}</Project>
+      <Name>HmLib</Name>
+    </ProjectReference>
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="$(ProgramFiles)\MSBuild\Microsoft\StyleCop\v4.4\Microsoft.StyleCop.Targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
index 596f0b6..70ff3b4 100644 (file)
@@ -1,11 +1,11 @@
-<?xml version="1.0" encoding="utf-8" ?>
+<?xml version="1.0"?>
 <configuration>
   <configSections>
-    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
-      <section name="Honememo.Wptscs.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+      <section name="Honememo.Wptscs.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
     </sectionGroup>
-    <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
-      <section name="Honememo.Wptscs.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
+    <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+      <section name="Honememo.Wptscs.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false"/>
     </sectionGroup>
   </configSections>
   <applicationSettings>
             <string>#tag</string>
             <string>gender</string>
             <string>groupconvert</string>
+            <string>#expr</string>
+            <string>#if</string>
+            <string>#ifeq</string>
+            <string>#ifexist</string>
+            <string>#ifexpr</string>
+            <string>#switch</string>
+            <string>#time</string>
+            <string>#rel2abs</string>
+            <string>#titleparts</string>
+            <string>#iferror</string>
           </ArrayOfString>
         </value>
       </setting>
       <setting name="MediaWikiExportPath" serializeAs="String">
-        <value>/wiki/Special:Export/{0}</value>
+        <value>/wiki/Special:Export/$1</value>
       </setting>
       <setting name="Bracket" serializeAs="String">
-        <value> ({0}) </value>
+        <value> ($1) </value>
       </setting>
       <setting name="MediaWikiRedirect" serializeAs="String">
         <value>#REDIRECT</value>
         <value>6</value>
       </setting>
       <setting name="ConfigurationCompatible" serializeAs="String">
-        <value>1.0.0.0</value>
+        <value>1.10.0.0</value>
       </setting>
       <setting name="MediaWikiNamespacePath" serializeAs="String">
         <value>/w/api.php?format=xml&amp;action=query&amp;meta=siteinfo&amp;siprop=namespaces|namespacealiases</value>
       </setting>
     </Honememo.Wptscs.Properties.Settings>
   </userSettings>
-</configuration>
\ No newline at end of file
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/></startup></configuration>
index 4b5a5d6..e6a4bc0 100644 (file)
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D8%B5%D8%AF%D9%8A%D8%B1/{0}</ExportPath>
+      <ExportPath>/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D8%B5%D8%AF%D9%8A%D8%B1/$1</ExportPath>
       <Redirect>#تحويل</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/شرح">قالب:توثيق</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/شرح">قالب:توثيق</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{وإو|عر=$1|لغ=$2|تر=$3|نص=$4}}</LinkInterwikiFormat>
+    </MediaWiki>
+    <MediaWiki>
+      <Location>http://bn.wikipedia.org</Location>
+      <Language Code="bn">
+        <Names>
+          <LanguageName Code="bn">
+            <Name>বাংলা ভাষা</Name>
+            <ShortName />
+          </LanguageName>
+          <LanguageName Code="en">
+            <Name>Bengali language</Name>
+            <ShortName>Bengali</ShortName>
+          </LanguageName>
+          <LanguageName Code="ja">
+            <Name>ベンガル語</Name>
+            <ShortName />
+          </LanguageName>
+        </Names>
+      </Language>
+      <ExportPath>/wiki/%E0%A6%AC%E0%A6%BF%E0%A6%B6%E0%A7%87%E0%A6%B7:Export/$1</ExportPath>
+      <MagicWords />
+      <DocumentationTemplates />
     </MediaWiki>
     <MediaWiki>
       <Location>http://de.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Spezial:Exportieren/{0}</ExportPath>
+      <ExportPath>/wiki/Spezial:Exportieren/$1</ExportPath>
       <Redirect>#WEITERLEITUNG</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/Doku">Vorlage:Dokumentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/Doku">Vorlage:Dokumentation</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://en.wikipedia.org</Location>
         </Names>
       </Language>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+        <DocumentationTemplate DefaultPage="/doc">Template:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{link-interwiki|en=$1|en_text=$4|lang=$2|lang_title=$3}}</LinkInterwikiFormat>
     </MediaWiki>
     <MediaWiki>
       <Location>http://es.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Especial:Exportar/{0}</ExportPath>
+      <ExportPath>/wiki/Especial:Exportar/$1</ExportPath>
       <Redirect>#REDIRECCIÓN</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Plantilla:Documentación</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Plantilla:Documentación</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://fr.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Sp%C3%A9cial:Exporter/{0}</ExportPath>
+      <ExportPath>/wiki/Sp%C3%A9cial:Exporter/$1</ExportPath>
       <Redirect>#REDIRECTION</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Modèle:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Modèle:Documentation</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{Lien|fr=$1|lang=$2|trad=$3|texte=$4}}</LinkInterwikiFormat>
+    </MediaWiki>
+    <MediaWiki>
+      <Location>http://hi.wikipedia.org</Location>
+      <Language Code="hi">
+        <Names>
+          <LanguageName Code="en">
+            <Name>Standard Hindi</Name>
+            <ShortName>Hindi</ShortName>
+          </LanguageName>
+          <LanguageName Code="hi">
+            <Name>हिन्दी</Name>
+            <ShortName />
+          </LanguageName>
+          <LanguageName Code="ja">
+            <Name>ヒンディー語</Name>
+            <ShortName />
+          </LanguageName>
+        </Names>
+      </Language>
+      <ExportPath>/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:Export/$1</ExportPath>
+      <MagicWords />
+      <DocumentationTemplates />
     </MediaWiki>
     <MediaWiki>
       <Location>http://it.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Speciale:Esporta/{0}</ExportPath>
+      <ExportPath>/wiki/Speciale:Esporta/$1</ExportPath>
       <Redirect>#RINVIA</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/man">Template:Man</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/man">Template:Man</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ja.wikipedia.org</Location>
             <ShortName />
           </LanguageName>
         </Names>
-        <Bracket>({0})</Bracket>
+        <Bracket>($1)</Bracket>
       </Language>
-      <ExportPath>/wiki/%E7%89%B9%E5%88%A5:%E3%83%87%E3%83%BC%E3%82%BF%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97/{0}</ExportPath>
+      <ExportPath>/wiki/%E7%89%B9%E5%88%A5:%E3%83%87%E3%83%BC%E3%82%BF%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97/$1</ExportPath>
       <Redirect>#転送</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+        <DocumentationTemplate DefaultPage="/doc">Template:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{仮リンク|$1|$2|$3|label=$4}}</LinkInterwikiFormat>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ko.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EB%82%B4%EB%B3%B4%EB%82%B4%EA%B8%B0/{0}</ExportPath>
+      <ExportPath>/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EB%82%B4%EB%B3%B4%EB%82%B4%EA%B8%B0/$1</ExportPath>
       <Redirect>#넘겨주기</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/설명문서">틀:틀 설명문서</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/설명문서">틀:틀 설명문서</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://nl.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Speciaal:Exporteren/{0}</ExportPath>
+      <ExportPath>/wiki/Speciaal:Exporteren/$1</ExportPath>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Sjabloon:Sjabdoc</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Sjabloon:Sjabdoc</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://pl.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Specjalna:Eksport/{0}</ExportPath>
+      <ExportPath>/wiki/Specjalna:Eksport/$1</ExportPath>
       <Redirect>#PATRZ</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/opis">Szablon:Dokumentacja</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/opis">Szablon:Dokumentacja</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://pt.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Especial:Exportar/{0}</ExportPath>
+      <ExportPath>/wiki/Especial:Exportar/$1</ExportPath>
       <Redirect>#REDIRECIONAMENTO</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Predefinição:Documentação</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Predefinição:Documentação</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ru.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Служебная:Export/{0}</ExportPath>
+      <ExportPath>/wiki/Служебная:Export/$1</ExportPath>
       <Redirect>#перенаправление</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Шаблон:Doc</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Шаблон:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://zh.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Special:%E5%AF%BC%E5%87%BA%E9%A1%B5%E9%9D%A2/{0}</ExportPath>
+      <ExportPath>/wiki/Special:%E5%AF%BC%E5%87%BA%E9%A1%B5%E9%9D%A2/$1</ExportPath>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">模板:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">模板:Documentation</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
   </Websites>
-  <ItemTables />
+  <ItemTables>
+    <ItemTable From="en" To="ja">
+      <Item From="Template:Refend" To="Template:Refend" />
+      <Item From="Template:Documentation" To="Template:Documentation" />
+    </ItemTable>
+    <ItemTable From="ja" To="en">
+      <Item From="Template:Refend" To="Template:Refend" />
+      <Item From="Template:Documentation" To="Template:Documentation" />
+    </ItemTable>
+  </ItemTables>
   <HeadingTable>
     <Group>
       <Word Lang="en">Advantages</Word>
diff --git a/WptscsTest/Data/MediaWiki/en/Discovery Channel.xml b/WptscsTest/Data/MediaWiki/en/Discovery Channel.xml
new file mode 100644 (file)
index 0000000..d53903c
--- /dev/null
@@ -0,0 +1,339 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.5/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.5/ http://www.mediawiki.org/xml/export-0.5.xsd" version="0.5" xml:lang="en">
+  <siteinfo>
+    <sitename>Wikipedia</sitename>
+    <base>http://en.wikipedia.org/wiki/Main_Page</base>
+    <generator>MediaWiki 1.18wmf1</generator>
+    <case>first-letter</case>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="4" case="first-letter">Wikipedia</namespace>
+      <namespace key="5" case="first-letter">Wikipedia talk</namespace>
+      <namespace key="6" case="first-letter">File</namespace>
+      <namespace key="7" case="first-letter">File talk</namespace>
+      <namespace key="8" case="first-letter">MediaWiki</namespace>
+      <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+      <namespace key="10" case="first-letter">Template</namespace>
+      <namespace key="11" case="first-letter">Template talk</namespace>
+      <namespace key="12" case="first-letter">Help</namespace>
+      <namespace key="13" case="first-letter">Help talk</namespace>
+      <namespace key="14" case="first-letter">Category</namespace>
+      <namespace key="15" case="first-letter">Category talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+      <namespace key="108" case="first-letter">Book</namespace>
+      <namespace key="109" case="first-letter">Book talk</namespace>
+    </namespaces>
+  </siteinfo>
+  <page>
+    <title>Discovery Channel</title>
+    <id>77807</id>
+    <revision>
+      <id>471865109</id>
+      <timestamp>2012-01-17T14:07:11Z</timestamp>
+      <contributor>
+        <username>AnomieBOT</username>
+        <id>7611264</id>
+      </contributor>
+      <minor/>
+      <comment>Dating maintenance tags: {{Cn}}</comment>
+      <text xml:space="preserve" bytes="29738">{{about|the TV channel in the United States}}
+{{pp-semi-vandalism|small=yes}}
+{{Infobox TV channel
+| name             = '''Discovery Channel'''
+| logofile         = Discovery Channel International.svg
+| logosize         = 220px
+| slogan           = ''The world is just awesome.''
+| launch           = June 17, 1985&lt;ref name=&quot;oscars.org&quot;&gt;{{cite web |url=http://www.oscars.org/awards/academyawards/legacy/ceremony/59th-winners.html |title=The 59th Academy Awards (1987) Nominees and Winners |accessdate=2011-07-23|work=oscars.org}}&lt;/ref&gt;
+| owner            = [[Discovery Communications|Discovery Communications, Inc.]]
+| CEO              = David Zaslav
+| headquarters     = [[Silver Spring, Maryland]]
+| country          = Worldwide
+| language         = English
+| sister names     = [[TLC (TV channel)|TLC]]&lt;br&gt;[[Animal Planet]]&lt;br&gt;[[OWN: Oprah Winfrey Network]]&lt;br&gt;[[Planet Green]]&lt;br&gt;[[Investigation Discovery]]&lt;br&gt;[[Discovery Fit &amp; Health]]&lt;br&gt;[[Military Channel]]&lt;br&gt;[[Science (TV channel)|Science]]&lt;br&gt;[[Velocity (TV channel)|Velocity]]
+| web              = http://www.discovery.com
+| picture format   = [[480i]] ([[SDTV]])&lt;br&gt;[[1080i]] ([[HDTV]])
+| terr serv 1 = [[Selective TV Inc.]]&lt;br/&gt;'''([[Alexandria, Minnesota]])'''
+| terr chan 1 = K47KZ (Channel 47)
+| sat serv 1 = [[DirecTV]]
+| sat chan 1 = Channel 278&lt;br&gt; Channel 1278 (VOD)
+| sat serv 2 = [[Dish Network]]
+| sat chan 2 = Channel 182 (SD/HD)&lt;br&gt; Channel 9487
+| sat serv 3 = [[C-Band]]
+| sat chan 3 = AMC 10-Channel 21
+| sat serv 7 = [[SKY México]]
+| sat chan 7 = Channel 251
+| sat serv 8 = Dish Network Mexico
+| sat chan 8 = Channel 402
+| sat serv 9 = [[Sky (UK and Ireland)]]
+| sat chan 9 = Channel 520
+| cable serv 1 = CableVision (Argentina)
+| cable chan 1 = Channel 52
+| cable serv 2 = Available on most cable systems
+| cable chan 2 = Check your local listings
+| cable serv 3 = [[Verizon FiOS]] 
+| cable chan 3 = Channel 120 (SD)&lt;br&gt;Channel 620 (HD)
+| adsl serv 1 =[[Sky Angel]]
+| adsl chan 1 =Channel 313
+| adsl serv 2 =[[AT&amp;T U-Verse]]
+| adsl chan 2 =Channel 120 (SD)&lt;br&gt; 1120 (HD)
+}}
+
+'''Discovery Channel''' (formerly '''The Discovery Channel''') is an American [[satellite]] and [[cable]] [[specialty channel]] (also delivered via [[IPTV]], [[terrestrial television]] and [[internet television]] in other parts of the world), founded by [[John Hendricks]] and distributed by [[Discovery Communications]]. It is a publicly traded company run by CEO David Zaslav. It provides [[Documentary film|documentary]] [[television program]]ming focused primarily on popular science, technology, and history. In the U.S., the programming for the main Discovery network is primarily focused on [[reality television]] themes, such as speculative investigation (with shows such as ''[[MythBusters]]'', ''[[Unsolved History]]'', and ''[[Best Evidence]]''), automobiles, and occupations (''[[Dirty Jobs]]'' and ''[[Deadliest Catch]]''); it also features documentaries specifically aimed at families and younger audiences.
+A popular annual feature is ''[[Shark Week]]''.&lt;ref&gt;[http://publications.mediapost.com/index.cfm?fuseaction=Articles.showArticleHomePage&amp;art_aid=64477 MediaPost Publications - Discovery Rebrands, Upgrades Marketing Efforts - 07/24/2007&lt;!-- Bot generated title --&gt;]&lt;/ref&gt;
+
+==History==
+On June 17, 1985, Discovery Channel was launched with $5&amp;nbsp;million in start-up capital from the [[BBC]], the American investment firm [[Allen &amp; Company]], Venture America and several other investors. In the beginning it was available to 156,000 households and would broadcast for 12&amp;nbsp;hours between 3&amp;nbsp;p.m. and 3&amp;nbsp;a.m. with about 75 percent of the content new to American viewers.&lt;ref&gt;{{cite news|url=http://www.nytimes.com/1985/06/16/movies/cable-tv-notes-a-channel-with-a-difference.html?&amp;pagewanted=all|title=CABLE TV NOTES; A CHANNEL WITH A DIFFERENCE|date=June 16, 1985|publisher=New York Times | first=Steve | last=Schneider | accessdate=May 1, 2010}}&lt;/ref&gt; [[John Hendricks]] is credited with founding of the channel and its parent company, then known as Cable Educational Network Inc, in 1982.&lt;ref&gt;{{cite news|url=http://pqasb.pqarchiver.com/washingtonpost/access/73614317.html?dids=73614317:73614317&amp;FMT=ABS&amp;FMTS=ABS:FT&amp;type=current&amp;date=Jun+19%2C+1988&amp;author=Martie+Zad&amp;pub=The+Washington+Post+%28pre-1997+Fulltext%29&amp;desc=The+Discovery+Channel%3B+Science%2C+Nature%2C+Adventure+and+Animals+That+Bite&amp;pqatl=google|title=The Discovery Channel; Science, Nature, Adventure and Animals That Bite|date=June 19, 1988|publisher=The Washington Post | first=Martie | last=Zad}}&lt;/ref&gt;
+
+In its early years, the channel broadcast some Soviet programming, including the news program ''[[Vremya]]''.&lt;ref&gt;{{cite news|url=http://www.time.com/time/magazine/article/0,9171,963577,00.html|title=Television: The Russians Are Coming|date=February 23, 1987|publisher=Time}}&lt;/ref&gt; In 1988, the channel premiered the nightly program ''World Monitor'', produced by the [[Christian Science Monitor]]. 1988 also saw the very first ''[[Shark Week]]'', which has since returned annually. Within five years, the channel's reach had extended to over 50&amp;nbsp;million households.
+
+On January 4, 2006, Discovery Communications announced that [[Ted Koppel]], longtime [[Executive Producer]] Tom Bettag, and eight former ''[[Nightline (U.S. news program)|Nightline]]'' staff members were joining the Discovery Channel.
+
+The network's ratings improved in 2006&lt;ref&gt;[http://corporate.discovery.com/news/press/06q2/083006.html DCI :: Press and News Releases]&lt;/ref&gt; after a drop widely attributed to an over-reliance on a few hit series such as ''[[Monster Garage]]'' and ''[[American Chopper]]''. Some critics said such series strayed from Discovery's mold of helping viewers learn about the world around them. Beginning in 2005, Discovery revamped its lineup to focus more closely on its traditional themes of popular science, history, and geography.&lt;ref&gt;[http://www.multichannel.com/article/CA6361880.html Dirty Work – 8/14/2006 – Multichannel News]&lt;/ref&gt; The network garnered a total of seven primetime [[Emmy]] award nominations in 2006 for shows including ''[[The Flight that Fought Back]]'' (about [[United Airlines Flight 93]]) and ''[[Deadliest Catch]]''.
+
+In 2007, Discovery Channel's top series include ''[[Dirty Jobs]]'' with Mike Rowe, the Emmy-award winning ''[[Planet Earth (TV series)|Planet Earth]]'', ''[[MythBusters]]'', and ''[[Deadliest Catch]]''. Discovery's announced plans for 2008 include a new series with Josh Bernstein, who left [[History Channel]] to join Discovery. Other announced series include ''[[Fight Quest]]'', ''[[Smash Lab]]'', and the fourth season of ''[[Deadliest Catch]]''.
+
+Discovery Channel is currently the most widely distributed cable network in the United States,&lt;ref&gt;[http://www.ncta.com/ContentView.aspx?contentId=74 Top 20 Cable Program Networks – NCTA.com]&lt;/ref&gt; reaching more than 92&amp;nbsp;million households, part of its global audience of 431&amp;nbsp;million homes in 170 countries and territories.&lt;ref&gt;[http://corporate.discovery.com/brands/discoverychannel.html DCI :: Businesses &amp; Brands :: Discovery Channel]&lt;/ref&gt; Versions of the channel are seen in Latin America, the United Kingdom, Australia, Canada, Japan, Taiwan, India, Malaysia and other countries.&lt;ref&gt;[http://corporate.discovery.com/news/press/06q1/011806.html DCI :: Press and News Releases]&lt;/ref&gt;
+
+On September 1, 2010, a man entered the building with a handgun and fired at least one shot, held hostages, and was later shot.&lt;ref name=&quot;Fox&quot;&gt;{{cite news|url=http://www.foxnews.com/us/2010/09/01/maryland-police-respond-hostage-situation-man-gun-enters-building/|title=Armed Man With Bomb Takes at Least One Hostage in Discovery Channel Building|date=September 1, 2010|publisher=[[Fox News Channel|Fox]]|accessdate=September 1, 2010}}&lt;/ref&gt;&lt;ref name=&quot;CNN&quot;&gt;{{cite news|url=http://edition.cnn.com/2010/CRIME/09/01/maryland.discovery.suspect/?hpt=Sbin#fbid=GouODzfd7D_&amp;wom=false|title=Suspect in Maryland hostage situation published angry online manifesto|date=September 1, 2010|publisher=[[CNN]]|accessdate=September 1, 2010}}&lt;/ref&gt; He has published criticisms of the network in an online manifesto at [[Savetheplanetprotest.com|SaveThePlanetProtest.com]].&lt;ref name=&quot;CNN&quot;/&gt; {{See|2010 Discovery Communications headquarters hostage crisis}}
+
+==Programming==
+{{main|List of programs broadcast by Discovery Channel}}
+
+Popular programs on the channel today include [[Shark Week]], an annual week of programming dedicated to facts about [[shark]]s; ''[[Deadliest Catch]]'', about fishing for crab in the [[Bering Sea]]; ''[[MythBusters]]'', in which the hosts use steps of the [[scientific method]] to test the validity of rumors or myths; ''[[How It's Made]]'', which features how everyday objects are manufactured; ''[[Dirty Jobs]]'', about unsanitary blue-collar occupations; ''[[Cash Cab]]'', a quiz show; and ''[[Man vs. Wild]]'', showing how a man can survive in the wild. [[Christopher Lowell]] won a [[Daytime Emmy Award]] in 2000 for ''The Christopher Lowell Show'', which aired on the Discovery Channel from 1998 to 2001.{{cn|date=January 2012}}
+
+==Non-television ventures==
+===Pro Cycling Team===
+{{main|Discovery Channel Pro Cycling Team}}
+Shortly before the 2004 [[Tour de France]], Discovery Channel announced it would become the primary sponsor of a professional bicycling team starting in 2005, featuring seven-time [[Tour de France]] winner [[Lance Armstrong]]. However, after the 2007 victory with the Spaniard [[Alberto Contador]] Discovery Channel announced its retirement from cycling sponsorship. This sponsorship ended after the 2007 cycling season.
+
+===Discovery Channel Radio===
+Discovery Channel Radio was a channel on the both major Canada [[satellite radio]] services. The programming consisted of [[sound|audio]] versions of popular programs from its multitude of TV channels. Discovery was previously on [[XM Satellite Radio]] but was dropped in early September 2005. [[Sirius Satellite Radio]] dropped Discovery Radio from its slate on February 21, 2007.
+
+===Store===
+Discovery Channel also lent its branding to retail stores in malls and other locations across America, as well as an online store. Educational gifts were the store's specialty. On May 17, 2007, Discovery Communications announced it was closing its stand-alone and mall-based stores. Hudson Group will continue to operate the Discovery Channel Airport Stores, and the website remains in operation.&lt;ref&gt;{{cite news| url=http://money.cnn.com/2007/05/17/news/companies/discovery/?postversion=2007051717 | work=CNN | title=Discovery shuttering 103 locations | date=May 17, 2007 | accessdate=May 1, 2010}}&lt;/ref&gt;
+
+===Telescope===
+{{main|Discovery Channel Telescope}}
+Discovery Channel is also funding the construction of the [http://www.lowell.edu/dct/index.php Discovery Channel Telescope], in partnership with [[Lowell Observatory]].
+
+===Website===
+[http://dsc.discovery.com/ Discovery.com] features several exclusive browser-based games, with various science-based or sociological challenges.
+
+==Marketing and branding==
+===Taglines===
+Discovery Channel's previous [[tagline]]s had been &quot;''Explore Your World''&quot; and &quot;''There's no thrill like discovery.''&quot; However in view of its changing focus towards more reality-based programming and away from strictly educational programming, the slogan was changed to &quot;''Entertain Your Brain''&quot;. The new tagline for the revamped Discovery Channel was &quot;''Let's All Discover...''&quot;, with a continuing phrase or sentence that relates to a show. For example, when advertising for ''[[MythBusters]]'', the commercial would end, &quot;''Let's All Discover, Why No Myth Is Safe''&quot;. With the 2008 logo change came a new tagline: &quot;''The World is Just...Awesome.''&quot; The newest commercials includes an unreleased mix of the song &quot;Wonders Never Cease&quot; by [[Morcheeba]], from the album entitled [[The Antidote (Morcheeba album)|''The Antidote'']] and the song [[Typical]] by [[MUTEMATH]]. Their most recent commercial ''[[I Love the World]]'', created by the 72andSunny agency, contains amended verses and the refrain from the traditional campfire song &quot;I Love The Mountains&quot;.
+
+===Logos===
+[[File:Discovery1985.svg|100px|thumb|left|The logo of Discovery Channel from 1985-1995.]]
+[[File:Discovery Channel 1995.svg|100px|thumb|right|The next logo of Discovery Channel from 1995-2000.]]
+[[File:Discovery Channel logo 2000.svg|100px|thumb|right|The third logo of Discovery Channel. Used from 2000-2009.]]
+
+The Discovery Channel's very first logo was a television screen picturing a map of the World.
+For two decades on the air, the logo incorporated the Discovery wordmark in an Aurora Bold Condensed font with a circle shape in front of it. The circle usually took the form of a rising sun, or an animated version of the [[Vitruvian Man]].
+
+In 1995, the word &quot;The&quot; was dropped from the channel's name. A globe became a permanent part of the logo, and a strap was added to the bottom of the logo. During this time, the company started expanding and launched several new networks. Many of the sister networks used designs similar to the one used by Discovery, often incorporation the globe and using the same typeface. Networks that had logos based on Discovery's were [[Animal Planet]], [[Travel Channel]], [[Science (TV channel)|Discovery Science]], [[Military Channel|Discovery Wings]] and [[Planet Green|Discovery Home &amp; Leisure]]. The logo was changed slightly in 2000 when the word &quot;Channel&quot; was moved into the strap, and the globe was altered to focus on the Pacific Ocean.
+
+On April 15, 2008, before the season premiere of ''[[Deadliest Catch]]'', Discovery Channel started using a new logo, new graphics and the new tagline &quot;The World is Just Awesome&quot;. The new logo has been designed by Viewpoint Creative in Boston and replaced Aurora Bold Condensed with [[Gotham (typeface)|Gotham]].&lt;ref&gt;{{cite web|url=http://www.viewpointcreative.com/news/index.php?nc=73|title=Viewpoint Creative Designs New Discovery Channel Logo|publisher=Viewpoint Creative}}&lt;/ref&gt; The globe has been merged with the &quot;D&quot; in &quot;Discovery&quot;.&lt;ref&gt;{{cite news|url=http://www.multichannel.com/article/132649-Discovery_Times_New_Branding_Campaign_To_Deadliest_Catch_Debut.php|title=Discovery Times New Branding Campaign To ‘Deadliest Catch’ Debut|date=March 31, 2008|publisher=Multichannel News}}&lt;/ref&gt; This D-globe part can be detached and used separately, for example it is used as the channel's [[Digital on-screen graphic|bug]]. Later in 2009, design agency Royale slightly modified the logo, detaching the globe from the D, and made the word ''CHANNEL'' slightly bigger. The modified logo was rolled out to the rest of the world during the first half of 2009.
+
+==International==
+Discovery Channel reaches 431&amp;nbsp;million homes in 170 countries. Currently, Discovery Communications offers 29 network brands in 33 languages. In a number of countries, Discovery's channels are available on [[digital]] [[satellite]] platforms with multiple [[language]] [[soundtrack]]s or [[subtitles]] including Spanish, German, Russian, [[Czech Language|Czech]], [[Hindi]], [[Tamil language|Tamil]], [[Dutch language|Dutch]], Portuguese, Italian, Norwegian, Swedish, Danish, Finnish, [[Turkish language|Turkish]], Greek, [[Polish language|Polish]], Hungarian, [[Romanian language|Romanian]], [[Arabic language|Arabic]], [[Slovene language|Slovene]], Indian, Japanese, Korean and [[Serbian language|Serbian]]. In [[Bulgaria]], Discovery has since 2000&amp;ndash;2001 been displayed with [[Bulgarian language|Bulgarian]] subtitles by all cable providers and since 2010 - with Bulgarian doubling for some of the shows.
+
+===Canada===
+{{main|Discovery Channel (Canada)}}
+Discovery Channel Canada has an ownership structure different from Discovery Channel. Canadian viewers receive almost identical English-language programming to the channel that American viewers watch, but with some added Canadian content to suit the audience. Most notably, the Canadian channel carries the daily science news show ''[[Daily Planet (television series)|Daily Planet]]'', originally ''@discovery.ca'', the first of its kind. Occasionally, several segments on similar topics are taken from various episodes and put together into one-hour specials that are broadcast on the original Discovery Channel. Canadian channels [[Discovery World HD]], [[Discovery Health (Canada)|Discovery Health]], [[Discovery Science (Canada)|Discovery Science]], [[Investigation Discovery (Canada)|Investigation Discovery]] and [[Animal Planet (Canada)|Animal Planet]] are also seen.
+
+===Europe===
+{{main|Discovery Channel (UK TV channel)}}
+In the United Kingdom, Discovery Channel UK has some programs in common with the US version, including ''[[MythBusters]]'', ''[[American Chopper]]'', ''[[How It's Made]]'' and ''[[Deadliest Catch]]''. The channel is carried as a basic subscription channel on digital satellite (SKY) and digital cable ([[Virgin Media]]. Discovery UK also operates many additional channels: Discovery HD, Discovery Knowledge, Discovery Turbo, Discovery Science, Animal Planet, DMAX, Discovery Real Time, Discovery Home &amp; Health, Discovery Travel &amp; Leisure and Discovery Shed. Many of these channels also have timeshifted versions.
+
+In the [[Republic of Ireland]] the UK edition is available on most cable/digital operators but with local advertisements on Discovery Channel.
+
+In Germany, Austria and Switzerland Discovery Channel is part of the [[Premiere (pay television network)|Premiere]]-digital-network and supplies specific programs to other networks like [[ZDF]] and [[kabel eins]]. [[Discovery Communications]] is also owner of the documentary-channel XXP. The channel was bought in spring 2006 from its former shareholders [[Der Spiegel|Spiegel TV]] and &quot;dctp&quot;. All programs are dubbed into German. The channel is now known as &quot;[[DMAX (TV channel)|DMAX]]&quot;, presumably to associate the channel with Discovery.
+
+In the Netherlands, the Discovery Channel is included in most cable subscriptions, as well as in the IPTV and DVB-T subscribtions. Nearly all of the programs are broadcast in their original language, but they are subtitled in [[Dutch language|Dutch]] as is the policy of all Dutch television stations. Some programs as well as most promos and program announcements have a Dutch voice-over.
+In Flanders, the Dutch speaking part of Belgium, a Flemish Discovery Channel launched (previously the Dutch version was available for IPTV, DVB-C and DVB-S) on Cable (and digital) Television on October 1, 2009.
+
+In Italy, the Discovery Channel (and HD) is distributed via satellite by [[Sky Italia]], the most Italian pay TV. It is incluse on the documentary pack. In addition, Italy has four Discovery-branded channels: Discovery Science, Discovery Real Time, Discovery Animal Planet and Discovery Travel and Living
+
+In [[Poland]], the Discovery Channel is included in most cable television offers. It is also available on satellite digital platforms (sometimes requiring an additional fee). [[Cyfra Plus]] makes it possible to see the programs in Polish as well as in English. Also on digital platform &quot;[[n (Poland)|n]]&quot; there is an additional channel Discovery Historia produced in cooperation with one of the biggest Polish broadcaster - [[TVN (Poland)|TVN]].
+
+In [[Slovenia]], the Discovery Channel is one of the most popular channels, with a very wide audience, especially after subtitling in [[Slovene language|Slovene]] was introduced. It is quite common for a bar in Slovenia to set their TV to Discovery Channel. Thus it is included in all (except some basic) Cable / IPTV subscriptions.
+
+In [[Serbia]], the Discovery Channel is distributed via cable television providers with [[Serbian language|Serbian]] subtitles. It enjoys moderate popularity, with shows like ''MythBusters'' and ''American Chopper'' being especially well received.
+
+In Spain, the channel shares a schedule and programs with Portugal and is available through most satellite and cable platforms, making it possible to broadcast both in Spanish and Portuguese. In Spain all programs are dubbed whereas in Portugal most of them are subtitled. In addition, Portugal has three Discovery-branded channels: [[Discovery Turbo]] (motorsports), [[Discovery Science]] (science and technology) and [[Discovery Civilization]] (ancient history, crimes, terrorist attacks, etc.). They follow the same model as the original Discovery Channel, except for advertising (which doesn't exist on this channels). Spanish adverts are broadcast to the Portuguese feed, non-subtitled or dubbed.
+
+===Australia and New Zealand===
+{{main|Discovery Channel (Australia)}}
+In Australia, the Discovery Channel is part of a six channel bouquet (not including timeshifts) on digital subscription television, available on [[Foxtel]], [[Optus TV]] and [[AUSTAR]].
+
+In New Zealand, the Australian version of Discovery is broadcast on [[SKY Network Television]].
+
+===South-East Asia===
+{{main|Discovery Channel (South East Asian TV channel)}}
+In India, China, Japan, South Korea, [[Philippines]], [[Malaysia]], [[Singapore]], [[Thailand]], [[Vietnam]] and the rest of [[South East Asia]], the S.E. Asian version of the Discovery Channel is available on digital subscription television. Discovery Channel Asia still shows crime programs e.g. ''[[Most Evil]]'', ''[[The FBI Files]]'', [[et cetera|etc.]]. There also is a large number of programming featuring development and society in Asian countries, especially in India and China. For example, Thailand, Malaysia and Singapore has a number of other channels branched from the main Discovery Channel: Discovery Turbo, Discovery Science, Discovery Home &amp; Health and [[Discovery Travel &amp; Living]].
+
+The [[Philippines]] on the other hand, has its own version of the said channel. The Philippine feed shares the program schedule as the SE Asian feed, except for the inclusion of the Philippine advertisements during commercial breaks.
+
+===South Africa===
+In South Africa, Discovery Channel shares a schedule and programming with all of Africa, the Middle East and Turkey. Discovery Channel as well as sibling channels Discovery World and Animal Planet are available on the DStv/Multichoice platform.
+
+==Controversy==
+===RFID===
+In August 2008 it was reported by The Consumerist that Discovery Channel had stopped their popular ''[[MythBusters]]'' program from airing an episode examining [[RFID]] security in regard to its implementation in [[credit cards]] because the episode would upset credit card companies, who are major advertisers on Discovery Channel.&lt;ref&gt;{{cite web|title=Mythbusters Gagged: Credit Card Companies Kill Episode Exposing RFID Security Flaws|url=http://consumerist.com/5043831/mythbusters-gagged-credit-card-companies-kill-episode-exposing-rfid-security-flaws|accessdate=2008-12-12}}&lt;/ref&gt; It was later determined that the decision not to investigate the issue was made by [[Beyond Productions]], the ''MythBusters'' production company, and was not made by Discovery Channel or their advertising department.&lt;ref&gt;{{cite web|title=Mythbusters Host Retracts RFID Censorship Comments|url=http://consumerist.com/5045633/mythbusters-host-retracts-rfid-censorship-comments|accessdate=2008-12-12}}&lt;/ref&gt;
+
+===Enigmatic Malaysia===
+{{Main|2009 Pendet controversy}}
+
+An ad promoting the network's ''Enigmatic Malaysia'', a special series meant to highlight the cultural heritages of [[Malaysia]], mistakenly featured [[Bali]]nese [[Pendet|Pendet dancers]]. This prompted outrage from Balinese dancers, who posted messages demanding that Malaysia apologize over the misinformation, which then sparked a series of street protests.&lt;ref&gt;{{cite web|title=Protests over presence of Pendet dance in Malaysia’s tourism ad continue|url=http://www.thejakartapost.com/news/2009/08/23/protests-over-presence-pendet-dance-malaysia%E2%80%99s-tourism-ad-continue.html?page=2|date=2009-09-03|accessdate=2009-09-03|author=Niken Prathivi and Irawaty Wardany|work=Jakarta Post}}&lt;/ref&gt; Further demands were made from the local governments, cultural historians as well as the tourism ministry in [[Indonesia]] for Malaysia to clarify the situation.&lt;ref&gt;{{cite web|author=Jakarta Post|date=2009-08-28|author=I Wayan Juniartha|title=Pendet, the dance that rocks the cradle|work=Jakarta Post|url=http://www.thejakartapost.com/news/2009/08/28/pendet-dance-rocks-cradle.html|accessdate=2009-09-03}}&lt;/ref&gt; The Malaysian government reportedly offered their apologies, which was rejected by the Indonesian tourism minister, since the apology was given informally by phone, the Indonesian tourism minister demanded a written apology to make it more accountable.&lt;ref&gt;{{cite web|date=2009-08-27|title=Indonesian Minister Rejects Malaysian Pendet Apology|author=Dessy Sagita|work=The Jakarta Globe|accessdate=2009-09-03|url=http://thejakartaglobe.com/national/indonesian-minister-rejects-malaysian-pendet-apology/326562}}&lt;/ref&gt;
+
+==List of series==
+{{Multicol-start}}
+*''[[A Haunting]]''
+*''[[Aircrash Confidential]]''
+*''[[American Chopper]]''
+*''[[American Loggers]]''
+*''[[Cash Cab]]''
+*''[[Construction Intervention]]''
+*''[[Curiosity (TV series)|Curiosity]]''
+*''[[Deadliest Catch]]''
+*''[[Destroyed In Seconds]]''
+*''[[Dirty Jobs]]''
+*''[[Dual Survival]]''
+*''[[Fight Quest]]''
+*''Factory Made''
+*''[[Ghost Lab]]''
+*''[[Heartland Thunder]]''
+{{Multicol-break}}
+*''[[How It's Made]]''
+*''[[I Shouldn't Be Alive]]''
+*''[[Into The Universe with Stephen Hawking]]''
+*''[[Life (BBC TV series)|Life]]''
+*''[[Man vs. Wild]]''
+*''[[Man, Woman, Wild]]''
+*''[[Monsters Resurrected]]''
+*''[[MythBusters]]''
+*''[[One Way Out (TV series)|One Way Out]]''
+*[[On the Case with Paula Zahn]]
+*''[[The Alaska Experiment|Out of the Wild: The Alaska Experiment]]''
+*''[[PitchMen]]''
+*''[[Planet Earth (TV series)|Planet Earth]]''
+*''Prehistoric''
+*''[[Prototype This!]]''
+{{Multicol-break}}
+*''[[Shark Week]]''
+*''[[Solving History with Olly Steeds]]''
+*''[[Sons of Guns]]''
+*''[[Storm Chasers]]''
+*''[[Survivorman]]''
+*''[[Swamp Loggers]]''
+*''[[Swords (TV series)|Swords]]''
+*''[[The Colony (TV series)|The Colony]]''
+*''[[Time Warp (TV series)|Time Warp]]''
+*''[[Treasure Quest (TV series)|Treasure Quest]]''
+*''[[Ultimate Car Buildoff]]''
+*''[[Verminators]]''
+*''[[Weird or What?]]''
+*''[[Worst Case Scenario (TV series)|Worst Case Scenario]]''
+*''[[Wreckreation Nation]]''
+{{Multicol-end}}
+
+==See also==
+* [[Discovery HD]]
+* [[HD Theater]]
+* [[List of channels on Virgin Television]]
+* [[List of DirecTV channels]]
+* [[List of Dish Network channels]]
+* [[List of documentary channels]]
+* [[Discovery Kids]]
+* [[Discovery Times Square Exposition]]
+
+==References==
+{{Reflist|2}}
+
+==External links==
+===Main===
+*{{official|http://dsc.discovery.com}}
+*''[http://store.discovery.com Official Discovery Store]''
+*''[http://www.youtube.com/user/discoverynetworks Discovery Channel's Official YouTube]''
+*''[http://www.discovery.com/ Discovery Communications Inc.]''
+
+===Other===
+*''[http://education.discovery.com/ Discovery Education]''
+*''[http://school.discovery.com/ Discovery Schools]''
+*''[http://www.cosmeo.com/ Cosmeo]'' (Discovery Education's online homework help service)
+*''[http://japan.discovery.com/ Discovery Channel Japan]''
+*''[http://www.discovery.de/emea/homepage.htm  Discovery Channel Germany]
+*''[http://www.discovery.fi/ Discovery Channel Finland]''
+*''[http://www.discovery.ro/ Discovery Channel Romania]''
+*''[http://www.discoverychannel.co.in/ Discovery Channel India]''
+*''[http://www.discoveryasia.com.cn/ Discovery Channel China]''
+*''[http://www.discoverybrasil.com/ Discovery Channel Brazil]''
+*''[http://www.discoverychannel.ca/ Discovery Channel Canada]''
+*''[http://www.yourdiscovery.com/ Discovery Channel International]''
+*''[http://www.discoverychannel.com.tw/ Discovery Channel Taiwan]''
+*''[http://www.discoverychannel.ru/ Discovery Channel Russia]
+*''[http://www.discoverychannelasia.com/ Discovery Channel Asia]''
+*''[http://www.discoverychannelkorea.com Discovery Channel Korea]''
+*''[http://www.tudiscovery.com/ Discovery Channel Latin America]''
+*''[http://www2.tv-ark.org.uk/otherchannels/discovery.html Discovery Channel at TV Ark]''
+
+{{Discovery Communications}}
+{{Webby Awards|cat=Science|year=1997|type=Nominee}}
+{{commonscat|Discovery Channel}}
+
+[[Category:Discovery Channel| ]]
+[[Category:English-language television stations in the United States]]
+[[Category:Television channels and stations established in 1985]]
+[[Category:Discovery Communications]]
+
+[[az:Discovery Channel]]
+[[bg:Discovery Channel]]
+[[ca:Discovery Channel]]
+[[cs:Discovery Channel]]
+[[da:Discovery Channel]]
+[[de:Discovery Channel]]
+[[el:Discovery Channel]]
+[[es:Discovery Channel]]
+[[eo:Discovery Channel]]
+[[fa:شبکه تلویزیونی دیسکاوری]]
+[[fr:Discovery Channel]]
+[[gl:Discovery Channel]]
+[[gu:ડિસ્કવરી ચેનલ]]
+[[ko:디스커버리 채널]]
+[[hr:Discovery Channel]]
+[[id:Discovery Channel]]
+[[it:Discovery Channel (Italia)]]
+[[he:ערוץ דיסקברי]]
+[[kn:ಡಿಸ್ಕವರಿ ಚಾನೆಲ್]]
+[[hu:Discovery Channel]]
+[[mk:Discovery Channel]]
+[[ml:ഡിസ്കവറി ചാനൽ]]
+[[ms:Discovery Channel]]
+[[nl:Discovery Channel]]
+[[ja:ディスカバリーチャンネル]]
+[[no:Discovery Channel]]
+[[nn:Discovery Channel]]
+[[pl:Discovery Channel]]
+[[pt:Discovery Channel]]
+[[ro:Discovery Channel]]
+[[ru:Discovery (телеканал)]]
+[[simple:Discovery Channel]]
+[[sk:Discovery Channel]]
+[[sr:Дискавери канал]]
+[[fi:Discovery Channel]]
+[[sv:Discovery Channel]]
+[[te:డిస్కవరీ ఛానల్]]
+[[th:ดิสคัฟเวอรี่ แชนแนล]]
+[[tr:Discovery Channel]]
+[[uk:Discovery]]
+[[vi:Discovery Channel]]
+[[zh:探索頻道]]</text>
+    </revision>
+  </page>
+</mediawiki>
diff --git a/WptscsTest/Data/MediaWiki/en/Fuji (Spacecraft).xml b/WptscsTest/Data/MediaWiki/en/Fuji (Spacecraft).xml
new file mode 100644 (file)
index 0000000..27d721a
--- /dev/null
@@ -0,0 +1,102 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.5/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.5/ http://www.mediawiki.org/xml/export-0.5.xsd" version="0.5" xml:lang="en">
+  <siteinfo>
+    <sitename>Wikipedia</sitename>
+    <base>http://en.wikipedia.org/wiki/Main_Page</base>
+    <generator>MediaWiki 1.18wmf1</generator>
+    <case>first-letter</case>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="4" case="first-letter">Wikipedia</namespace>
+      <namespace key="5" case="first-letter">Wikipedia talk</namespace>
+      <namespace key="6" case="first-letter">File</namespace>
+      <namespace key="7" case="first-letter">File talk</namespace>
+      <namespace key="8" case="first-letter">MediaWiki</namespace>
+      <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+      <namespace key="10" case="first-letter">Template</namespace>
+      <namespace key="11" case="first-letter">Template talk</namespace>
+      <namespace key="12" case="first-letter">Help</namespace>
+      <namespace key="13" case="first-letter">Help talk</namespace>
+      <namespace key="14" case="first-letter">Category</namespace>
+      <namespace key="15" case="first-letter">Category talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+      <namespace key="108" case="first-letter">Book</namespace>
+      <namespace key="109" case="first-letter">Book talk</namespace>
+    </namespaces>
+  </siteinfo>
+  <page>
+    <title>Fuji (Spacecraft)</title>
+    <id>5207391</id>
+    <revision>
+      <id>395647839</id>
+      <timestamp>2010-11-09T00:39:31Z</timestamp>
+      <contributor>
+        <username>Rich Farmbrough</username>
+        <id>82835</id>
+      </contributor>
+      <minor/>
+      <comment>Delink date fragment or minor clean up using [[Project:AWB|AWB]]</comment>
+      <text xml:space="preserve" bytes="4618">'''Fuji''' ('''ふじ''') was a manned [[spacecraft]] of the [[space capsule]] kind, proposed by Japan's [[Japan Aerospace Exploration Agency#History|National Space Development Agency]] (NASDA) Advanced mission Research center in December 2001. The Fuji design was ultimately not adopted.
+
+==Background==
+
+In [[Japan]], manned space flight prior to 2001 had depended on the [[United States|USA]] [[Space Shuttle program]], and independent development of spacecraft was not adopted as a short to mid-term goal. Instead, NASDA choose to recommend use of [[Reusable Launch Systems]] with wings such as the US [[Space Shuttle]] and [[HOPE-X|HOPE]].
+
+However,  after the Japanese government reorganized their space exploration efforts under the [[Japan Aerospace Exploration Agency]] (JAXA), Fuji was proposed as a candidate for space missions. The [[Space Shuttle Columbia disaster]] in 2003 also raised questions about the safety of reusable systems, and JAXA felt that the Fuji design, relying on a disposable capsule, would be possible to develop in relatively short span of eight years.
+
+==Design==
+
+The proposed design of Fuji was a modular spacecraft (similar to the [[Gemini spacecraft|Gemini]], [[Apollo spacecraft|Apollo]] and [[Soyuz spacecraft|Soyuz]] spacecraft), with various configurations filling different roles.
+
+===Minimum System===
+
+The Minimum System configuration of Fuji was essentially just a [[Reentry capsule]] with a [[Human spaceflight|manned space flight]] endurance of 24 hours. It was scheduled for development at the early stage of the overall project. This minimum system called the Core Module (CM), and was to be a [[cone (geometry)|cone]]-shaped module with a diameter of 3.7m, gross weight of 3 tonnes or less, and room for a crew of three.  It was to have thrusters for position control and a [[rocket engine]] capable of producing a small amount of thrust. The targeted manufacturing cost was to be about 800 million yen each.
+
+===Standard System===
+
+The Standard System was designed to be capable of flights of one month's duration and into lunar orbit. This system was to consist of the minimum system and two additional modules, an Expansion Module (EM) and a Propulsion Module (PM). The EM was envisioned as a living space, used for a long-term space flights, roughly similar in purpose to the [[Soyuz spacecraft|Soyuz]] spacecraft's Orbital Module (OM). The PM has rocket engine to facilitate orbit changes, and also a [[solar cell]] paddle for production of electricity.
+
+===Economy System===
+The design of the Economy System sought to lower the cost of [[Spaceflight|space travel]], and was based on the Minimum System. This system was to be a low cost version of the CM, capable of carrying a pilot and four passengers.
+
+===Other modules===
+Additional modules were planned, including a Space Laboratory Module equipped with a heat radiation mechanism, a [[Robotic arm]] module, and an inflatable Simple Life Support Module.
+
+==Features==
+The Fuji spacecraft was designed to ensure safety by adopting common existing technologies such as [[Launch escape system|escape rocket]]s and [[Atmospheric_reentry#Ablative|ablative]] heat shields.
+After reentry, the Fuji capsule was to use a [[parafoil]] and an automatic guidance system based [[Global Positioning System|GPS]] technology for controlled descent and landing.
+
+An [[open architecture]] method was used in development of Fuji to control costs, promote technological development, and to expand the potential market. In particular, the developers disclosed information in the machine/electrical/thermal interface between systems, did not restrict the use of this interface, and provided the test criterion of systems.
+
+In addition, Fuji was not to be limited to the [[H-IIA]] rocket system for launching.
+
+==Advanced Concepts==
+A variety of purpose were envisioned for the Fuji system, including uses for [[Space tourism]], with a [[Space station]], and flights to the [[moon]] or [[asteroid]]s
+
+==See also==
+*[[H-IIA]]
+*[[HOPE-X|H-II Orbiting Plan]] (HOPE)
+*[[Soyuz spacecraft]]
+
+==External links==
+* {{Wayback|url=giken.tksc.nasda.go.jp/Group/sentan/|title=NASDA Advanced mission Research center (Japanese)|date=20040603193127}}
+* [http://www.shokabo.co.jp/author/8758/ Wallpaper of the Fuji Spacecraft] (Japanese)
+
+==References==
+*Sinya Matsuura. &quot;われらの有人宇宙船 -日本独自の宇宙輸送システム「ふじ」-&quot;, shokabo, 2003. ISBN 4-7853-8758-0
+
+[[Category:Manned spacecraft]]
+[[Category:Proposed spacecraft]]
+[[Category:Japanese space program]]
+
+[[ja:ふじ (宇宙船)]]
+[[zh:富士號太空船]]</text>
+    </revision>
+  </page>
+</mediawiki>
diff --git a/WptscsTest/Data/MediaWiki/en/Template_Citation needed.xml b/WptscsTest/Data/MediaWiki/en/Template_Citation needed.xml
new file mode 100644 (file)
index 0000000..17f8da3
--- /dev/null
@@ -0,0 +1,62 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.5/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.5/ http://www.mediawiki.org/xml/export-0.5.xsd" version="0.5" xml:lang="en">
+  <siteinfo>
+    <sitename>Wikipedia</sitename>
+    <base>http://en.wikipedia.org/wiki/Main_Page</base>
+    <generator>MediaWiki 1.18wmf1</generator>
+    <case>first-letter</case>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="4" case="first-letter">Wikipedia</namespace>
+      <namespace key="5" case="first-letter">Wikipedia talk</namespace>
+      <namespace key="6" case="first-letter">File</namespace>
+      <namespace key="7" case="first-letter">File talk</namespace>
+      <namespace key="8" case="first-letter">MediaWiki</namespace>
+      <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+      <namespace key="10" case="first-letter">Template</namespace>
+      <namespace key="11" case="first-letter">Template talk</namespace>
+      <namespace key="12" case="first-letter">Help</namespace>
+      <namespace key="13" case="first-letter">Help talk</namespace>
+      <namespace key="14" case="first-letter">Category</namespace>
+      <namespace key="15" case="first-letter">Category talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+      <namespace key="108" case="first-letter">Book</namespace>
+      <namespace key="109" case="first-letter">Book talk</namespace>
+    </namespaces>
+  </siteinfo>
+  <page>
+    <title>Template:Citation needed</title>
+    <id>2048472</id>
+    <restrictions>edit=sysop:move=sysop</restrictions>
+    <revision>
+      <id>437098884</id>
+      <timestamp>2011-06-30T19:13:33Z</timestamp>
+      <contributor>
+        <username>Rich Farmbrough</username>
+        <id>82835</id>
+      </contributor>
+      <text xml:space="preserve" bytes="549">{{ {{{|safesubst:}}}ifsubst |&lt;includeonly&gt;{{subst:Unsubst|Citation needed| name|{{{name|¬}}}|reason|{{{reason|¬}}}| date|{{{date|{{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}} }}&lt;/includeonly&gt;|
+{{Fix
+|name={{{name|Citation needed}}}
+|link=Wikipedia:Citation needed
+|text=citation needed
+|class=Template-Fact
+|title=This claim needs references to reliable sources
+|date={{{date|}}}
+|cat=[[Category:All articles with unsourced statements]]
+|cat-date=Category:Articles with unsourced statements
+}}
+
+}}&lt;noinclude&gt;
+
+{{Documentation}}
+
+&lt;/noinclude&gt;</text>
+    </revision>
+  </page>
+</mediawiki>
diff --git a/WptscsTest/Data/MediaWiki/en/Template_Citation needed_doc.xml b/WptscsTest/Data/MediaWiki/en/Template_Citation needed_doc.xml
new file mode 100644 (file)
index 0000000..20dadfa
--- /dev/null
@@ -0,0 +1,223 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.5/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.5/ http://www.mediawiki.org/xml/export-0.5.xsd" version="0.5" xml:lang="en">
+  <siteinfo>
+    <sitename>Wikipedia</sitename>
+    <base>http://en.wikipedia.org/wiki/Main_Page</base>
+    <generator>MediaWiki 1.18wmf1</generator>
+    <case>first-letter</case>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="4" case="first-letter">Wikipedia</namespace>
+      <namespace key="5" case="first-letter">Wikipedia talk</namespace>
+      <namespace key="6" case="first-letter">File</namespace>
+      <namespace key="7" case="first-letter">File talk</namespace>
+      <namespace key="8" case="first-letter">MediaWiki</namespace>
+      <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+      <namespace key="10" case="first-letter">Template</namespace>
+      <namespace key="11" case="first-letter">Template talk</namespace>
+      <namespace key="12" case="first-letter">Help</namespace>
+      <namespace key="13" case="first-letter">Help talk</namespace>
+      <namespace key="14" case="first-letter">Category</namespace>
+      <namespace key="15" case="first-letter">Category talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+      <namespace key="108" case="first-letter">Book</namespace>
+      <namespace key="109" case="first-letter">Book talk</namespace>
+    </namespaces>
+  </siteinfo>
+  <page>
+    <title>Template:Citation needed/doc</title>
+    <id>6582655</id>
+    <revision>
+      <id>472288974</id>
+      <timestamp>2012-01-20T17:42:32Z</timestamp>
+      <contributor>
+        <username>Tim Thomason</username>
+        <id>4328506</id>
+      </contributor>
+      <minor/>
+      <text xml:space="preserve" bytes="12747">{{Documentation subpage}}
+{{High-risk| 236,000+}}
+{{Notice|An introductory version of this documentation is provided at [[Wikipedia:Citation needed]].}}
+
+{{Tl|Citation needed}} (also known by the redirects {{Tl|Cn}} and {{Tl|Fact}}) is a [[Wikipedia:Template messages|template]] used to identify questionable claims that lack a [[Wikipedia:Citing sources|citation]] to a [[WP:Reliable sources|reliable source]]. The template produces a superscripted notation like the following:
+
+:[[Humphrey Bogart]] has won several [[snooker]] world championships.{{Citation needed|date={{CURRENTMONTHNAME}} {{CURRENTYEAR}}}}
+
+===Usage===
+You may append a date to the template in the following format:
+:&lt;code&gt;{{Tlx|Citation needed|date{{=}}{{CURRENTMONTHNAME}} {{CURRENTYEAR}}}}&lt;/code&gt;
+
+'''Notes''':
+* [[Help:Substitution|Substitution]] of this template will automatically fill the date parameter.
+* If you don't add a date parameter, a [[Wikipedia:Bots|bot]] will date your entry with the month and year at a later time.
+* The date parameter consists of the name of the current month and the year only, not full dates. The names of the months are capitalised in English. ''Any deviation from these two rules will result in an &quot;invalid date parameter&quot; error.''
+
+It is also recommended to add the (non-displayed) {{para|reason}} parameter to leave a better record for future editors. For example, the following usage might be appropriate to the claim that &quot;Humphrey Bogart has won several snooker world championships.&quot;:
+:&lt;code&gt;{{Tlx|Citation needed|reason{{=}}This claim needs a reliable source; Bogart was a famous actor, and his major biographies don't mention snooker.}}&lt;/code&gt;
+
+Adding this template to an article places the article into [[:Category:Articles with unsourced statements]] or a dated subcategory thereof.
+
+Please remove the template when you add a citation for a statement.
+
+===Examples===
+:&lt;code&gt;This sentence shows the template used at the end.{{Tlx|Citation needed|reason{{=}}reliable source needed for the whole sentence}}&lt;/code&gt;
+
+''The above wikitext will render as follows:''
+
+:This sentence shows the template used at the end.{{Citation needed|reason=reliable source needed for the whole sentence|date={{CURRENTMONTHNAME}} {{CURRENTYEAR}}}}
+
+The template should be inserted ''after'' punctuation, such as a period or comma.
+&lt;!--
+:&lt;code&gt;While this example{{Tlx|Citation needed|2=1=shows the template}} used to request a citation for a specific part of a sentence.&lt;/code&gt;
+
+''The above wikitext will render as follows:''
+
+While this example {{Citation needed|1=shows the template}} used to request a citation for a specific part of a sentence.
+ --&gt;
+
+===When not to use this template===
+Unsourced or poorly sourced contentious material about [[Wikipedia:Biographies of living persons|living persons]] should be '''removed immediately'''. Do not tag it: immediately remove it. For more information, see the section on [[Wikipedia:Biographies of living persons#Remove unsourced or poorly sourced contentious material|poorly sourced contentious material]] in the policy page ''Biographies of living persons''.
+
+Material that is doubtful and harmful may be removed immediately, rather than tagged. See [[Wikipedia:Verifiability#Burden of evidence|Burden of evidence]]. 
+
+The &lt;nowiki&gt;{{Citation needed}}&lt;/nowiki&gt; template is intended for use when there is a general question of the verifiability of a statement, or when an editor believes that a reference verifying the statement should be provided. Other templates are available for other or more specific issues, see the [[Template:Cn#Inline_templates|list of inline templates]]. For example, claims that you think are incorrect should be tagged with &lt;nowiki&gt;{{Dubious}}&lt;/nowiki&gt;, and those which represent a non-neutral view should be tagged with &lt;nowiki&gt;{{POV-statement}}&lt;/nowiki&gt;. Being specific about the nature of the problem will help other editors correct it.
+
+If you have the time and ability to find an authoritative reference, please do so. Then add the citation yourself, or correct the article text. After all, the ultimate goal is not to merely identify problems, but to fix them.
+
+While an editor may add this template to any uncited passage for any reason, many editors object to what they perceive as overuse of this tag, particularly in what is known as &quot;drive-by&quot; tagging, which is applying the tag without attempting to address the issues at all. Consider whether adding this tag in an article is the best approach before using it, and use it judiciously. Wikipedia's [[WP:V|verifiability policy]] does '''not require''' reliable sources for common well-known facts (e.g., &quot;The Moon orbits the Earth&quot;), or that citations be repeated through every sentence in a paragraph. All direct quotations and facts whose accuracy might be challenged (e.g., statistics) require citations.  See [[WP:MINREF]] for the list of material that is absolutely required to be followed by an [[Wikipedia:Inline citation|inline citation]], rather than a [[Wikipedia:General references|general reference]] or no citation at all.
+
+This template is intended for specific passages that need citation. For entire articles or sections that contain significant material lacking sources (rather than just specific short passages), there are other, more appropriate templates, such as {{Tl|Unreferenced}} or {{Tl|Refimprove}}.
+
+=== How to respond to this tag ===
+The addition of this tag is a request for an inline citation to support the tagged statement.  If you are able to provide a citation to support the claim, then please do so.  
+
+Except for [[WP:BLP|certain kinds of claims about living people]], which require immediate production of inline citations, there is no specific deadline for providing citations. Please do not delete information that you believe is correct simply because no-one has provided a citation within an arbitrary time limit. Where there is some uncertainty about its accuracy, most editors are willing to wait about a month to see whether a citation can be provided.
+
+=== See also ===
+* [[Wikipedia:Citing sources]]
+** Especially: [[Wikipedia:Citing sources#Dealing with unsourced material|Unsourced material]]
+* [[Wikipedia:Verifiability]]
+* [[Wikipedia:Reliable sources]]
+* [[Wikipedia:WikiProject Fact and Reference Check]]
+
+==== Inline templates ====
+*{{Tl|Cite quote}}{{Cite quote}}: for &quot;actual quotations&quot; which need citations to make them proper
+*{{Tl|Clarify}}{{Clarify}}: request clarification of wording or interpretation
+*{{Tl|Examples}}{{Examples}}: request examples for clarification
+*{{Tl|List fact}}{{List fact}}: request a citation of a source which justifies inclusion of a given entry in a [[Wikipedia:List guideline|list]]
+*{{Tl|Nonspecific}}{{Nonspecific}}: flag a general, yet factual statement as needing to be made more specific before it can be verified
+*{{Tl|Page needed}}{{Page number}}: request a page number for an existing citation
+
+===== Highlighting some text that needs a citation =====
+*{{Tl|Cn-span}}/{{Tl|Citation needed-span}}: {{Cn-span|Example text|date={{CURRENTMONTHNAME}} {{CURRENTYEAR}}}}: similar, except that it outlines the text that needs a citation with a box instead
+*{{Tl|Reference necessary}}: {{Reference necessary|Example text|date={{CURRENTMONTHNAME}} {{CURRENTYEAR}}}}: lightly underlines the text that needs a citation
+
+===== Incomplete citations =====
+*{{Tl|Full}}{{Full}}: in-line request for full citation, for example when only (Author, YEAR) is given.
+*{{Tl|Page needed}}{{Page needed}}: in-line request for the page number or page numbers in a work such as journal for an existing citation.
+*{{Tl|Season needed}}{{Season needed}}, similar to {{Tl|page needed}}: for missing TV season &amp; episode number
+*{{Tl|Volume needed}}{{Volume needed}}, similar to {{Tl|page needed}}: for missing journal, newspaper, comic, etc., volume and issue numbers)
+
+===== Verification =====
+*{{Tl|Better source}}&lt;!--{{Better source}}--&gt;: flags a statement as requiring a better source 
+*{{Tl|Dead link}}{{Dead link}}: request a fix for a [[Wikipedia:Dead external links|dead external link]] within a paragraph or a reference citation.
+*{{Tl|Failed verification}}{{Failed verification}}: source was checked, and did not contain the cited material
+*{{Tl|Request quotation}}{{Request quotation}}: request a direct quote from an inaccessible source, for verification purposes
+*{{Tl|Self-published inline}}{{Self-published inline}}: flag a source that cites the author
+*{{Tl|Verify credibility}}{{Verify credibility|name=Citation needed}}: flag a source as possibly being [[WP:RS|unreliable]] and/or [[WP:V|unverifiable]]
+*{{Tl|Verify source}}{{Verify source}}: request that someone verify the cited source backs up the material in the passage
+
+===== Content =====
+*{{Tl|Definition}}{{Definition}}: flag a definition as being ambiguous/confusing
+*{{Tl|Dubious}}{{Dubious}}: flag something as suspected of being incorrect
+*{{Tl|Technical-statement}}{{Technical-statement}}: ...flag a word or phrase [[Wikipedia:Make technical articles understandable]]
+*{{Tl|Or}}{{or}}: flag something as possibly containing [[Wikipedia:No original research|original research]]
+*{{Tl|Peacock term}}{{Peacock term}}: [[Wikipedia:Avoid peacock terms|Avoid peacock terms]] too
+*{{Tl|POV-statement}}{{POV-statement}}: dispute the [[Wikipedia:Neutral point of view|neutrality]] of a passage
+*{{Tl|Quantify}}{{Quantify}}: flag a statement as being vague regarding the amount of something
+*{{Tl|Time fact}}/{{Tl|Chronology citation needed}}{{Time fact}}: request a source confirming or providing the chronology or timeline of a statement
+*{{Tl|Undue-inline}}{{Undue-inline}}: show that a statement does not ascribe appropriate weight to its sources, according to their prominence; use in preference to...
+*{{TL|Vague}}{{Vague}}: flag a statement that is too vague to be unambiguously verifiable. 
+*{{Tl|Weasel-inline}}{{Weasel-inline}}: flag for [[Wikipedia:Avoid weasel words|weasel word]] cleanup
+*{{Tl|When}}{{When}}: flags a particular time period as being vague or ambiguous
+*{{Tl|Who}}{{who}}: for placement after descriptions of a group of persons
+*{{Tl|Whom}}/{{Tl|By whom}}{{whom}}: placement after mention of a vague third party claim that is not sourced
+*{{Tl|Whom?}}{{Whom?}}
+
+=====Timeliness=====
+*{{Tl|Update after}}{{Update after}}: a template that only shows itself after a specified time, indicating an exceptional statement that will [[Wikipedia:Avoid statements that will date quickly|date quickly]]
+
+====Article message box templates====
+*{{Tl|Cite check}}, article/section may have inappropriate or misinterpreted citations
+*{{Tl|Refimprove}}, article/section has weak or incomplete sources/references/citations
+*{{Tl|Unreferenced}}, article/section has no sources/references/citations given at all
+* Citation method and style
+** {{Tl|Citation style}}
+** {{Tl|No footnotes}}
+
+{{Inline tags}}
+&lt;includeonly&gt;
+&lt;!-- template categories and interwiki links --&gt;
+[[Category:Inline citation and verifiability dispute templates|Citation Needed]]
+
+[[af:Sjabloon:Feit]]
+[[als:Vorlage:Beleg]]
+[[ar:قالب:حقيقة]]
+[[az:Şablon:Fact]]
+[[be:Шаблон:Крыніца?]]
+[[be-x-old:Шаблён:Няма крыніцы інфармацыі]]
+[[bg:Шаблон:Посочи източник]]
+[[ca:Plantilla:Citació necessària]]
+[[cs:Šablona:Doplňte zdroj]]
+[[cy:Nodyn:Angen ffynhonnell]]
+[[da:Skabelon:Kilde mangler]]
+[[dsb:Pśedłoga:Fakt]]
+[[eo:Ŝablono:Citon]]
+[[el:Πρότυπο:Εκκρεμεί παραπομπή]]
+[[es:Plantilla:Cita requerida]]
+[[et:Mall:Lisa viide]]
+[[fa:الگو:مدرک]]
+[[fi:Malline:Lähde]]
+[[fr:Modèle:Citation nécessaire]]
+[[ko:틀:출처]]
+[[he:תבנית:מקור]]
+[[hr:Predložak:Nedostaje izvor]]
+[[hsb:Předłoha:Fakt]]
+[[hu:Sablon:Forr]]
+[[is:Snið:Heimild vantar]]
+[[it:Template:Citazione necessaria]]
+[[ja:Template:要出典]]
+[[ka:თარგი:ფაქტი]]
+[[lv:Template:Nepieciešama atsauce]]
+[[lt:Šablonas:Faktas]]
+[[ml:ഫലകം:തെളിവ്]]
+[[nl:Sjabloon:Bron?]]
+[[no:Mal:Trenger referanse]]
+[[nn:mal:kjelde manglar]]
+[[pl:Szablon:Fakt]]
+[[pt:Predefinição:Carece de fontes]]
+[[ro:Format:Necesită citare]]
+[[ru:Шаблон:Нет источника]]
+[[sc:Template:Esigit tzitassione]]
+[[simple:Template:Fact]]
+[[sk:Šablóna:Bez citácie]]
+[[sl:Predloga:Navedi vir]]
+[[sq:Stampa:Citim i duhur]]
+[[sr:Шаблон:Чињеница]]
+[[sv:Mall:Källa behövs]]
+[[te:Template:మూలాలు అవసరం]]
+[[th:แม่แบบ:ต้องการอ้างอิงเฉพาะส่วน]]
+[[tl:Template:Fact]]
+[[vi:Tiêu bản:Cần chú thích]]
+[[uk:Шаблон:Fact]]
+[[zh:Template:Fact]]
+&lt;!-- Keep Interwiki's above this line please ---&gt;
+&lt;/includeonly&gt;</text>
+    </revision>
+  </page>
+</mediawiki>
diff --git a/WptscsTest/Data/MediaWiki/en/Template_Planetbox begin.xml b/WptscsTest/Data/MediaWiki/en/Template_Planetbox begin.xml
new file mode 100644 (file)
index 0000000..4aba7f5
--- /dev/null
@@ -0,0 +1,80 @@
+<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.5/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.5/ http://www.mediawiki.org/xml/export-0.5.xsd" version="0.5" xml:lang="en">
+  <siteinfo>
+    <sitename>Wikipedia</sitename>
+    <base>http://en.wikipedia.org/wiki/Main_Page</base>
+    <generator>MediaWiki 1.18wmf1</generator>
+    <case>first-letter</case>
+    <namespaces>
+      <namespace key="-2" case="first-letter">Media</namespace>
+      <namespace key="-1" case="first-letter">Special</namespace>
+      <namespace key="0" case="first-letter" />
+      <namespace key="1" case="first-letter">Talk</namespace>
+      <namespace key="2" case="first-letter">User</namespace>
+      <namespace key="3" case="first-letter">User talk</namespace>
+      <namespace key="4" case="first-letter">Wikipedia</namespace>
+      <namespace key="5" case="first-letter">Wikipedia talk</namespace>
+      <namespace key="6" case="first-letter">File</namespace>
+      <namespace key="7" case="first-letter">File talk</namespace>
+      <namespace key="8" case="first-letter">MediaWiki</namespace>
+      <namespace key="9" case="first-letter">MediaWiki talk</namespace>
+      <namespace key="10" case="first-letter">Template</namespace>
+      <namespace key="11" case="first-letter">Template talk</namespace>
+      <namespace key="12" case="first-letter">Help</namespace>
+      <namespace key="13" case="first-letter">Help talk</namespace>
+      <namespace key="14" case="first-letter">Category</namespace>
+      <namespace key="15" case="first-letter">Category talk</namespace>
+      <namespace key="100" case="first-letter">Portal</namespace>
+      <namespace key="101" case="first-letter">Portal talk</namespace>
+      <namespace key="108" case="first-letter">Book</namespace>
+      <namespace key="109" case="first-letter">Book talk</namespace>
+    </namespaces>
+  </siteinfo>
+  <page>
+    <title>Template:Planetbox begin</title>
+    <id>4760736</id>
+    <revision>
+      <id>464987264</id>
+      <timestamp>2011-12-09T18:17:25Z</timestamp>
+      <contributor>
+        <username>Yahia.barie</username>
+        <id>9812597</id>
+      </contributor>
+      <comment>bn:টেমপ্লেট:Planetbox begin</comment>
+      <text xml:space="preserve" bytes="1216">{| class=&quot;infobox&quot; style=&quot;width: 22em; font-size: 88%; &lt;!--text-align: left; --&gt; line-height: 1.5em&quot;
+|+ style=&quot;font-size: 125%; font-weight: bold&quot; | {{{name&lt;includeonly&gt;|{{PAGENAME}}&lt;/includeonly&gt;}}}
+! colspan=2 style=&quot;background-color: #a0b0ff&quot; |&lt;small&gt;[[Extrasolar planet]]&lt;/small&gt;
+| align=&quot;center&quot; | &lt;small&gt;[[List of extrasolar planets]]&lt;/small&gt;&lt;noinclude&gt;
+|}{{doc|content=
+{{Astro talk}}
+
+This template is part of a group of templates that are used to display information about a specific [[extrasolar planet]].  The {{tl|Planetbox begin}} is always the first in the list, while {{tl|Planetbox end}} is always the last in the list.
+
+=== Usage ===
+
+This particular template can be used as follows:
+
+&lt;pre&gt;{{Planetbox begin
+| name = &lt;!--Planet name--&gt;
+}}&lt;/pre&gt;
+
+The following templates are used together and are usually placed in the order listed below.
+{{Planetboxes}}
+
+=== See also ===
+
+* {{tl|Orbitboxes}}
+* {{tl|Starboxes}}
+
+&lt;!--Categories--&gt;
+[[Category:Astronomy infobox templates|{{PAGENAME}}]]
+
+&lt;!--Interwikis--&gt;
+[[bn:টেমপ্লেট:Planetbox begin]]
+[[de:Vorlage:Planetenbox Beginn]]
+[[id:Templat:Planetbox begin]]
+[[ja:Template:Planetbox begin]]
+[[ru:Шаблон:Planetbox begin]]
+}}&lt;/noinclude&gt;</text>
+    </revision>
+  </page>
+</mediawiki>
diff --git a/WptscsTest/Data/MediaWiki/result/config.xml b/WptscsTest/Data/MediaWiki/result/config.xml
new file mode 100644 (file)
index 0000000..cbdabe6
Binary files /dev/null and b/WptscsTest/Data/MediaWiki/result/config.xml differ
index cdd0620..2d6da21 100644 (file)
@@ -1,4 +1,4 @@
-'''xxx'''([[英語|英]]: '''example''')
+'''xxx'''([[英語|英]]: '''Example''')
 
 [[ファイル:Example.png|thumb|Wikipedia's example image. (Example.png)]]
 [[:en:Template:wiktionary]]<!-- {{wiktionary}} -->
@@ -17,5 +17,5 @@
 [[fr:Example]]
 [[ksh:Example (Watt ėßß datt?)]]
 
-[[en:example]]
-<!-- Wikipedia 翻訳支援ツール Ver0.73、[[:en:example]](2010年7月13日 0:49:18(UTC))より -->
+[[en:Example]]
+<!-- Wikipedia 翻訳支援ツール Ver0.73、[[:en:Example]](2010年7月13日 0:49:18(UTC))より -->
index b32fe7a..4fde943 100644 (file)
@@ -1,4 +1,4 @@
-'''xxx'''([[英語|英]]: '''example''')
+'''xxx'''([[英語|英]]: '''Example''')
 
 [[ファイル:Example.png|thumb|Wikipedia's example image. (Example.png)]]
 {{Wiktionary}}
@@ -17,5 +17,5 @@
 [[fr:Example]]
 [[ksh:Example (Watt ėßß datt?)]]
 
-[[en:example]]
-<!-- Wikipedia 翻訳支援ツール Ver0.xx、[[:en:example]](2010年7月13日 0:49:18(UTC))より -->
+[[en:Example]]
+<!-- Wikipedia 翻訳支援ツール Ver0.xx、[[:en:Example]](2010年7月13日 0:49:18(UTC))より -->
diff --git a/WptscsTest/Data/MediaWiki/result/example_仮リンク有効.txt b/WptscsTest/Data/MediaWiki/result/example_仮リンク有効.txt
new file mode 100644 (file)
index 0000000..8742fda
--- /dev/null
@@ -0,0 +1,21 @@
+'''xxx'''([[英語|英]]: '''Example''')
+
+[[ファイル:Example.png|thumb|Wikipedia's example image. (Example.png)]]
+[[:en:Template:wiktionary]]<!-- {{wiktionary}} -->
+[[:en:Template:wikiquote]]<!-- {{wikiquote}} -->
+'''Example''' may refer to:
+
+*{{仮リンク|Example (rapper)|en|Example (rapper)|label=Example (rapper)}}, a British rapper
+*[[Example.com|example.com]], [[Example.com|example.net]], [[Example.com|example.org]]  and [[トップレベルドメイン|.example]], domain names reserved for use in documentation as examples 
+
+==関連項目==
+*{{仮リンク|Exemplum|en|Exemplum|label=Exemplum}}, medieval collections of short stories to be told in sermons
+*{{仮リンク|Exemplar|en|Exemplar|label=Exemplar}}, a prototype or model which others can use to understand a topic better
+
+[[:en:Template:disambig]]<!-- {{disambig}} -->
+
+[[fr:Example]]
+[[ksh:Example (Watt ėßß datt?)]]
+
+[[en:Example]]
+<!-- Wikipedia 翻訳支援ツール Ver0.73、[[:en:Example]](2010年7月13日 0:49:18(UTC))より -->
index 68f7d57..12fe585 100644 (file)
@@ -1,4 +1,4 @@
-'''xxx'''([[英語|英]]: '''example''')
+'''xxx'''([[英語|英]]: '''Example''')
 
 [[ファイル:Example.png|thumb|Wikipedia's example image. (Example.png)]]
 [[:en:Template:wiktionary]]<!-- {{wiktionary}} -->
@@ -17,5 +17,5 @@
 [[fr:Example]]
 [[ksh:Example (Watt ėßß datt?)]]
 
-[[en:example]]
-<!-- Wikipedia 翻訳支援ツール Ver0.xx、[[:en:example]](2010年7月13日 0:49:18(UTC))より -->
+[[en:Example]]
+<!-- Wikipedia 翻訳支援ツール Ver0.xx、[[:en:Example]](2010年7月13日 0:49:18(UTC))より -->
index 1daf36a..aa4c13f 100644 (file)
@@ -35,7 +35,7 @@
 {{Reusable launch systems}}
 
 {{DEFAULTSORT:すへすしつふ2}}
-[[Category:Manned spacecraft]]<!-- [[Category:宇宙船]] -->
+[[Category:Manned spacecraft]]
 
 [[cs:SpaceShipTwo]]
 [[de:SpaceShipTwo]]
index 4b5a5d6..e3a76bc 100644 (file)
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D8%B5%D8%AF%D9%8A%D8%B1/{0}</ExportPath>
+      <ExportPath>/wiki/%D8%AE%D8%A7%D8%B5:%D8%AA%D8%B5%D8%AF%D9%8A%D8%B1/$1</ExportPath>
       <Redirect>#تحويل</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/شرح">قالب:توثيق</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/شرح">قالب:توثيق</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{وإو|عر=$1|لغ=$2|تر=$3|نص=$4}}</LinkInterwikiFormat>
+    </MediaWiki>
+    <MediaWiki>
+      <Location>http://bn.wikipedia.org</Location>
+      <Language Code="bn">
+        <Names>
+          <LanguageName Code="bn">
+            <Name>বাংলা ভাষা</Name>
+            <ShortName />
+          </LanguageName>
+          <LanguageName Code="en">
+            <Name>Bengali language</Name>
+            <ShortName>Bengali</ShortName>
+          </LanguageName>
+          <LanguageName Code="ja">
+            <Name>ベンガル語</Name>
+            <ShortName />
+          </LanguageName>
+        </Names>
+      </Language>
+      <ExportPath>/wiki/%E0%A6%AC%E0%A6%BF%E0%A6%B6%E0%A7%87%E0%A6%B7:Export/$1</ExportPath>
+      <MagicWords />
+      <DocumentationTemplates />
     </MediaWiki>
     <MediaWiki>
       <Location>http://de.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Spezial:Exportieren/{0}</ExportPath>
+      <ExportPath>/wiki/Spezial:Exportieren/$1</ExportPath>
       <Redirect>#WEITERLEITUNG</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/Doku">Vorlage:Dokumentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/Doku">Vorlage:Dokumentation</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://en.wikipedia.org</Location>
         </Names>
       </Language>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+        <DocumentationTemplate DefaultPage="/doc">Template:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{link-interwiki|en=$1|en_text=$4|lang=$2|lang_title=$3}}</LinkInterwikiFormat>
     </MediaWiki>
     <MediaWiki>
       <Location>http://es.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Especial:Exportar/{0}</ExportPath>
+      <ExportPath>/wiki/Especial:Exportar/$1</ExportPath>
       <Redirect>#REDIRECCIÓN</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Plantilla:Documentación</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Plantilla:Documentación</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://fr.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Sp%C3%A9cial:Exporter/{0}</ExportPath>
+      <ExportPath>/wiki/Sp%C3%A9cial:Exporter/$1</ExportPath>
       <Redirect>#REDIRECTION</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Modèle:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Modèle:Documentation</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{Lien|fr=$1|lang=$2|trad=$3|texte=$4}}</LinkInterwikiFormat>
+    </MediaWiki>
+    <MediaWiki>
+      <Location>http://hi.wikipedia.org</Location>
+      <Language Code="hi">
+        <Names>
+          <LanguageName Code="en">
+            <Name>Standard Hindi</Name>
+            <ShortName>Hindi</ShortName>
+          </LanguageName>
+          <LanguageName Code="hi">
+            <Name>हिन्दी</Name>
+            <ShortName />
+          </LanguageName>
+          <LanguageName Code="ja">
+            <Name>ヒンディー語</Name>
+            <ShortName />
+          </LanguageName>
+        </Names>
+      </Language>
+      <ExportPath>/wiki/%E0%A4%B5%E0%A4%BF%E0%A4%B6%E0%A5%87%E0%A4%B7:Export/$1</ExportPath>
+      <MagicWords />
+      <DocumentationTemplates />
     </MediaWiki>
     <MediaWiki>
       <Location>http://it.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Speciale:Esporta/{0}</ExportPath>
+      <ExportPath>/wiki/Speciale:Esporta/$1</ExportPath>
       <Redirect>#RINVIA</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/man">Template:Man</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/man">Template:Man</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ja.wikipedia.org</Location>
             <ShortName />
           </LanguageName>
         </Names>
-        <Bracket>({0})</Bracket>
+        <Bracket>($1)</Bracket>
       </Language>
-      <ExportPath>/wiki/%E7%89%B9%E5%88%A5:%E3%83%87%E3%83%BC%E3%82%BF%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97/{0}</ExportPath>
+      <ExportPath>/wiki/%E7%89%B9%E5%88%A5:%E3%83%87%E3%83%BC%E3%82%BF%E6%9B%B8%E3%81%8D%E5%87%BA%E3%81%97/$1</ExportPath>
       <Redirect>#転送</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Template:Documentation</DocumentationTemplate>
+        <DocumentationTemplate DefaultPage="/doc">Template:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
+      <LinkInterwikiFormat>{{仮リンク|$1|$2|$3|label=$4}}</LinkInterwikiFormat>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ko.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EB%82%B4%EB%B3%B4%EB%82%B4%EA%B8%B0/{0}</ExportPath>
+      <ExportPath>/wiki/%ED%8A%B9%EC%88%98%EA%B8%B0%EB%8A%A5:%EB%82%B4%EB%B3%B4%EB%82%B4%EA%B8%B0/$1</ExportPath>
       <Redirect>#넘겨주기</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/설명문서">틀:틀 설명문서</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/설명문서">틀:틀 설명문서</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://nl.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Speciaal:Exporteren/{0}</ExportPath>
+      <ExportPath>/wiki/Speciaal:Exporteren/$1</ExportPath>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Sjabloon:Sjabdoc</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Sjabloon:Sjabdoc</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://pl.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Specjalna:Eksport/{0}</ExportPath>
+      <ExportPath>/wiki/Specjalna:Eksport/$1</ExportPath>
       <Redirect>#PATRZ</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/opis">Szablon:Dokumentacja</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/opis">Szablon:Dokumentacja</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://pt.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Especial:Exportar/{0}</ExportPath>
+      <ExportPath>/wiki/Especial:Exportar/$1</ExportPath>
       <Redirect>#REDIRECIONAMENTO</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Predefinição:Documentação</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Predefinição:Documentação</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://ru.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Служебная:Export/{0}</ExportPath>
+      <ExportPath>/wiki/Служебная:Export/$1</ExportPath>
       <Redirect>#перенаправление</Redirect>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">Шаблон:Doc</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">Шаблон:Doc</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
     <MediaWiki>
       <Location>http://zh.wikipedia.org</Location>
           </LanguageName>
         </Names>
       </Language>
-      <ExportPath>/wiki/Special:%E5%AF%BC%E5%87%BA%E9%A1%B5%E9%9D%A2/{0}</ExportPath>
+      <ExportPath>/wiki/Special:%E5%AF%BC%E5%87%BA%E9%A1%B5%E9%9D%A2/$1</ExportPath>
       <MagicWords />
-      <DocumentationTemplate DefaultPage="/doc">模板:Documentation</DocumentationTemplate>
+      <DocumentationTemplates>
+        <DocumentationTemplate DefaultPage="/doc">模板:Documentation</DocumentationTemplate>
+      </DocumentationTemplates>
     </MediaWiki>
   </Websites>
-  <ItemTables />
+  <ItemTables>
+    <ItemTable From="en" To="ja">
+      <Item From="Template:Refend" To="Template:Refend" />
+    </ItemTable>
+    <ItemTable From="ja" To="en">
+      <Item From="Template:Refend" To="Template:Refend" />
+    </ItemTable>
+  </ItemTables>
   <HeadingTable>
     <Group>
       <Word Lang="en">Advantages</Word>
index b5b2831..72d457a 100644 (file)
@@ -3,7 +3,7 @@
 //      MediaWikiTranslatorのテストクラスソース。</summary>
 //
 // <copyright file="MediaWikiTranslatorTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -16,8 +16,12 @@ namespace Honememo.Wptscs.Logics
     using System.Reflection;
     using NUnit.Framework;
     using Honememo.Tests;
+    using Honememo.Parsers;
     using Honememo.Utilities;
     using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Parsers;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// MediaWikiTranslatorのテストクラスです。
@@ -25,38 +29,486 @@ namespace Honememo.Wptscs.Logics
     [TestFixture]
     public class MediaWikiTranslatorTest
     {
+        #region テスト用クラス
+
+        /// <summary>
+        /// MediaWikiTranslatorテスト用のクラスです。
+        /// </summary>
+        public class TestMediaWikiTranslator : MediaWikiTranslator
+        {
+            #region 非公開メソッドテスト用のオーラーライドメソッド
+
+            /// <summary>
+            /// 内部リンクを解析し、変換先言語の記事へのリンクに変換する。
+            /// </summary>
+            /// <param name="link">変換元リンク。</param>
+            /// <param name="parent">サブページ用の親記事タイトル。</param>
+            /// <returns>変換済みリンク。</returns>
+            public new IElement ReplaceLink(MediaWikiLink link, string parent)
+            {
+                return base.ReplaceLink(link, parent);
+            }
+
+            /// <summary>
+            /// テンプレートを解析し、変換先言語の記事へのテンプレートに変換する。
+            /// </summary>
+            /// <param name="template">変換元テンプレート。</param>
+            /// <param name="parent">サブページ用の親記事タイトル。</param>
+            /// <returns>変換済みテンプレート。</returns>
+            public new IElement ReplaceTemplate(MediaWikiTemplate template, string parent)
+            {
+                return base.ReplaceTemplate(template, parent);
+            }
+
+            /// <summary>
+            /// 指定された見出しに対して、対訳表による変換を行う。
+            /// </summary>
+            /// <param name="heading">見出し。</param>
+            /// <param name="parent">サブページ用の親記事タイトル。</param>
+            /// <returns>変換後の見出し。</returns>
+            public new IElement ReplaceHeading(MediaWikiHeading heading, string parent)
+            {
+                return base.ReplaceHeading(heading, parent);
+            }
+
+            #endregion
+        }
+
+        #endregion
+
         #region 定数
 
         /// <summary>
-        /// テストデータが格納されているフォルダパス。
+        /// テスト結果が格納されているフォルダパス。
         /// </summary>
-        private static readonly string testDir = "Data\\MediaWiki";
+        private static readonly string resultDir = Path.Combine(MockFactory.TestMediaWikiDir, "result");
 
         #endregion
 
-        #region テスト支援メソッド
+        #region 各処理のメソッドテストケース
+
+        /// <summary>
+        /// ReplaceLinkメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestReplaceLink()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("ja");
+            translator.To = mock.GetMediaWiki("en");
+            translator.To.LinkInterwikiFormat = null;
+
+            // 見出しの変換パターンを設定
+            translator.HeadingTable = new TranslationTable();
+            IDictionary<string, string> dic = new Dictionary<string, string>();
+            dic["en"] = "External links";
+            dic["ja"] = "外部リンク";
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "ja";
+            translator.HeadingTable.To = "en";
+            MediaWikiLink link;
+
+            // 記事名だけの内部リンクで言語間リンクあり、変換先言語へのリンクとなる
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            link = new MediaWikiLink();
+            link.Title = "ホワイトナイトツー";
+            Assert.AreEqual("[[Scaled Composites White Knight Two|ホワイトナイトツー]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 見出しあり
+            link = new MediaWikiLink();
+            link.Title = "ホワイトナイトツー";
+            link.Section = "見出し";
+            Assert.AreEqual("[[Scaled Composites White Knight Two#見出し|ホワイトナイトツー#見出し]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 変換パターンに該当する見出しの場合
+            link = new MediaWikiLink();
+            link.Title = "ホワイトナイトツー";
+            link.Section = "外部リンク";
+            Assert.AreEqual("[[Scaled Composites White Knight Two#External links|ホワイトナイトツー#外部リンク]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 表示名あり
+            link = new MediaWikiLink();
+            link.Title = "ホワイトナイトツー";
+            link.Section = "外部リンク";
+            link.PipeTexts.Add(new TextElement("母機"));
+            Assert.AreEqual("[[Scaled Composites White Knight Two#External links|母機]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 記事名だけの内部リンクで言語間リンクなし、変換元言語へのリンクとなる
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+            translator.To.LinkInterwikiFormat = null;
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            Assert.AreEqual("[[:en:Exemplum|Exemplum]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 見出しあり
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "Three examples of exempla";
+            Assert.AreEqual("[[:en:Exemplum#Three examples of exempla|Exemplum#Three examples of exempla]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 変換パターンに該当する見出しの場合
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "External links";
+            Assert.AreEqual("[[:en:Exemplum#外部リンク|Exemplum#External links]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 表示名あり
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "External links";
+            link.PipeTexts.Add(new TextElement("Exemplum_1"));
+            Assert.AreEqual("[[:en:Exemplum#外部リンク|Exemplum_1]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 記事名だけの内部リンクで赤リンク、処理されない
+            link = new MediaWikiLink();
+            link.Title = "Nothing Page";
+            Assert.AreEqual("[[Nothing Page]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 見出しあり
+            link = new MediaWikiLink();
+            link.Title = "Nothing Page";
+            link.Section = "Section A";
+            Assert.AreEqual("[[Nothing Page#Section A]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 変換パターンに該当する見出しの場合
+            link = new MediaWikiLink();
+            link.Title = "Nothing Page";
+            link.Section = "External links";
+            Assert.AreEqual("[[Nothing Page#外部リンク]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 表示名あり
+            link = new MediaWikiLink();
+            link.Title = "Nothing Page";
+            link.PipeTexts.Add(new TextElement("Dummy Link"));
+            Assert.AreEqual("[[Nothing Page|Dummy Link]]", translator.ReplaceLink(link, "example").ToString());
+
+            // [[Apollo&nbsp;17]] のように文字参照が入っていても処理できる
+            link = new MediaWikiLink();
+            link.Title = "Fuji&nbsp;(Spacecraft)";
+            Assert.AreEqual("[[ふじ (宇宙船)|Fuji&nbsp;(Spacecraft)]]", translator.ReplaceLink(link, "example").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceLinkメソッドテストケース(カテゴリ)。
+        /// </summary>
+        [Test]
+        public void TestReplaceLinkCategory()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("ja");
+            translator.To = mock.GetMediaWiki("en");
+            MediaWikiLink link;
+
+            // 記事名だけの内部リンクで言語間リンクあり、変換先言語でのカテゴリとなる
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            link = new MediaWikiLink();
+            link.Title = "Category:宇宙船";
+            Assert.AreEqual("[[Category:Manned spacecraft]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // ソートキーあり
+            link = new MediaWikiLink();
+            link.Title = "Category:宇宙船";
+            link.PipeTexts.Add(new TextElement("すへえすしつふつう"));
+            Assert.AreEqual("[[Category:Manned spacecraft|すへえすしつふつう]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 記事名だけの内部リンクで言語間リンクなし、変換元言語へのリンクとなり元のカテゴリはコメントとなる
+            translator.To = mock.GetMediaWiki("it");
+            link = new MediaWikiLink();
+            link.Title = "Category:宇宙船";
+            Assert.AreEqual("[[:ja:Category:宇宙船]]<!-- [[Category:宇宙船]] -->", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // ソートキーあり
+            link = new MediaWikiLink();
+            link.Title = "Category:宇宙船";
+            link.PipeTexts.Add(new TextElement("すへえすしつふつう"));
+            Assert.AreEqual("[[:ja:Category:宇宙船]]<!-- [[Category:宇宙船|すへえすしつふつう]] -->", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // 記事名だけの内部リンクで赤リンク、処理されない
+            link = new MediaWikiLink();
+            link.Title = "Category:xx国の宇宙船";
+            Assert.AreEqual("[[Category:xx国の宇宙船]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+
+            // ソートキーあり
+            link = new MediaWikiLink();
+            link.Title = "Category:xx国の宇宙船";
+            link.PipeTexts.Add(new TextElement("すへえすしつふつう"));
+            Assert.AreEqual("[[Category:xx国の宇宙船|すへえすしつふつう]]", translator.ReplaceLink(link, "スペースシップツー").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceLinkメソッドテストケース(ファイル)。
+        /// </summary>
+        [Test]
+        public void TestReplaceLinkFile()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+            MediaWikiLink link;
+
+            // 画像などのファイルについては、名前空間を他国語に置き換えるだけ
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            link = new MediaWikiLink();
+            link.Title = "File:Kepler22b-artwork.jpg";
+            Assert.AreEqual("[[ファイル:Kepler22b-artwork.jpg]]", translator.ReplaceLink(link, "Kepler-22b").ToString());
+
+            // パラメータあり
+            link = new MediaWikiLink();
+            link.Title = "File:Kepler22b-artwork.jpg";
+            link.PipeTexts.Add(new TextElement("thumb"));
+            link.PipeTexts.Add(new TextElement("right"));
+            link.PipeTexts.Add(new TextElement("Artist's conception of Kepler-22b."));
+            Assert.AreEqual("[[ファイル:Kepler22b-artwork.jpg|thumb|right|Artist's conception of Kepler-22b.]]", translator.ReplaceLink(link, "Kepler-22b").ToString());
+
+            // ja→enの場合もちゃんと置き換える(en→jaはFileのままでも使えるが逆は置き換えないと動かない)
+            translator.From = mock.GetMediaWiki("ja");
+            translator.To = mock.GetMediaWiki("en");
+
+            link = new MediaWikiLink();
+            link.Title = "ファイル:Kepler22b-artwork.jpg";
+            Assert.AreEqual("[[File:Kepler22b-artwork.jpg]]", translator.ReplaceLink(link, "ケプラー22b").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceLinkメソッドテストケース(仮リンク)。
+        /// </summary>
+        [Test]
+        public void TestReplaceLinkLinkInterwiki()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+
+            // 見出しの変換パターンを設定
+            translator.HeadingTable = new TranslationTable();
+            IDictionary<string, string> dic = new Dictionary<string, string>();
+            dic["en"] = "External links";
+            dic["ja"] = "外部リンク";
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+            MediaWikiLink link;
+
+            // 記事名だけの内部リンクで言語間リンクなし、変換元言語へのリンクとなる
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            Assert.AreEqual("{{仮リンク|Exemplum|en|Exemplum|label=Exemplum}}", translator.ReplaceLink(link, "example").ToString());
+
+            // 見出しあり
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "Three examples of exempla";
+            Assert.AreEqual("{{仮リンク|Exemplum#Three examples of exempla|en|Exemplum#Three examples of exempla|label=Exemplum#Three examples of exempla}}", translator.ReplaceLink(link, "example").ToString());
+
+            // 変換パターンに該当する見出しの場合
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "External links";
+            Assert.AreEqual("{{仮リンク|Exemplum#外部リンク|en|Exemplum#External links|label=Exemplum#External links}}", translator.ReplaceLink(link, "example").ToString());
+
+            // 表示名あり
+            link = new MediaWikiLink();
+            link.Title = "Exemplum";
+            link.Section = "External links";
+            link.PipeTexts.Add(new TextElement("Exemplum_1"));
+            Assert.AreEqual("{{仮リンク|Exemplum#外部リンク|en|Exemplum#External links|label=Exemplum_1}}", translator.ReplaceLink(link, "example").ToString());
+
+            // 言語間リンクありの場合、仮リンクは使用されない
+            link = new MediaWikiLink();
+            link.Title = "Example.com";
+            Assert.AreEqual("[[Example.com|Example.com]]", translator.ReplaceLink(link, "example").ToString());
+
+            // 記事名だけの内部リンクで赤リンクも、使用されない
+            link = new MediaWikiLink();
+            link.Title = "Nothing Page";
+            Assert.AreEqual("[[Nothing Page]]", translator.ReplaceLink(link, "example").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceTemplateメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestReplaceTemplate()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+            MediaWikiTemplate template;
+
+            // テンプレート名だけで言語間リンクあり、変換先言語でのテンプレートとなる
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            template = new MediaWikiTemplate("Citation needed");
+            Assert.AreEqual("{{要出典}}", translator.ReplaceTemplate(template, "example").ToString());
+
+            // パラメータあり
+            template = new MediaWikiTemplate("Citation needed");
+            template.PipeTexts.Add(new TextElement("date=January 2012"));
+            Assert.AreEqual("{{要出典|date=January 2012}}", translator.ReplaceTemplate(template, "example").ToString());
+
+            // テンプレート名だけで言語間リンクなし、変換元言語へのリンクとなり元のテンプレートはコメントとなる
+            template = new MediaWikiTemplate("Wiktionary");
+            Assert.AreEqual("[[:en:Template:Wiktionary]]<!-- {{Wiktionary}} -->", translator.ReplaceTemplate(template, "example").ToString());
+
+            // パラメータあり
+            template = new MediaWikiTemplate("Wiktionary");
+            template.PipeTexts.Add(new TextElement("Sample"));
+            Assert.AreEqual("[[:en:Template:Wiktionary]]<!-- {{Wiktionary|Sample}} -->", translator.ReplaceTemplate(template, "example").ToString());
+
+            // テンプレート名だけで赤リンク、処理されない
+            template = new MediaWikiTemplate("Invalid Template");
+            Assert.AreEqual("{{Invalid Template}}", translator.ReplaceTemplate(template, "example").ToString());
+
+            // パラメータあり
+            template = new MediaWikiTemplate("Invalid Template");
+            template.PipeTexts.Add(new TextElement("parameter=1"));
+            Assert.AreEqual("{{Invalid Template|parameter=1}}", translator.ReplaceTemplate(template, "example").ToString());
+
+            // システム定義変数、処理されない
+            template = new MediaWikiTemplate("PAGENAME");
+            Assert.AreEqual("{{PAGENAME}}", translator.ReplaceTemplate(template, "example").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceTemplateメソッドテストケース(入れ子)。
+        /// </summary>
+        [Test]
+        public void TestReplaceTemplateNested()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+            MediaWikiTemplate template;
+            MediaWikiLink link;
+            ListElement list;
+
+            // テンプレート名だけで言語間リンクあり、入れ子も処理される
+            // ※ 以下オブジェクトを毎回作り直しているのは、更新されてしまうケースがあるため
+            template = new MediaWikiTemplate("Citation needed");
+            template.PipeTexts.Add(new TextElement("date=January 2012"));
+            list = new ListElement();
+            list.Add(new TextElement("note=See also "));
+            link = new MediaWikiLink();
+            link.Title = "Fuji (Spacecraft)";
+            list.Add(link);
+            template.PipeTexts.Add(list);
+            Assert.AreEqual("{{要出典|date=January 2012|note=See also [[ふじ (宇宙船)|Fuji (Spacecraft)]]}}", translator.ReplaceTemplate(template, "example").ToString());
+
+            // テンプレート名だけで言語間リンクなし、入れ子は処理されない
+            template = new MediaWikiTemplate("Wiktionary");
+            template.PipeTexts.Add(new TextElement("Sample"));
+            list = new ListElement();
+            list.Add(new TextElement("note=See also "));
+            link = new MediaWikiLink();
+            link.Title = "Fuji (Spacecraft)";
+            list.Add(link);
+            template.PipeTexts.Add(list);
+            Assert.AreEqual("[[:en:Template:Wiktionary]]<!-- {{Wiktionary|Sample|note=See also [[Fuji (Spacecraft)]]}} -->", translator.ReplaceTemplate(template, "example").ToString());
+
+            // テンプレート名だけで赤リンク、入れ子は処理されない
+            template = new MediaWikiTemplate("Invalid Template");
+            template.PipeTexts.Add(new TextElement("parameter=1"));
+            list = new ListElement();
+            list.Add(new TextElement("note=See also "));
+            link = new MediaWikiLink();
+            link.Title = "Fuji (Spacecraft)";
+            list.Add(link);
+            template.PipeTexts.Add(list);
+            Assert.AreEqual("{{Invalid Template|parameter=1|note=See also [[Fuji (Spacecraft)]]}}", translator.ReplaceTemplate(template, "example").ToString());
+        }
+
+        /// <summary>
+        /// ReplaceHeadingメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestReplaceHeading()
+        {
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("en");
+            translator.To = mock.GetMediaWiki("ja");
+
+            // 見出しの変換パターンを設定
+            translator.HeadingTable = new TranslationTable();
+            IDictionary<string, string> dic = new Dictionary<string, string>();
+            dic["en"] = "External links";
+            dic["ja"] = "外部リンク";
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+
+            MediaWikiHeading heading = new MediaWikiHeading();
+
+            // 対訳表に登録されていない見出し
+            // ※ 以下リストを毎回作り直しているのは、更新されてしまうケースがあるため
+            heading.Level = 2;
+            heading.Add(new TextElement(" invalid section "));
+            Assert.AreEqual("== invalid section ==", translator.ReplaceHeading(heading, "example").ToString());
+
+            // 対訳表に登録されている見出し
+            heading.Clear();
+            heading.Add(new TextElement(" External links "));
+            Assert.AreEqual("==外部リンク==", translator.ReplaceHeading(heading, "example").ToString());
+
+            // 一部が内部リンク等になっている場合、対訳表とは一致しない
+            heading.Clear();
+            heading.Add(new TextElement(" External "));
+            heading.Add(new MediaWikiLink("link"));
+            heading.Add(new TextElement("s "));
+            Assert.AreEqual("== External [[link]]s ==", translator.ReplaceHeading(heading, "example").ToString());
+        }
 
         /// <summary>
-        /// テスト用の値を設定したMediaWikiオブジェクトを返す
+        /// ReplaceHeadingメソッドテストケース(入れ子)
         /// </summary>
-        public MediaWiki GetTestServer(string language)
+        [Test]
+        public void TestReplaceHeadingNested()
         {
-            // ※ 下記URL生成時は、きちんとパス区切り文字を入れてやら無いとフォルダが認識されない。
-            //    また、httpで取得した場合とfileで取得した場合では先頭の大文字小文字が異なることが
-            //    あるため、それについては随時期待値を調整して対処。
-            MediaWiki server = ObjectUtils.DefaultIfNull<MediaWiki>(
-                TestingConfig.GetInstance("Data\\config.xml").GetWebsite(language) as MediaWiki,
-                new MediaWiki(new Language(language)));
-            UriBuilder b = new UriBuilder("file", "");
-            b.Path = Path.GetFullPath(testDir) + "\\";
-            server.Location = new Uri(b.Uri, language + "/").ToString();
-            server.ExportPath = "{0}.xml";
-            server.NamespacePath = "_api.xml";
-            return server;
+            TestMediaWikiTranslator translator = new TestMediaWikiTranslator();
+            MockFactory mock = new MockFactory();
+            translator.From = mock.GetMediaWiki("ja");
+            translator.To = mock.GetMediaWiki("en");
+
+            // 見出しの変換パターンを設定
+            translator.HeadingTable = new TranslationTable();
+            IDictionary<string, string> dic = new Dictionary<string, string>();
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "ja";
+            translator.HeadingTable.To = "en";
+
+            MediaWikiHeading heading = new MediaWikiHeading();
+
+            // 対訳表に登録されていない見出しの場合、入れ子も処理される
+            // ※ 以下リストを毎回作り直しているのは、更新されてしまうケースがあるため
+            heading.Level = 3;
+            heading.Add(new TextElement(" "));
+            heading.Add(new MediaWikiLink("宇宙旅行"));
+            heading.Add(new MediaWikiTemplate("ref-en"));
+            heading.Add(new TextElement(" "));
+            Assert.AreEqual("=== [[Space tourism|宇宙旅行]]{{En icon}} ===", translator.ReplaceHeading(heading, "スペースシップツー").ToString());
+
+            // 対訳表に登録されている見出しの場合、入れ子は処理されない
+            dic["ja"] = "[[宇宙旅行]]{{ref-en}}";
+            dic["en"] = "[[弾道飛行]]{{ref-en}}";
+            heading.Clear();
+            heading.Add(new TextElement(" "));
+            heading.Add(new MediaWikiLink("宇宙旅行"));
+            heading.Add(new MediaWikiTemplate("ref-en"));
+            heading.Add(new TextElement(" "));
+            Assert.AreEqual("===[[弾道飛行]]{{ref-en}}===", translator.ReplaceHeading(heading, "スペースシップツー").ToString());
         }
 
         #endregion
-        
+
         #region 全体テストケース
 
         /// <summary>
@@ -65,41 +517,30 @@ namespace Honememo.Wptscs.Logics
         [Test]
         public void TestExampleIgnoreHeading()
         {
-            MediaWiki from = this.GetTestServer("en");
-            Translator translate = new MediaWikiTranslator();
-            translate.From = from;
-            translate.To = this.GetTestServer("ja");
-            translate.HeadingTable = new TranslationTable();
-            translate.HeadingTable.From = "en";
-            translate.HeadingTable.To = "ja";
-
-            Assert.IsTrue(translate.Run("example"));
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("en");
+            MediaWikiTranslator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("ja");
+            translator.To.LinkInterwikiFormat = null;
+            translator.HeadingTable = new TranslationTable();
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+            translator.Run("example");
 
             // テストデータの変換結果を期待される結果と比較する
-            string expectedText;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example_定型句なし.txt"))) 
-            {
-                expectedText = sr.ReadToEnd();
-            }
-
             // バージョン表記部分は毎回変化するため、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExampleIgnoreHeading Text > " + translate.Text);
             Assert.AreEqual(
-                expectedText.Replace("<!-- Wikipedia 翻訳支援ツール Ver0.xx", "<!-- " + FormUtils.ApplicationName()),
-                translate.Text);
+                File.ReadAllText(Path.Combine(resultDir, "example_定型句なし.txt")).Replace("<!-- Wikipedia 翻訳支援ツール Ver0.xx", "<!-- " + FormUtils.ApplicationName()),
+                translator.Text);
 
             // テストデータの変換ログを期待されるログと比較する
-            string expectedLog;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example_定型句なし.log")))
-            {
-                expectedLog = sr.ReadToEnd();
-            }
-
             // 1行目のパスが一致しないので、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExampleIgnoreHeading Log > " + translate.Log);
             Assert.AreEqual(
-                expectedLog.Replace("file:///xxx/Data/MediaWiki/en/", from.Location),
-                translate.Log);
+                File.ReadAllText(Path.Combine(resultDir, "example_定型句なし.log")).Replace("file:///xxx/Data/MediaWiki/en/", from.Location),
+                translator.Log);
         }
 
         /// <summary>
@@ -109,47 +550,36 @@ namespace Honememo.Wptscs.Logics
         [Test]
         public void TestExample()
         {
-            MediaWiki from = this.GetTestServer("en");
-            Translator translate = new MediaWikiTranslator();
-            translate.From = from;
-            translate.To = this.GetTestServer("ja");
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("en");
+            MediaWikiTranslator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("ja");
+            translator.To.LinkInterwikiFormat = null;
 
             // 見出しの変換パターンを設定
-            translate.HeadingTable = new TranslationTable();
+            translator.HeadingTable = new TranslationTable();
             IDictionary<string, string> dic = new Dictionary<string, string>();
             dic["en"] = "See also";
             dic["ja"] = "関連項目";
-            translate.HeadingTable.Add(dic);
-            translate.HeadingTable.From = "en";
-            translate.HeadingTable.To = "ja";
-
-            Assert.IsTrue(translate.Run("example"));
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+            translator.Run("example");
 
             // テストデータの変換結果を期待される結果と比較する
-            string expectedText;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example.txt")))
-            {
-                expectedText = sr.ReadToEnd();
-            }
-
             // バージョン表記部分は毎回変化するため、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Text > " + translate.Text);
             Assert.AreEqual(
-                expectedText.Replace("<!-- Wikipedia 翻訳支援ツール Ver0.73", "<!-- " + FormUtils.ApplicationName()),
-                translate.Text);
+                File.ReadAllText(Path.Combine(resultDir, "example.txt")).Replace("<!-- Wikipedia 翻訳支援ツール Ver0.73", "<!-- " + FormUtils.ApplicationName()),
+                translator.Text);
 
             // テストデータの変換ログを期待されるログと比較する
-            string expectedLog;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example.log")))
-            {
-                expectedLog = sr.ReadToEnd();
-            }
-
             // 1行目のパスが一致しないので、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Log > " + translate.Log);
             Assert.AreEqual(
-                expectedLog.Replace("http://en.wikipedia.org", from.Location),
-                translate.Log);
+                File.ReadAllText(Path.Combine(resultDir, "example.log")).Replace("http://en.wikipedia.org", from.Location),
+                translator.Log);
         }
 
         /// <summary>
@@ -158,19 +588,21 @@ namespace Honememo.Wptscs.Logics
         [Test]
         public void TestExampleWithCache()
         {
-            MediaWiki from = this.GetTestServer("en");
-            Translator translate = new MediaWikiTranslator();
-            translate.From = from;
-            translate.To = this.GetTestServer("ja");
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("en");
+            MediaWikiTranslator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("ja");
+            translator.To.LinkInterwikiFormat = null;
 
             // 見出しの変換パターンを設定
-            translate.HeadingTable = new TranslationTable();
+            translator.HeadingTable = new TranslationTable();
             IDictionary<string, string> dic = new Dictionary<string, string>();
             dic["en"] = "See also";
             dic["ja"] = "関連項目";
-            translate.HeadingTable.Add(dic);
-            translate.HeadingTable.From = "en";
-            translate.HeadingTable.To = "ja";
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
 
             // 以下のキャッシュパターンを指定して実行
             TranslationDictionary table = new TranslationDictionary("en", "ja");
@@ -178,9 +610,8 @@ namespace Honememo.Wptscs.Logics
             table.Add("example.org", new TranslationDictionary.Item());
             table.Add(".example", new TranslationDictionary.Item { Word = "。さんぷる", Alias = ".dummy" });
             table.Add("Template:Disambig", new TranslationDictionary.Item { Word = "Template:曖昧さ回避" });
-            translate.ItemTable = table;
-
-            Assert.IsTrue(translate.Run("example"));
+            translator.ItemTable = table;
+            translator.Run("example");
 
             // キャッシュに今回の処理で取得した内容が更新されているかを確認
             Assert.IsTrue(table.ContainsKey("example.com"));
@@ -197,30 +628,55 @@ namespace Honememo.Wptscs.Logics
             Assert.IsNotNull(table["example.net"].Timestamp);
 
             // テストデータの変換結果を期待される結果と比較する
-            string expectedText;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example_キャッシュ使用.txt")))
-            {
-                expectedText = sr.ReadToEnd();
-            }
-
             // バージョン表記部分は毎回変化するため、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExampleWithCache Text > " + translate.Text);
             Assert.AreEqual(
-                expectedText.Replace("<!-- Wikipedia 翻訳支援ツール Ver0.xx", "<!-- " + FormUtils.ApplicationName()),
-                translate.Text);
+                File.ReadAllText(Path.Combine(resultDir, "example_キャッシュ使用.txt")).Replace("<!-- Wikipedia 翻訳支援ツール Ver0.xx", "<!-- " + FormUtils.ApplicationName()),
+                translator.Text);
 
             // テストデータの変換ログを期待されるログと比較する
-            string expectedLog;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\example_キャッシュ使用.log")))
-            {
-                expectedLog = sr.ReadToEnd();
-            }
-
             // 1行目のパスが一致しないので、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExampleWithCache Log > " + translate.Log);
             Assert.AreEqual(
-                expectedLog.Replace("file:///xxx/Data/MediaWiki/en/", from.Location),
-                translate.Log);
+                File.ReadAllText(Path.Combine(resultDir, "example_キャッシュ使用.log")).Replace("file:///xxx/Data/MediaWiki/en/", from.Location),
+                translator.Log);
+        }
+
+        /// <summary>
+        /// テストデータを用い、Runを通しで実行するテストケース。基本動作見出しの変換、{{仮リンク}}への置き換え含む。
+        /// </summary>
+        [Test]
+        public void TestExampleWithLinkInterwiki()
+        {
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("en");
+            MediaWikiTranslator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("ja");
+
+            // 見出しの変換パターンを設定
+            translator.HeadingTable = new TranslationTable();
+            IDictionary<string, string> dic = new Dictionary<string, string>();
+            dic["en"] = "See also";
+            dic["ja"] = "関連項目";
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "en";
+            translator.HeadingTable.To = "ja";
+            translator.Run("example");
+
+            // テストデータの変換結果を期待される結果と比較する
+            // バージョン表記部分は毎回変化するため、期待される結果のうち該当部分を更新する
+            //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Text > " + translate.Text);
+            Assert.AreEqual(
+                File.ReadAllText(Path.Combine(resultDir, "example_仮リンク有効.txt")).Replace("<!-- Wikipedia 翻訳支援ツール Ver0.73", "<!-- " + FormUtils.ApplicationName()),
+                translator.Text);
+
+            // テストデータの変換ログを期待されるログと比較する
+            // 1行目のパスが一致しないので、期待される結果のうち該当部分を更新する
+            //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Log > " + translate.Log);
+            Assert.AreEqual(
+                File.ReadAllText(Path.Combine(resultDir, "example.log")).Replace("http://en.wikipedia.org", from.Location),
+                translator.Log);
         }
 
         /// <summary>
@@ -230,56 +686,45 @@ namespace Honememo.Wptscs.Logics
         [Test]
         public void TestSpaceShipTwo()
         {
-            MediaWiki from = this.GetTestServer("ja");
-            Translator translate = new MediaWikiTranslator();
-            translate.From = from;
-            translate.To = this.GetTestServer("en");
-            translate.ItemTable = new TranslationDictionary("ja", "en");
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("ja");
+            MediaWikiTranslator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("en");
+            translator.To.LinkInterwikiFormat = null;
+            translator.ItemTable = new TranslationDictionary("ja", "en");
 
             // 見出しの変換パターンを設定
-            translate.HeadingTable = new TranslationTable();
+            translator.HeadingTable = new TranslationTable();
             IDictionary<string, string> dic = new Dictionary<string, string>();
             dic["en"] = "See Also";
             dic["ja"] = "関連項目";
-            translate.HeadingTable.Add(dic);
+            translator.HeadingTable.Add(dic);
             dic = new Dictionary<string, string>();
             dic["en"] = "External links";
             dic["ja"] = "外部リンク";
-            translate.HeadingTable.Add(dic);
+            translator.HeadingTable.Add(dic);
             dic = new Dictionary<string, string>();
             dic["en"] = "Notes";
             dic["ja"] = "脚注";
-            translate.HeadingTable.Add(dic);
-            translate.HeadingTable.From = "ja";
-            translate.HeadingTable.To = "en";
-
-            Assert.IsTrue(translate.Run("スペースシップツー"));
+            translator.HeadingTable.Add(dic);
+            translator.HeadingTable.From = "ja";
+            translator.HeadingTable.To = "en";
+            translator.Run("スペースシップツー");
 
             // テストデータの変換結果を期待される結果と比較する
-            string expectedText;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\スペースシップツー.txt")))
-            {
-                expectedText = sr.ReadToEnd();
-            }
-
             // バージョン表記部分は毎回変化するため、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Text > " + translate.Text);
             Assert.AreEqual(
-                expectedText.Replace("<!-- Wikipedia 翻訳支援ツール Ver0.73", "<!-- " + FormUtils.ApplicationName()),
-                translate.Text);
+                File.ReadAllText(Path.Combine(resultDir, "スペースシップツー.txt")).Replace("<!-- Wikipedia 翻訳支援ツール Ver0.73", "<!-- " + FormUtils.ApplicationName()),
+                translator.Text);
 
             // テストデータの変換ログを期待されるログと比較する
-            string expectedLog;
-            using (StreamReader sr = new StreamReader(Path.Combine(testDir, "result\\スペースシップツー.log")))
-            {
-                expectedLog = sr.ReadToEnd();
-            }
-
             // 1行目のパスが一致しないので、期待される結果のうち該当部分を更新する
             //System.Diagnostics.Debug.WriteLine("TranslateMediaWikiTest.TestExample Log > " + translate.Log);
             Assert.AreEqual(
-                expectedLog.Replace("http://ja.wikipedia.org", from.Location),
-                translate.Log);
+                File.ReadAllText(Path.Combine(resultDir, "スペースシップツー.log")).Replace("http://ja.wikipedia.org", from.Location),
+                translator.Log);
         }
 
         /// <summary>
@@ -288,59 +733,26 @@ namespace Honememo.Wptscs.Logics
         [Test]
         public void TestPageNothing()
         {
-            MediaWiki from = this.GetTestServer("en");
-            Translator translate = new MediaWikiTranslator();
-            translate.From = from;
-            translate.To = this.GetTestServer("ja");
+            MockFactory mock = new MockFactory();
+            MediaWiki from = mock.GetMediaWiki("en");
+            Translator translator = new MediaWikiTranslator();
+            translator.From = from;
+            translator.To = mock.GetMediaWiki("ja");
 
-            Assert.IsFalse(translate.Run("Nothing Page"));
-
-            // 実行ログを期待されるログと比較する
-            Assert.AreEqual(
-                ("http://en.wikipedia.org より [[Nothing Page]] を取得。\r\n"
-                + "→ 翻訳元として指定された記事は存在しません。記事名を確認してください。")
-                .Replace("http://en.wikipedia.org", from.Location),
-                translate.Log);
-        }
-
-        #endregion
-
-        #region 整理予定の静的メソッドテストケース
-
-        /// <summary>
-        /// ChkCommentメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestChkComment()
-        {
-            // TryParseComment互換用の旧メソッド
-            string comment;
-            Assert.AreEqual(14, MediaWikiTranslator.ChkComment(out comment, "ab<!-- test -->cd", 2));
-            Assert.AreEqual("<!-- test -->", comment);
-            Assert.AreEqual(15, MediaWikiTranslator.ChkComment(out comment, "ab<!-- test --cd", 2));
-            Assert.AreEqual("<!-- test --cd", comment);
-            Assert.AreEqual(-1, MediaWikiTranslator.ChkComment(out comment, "ab<!-- test -->cd", 1));
-            Assert.IsEmpty(comment);
-            Assert.AreEqual(-1, MediaWikiTranslator.ChkComment(out comment, "ab<!-- test -->cd", 3));
-            Assert.IsEmpty(comment);
-        }
-        
-        /// <summary>
-        /// ChkNowikiメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestChkNowiki()
-        {
-            // TryParseNowiki互換用の旧メソッド
-            string nowiki;
-            Assert.AreEqual(26, MediaWikiTranslator.ChkNowiki(out nowiki, "ab<nowiki>[[test]]</nowiki>cd", 2));
-            Assert.AreEqual("<nowiki>[[test]]</nowiki>", nowiki);
-            Assert.AreEqual(27, MediaWikiTranslator.ChkNowiki(out nowiki, "ab<nowiki>[[test]]</nowikicd", 2));
-            Assert.AreEqual("<nowiki>[[test]]</nowikicd", nowiki);
-            Assert.AreEqual(-1, MediaWikiTranslator.ChkNowiki(out nowiki, "ab<nowiki>[[test]]</nowiki>cd", 1));
-            Assert.IsEmpty(nowiki);
-            Assert.AreEqual(-1, MediaWikiTranslator.ChkNowiki(out nowiki, "ab<nowiki>[[test]]</nowiki>cd", 3));
-            Assert.IsEmpty(nowiki);
+            try
+            {
+                translator.Run("Nothing Page");
+                Assert.Fail();
+            }
+            catch (ApplicationException)
+            {
+                // 実行ログを期待されるログと比較する
+                Assert.AreEqual(
+                    ("http://en.wikipedia.org より [[Nothing Page]] を取得。\r\n"
+                    + "→ 翻訳元として指定された記事は存在しません。記事名を確認してください。")
+                    .Replace("http://en.wikipedia.org", from.Location),
+                    translator.Log);
+            }
         }
 
         #endregion
diff --git a/WptscsTest/Logics/TranslatorTest.cs b/WptscsTest/Logics/TranslatorTest.cs
new file mode 100644 (file)
index 0000000..739e833
--- /dev/null
@@ -0,0 +1,476 @@
+// ================================================================================================
+// <summary>
+//      Translatorのテストクラスソース。</summary>
+//
+// <copyright file="TranslatorTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Logics
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Reflection;
+    using NUnit.Framework;
+    using Honememo.Tests;
+    using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// Translatorのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class TranslatorTest
+    {
+        #region モッククラス
+
+        /// <summary>
+        /// Translatorテスト用のモッククラスです。
+        /// </summary>
+        public class TranslatorMock : Translator
+        {
+            #region テスト支援用パラメータ
+
+            /// <summary>
+            /// <see cref="RunBody"/>で例外を投げるか?
+            /// </summary>
+            public bool exception = false;
+
+            #endregion
+
+            #region 非公開プロパティテスト用のオーラーライドプロパティ
+
+            /// <summary>
+            /// ログメッセージ。
+            /// </summary>
+            public new string Log
+            {
+                get
+                {
+                    return base.Log;
+                }
+
+                set
+                {
+                    base.Log = value;
+                }
+            }
+
+            /// <summary>
+            /// 変換後テキスト。
+            /// </summary>
+            public new string Text
+            {
+                get
+                {
+                    return base.Text;
+                }
+
+                set
+                {
+                    base.Text = value;
+                }
+            }
+
+            #endregion
+
+            #region 非公開メソッドテスト用のオーラーライドメソッド
+
+            /// <summary>
+            /// ログメッセージを1行追加出力。
+            /// </summary>
+            /// <param name="log">ログメッセージ。</param>
+            public new void LogLine(string log)
+            {
+                base.LogLine(log);
+            }
+
+            /// <summary>
+            /// ログメッセージを1行追加出力(入力された文字列を書式化して表示)。
+            /// </summary>
+            /// <param name="format">書式項目を含んだログメッセージ。</param>
+            /// <param name="args">書式設定対象オブジェクト配列。</param>
+            public new void LogLine(string format, params object[] args)
+            {
+                base.LogLine(format, args);
+            }
+
+            /// <summary>
+            /// ログメッセージを出力しつつページを取得。
+            /// </summary>
+            /// <param name="title">ページタイトル。</param>
+            /// <param name="notFoundMsg">取得できない場合に出力するメッセージ。</param>
+            /// <returns>取得したページ。ページが存在しない場合は <c>null</c> を返す。</returns>
+            /// <remarks>通信エラーなど例外が発生した場合は、別途エラーログを出力する。</remarks>
+            public new Page GetPage(string title, string notFoundMsg)
+            {
+                return base.GetPage(title, notFoundMsg);
+            }
+
+            #endregion
+
+            #region ダミーメソッド
+
+            /// <summary>
+            /// 翻訳支援処理実行部の本体。
+            /// </summary>
+            /// <param name="name">記事名。</param>
+            /// <returns><c>true</c> 処理成功</returns>
+            protected override void RunBody(string name)
+            {
+                if (exception)
+                {
+                    throw new ApplicationException("Dummy");
+                }
+            }
+
+            #endregion
+        }
+
+        /// <summary>
+        /// Translatorテスト用のモッククラスです。
+        /// </summary>
+        public class TranslatorIgnoreMock : Translator
+        {
+            #region コンストラクタ
+
+            /// <summary>
+            /// デフォルトコンストラクタを隠すためのダミーコンストラクタ。
+            /// </summary>
+            /// <param name="dummy">ダミー。</param>
+            public TranslatorIgnoreMock(string dummy)
+            {
+            }
+
+            #endregion
+
+            #region ダミーメソッド
+
+            /// <summary>
+            /// 翻訳支援処理実行部の本体。
+            /// </summary>
+            /// <param name="name">記事名。</param>
+            /// <returns><c>true</c> 処理成功</returns>
+            protected override void RunBody(string name)
+            {
+            }
+
+            #endregion
+        }
+
+        /// <summary>
+        /// Websiteテスト用のモッククラスです。
+        /// </summary>
+        public class WebsiteMock : Website
+        {
+            #region ダミーメソッド
+
+            /// <summary>
+            /// ページを取得。
+            /// </summary>
+            /// <param name="title">ページタイトル。</param>
+            /// <returns>取得したページ。</returns>
+            /// <remarks>取得できない場合(通信エラーなど)は例外を投げる。</remarks>
+            public override Page GetPage(string title)
+            {
+                return null;
+            }
+
+            #endregion
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// ItemTableプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestItemTable()
+        {
+            // 初期状態がnull、設定すればそのオブジェクトが返されること
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsNull(translator.ItemTable);
+            TranslationDictionary table = new TranslationDictionary("en", "ja");
+            translator.ItemTable = table;
+            Assert.AreSame(table, translator.ItemTable);
+        }
+
+        /// <summary>
+        /// HeadingTableプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestHeadingTable()
+        {
+            // 初期状態がnull、設定すればそのオブジェクトが返されること
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsNull(translator.HeadingTable);
+            TranslationTable table = new TranslationTable();
+            translator.HeadingTable = table;
+            Assert.AreSame(table, translator.HeadingTable);
+        }
+
+        /// <summary>
+        /// Logプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestLog()
+        {
+            // 初期状態は空
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsEmpty(translator.Log);
+
+            // null設定時は空白が設定されること、それ以外はそのまま
+            translator.Log = null;
+            Assert.IsEmpty(translator.Log);
+            translator.Log = "test";
+            Assert.AreEqual("test", translator.Log);
+
+            // 更新時にLogUpdateイベントが実行されること
+            int count = 0;
+            translator.LogUpdate += new EventHandler((object sender, EventArgs e) => { ++count; });
+            Assert.AreEqual(0, count);
+            translator.Log = "ログ";
+            Assert.AreEqual(1, count);
+            Assert.AreEqual("ログ", translator.Log);
+            translator.Log += "add";
+            Assert.AreEqual(2, count);
+            Assert.AreEqual("ログadd", translator.Log);
+        }
+
+        /// <summary>
+        /// Textプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestText()
+        {
+            // 初期状態は空
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsEmpty(translator.Text);
+
+            // null設定時は空白が設定されること、それ以外はそのまま
+            translator.Text = null;
+            Assert.IsEmpty(translator.Text);
+            translator.Text = "test";
+            Assert.AreEqual("test", translator.Text);
+        }
+
+        /// <summary>
+        /// CancellationPendingプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestCancellationPending()
+        {
+            // 初期状態はfalse、設定すればそのオブジェクトが返されること
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsFalse(translator.CancellationPending);
+            translator.CancellationPending = true;
+            Assert.IsTrue(translator.CancellationPending);
+            translator.CancellationPending = false;
+            Assert.IsFalse(translator.CancellationPending);
+        }
+
+        /// <summary>
+        /// Fromプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestFrom()
+        {
+            // 初期状態がnull、設定すればそのオブジェクトが返されること
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsNull(translator.From);
+            WebsiteMock website = new WebsiteMock();
+            translator.From = website;
+            Assert.AreSame(website, translator.From);
+        }
+
+        /// <summary>
+        /// Toプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestTo()
+        {
+            // 初期状態がnull、設定すればそのオブジェクトが返されること
+            TranslatorMock translator = new TranslatorMock();
+            Assert.IsNull(translator.To);
+            WebsiteMock website = new WebsiteMock();
+            translator.To = website;
+            Assert.AreSame(website, translator.To);
+        }
+
+        #endregion
+
+        #region 静的メソッドテストケース
+
+        /// <summary>
+        /// Createメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestCreate()
+        {
+            // コンフィグの情報から対応するトランスレータが生成されること
+            Translator translator = Translator.Create(new MockFactory().GetConfig(), "en", "ja");
+            Assert.IsNotNull(translator);
+            Assert.IsInstanceOf(typeof(MediaWikiTranslator), translator);
+            Assert.IsNotNull(translator.From);
+            Assert.AreEqual("en", translator.From.Language.Code);
+            Assert.IsNotNull(translator.To);
+            Assert.AreEqual("ja", translator.To.Language.Code);
+            Assert.IsNotNull(translator.ItemTable);
+            Assert.AreEqual("en", translator.ItemTable.From);
+            Assert.AreEqual("ja", translator.ItemTable.To);
+            Assert.IsNotNull(translator.HeadingTable);
+            Assert.AreEqual("en", translator.HeadingTable.From);
+            Assert.AreEqual("ja", translator.HeadingTable.To);
+        }
+
+        /// <summary>
+        /// Createメソッドテストケース(未対応のトランスレータクラス)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(NotImplementedException))]
+        public void TestCreateUnsupportedConstructor()
+        {
+            // コンフィグに引数無しのコンストラクタを持たないトランスレータクラス
+            // が指定されていない場合、例外となること
+            Config config = new MockFactory().GetConfig();
+            config.Translator = typeof(TranslatorIgnoreMock);
+            Translator.Create(config, "en", "ja");
+        }
+
+        /// <summary>
+        /// Createメソッドテストケース(トランスレータクラス以外の指定)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(InvalidCastException))]
+        public void TestCreateIgnoreConstructor()
+        {
+            // コンフィグに引数無しのコンストラクタを持たないトランスレータクラス
+            // が指定されていない場合、例外となること
+            Config config = new MockFactory().GetConfig();
+            config.Translator = this.GetType();
+            Translator.Create(config, "en", "ja");
+        }
+
+        #endregion
+
+        #region publicメソッドテストケース
+
+        /// <summary>
+        /// Runメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestRun()
+        {
+            TranslatorMock translator = new TranslatorMock();
+            translator.From = new WebsiteMock();
+            translator.From.Location = "file://";
+            translator.To = new WebsiteMock();
+
+            // 正常に実行が行えること
+            // また、実行ごとに結果が初期化されること
+            translator.Run("test");
+            Assert.IsEmpty(translator.Log);
+            Assert.IsEmpty(translator.Text);
+            translator.Log = "testlog";
+            translator.Text = "testtext";
+            translator.Run("test");
+            Assert.IsEmpty(translator.Log);
+            Assert.IsEmpty(translator.Text);
+
+            // 失敗はApplicationExceptionで表現、RunBodyから例外が投げられること
+            translator.Log = "testlog";
+            translator.Text = "testtext";
+            translator.exception = true;
+            try
+            {
+                translator.Run("test");
+                Assert.Fail();
+            }
+            catch (ApplicationException e)
+            {
+                Assert.AreEqual("Dummy", e.Message);
+            }
+
+            Assert.IsEmpty(translator.Log);
+            Assert.IsEmpty(translator.Text);
+        }
+
+        /// <summary>
+        /// Runメソッドテストケース(必須パラメータ未設定)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(InvalidOperationException))]
+        public void TestRunLangEmpty()
+        {
+            // From, To が未設定の場合処理不能
+            new TranslatorMock().Run("test");
+        }
+
+        /// <summary>
+        /// Runメソッドテストケース(ping成功)。
+        /// </summary>
+        [Test]
+        public void TestRunPing()
+        {
+            TranslatorMock translator = new TranslatorMock();
+            translator.From = new WebsiteMock();
+            translator.To = new WebsiteMock();
+
+            // Fromにホストが指定されている場合、pingチェックが行われる
+            translator.From.Location = "http://localhost";
+            translator.Run("test");
+        }
+
+        /// <summary>
+        /// Runメソッドテストケース(ping失敗)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ApplicationException))]
+        public void TestRunPingFailed()
+        {
+            TranslatorMock translator = new TranslatorMock();
+            translator.From = new WebsiteMock();
+            translator.To = new WebsiteMock();
+
+            // Fromにホストが指定されている場合、pingチェックが行われる
+            translator.From.Location = "http://xxx.invalid";
+            translator.Run("test");
+        }
+
+        #endregion
+
+        #region protectedメソッドテストケース
+
+        /// <summary>
+        /// LogLineメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestLogLine()
+        {
+            TranslatorMock translator = new TranslatorMock();
+            
+            // 通常は一行が出力される
+            Assert.IsEmpty(translator.Log);
+            translator.LogLine("1st string");
+            Assert.AreEqual("1st string\r\n", translator.Log);
+            translator.LogLine("2nd string");
+            Assert.AreEqual("1st string\r\n2nd string\r\n", translator.Log);
+
+            // 直前のログが改行されていない場合、改行して出力される
+            translator.Log += "3rd ";
+            translator.LogLine("string");
+            Assert.AreEqual("1st string\r\n2nd string\r\n3rd \r\nstring\r\n", translator.Log);
+
+            // パラメータが二つの方は、String.Formatした値を出力する
+            translator.LogLine("{0}th string", 4);
+            Assert.AreEqual("1st string\r\n2nd string\r\n3rd \r\nstring\r\n4th string\r\n", translator.Log);
+        }
+
+        #endregion
+    }
+}
index 4bb6aa3..ad304d8 100644 (file)
@@ -3,7 +3,7 @@
 //      Configのテストクラスソース。</summary>
 //
 // <copyright file="ConfigTest.cs.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -20,6 +20,7 @@ namespace Honememo.Wptscs.Models
     using NUnit.Framework;
     using Honememo.Utilities;
     using Honememo.Wptscs.Logics;
+    using Honememo.Wptscs.Websites;
 
     /// <summary>
     /// Configのテストクラスです。
@@ -27,7 +28,26 @@ namespace Honememo.Wptscs.Models
     [TestFixture]
     public class ConfigTest
     {
-        // TODO: テストケース全然足りない
+        #region モッククラス
+
+        /// <summary>
+        /// Configテスト用のモッククラスです。
+        /// </summary>
+        /// <remarks>そのままではnewすることができないため。</remarks>
+        public class DummyConfig : Config
+        {
+        }
+
+        #endregion
+
+        #region 定数
+
+        /// <summary>
+        /// テスト結果が格納されているフォルダパス。
+        /// </summary>
+        private static readonly string resultXml = Path.Combine(MockFactory.TestMediaWikiDir, "result\\config.xml");
+
+        #endregion
 
         #region XMLシリアライズ用メソッドケース
 
@@ -39,7 +59,7 @@ namespace Honememo.Wptscs.Models
         {
             // TODO: デシリアライズでも細かい動作の差異があるので、もう少しテストケースが必要
             Config config;
-            using (Stream stream = new FileStream("Data\\config.xml", FileMode.Open, FileAccess.Read))
+            using (Stream stream = new FileStream(MockFactory.TestConfigXml, FileMode.Open, FileAccess.Read))
             {
                 config = new XmlSerializer(typeof(Config)).Deserialize(stream) as Config;
             }
@@ -51,7 +71,7 @@ namespace Honememo.Wptscs.Models
             Assert.AreEqual("http://en.wikipedia.org", en.Location);
             Assert.IsTrue(en.Language.Names.ContainsKey("ja"));
             // TODO: この辺も、内容の確認が必要
-            Assert.IsTrue(config.ItemTables.Count == 0);
+            Assert.IsTrue(config.ItemTables.Count > 0);
             Assert.IsTrue(config.HeadingTable.Count > 0);
             Assert.AreEqual("関連項目", config.HeadingTable.GetWord("en", "ja", "See Also"));
         }
@@ -63,7 +83,7 @@ namespace Honememo.Wptscs.Models
         public void TestWriteXml()
         {
             // TODO: シリアライズでも細かい動作の差異があるので、もう少しテストケースが必要
-            Config config = new TestingConfig();
+            Config config = new DummyConfig();
             config.Translator = typeof(MediaWikiTranslator);
             TranslationDictionary dic = new TranslationDictionary("en", "ja");
             dic.Add("dicKey", new TranslationDictionary.Item { Word = "dicTest" });
@@ -87,6 +107,30 @@ namespace Honememo.Wptscs.Models
                 + "<HeadingTable><Group><Word Lang=\"recordKey\">recordValue</Word></Group></HeadingTable></Config>", b.ToString());
         }
 
+        /// <summary>
+        /// XMLデシリアライズ→シリアライズの通しのテストケース。
+        /// </summary>
+        [Test]
+        public void TestReadXmlToWriteXml()
+        {
+            Config config;
+            using (Stream stream = new FileStream(MockFactory.TestConfigXml, FileMode.Open, FileAccess.Read))
+            {
+                config = new XmlSerializer(typeof(Config)).Deserialize(stream) as Config;
+            }
+
+            XmlWriterSettings settings = new XmlWriterSettings();
+            settings.Indent = true;
+
+            StringBuilder b = new StringBuilder();
+            using (XmlWriter w = XmlWriter.Create(b, settings))
+            {
+                new XmlSerializer(typeof(Config)).Serialize(w, config);
+            }
+
+            Assert.AreEqual(File.ReadAllText(resultXml), b.ToString());
+        }
+
         #endregion
     }
 }
index c6ab8ab..e397a1d 100644 (file)
@@ -3,7 +3,7 @@
 //      Languageのテストクラスソース。</summary>
 //
 // <copyright file="LanguageTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
@@ -95,7 +95,7 @@ namespace Honememo.Wptscs.Models
             Language lang = new Language("en");
 
             // 設定ファイルからデフォルト値が設定されていること
-            Assert.AreEqual(" ({0}) ", lang.Bracket);
+            Assert.AreEqual(" ($1) ", lang.Bracket);
 
             // 設定後はそちらが有効になること
             lang.Bracket = "test";
@@ -103,9 +103,33 @@ namespace Honememo.Wptscs.Models
 
             // 消すとデフォルトが有効になること
             lang.Bracket = null;
-            Assert.AreEqual(" ({0}) ", lang.Bracket);
+            Assert.AreEqual(" ($1) ", lang.Bracket);
             lang.Bracket = "";
-            Assert.AreEqual(" ({0}) ", lang.Bracket);
+            Assert.AreEqual(" ($1) ", lang.Bracket);
+        }
+
+        #endregion
+
+        #region 公開メソッドテストケース
+
+        /// <summary>
+        /// FormatLinkInterwikiメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestFormatLinkInterwiki()
+        {
+            Language lang = new Language("en");
+
+            // パラメータを埋め込んで書式化される
+            Assert.AreEqual(" (値1) ", lang.FormatBracket("値1"));
+            lang.Bracket = " {$1} ";
+            Assert.AreEqual(" {値2} ", lang.FormatBracket("値2"));
+            lang.Bracket = "xxx";
+            Assert.AreEqual("xxx", lang.FormatBracket("値3"));
+
+            // 値がnull等でも特に制限はない
+            lang.Bracket = null;
+            Assert.AreEqual(" () ", lang.FormatBracket(null));
         }
 
         #endregion
diff --git a/WptscsTest/Models/MockFoctory.cs b/WptscsTest/Models/MockFoctory.cs
new file mode 100644 (file)
index 0000000..aed2627
--- /dev/null
@@ -0,0 +1,121 @@
+// ================================================================================================
+// <summary>
+//      モックオブジェクト生成処理をまとめたファクトリークラスソース</summary>
+//
+// <copyright file="MockFactory.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Models
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Xml.Serialization;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// モックオブジェクト生成処理をまとめたファクトリークラスです。
+    /// </summary>
+    public class MockFactory
+    {
+        #region 定数
+
+        /// <summary>
+        /// テスト用のconfig.xmlファイルパス。
+        /// </summary>
+        public static readonly string TestConfigXml = "Data\\config.xml";
+
+        /// <summary>
+        /// テストデータが格納されているフォルダパス。
+        /// </summary>
+        public static readonly string TestMediaWikiDir = "Data\\MediaWiki";
+
+        #endregion
+
+        #region private変数
+
+        /// <summary>
+        /// テスト用設定。
+        /// </summary>
+        private Config config;
+
+        #endregion
+
+        #region コンストラクタ
+
+        /// <summary>
+        /// テスト用のconfig.xmlを元に、モックファクトリーを生成する。
+        /// </summary>
+        public MockFactory()
+        {
+            this.config = MockFactory.GetConfig(MockFactory.TestConfigXml);
+        }
+
+        #endregion
+
+        #region テスト支援静的メソッド
+
+        /// <summary>
+        /// ファイルからアプリケーションの設定を取得する。
+        /// </summary>
+        /// <param name="file">設定ファイル名。</param>
+        /// <returns>作成したインスタンス。</returns>
+        public static Config GetConfig(string file)
+        {
+            // 設定ファイルを読み込み
+            using (Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
+            {
+                return (Config)new XmlSerializer(typeof(Config)).Deserialize(stream);
+            }
+        }
+
+        #endregion
+
+        #region テスト支援メソッド
+
+        /// <summary>
+        /// アプリケーションの設定を取得する。
+        /// </summary>
+        /// <returns>作成したインスタンス。</returns>
+        public Config GetConfig()
+        {
+            return this.config;
+        }
+
+        /// <summary>
+        /// 指定された言語のMediaWikiを取得する。
+        /// </summary>
+        /// <param name="lang">言語コード。</param>
+        /// <returns>ウェブサイトの情報。</returns>
+        public MediaWiki GetMediaWiki(string lang)
+        {
+            Website site = this.config.GetWebsite(lang);
+            MediaWiki wiki = null;
+            if (site != null)
+            {
+                wiki = site as MediaWiki;
+            }
+
+            if (wiki == null)
+            {
+                wiki = new MediaWiki(new Language(lang));
+            }
+
+            // テスト用にサーバー設定を書き換え
+            // ※ フルパスじゃないとURIで取得できないので、ここで書き換える必要有り
+            UriBuilder b = new UriBuilder("file", "");
+            b.Path = Path.GetFullPath(MockFactory.TestMediaWikiDir) + "\\";
+            wiki.Location = new Uri(b.Uri, lang + "/").ToString();
+            wiki.ExportPath = "$1.xml";
+            wiki.NamespacePath = "_api.xml";
+
+            return wiki;
+        }
+
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Models/TestingConfig.cs b/WptscsTest/Models/TestingConfig.cs
deleted file mode 100644 (file)
index 226b02f..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-// ================================================================================================
-// <summary>
-//      テスト用に拡張したConfigクラスソース。</summary>
-//
-// <copyright file="TestingConfig.cs.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
-// <author>
-//      Honeplus</author>
-// ================================================================================================
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Xml.Serialization;
-
-namespace Honememo.Wptscs.Models
-{
-    /// <summary>
-    /// テスト用に拡張したConfigクラスです。
-    /// </summary>
-    public class TestingConfig : Config
-    {        
-        #region テスト支援メソッド
-
-        /// <summary>
-        /// アプリケーションの設定を取得する。
-        /// </summary>
-        /// <param name="file">設定ファイル名。</param>
-        /// <returns>作成したインスタンス。</returns>
-        /// <remarks>テスト用のため、特に親クラスのようなシングルトンといった制御はせず。</remarks>
-        public static new Config GetInstance(string file)
-        {
-            // 設定ファイルを読み込み
-            using (Stream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
-            {
-                return new XmlSerializer(typeof(Config)).Deserialize(stream) as Config;
-            }
-        }
-
-        #endregion
-    }
-}
index d189fbd..da96052 100644 (file)
@@ -3,7 +3,7 @@
 //      TranslationDictionaryのテストクラスソース。</summary>
 //
 // <copyright file="TranslationDictionaryTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
index b7bf667..b60cfb5 100644 (file)
@@ -3,7 +3,7 @@
 //      TranslationTableのテストクラスソース。</summary>
 //
 // <copyright file="TranslationTableTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
diff --git a/WptscsTest/Parsers/MediaWikiHeadingParserTest.cs b/WptscsTest/Parsers/MediaWikiHeadingParserTest.cs
new file mode 100644 (file)
index 0000000..1d506da
--- /dev/null
@@ -0,0 +1,169 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiHeadingParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiHeadingParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiHeadingParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiHeadingParserTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            MediaWikiHeadingParser parser = new MediaWikiHeadingParser(
+                new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+            // TODO: ちゃんと設定されているかも確認する?
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            IElement element;
+            MediaWikiHeading heading;
+            MediaWikiHeadingParser parser = new MediaWikiHeadingParser(
+                new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 基本形
+            Assert.IsTrue(parser.TryParse("==test==", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("==test==", heading.ToString());
+            Assert.AreEqual(2, heading.Level);
+            Assert.AreEqual(1, heading.Count);
+            Assert.AreEqual("test", heading[0].ToString());
+
+            // 後ろが改行
+            Assert.IsTrue(parser.TryParse("== test == \r\ntest", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("== test == ", heading.ToString());
+            Assert.AreEqual(2, heading.Level);
+            Assert.AreEqual(1, heading.Count);
+            Assert.AreEqual(" test ", heading[0].ToString());
+
+            // 改行以外はNG
+            Assert.IsFalse(parser.TryParse("== test == test", out element));
+            Assert.IsNull(element);
+
+            // 複数の要素を含む
+            Assert.IsTrue(parser.TryParse("===[[test]] and sample===", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("===[[test]] and sample===", heading.ToString());
+            Assert.AreEqual(3, heading.Level);
+            Assert.AreEqual(2, heading.Count);
+            Assert.AreEqual("[[test]]", heading[0].ToString());
+            Assert.IsInstanceOf(typeof(MediaWikiLink), heading[0]);
+            Assert.AreEqual(" and sample", heading[1].ToString());
+
+            // 前後で数が違うのはOK、少ない側の階層と判定
+            Assert.IsTrue(parser.TryParse("=test==", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("=test==", heading.ToString());
+            Assert.AreEqual(1, heading.Level);
+
+            // 前後で数が違うの逆パターン
+            Assert.IsTrue(parser.TryParse("====test==", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("====test==", heading.ToString());
+            Assert.AreEqual(2, heading.Level);
+
+            // 先頭が = 以外はNG
+            Assert.IsFalse(parser.TryParse(" ==test==", out element));
+            Assert.IsNull(element);
+
+            // 内部要素に改行を含むのはOK
+            Assert.IsTrue(parser.TryParse("== {{lang\n|ja|見出し}} ==\n", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("== {{lang\n|ja|見出し}} ==", heading.ToString());
+            Assert.AreEqual(2, heading.Level);
+            Assert.AreEqual(3, heading.Count);
+            Assert.AreEqual(" ", heading[0].ToString());
+            Assert.AreEqual("{{lang\n|ja|見出し}}", heading[1].ToString());
+            Assert.IsInstanceOf(typeof(MediaWikiTemplate), heading[1]);
+            Assert.AreEqual(" ", heading[2].ToString());
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(コメント)。
+        /// </summary>
+        [Test]
+        public void TestTryParseComment()
+        {
+            IElement element;
+            MediaWikiHeading heading;
+            MediaWikiHeadingParser parser = new MediaWikiHeadingParser(
+                new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // ↓1.01以前のバージョンで対応していたコメント、中のコメントが認識されなかった
+            // // こんな無茶なコメントも一応対応
+            // Assert.IsTrue(parser.TryParse("<!--test-->=<!--test-->=関連項目<!--test-->==<!--test-->\n", out element));
+            // Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            // heading = (MediaWikiHeading)element;
+            // Assert.AreEqual("<!--test-->=<!--test-->=関連項目<!--test-->==<!--test-->", heading.ToString());
+            // Assert.AreEqual(2, heading.Level);
+            // // TODO: 本当は2でコメントも見つけるべきだが、コメントは中も除外しているので現状1
+            // // Assert.AreEqual(2, heading.Count);
+            // // Assert.AreEqual("関連項目", heading[0].ToString());
+            // // Assert.AreEqual("<!--test-->", heading[1].ToString());
+            // Assert.AreEqual(1, heading.Count);
+            // Assert.AreEqual("関連項目", heading[0].ToString());
+            // ↓1.10改修後での動作
+            Assert.IsFalse(parser.TryParse("<!--test-->=<!--test-->=関連項目<!--test-->==<!--test-->\n", out element));
+            Assert.IsTrue(parser.TryParse("=<!--test-->=関連項目<!--test-->==\n", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual(1, heading.Level);
+            Assert.AreEqual(4, heading.Count);
+            Assert.AreEqual("<!--test-->", heading[0].ToString());
+            Assert.AreEqual("=関連項目", heading[1].ToString());
+            Assert.AreEqual("<!--test-->", heading[2].ToString());
+            Assert.AreEqual("=", heading[3].ToString());
+            Assert.IsFalse(parser.TryParse("==関連項目<!--test-->==<!--test-->\n", out element));
+
+            // ↓1.10改修後に対応しているコメント、変なところのコメントは駄目だが中のものを認識する
+            Assert.IsTrue(parser.TryParse("==<!--test1-->関連項目<!--test2-->==\n", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiHeading), element);
+            heading = (MediaWikiHeading)element;
+            Assert.AreEqual("==<!--test1-->関連項目<!--test2-->==", heading.ToString());
+            Assert.AreEqual(2, heading.Level);
+            Assert.AreEqual(3, heading.Count);
+            Assert.AreEqual("<!--test1-->", heading[0].ToString());
+            Assert.IsInstanceOf(typeof(XmlCommentElement), heading[0]);
+            Assert.AreEqual("関連項目", heading[1].ToString());
+            Assert.AreEqual("<!--test2-->", heading[2].ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiHeadingTest.cs b/WptscsTest/Parsers/MediaWikiHeadingTest.cs
new file mode 100644 (file)
index 0000000..0719504
--- /dev/null
@@ -0,0 +1,80 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiHeadingのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiHeadingTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+
+    /// <summary>
+    /// MediaWikiHeadingのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiHeadingTest
+    {
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Levelプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestLevel()
+        {
+            MediaWikiHeading element = new MediaWikiHeading();
+
+            Assert.AreEqual(0, element.Level);
+            element.Level = 2;
+            Assert.AreEqual(2, element.Level);
+            element.Level = -5;
+            Assert.AreEqual(-5, element.Level);
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            MediaWikiHeading element = new MediaWikiHeading();
+
+            // 初期状態
+            Assert.IsEmpty(element.ToString());
+
+            // 見出し1階層
+            element.Level = 1;
+            Assert.AreEqual("==", element.ToString());
+
+            // 見出し中身設定
+            element.Add(new TextElement("見出し"));
+            Assert.AreEqual("=見出し=", element.ToString());
+
+            // 階層をいろいろ変更
+            element.Level = 0;
+            Assert.AreEqual("見出し", element.ToString());
+            element.Level = 2;
+            Assert.AreEqual("==見出し==", element.ToString());
+            element.Level = -4;
+            Assert.AreEqual("見出し", element.ToString());
+            element.Level = 3;
+            Assert.AreEqual("===見出し===", element.ToString());
+
+            // 見出し中身追加
+            element.Add(new XmlCommentElement("コメント"));
+            Assert.AreEqual("===見出し<!--コメント-->===", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiLinkParserTest.cs b/WptscsTest/Parsers/MediaWikiLinkParserTest.cs
new file mode 100644 (file)
index 0000000..b9a5aac
--- /dev/null
@@ -0,0 +1,238 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiLinkParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiLinkParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiLinkParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiLinkParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース(基本的な構文)。
+        /// </summary>
+        [Test]
+        public void TestTryParseBasic()
+        {
+            IElement element;
+            MediaWikiLink link;
+            MediaWikiLinkParser parser = new MediaWikiLinkParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // タイトルのみ
+            Assert.IsTrue(parser.TryParse("[[testtitle]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle", link.Title);
+            Assert.IsNull(link.Section);
+            Assert.AreEqual(0, link.PipeTexts.Count);
+            Assert.IsNull(link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // タイトルとセクション
+            Assert.IsTrue(parser.TryParse("[[testtitle#testsection]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle", link.Title);
+            Assert.AreEqual("testsection", link.Section);
+            Assert.AreEqual(0, link.PipeTexts.Count);
+            Assert.IsNull(link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // タイトルとセクションとパイプ後の文字列
+            Assert.IsTrue(parser.TryParse("[[testtitle#testsection|testpipe1|testpipe2]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle", link.Title);
+            Assert.AreEqual("testsection", link.Section);
+            Assert.AreEqual(2, link.PipeTexts.Count);
+            Assert.AreEqual("testpipe1", link.PipeTexts[0].ToString());
+            Assert.AreEqual("testpipe2", link.PipeTexts[1].ToString());
+            Assert.IsNull(link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // タイトルとセクションとパイプ後の文字列とコード
+            Assert.IsTrue(parser.TryParse("[[en:testtitle#testsection|testpipe1|testpipe2]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle", link.Title);
+            Assert.AreEqual("testsection", link.Section);
+            Assert.AreEqual(2, link.PipeTexts.Count);
+            Assert.AreEqual("testpipe1", link.PipeTexts[0].ToString());
+            Assert.AreEqual("testpipe2", link.PipeTexts[1].ToString());
+            Assert.AreEqual("en", link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // タイトルとセクションとパイプ後の文字列とコードとコロン
+            Assert.IsTrue(parser.TryParse("[[:en:testtitle#testsection|testpipe1|testpipe2]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle", link.Title);
+            Assert.AreEqual("testsection", link.Section);
+            Assert.AreEqual(2, link.PipeTexts.Count);
+            Assert.AreEqual("testpipe1", link.PipeTexts[0].ToString());
+            Assert.AreEqual("testpipe2", link.PipeTexts[1].ToString());
+            Assert.AreEqual("en", link.Code);
+            Assert.IsTrue(link.IsColon);
+
+            // コメントはどこにあってもOK
+            // TODO: [<!--test-->[タイトル]] みたいなのもMediaWiki上では認識されるが、2012年1月現在未対応
+            Assert.IsTrue(parser.TryParse("[[testtitle<!--仮-->|testpipe1<!--コメントアウト-->]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle<!--仮-->", link.Title);
+            Assert.AreEqual(1, link.PipeTexts.Count);
+            Assert.AreEqual("testpipe1<!--コメントアウト-->", link.PipeTexts[0].ToString());
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(NGパターン)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNg()
+        {
+            IElement element;
+            MediaWikiLinkParser parser = new MediaWikiLinkParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 開始タグが無い
+            Assert.IsFalse(parser.TryParse("testtitle]]", out element));
+
+            // 閉じタグが無い
+            Assert.IsFalse(parser.TryParse("[[testtitle", out element));
+
+            // 先頭が開始タグではない
+            Assert.IsFalse(parser.TryParse(" [[testtitle]]", out element));
+
+            // 外部リンクタグ
+            Assert.IsFalse(parser.TryParse("[testtitle]", out element));
+
+            // テンプレートタグ
+            Assert.IsFalse(parser.TryParse("{{testtitle}}", out element));
+
+            // 記事名名の部分に < > [ ] { } \n のいずれかの文字が存在する場合、NG
+            // ※ コメントや変数であれば可能、それ以外で存在するのがNG
+            Assert.IsFalse(parser.TryParse("[[test<title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[test>title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[test[title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[test]title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[test{title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[test}title]]", out element));
+            Assert.IsFalse(parser.TryParse("[[testtitle\n]]", out element));
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(入れ子)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNested()
+        {
+            IElement element;
+            MediaWikiLink link;
+            MediaWikiLinkParser parser = new MediaWikiLinkParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // テンプレートはパイプ以後にある分には全てOK
+            Assert.IsTrue(parser.TryParse("[[ロシア語|{{lang|ru|русский язык}}]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("ロシア語", link.Title);
+            Assert.AreEqual(1, link.PipeTexts.Count);
+            Assert.AreEqual("{{lang|ru|русский язык}}", link.PipeTexts[0].ToString());
+
+            // 変数の場合、記事名の部分にも入れられる
+            Assert.IsTrue(parser.TryParse("[[{{{title|デフォルトジャンル}}}-stub]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("{{{title|デフォルトジャンル}}}-stub", link.Title);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(名前空間)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNamespace()
+        {
+            IElement element;
+            MediaWikiLink link;
+            MediaWikiLinkParser parser = new MediaWikiLinkParser(new MediaWikiParser(new MockFactory().GetMediaWiki("ja")));
+
+            // カテゴリ標準
+            Assert.IsTrue(parser.TryParse("[[Category:test]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("Category:test", link.Title);
+            Assert.AreEqual(0, link.PipeTexts.Count);
+            Assert.IsNull(link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // カテゴリソート名指定
+            Assert.IsTrue(parser.TryParse("[[Category:test|てすと]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("Category:test", link.Title);
+            Assert.AreEqual(1, link.PipeTexts.Count);
+            Assert.AreEqual("てすと", link.PipeTexts[0].ToString());
+            Assert.IsNull(link.Code);
+            Assert.IsFalse(link.IsColon);
+
+            // カテゴリにならないような指定
+            Assert.IsTrue(parser.TryParse("[[:Category:test]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("Category:test", link.Title);
+            Assert.AreEqual(0, link.PipeTexts.Count);
+            Assert.IsNull(link.Code);
+            Assert.IsTrue(link.IsColon);
+
+            // ファイル、入れ子もあり
+            Assert.IsTrue(parser.TryParse("[[ファイル:test.png|thumb|100px|テスト[[画像]]]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("ファイル:test.png", link.Title);
+            Assert.AreEqual(3, link.PipeTexts.Count);
+            Assert.AreEqual("thumb", link.PipeTexts[0].ToString());
+            Assert.AreEqual("100px", link.PipeTexts[1].ToString());
+            Assert.AreEqual("テスト[[画像]]", link.PipeTexts[2].ToString());
+            Assert.IsInstanceOf(typeof(ListElement), link.PipeTexts[2]);
+            ListElement list = ((ListElement)link.PipeTexts[2]);
+            Assert.AreEqual(2, list.Count);
+            Assert.AreEqual("テスト", list[0].ToString());
+            Assert.AreEqual("[[画像]]", list[1].ToString());
+            Assert.IsNull(link.Code);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(サブページ)。
+        /// </summary>
+        [Test]
+        public void TestTryParseSubpage()
+        {
+            IElement element;
+            MediaWikiLink link;
+            MediaWikiLinkParser parser = new MediaWikiLinkParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 全て指定されているケースは通常の記事と同じ扱い
+            Assert.IsTrue(parser.TryParse("[[testtitle/subpage]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("testtitle/subpage", link.Title);
+            Assert.IsFalse(link.IsSubpage);
+
+            // 記事名が省略されているケース
+            Assert.IsTrue(parser.TryParse("[[/subpage]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("/subpage", link.Title);
+            Assert.IsTrue(link.IsSubpage);
+
+            // 記事名が省略されているケース2
+            // TODO: サブページの相対パスは2012年1月現在未対応、対応するなら方法から要検討
+            Assert.IsTrue(parser.TryParse("[[../../subpage]]", out element));
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("../../subpage", link.Title);
+            Assert.IsFalse(link.IsSubpage);
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiLinkTest.cs b/WptscsTest/Parsers/MediaWikiLinkTest.cs
new file mode 100644 (file)
index 0000000..200069f
--- /dev/null
@@ -0,0 +1,214 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiLinkのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiLinkTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using System.Collections.Generic;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+
+    /// <summary>
+    /// MediaWikiLinkのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiLinkTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            MediaWikiLink element;
+
+            element = new MediaWikiLink();
+            Assert.IsNull(element.Title);
+            Assert.IsNotNull(element.PipeTexts);
+            Assert.AreEqual(0, element.PipeTexts.Count);
+
+            element = new MediaWikiLink("記事名");
+            Assert.AreEqual("記事名", element.Title);
+            Assert.IsNotNull(element.PipeTexts);
+            Assert.AreEqual(0, element.PipeTexts.Count);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Titleプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestTitle()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            Assert.IsNull(element.Title);
+            element.Title = "test";
+            Assert.AreEqual("test", element.Title);
+        }
+
+        /// <summary>
+        /// Sectionプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestSection()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            Assert.IsNull(element.Section);
+            element.Section = "test";
+            Assert.AreEqual("test", element.Section);
+        }
+
+        /// <summary>
+        /// PipeTextsプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestPipeTexts()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            Assert.AreEqual(0, element.PipeTexts.Count);
+            IList<IElement> list = new List<IElement>();
+            list.Add(new TextElement("test"));
+            element.PipeTexts = list;
+            Assert.AreEqual(1, element.PipeTexts.Count);
+        }
+
+        /// <summary>
+        /// Codeプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestCode()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            Assert.IsNull(element.Code);
+            element.Code = "test";
+            Assert.AreEqual("test", element.Code);
+        }
+
+        /// <summary>
+        /// IsColonプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestIsColon()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            Assert.IsFalse(element.IsColon);
+            element.IsColon = true;
+            Assert.IsTrue(element.IsColon);
+            element.IsColon = false;
+            Assert.IsFalse(element.IsColon);
+        }
+
+        #endregion
+
+        #region 公開メソッドテストケース
+
+        /// <summary>
+        /// GetLinkStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestGetLinkString()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            // タイトルのみ
+            element.Title = "testtitle";
+            Assert.AreEqual("testtitle", element.GetLinkString());
+
+            // タイトルとセクション
+            element.Section = String.Empty;
+            Assert.AreEqual("testtitle#", element.GetLinkString());
+            element.Section = "testsection";
+            Assert.AreEqual("testtitle#testsection", element.GetLinkString());
+
+            // タイトルとセクションとパイプ後の文字列
+            element.PipeTexts.Add(new TextElement("testpipe1"));
+            element.PipeTexts.Add(new TextElement("testpipe2"));
+            Assert.AreEqual("testtitle#testsection", element.GetLinkString());
+
+            // タイトルとセクションとパイプ後の文字列とコード
+            element.Code = "en";
+            Assert.AreEqual("en:testtitle#testsection", element.GetLinkString());
+
+            // タイトルとセクションとパイプ後の文字列とコードとコロン
+            element.IsColon = true;
+            Assert.AreEqual(":en:testtitle#testsection", element.GetLinkString());
+
+            // 実例)ファイルタグ
+            element.Title = "ファイル:Kepler22b-artwork.jpg";
+            element.Section = null;
+            element.PipeTexts.Clear();
+            element.PipeTexts.Add(new TextElement("thumb"));
+            element.PipeTexts.Add(new TextElement("right"));
+            element.PipeTexts.Add(new TextElement("[[ケプラー22b]](想像図)"));
+            element.Code = null;
+            element.IsColon = false;
+            Assert.AreEqual("ファイル:Kepler22b-artwork.jpg", element.GetLinkString());
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            MediaWikiLink element = new MediaWikiLink();
+
+            // タイトルのみ
+            element.Title = "testtitle";
+            Assert.AreEqual("[[testtitle]]", element.ToString());
+
+            // タイトルとセクション
+            element.Section = String.Empty;
+            Assert.AreEqual("[[testtitle#]]", element.ToString());
+            element.Section = "testsection";
+            Assert.AreEqual("[[testtitle#testsection]]", element.ToString());
+
+            // タイトルとセクションとパイプ後の文字列
+            element.PipeTexts.Add(new TextElement("testpipe1"));
+            element.PipeTexts.Add(new TextElement("testpipe2"));
+            Assert.AreEqual("[[testtitle#testsection|testpipe1|testpipe2]]", element.ToString());
+
+            // タイトルとセクションとパイプ後の文字列とコード
+            element.Code = "en";
+            Assert.AreEqual("[[en:testtitle#testsection|testpipe1|testpipe2]]", element.ToString());
+
+            // タイトルとセクションとパイプ後の文字列とコードとコロン
+            element.IsColon = true;
+            Assert.AreEqual("[[:en:testtitle#testsection|testpipe1|testpipe2]]", element.ToString());
+
+            // 実例)ファイルタグ
+            element.Title = "ファイル:Kepler22b-artwork.jpg";
+            element.Section = null;
+            element.PipeTexts.Clear();
+            element.PipeTexts.Add(new TextElement("thumb"));
+            element.PipeTexts.Add(new TextElement("right"));
+            element.PipeTexts.Add(new TextElement("[[ケプラー22b]](想像図)"));
+            element.Code = null;
+            element.IsColon = false;
+            Assert.AreEqual("[[ファイル:Kepler22b-artwork.jpg|thumb|right|[[ケプラー22b]](想像図)]]", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiNowikiParserTest.cs b/WptscsTest/Parsers/MediaWikiNowikiParserTest.cs
new file mode 100644 (file)
index 0000000..1baad4a
--- /dev/null
@@ -0,0 +1,83 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiNowikiParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiNowikiParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+
+    /// <summary>
+    /// MediaWikiNowikiParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiNowikiParserTest
+    {
+        #region インスタンス実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            IElement element;
+            MediaWikiNowikiParser parser = new MediaWikiNowikiParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // nowikiは大文字小文字区別せず動作、閉じタグが無くても機能する
+            // (その判断はMediaWikiNowikiParserではなくMediaWikiParserでの設定次第によるものだが)
+            // 属性値などが指定されていても機能する
+            // nowiki区間ではコメントも機能しない
+            Assert.IsTrue(parser.TryParse("<nowiki>[[test]]</nowiki>", out element));
+            Assert.AreEqual("<nowiki>[[test]]</nowiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<NOWIKI>[[test]]</NOWIKI>", out element));
+            Assert.AreEqual("<NOWIKI>[[test]]</NOWIKI>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<Nowiki>[[test]]</noWiki>", out element));
+            Assert.AreEqual("<Nowiki>[[test]]</noWiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki>[[test]]</nowiki></nowiki>", out element));
+            Assert.AreEqual("<nowiki>[[test]]</nowiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki>[[test]]nowiki", out element));
+            Assert.AreEqual("<nowiki>[[test]]nowiki", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki>\n\n[[test]]\r\n</nowiki>", out element));
+            Assert.AreEqual("<nowiki>\n\n[[test]]\r\n</nowiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki><!--[[test]]--></nowiki>", out element));
+            Assert.AreEqual("<nowiki><!--[[test]]--></nowiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki><!--<nowiki>[[test]]</nowiki>--></nowiki>", out element));
+            Assert.AreEqual("<nowiki><!--<nowiki>[[test]]</nowiki>", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki><!--[[test]]", out element));
+            Assert.AreEqual("<nowiki><!--[[test]]", element.ToString());
+            Assert.IsTrue(parser.TryParse("<nowiki attr=\"Value\">[[test]]</nowiki>", out element));
+            Assert.AreEqual("<nowiki attr=\"Value\">[[test]]</nowiki>", element.ToString());
+            Assert.IsFalse(parser.TryParse("<nowik>[[test]]</nowik>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<nowiki[[test]]</nowiki>", out element));
+            Assert.IsNull(element);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(nowiki以外のタグ)。
+        /// </summary>
+        /// <remarks>nowiki以外の場合に変な動きをしているようなバグがあったので。</remarks>
+        [Test]
+        public void TestTryParseNg()
+        {
+            IElement element;
+            MediaWikiNowikiParser parser = new MediaWikiNowikiParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            Assert.IsFalse(parser.TryParse("<ref name=\"oscars.org\">{{cite web |url=http://www.oscars.org/awards/academyawards/legacy/ceremony/59th-winners.html |title=The 59th Academy Awards (1987) Nominees and Winners |accessdate=2011-07-23|work=oscars.org}}</ref> | owner = [[Discovery Communications|Discovery Communications, Inc.]] | CEO = David Zaslav | headquarters = [[Silver Spring, Maryland]] | country = Worldwide | language = English | sister names = [[TLC (TV channel)|TLC]]<br>[[Animal Planet]]<br>[[OWN: Oprah Winfrey Network]]<br>[[Planet Green]]<br>", out element));
+            Assert.IsNull(element);
+            Assert.IsFalse(parser.TryParse("<br>[[Animal Planet]]<br>[[OWN: Oprah Winfrey Network]]<br>[[Planet Green]]", out element));
+            Assert.IsNull(element);
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiParserTest.cs b/WptscsTest/Parsers/MediaWikiParserTest.cs
new file mode 100644 (file)
index 0000000..892fa9b
--- /dev/null
@@ -0,0 +1,55 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiParserTest
+    {
+        // TODO: いっぱい足りない
+
+        #region インスタンス実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            IElement element;
+            ListElement list;
+            MediaWikiParser parser = new MediaWikiParser(new MockFactory().GetMediaWiki("en"));
+
+            Assert.IsTrue(parser.TryParse("'''Article Name''' is [[xxx]]\r\n==test head==\r\n<p>test</p><nowiki>[[test]]</nowiki><!--comment-->{{reflist}}", out element));
+            Assert.IsInstanceOf(typeof(ListElement), element);
+            list = (ListElement)element;
+            Assert.AreEqual(8, list.Count);
+            Assert.AreEqual("'''Article Name''' is ", list[0].ToString());
+            Assert.AreEqual("[[xxx]]", list[1].ToString());
+            Assert.AreEqual("\r\n", list[2].ToString());
+            Assert.AreEqual("==test head==", list[3].ToString());
+            Assert.AreEqual("\r\n<p>test</p>", list[4].ToString());
+            Assert.AreEqual("<nowiki>[[test]]</nowiki>", list[5].ToString());
+            Assert.AreEqual("<!--comment-->", list[6].ToString());
+            Assert.AreEqual("{{reflist}}", list[7].ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiRedirectParserTest.cs b/WptscsTest/Parsers/MediaWikiRedirectParserTest.cs
new file mode 100644 (file)
index 0000000..f044ad7
--- /dev/null
@@ -0,0 +1,69 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiRedirectParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiRedirectParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiRedirectParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiRedirectParserTest
+    {
+        #region インスタンス実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestTryParse()
+        {
+            IElement element;
+            MediaWikiLink link;
+            MediaWikiRedirectParser parser = new MediaWikiRedirectParser(new MockFactory().GetMediaWiki("en"));
+
+            // 通常のリダイレクト
+            Assert.IsTrue(parser.TryParse("#redirect [[Test]]", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiLink), element);
+            link = (MediaWikiLink) element;
+            Assert.AreEqual("Test", link.Title);
+            Assert.IsNull(link.Section);
+
+            // セクション指定付きのリダイレクト
+            Assert.IsTrue(parser.TryParse("#redirect [[Test#Section]]", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiLink), element);
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("Test", link.Title);
+            Assert.AreEqual("Section", link.Section);
+
+            // 普通の記事
+            Assert.IsFalse(parser.TryParse("'''Example''' may refer to:", out element));
+            Assert.IsNull(element);
+
+            // enで日本語の転送書式
+            Assert.IsFalse(parser.TryParse("#転送 [[Test]]", out element));
+            Assert.IsNull(element);
+
+            // jaで日本語の転送書式
+            parser = new MediaWikiRedirectParser(new MockFactory().GetMediaWiki("ja"));
+            Assert.IsTrue(parser.TryParse("#転送 [[Test]]", out element));
+            Assert.IsInstanceOf(typeof(MediaWikiLink), element);
+            link = (MediaWikiLink)element;
+            Assert.AreEqual("Test", link.Title);
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiTemplateParserTest.cs b/WptscsTest/Parsers/MediaWikiTemplateParserTest.cs
new file mode 100644 (file)
index 0000000..b101d2f
--- /dev/null
@@ -0,0 +1,268 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiTemplateParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiTemplateParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiTemplateParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiTemplateParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース(基本的な構文)。
+        /// </summary>
+        [Test]
+        public void TestTryParseBasic()
+        {
+            IElement element;
+            MediaWikiTemplate template;
+            MediaWikiTemplateParser parser = new MediaWikiTemplateParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // タイトルのみ
+            Assert.IsTrue(parser.TryParse("{{testtitle}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle", template.Title);
+            Assert.AreEqual(0, template.PipeTexts.Count);
+            Assert.IsNull(template.Code);
+            Assert.IsFalse(template.IsColon);
+            Assert.IsFalse(template.IsMsgnw);
+            Assert.IsFalse(template.NewLine);
+
+            // テンプレートではセクションは無いはず
+            Assert.IsTrue(parser.TryParse("{{testtitle#testsection}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle#testsection", template.Title);
+            Assert.IsNull(template.Section);
+            Assert.AreEqual(0, template.PipeTexts.Count);
+
+            // タイトルとパイプ後の文字列
+            Assert.IsTrue(parser.TryParse("{{testtitle|testpipe1|testpipe2}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle", template.Title);
+            Assert.AreEqual(2, template.PipeTexts.Count);
+            Assert.AreEqual("testpipe1", template.PipeTexts[0].ToString());
+            Assert.AreEqual("testpipe2", template.PipeTexts[1].ToString());
+
+            // タイトルに名前空間まで指定されている場合
+            // ※ テンプレートでは言語間リンク等は無いはず
+            Assert.IsTrue(parser.TryParse("{{Template:testtitle}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("Template:testtitle", template.Title);
+            Assert.AreEqual(0, template.PipeTexts.Count);
+
+            // 先頭のコロンは認識する
+            Assert.IsTrue(parser.TryParse("{{:Template:testtitle}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("Template:testtitle", template.Title);
+            Assert.AreEqual(0, template.PipeTexts.Count);
+            Assert.IsTrue(template.IsColon);
+
+            // msgnw(テンプレートのソースを出力する指定)は特別扱い
+            Assert.IsTrue(parser.TryParse("{{msgnw:testtitle}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle", template.Title);
+            Assert.AreEqual(0, template.PipeTexts.Count);
+            Assert.IsTrue(template.IsMsgnw);
+
+            // テンプレートでは、こんな風に改行が含まれるケースも多々存在
+            Assert.IsTrue(parser.TryParse("{{testtitle\n|testpipe1\n|testpipe2\n}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle", template.Title);
+            Assert.AreEqual(2, template.PipeTexts.Count);
+            Assert.AreEqual("testpipe1\n", template.PipeTexts[0].ToString());
+            Assert.AreEqual("testpipe2\n", template.PipeTexts[1].ToString());
+            Assert.IsTrue(template.NewLine);
+
+            // コメントはどこにあってもOK
+            Assert.IsTrue(parser.TryParse("{{Cite web<!--newsの方がよいか?-->|url=http://www.example.com|title=test|publisher=[[company]]<!--|date=2011-08-05-->|accessdate=2011-11-10}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("Cite web<!--newsの方がよいか?-->", template.Title);
+            Assert.AreEqual(4, template.PipeTexts.Count);
+            Assert.AreEqual("url=http://www.example.com", template.PipeTexts[0].ToString());
+            Assert.AreEqual("title=test", template.PipeTexts[1].ToString());
+            Assert.AreEqual("publisher=[[company]]<!--|date=2011-08-05-->", template.PipeTexts[2].ToString());
+            Assert.AreEqual("accessdate=2011-11-10", template.PipeTexts[3].ToString());
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(NGパターン)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNg()
+        {
+            IElement element;
+            MediaWikiTemplateParser parser = new MediaWikiTemplateParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 開始タグが無い
+            Assert.IsFalse(parser.TryParse("testtitle}}", out element));
+
+            // 閉じタグが無い
+            Assert.IsFalse(parser.TryParse("{{testtitle", out element));
+
+            // 先頭が開始タグではない
+            Assert.IsFalse(parser.TryParse(" {{testtitle}}", out element));
+
+            // 外部リンクタグ
+            Assert.IsFalse(parser.TryParse("[testtitle]", out element));
+
+            // 内部リンクタグ
+            Assert.IsFalse(parser.TryParse("[[testtitle]]", out element));
+
+            // 無理な位置のコメント(2012年1月現在、内部リンクはこれでもMediaWikiに認識されたがテンプレートはNG)
+            Assert.IsFalse(parser.TryParse("{<!--test-->{テンプレート}}", out element));
+
+            // テンプレート名の部分に < > [ ] { } のいずれかの文字が存在する場合、NG
+            // ※ コメントや変数であれば可能、それ以外で存在するのがNG
+            Assert.IsFalse(parser.TryParse("{{test<title}}", out element));
+            Assert.IsFalse(parser.TryParse("{{test>title}}", out element));
+            Assert.IsFalse(parser.TryParse("{{test[title}}", out element));
+            Assert.IsFalse(parser.TryParse("{{test]title}}", out element));
+            Assert.IsFalse(parser.TryParse("{{test{title}}", out element));
+            Assert.IsFalse(parser.TryParse("{{test}title}}", out element));
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(入れ子)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNested()
+        {
+            IElement element;
+            MediaWikiTemplate template;
+            MediaWikiTemplateParser parser = new MediaWikiTemplateParser(new MediaWikiParser(new MockFactory().GetMediaWiki("ja")));
+
+            // 入れ子もあり
+            Assert.IsTrue(parser.TryParse("{{outertemplate|test=[[innerlink]]{{innertemplate}}}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("outertemplate", template.Title);
+            Assert.AreEqual(1, template.PipeTexts.Count);
+            Assert.AreEqual("test=[[innerlink]]{{innertemplate}}", template.PipeTexts[0].ToString());
+            Assert.IsInstanceOf(typeof(ListElement), template.PipeTexts[0]);
+            ListElement list = ((ListElement)template.PipeTexts[0]);
+            Assert.AreEqual(3, list.Count);
+            Assert.AreEqual("test=", list[0].ToString());
+            Assert.AreEqual("[[innerlink]]", list[1].ToString());
+            Assert.AreEqual("{{innertemplate}}", list[2].ToString());
+
+            // 変数の場合、テンプレート名の部分にも入れられる
+            Assert.IsTrue(parser.TryParse("{{{{{title|デフォルトジャンル}}}メニュー}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("{{{title|デフォルトジャンル}}}メニュー", template.Title);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(サブページ)。
+        /// </summary>
+        [Test]
+        public void TestTryParseSubpage()
+        {
+            IElement element;
+            MediaWikiTemplate template;
+            MediaWikiTemplateParser parser = new MediaWikiTemplateParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 全て指定されているケースは通常の記事と同じ扱い
+            Assert.IsTrue(parser.TryParse("{{testtitle/subpage}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("testtitle/subpage", template.Title);
+            Assert.IsFalse(template.IsSubpage);
+
+            // 記事名が省略されているケース
+            Assert.IsTrue(parser.TryParse("{{/subpage}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("/subpage", template.Title);
+            Assert.IsTrue(template.IsSubpage);
+
+            // 記事名が省略されているケース2
+            // TODO: サブページの相対パスは2012年1月現在未対応、対応するなら方法から要検討
+            Assert.IsTrue(parser.TryParse("{{../../subpage}}", out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("../../subpage", template.Title);
+            Assert.IsFalse(template.IsSubpage);
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(実データ複雑なinfobox)。
+        /// </summary>
+        [Test]
+        public void TestTryParseInfoboxTvChannel()
+        {
+            IElement element;
+            MediaWikiTemplate template;
+            MediaWikiTemplateParser parser = new MediaWikiTemplateParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 全て指定されているケースは通常の記事と同じ扱い
+            // ※ 以下、[[:en:Discovery Channel]](2012年1月17日 14:07:11(UTC))より
+            Assert.IsTrue(parser.TryParse(
+                "{{Infobox TV channel\n"
+                + "| name             = '''Discovery Channel'''\n"
+                + "| logofile         = Discovery Channel International.svg\n"
+                + "| logosize         = 220px\n"
+                + "| slogan           = ''The world is just awesome.''\n"
+                + "| launch           = June 17, 1985&lt;ref name=&quot;oscars.org&quot;&gt;{{cite web |url=http://www.oscars.org/awards/academyawards/legacy/ceremony/59th-winners.html |title=The 59th Academy Awards (1987) Nominees and Winners |accessdate=2011-07-23|work=oscars.org}}&lt;/ref&gt;\n"
+                + "| owner            = [[Discovery Communications|Discovery Communications, Inc.]]\n"
+                + "| CEO              = David Zaslav\n"
+                + "| headquarters     = [[Silver Spring, Maryland]]\n"
+                + "| country          = Worldwide\n"
+                + "| language         = English\n"
+                + "| sister names     = [[TLC (TV channel)|TLC]]&lt;br&gt;[[Animal Planet]]&lt;br&gt;[[OWN: Oprah Winfrey Network]]&lt;br&gt;[[Planet Green]]&lt;br&gt;[[Investigation Discovery]]&lt;br&gt;[[Discovery Fit &amp; Health]]&lt;br&gt;[[Military Channel]]&lt;br&gt;[[Science (TV channel)|Science]]&lt;br&gt;[[Velocity (TV channel)|Velocity]]\n"
+                + "| web              = http://www.discovery.com\n"
+                + "| picture format   = [[480i]] ([[SDTV]])&lt;br&gt;[[1080i]] ([[HDTV]])\n"
+                + "| terr serv 1 = [[Selective TV Inc.]]&lt;br/&gt;'''([[Alexandria, Minnesota]])'''\n"
+                + "| terr chan 1 = K47KZ (Channel 47)\n"
+                + "| sat serv 1 = [[DirecTV]]\n"
+                + "| sat chan 1 = Channel 278&lt;br&gt; Channel 1278 (VOD)\n"
+                + "| sat serv 2 = [[Dish Network]]\n"
+                + "| sat chan 2 = Channel 182 (SD/HD)&lt;br&gt; Channel 9487\n"
+                + "| sat serv 3 = [[C-Band]]\n"
+                + "| sat chan 3 = AMC 10-Channel 21\n"
+                + "| sat serv 7 = [[SKY México]]\n"
+                + "| sat chan 7 = Channel 251\n"
+                + "| sat serv 8 = Dish Network Mexico\n"
+                + "| sat chan 8 = Channel 402\n"
+                + "| sat serv 9 = [[Sky (UK and Ireland)]]\n"
+                + "| sat chan 9 = Channel 520\n"
+                + "| cable serv 1 = CableVision (Argentina)\n"
+                + "| cable chan 1 = Channel 52\n"
+                + "| cable serv 2 = Available on most cable systems\n"
+                + "| cable chan 2 = Check your local listings\n"
+                + "| cable serv 3 = [[Verizon FiOS]] \n"
+                + "| cable chan 3 = Channel 120 (SD)&lt;br&gt;Channel 620 (HD)\n"
+                + "| adsl serv 1 =[[Sky Angel]]\n"
+                + "| adsl chan 1 =Channel 313\n"
+                + "| adsl serv 2 =[[AT&amp;T U-Verse]]\n"
+                + "| adsl chan 2 =Channel 120 (SD)&lt;br&gt; 1120 (HD)\n"
+                + "}}\n\n"
+                + "'''Discovery Channel''' (formerly '''The Discovery Channel''') is an American [[satellite]] and [[cable]] [[specialty channel]] (also delivered via [[IPTV]], [[terrestrial television]] and [[internet television]] in other parts of the world), founded by [[John Hendricks]] and distributed by [[Discovery Communications]].",
+                out element));
+            template = (MediaWikiTemplate)element;
+            Assert.AreEqual("Infobox TV channel", template.Title);
+            Assert.AreEqual(37, template.PipeTexts.Count);
+            Assert.AreEqual(" terr serv 1 = [[Selective TV Inc.]]&lt;br/&gt;'''([[Alexandria, Minnesota]])'''\n", template.PipeTexts[13].ToString());
+            Assert.AreEqual(" adsl chan 2 =Channel 120 (SD)&lt;br&gt; 1120 (HD)\n", template.PipeTexts[36].ToString());
+            Assert.IsNull(template.Code);
+            Assert.IsFalse(template.IsColon);
+            Assert.IsFalse(template.IsMsgnw);
+            Assert.IsTrue(template.NewLine);
+
+            // TODO: パイプ後の部分を入れ子も含めてちゃんと動いているかもうちょい検証する
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiTemplateTest.cs b/WptscsTest/Parsers/MediaWikiTemplateTest.cs
new file mode 100644 (file)
index 0000000..28c45f9
--- /dev/null
@@ -0,0 +1,154 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiTemplateのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiTemplateTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+
+    /// <summary>
+    /// MediaWikiTemplateのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiTemplateTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+            Assert.AreEqual("テンプレート名", element.Title);
+            Assert.AreEqual(0, element.PipeTexts.Count);
+        }
+
+        /// <summary>
+        /// コンストラクタテストケース(null)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentNullException))]
+        public void TestConstructorNull()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate(null);
+        }
+
+        /// <summary>
+        /// コンストラクタテストケース(空白)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentException))]
+        public void TestConstructorBlank()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate(" ");
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Titleプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestTitle()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+
+            Assert.AreEqual("テンプレート名", element.Title);
+            element.Title = "test";
+            Assert.AreEqual("test", element.Title);
+        }
+
+        /// <summary>
+        /// Titleプロパティテストケース(空白)。
+        /// </summary>
+        [Test]
+        [ExpectedException(typeof(ArgumentException))]
+        public void TestTitleBlank()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+            element.Title = " ";
+        }
+
+        /// <summary>
+        /// IsMsgnwプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestIsMsgnw()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+
+            Assert.IsFalse(element.IsMsgnw);
+            element.IsMsgnw = true;
+            Assert.IsTrue(element.IsMsgnw);
+            element.IsMsgnw = false;
+            Assert.IsFalse(element.IsMsgnw);
+        }
+
+        /// <summary>
+        /// NewLineプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestNewLine()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+
+            Assert.IsFalse(element.NewLine);
+            element.NewLine = true;
+            Assert.IsTrue(element.NewLine);
+            element.NewLine = false;
+            Assert.IsFalse(element.NewLine);
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            MediaWikiTemplate element = new MediaWikiTemplate("テンプレート名");
+
+            // タイトルのみ
+            Assert.AreEqual("{{テンプレート名}}", element.ToString());
+
+            // タイトルとパイプ後の文字列
+            element.PipeTexts.Add(new TextElement("パラメータ1"));
+            element.PipeTexts.Add(new TextElement("パラメータ2"));
+            Assert.AreEqual("{{テンプレート名|パラメータ1|パラメータ2}}", element.ToString());
+
+            // msgnw: の付加
+            element.IsMsgnw = true;
+            Assert.AreEqual("{{msgnw:テンプレート名|パラメータ1|パラメータ2}}", element.ToString());
+
+            // : の付加
+            element.IsColon = true;
+            Assert.AreEqual("{{:msgnw:テンプレート名|パラメータ1|パラメータ2}}", element.ToString());
+            element.IsMsgnw = false;
+            Assert.AreEqual("{{:テンプレート名|パラメータ1|パラメータ2}}", element.ToString());
+
+            // 改行の付加
+            // ※ テンプレート名直後以外のものは、普通に文字列中に格納される
+            element.NewLine = true;
+            Assert.AreEqual("{{:テンプレート名\n|パラメータ1|パラメータ2}}", element.ToString());
+            element.IsColon = false;
+            Assert.AreEqual("{{テンプレート名\n|パラメータ1|パラメータ2}}", element.ToString());
+        }
+
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiVariableParserTest.cs b/WptscsTest/Parsers/MediaWikiVariableParserTest.cs
new file mode 100644 (file)
index 0000000..413fc17
--- /dev/null
@@ -0,0 +1,117 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiVariableParserのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiVariableParserTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+    using Honememo.Wptscs.Models;
+    using Honememo.Wptscs.Websites;
+
+    /// <summary>
+    /// MediaWikiVariableParserのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiVariableParserTest
+    {
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// TryParseメソッドテストケース(基本的な構文)。
+        /// </summary>
+        [Test]
+        public void TestTryParseBasic()
+        {
+            IElement element;
+            MediaWikiVariable variable;
+            MediaWikiVariableParser parser = new MediaWikiVariableParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 変数のみ
+            Assert.IsTrue(parser.TryParse("{{{変数名}}}", out element));
+            variable = (MediaWikiVariable)element;
+            Assert.AreEqual("変数名", variable.Variable);
+            Assert.IsNull(variable.Value);
+
+            // タイトルとパイプ後の文字列
+            Assert.IsTrue(parser.TryParse("{{{変数名|デフォルト値}}}", out element));
+            variable = (MediaWikiVariable)element;
+            Assert.AreEqual("変数名", variable.Variable);
+            Assert.AreEqual("デフォルト値", variable.Value.ToString());
+
+            // よく見かけるパイプがあって後ろが無い奴
+            Assert.IsTrue(parser.TryParse("{{{変数名|}}}", out element));
+            variable = (MediaWikiVariable)element;
+            Assert.AreEqual("変数名", variable.Variable);
+            Assert.IsNotNull(variable.Value);
+            Assert.IsEmpty(variable.Value.ToString());
+
+            // コメントについてはあっても特に問題ない
+            Assert.IsTrue(parser.TryParse("{{{変数名<!--必要に応じて変更1-->|デフォルト値<!--必要に応じて変更2-->}}}", out element));
+            variable = (MediaWikiVariable)element;
+            Assert.AreEqual("変数名<!--必要に応じて変更1-->", variable.Variable);
+            Assert.AreEqual("デフォルト値<!--必要に応じて変更2-->", variable.Value.ToString());
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(NGパターン)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNg()
+        {
+            IElement element;
+            MediaWikiVariableParser parser = new MediaWikiVariableParser(new MediaWikiParser(new MockFactory().GetMediaWiki("en")));
+
+            // 開始タグが無い
+            Assert.IsFalse(parser.TryParse("変数名}}}", out element));
+
+            // 閉じタグが無い
+            Assert.IsFalse(parser.TryParse("{{{変数名", out element));
+
+            // 先頭が開始タグではない
+            Assert.IsFalse(parser.TryParse(" {{{変数名}}}", out element));
+
+            // 外部リンクタグ
+            Assert.IsFalse(parser.TryParse("[変数名]", out element));
+
+            // 内部リンクタグ
+            Assert.IsFalse(parser.TryParse("[[変数名]]", out element));
+
+            // テンプレートリンクタグ
+            Assert.IsFalse(parser.TryParse("{{変数名}}", out element));
+        }
+
+        /// <summary>
+        /// TryParseメソッドテストケース(入れ子)。
+        /// </summary>
+        [Test]
+        public void TestTryParseNested()
+        {
+            IElement element;
+            MediaWikiVariable variable;
+            MediaWikiVariableParser parser = new MediaWikiVariableParser(new MediaWikiParser(new MockFactory().GetMediaWiki("ja")));
+
+            // 入れ子もあり
+            Assert.IsTrue(parser.TryParse("{{{変数名|[[内部リンク]]{{ref-en}}}}}", out element));
+            variable = (MediaWikiVariable)element;
+            Assert.AreEqual("変数名", variable.Variable);
+            Assert.AreEqual("[[内部リンク]]{{ref-en}}", variable.Value.ToString());
+            Assert.IsInstanceOf(typeof(ListElement), variable.Value);
+            ListElement list = ((ListElement)variable.Value);
+            Assert.AreEqual(2, list.Count);
+            Assert.IsInstanceOf(typeof(MediaWikiLink), list[0]);
+            Assert.AreEqual("[[内部リンク]]", list[0].ToString());
+            Assert.IsInstanceOf(typeof(MediaWikiTemplate), list[1]);
+            Assert.AreEqual("{{ref-en}}", list[1].ToString());
+        }
+        
+        #endregion
+    }
+}
diff --git a/WptscsTest/Parsers/MediaWikiVariableTest.cs b/WptscsTest/Parsers/MediaWikiVariableTest.cs
new file mode 100644 (file)
index 0000000..1bcbf83
--- /dev/null
@@ -0,0 +1,112 @@
+// ================================================================================================
+// <summary>
+//      MediaWikiVariableのテストクラスソース。</summary>
+//
+// <copyright file="MediaWikiVariableTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Parsers
+{
+    using System;
+    using NUnit.Framework;
+    using Honememo.Parsers;
+
+    /// <summary>
+    /// MediaWikiVariableのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class MediaWikiVariableTest
+    {
+        #region コンストラクタテストケース
+
+        /// <summary>
+        /// コンストラクタテストケース。
+        /// </summary>
+        [Test]
+        public void TestConstructor()
+        {
+            MediaWikiVariable element;
+            IElement value;
+
+            element = new MediaWikiVariable(null);
+            Assert.IsNull(element.Variable);
+            Assert.IsNull(element.Value);
+
+            element = new MediaWikiVariable("変数名1");
+            Assert.AreEqual("変数名1", element.Variable);
+            Assert.IsNull(element.Value);
+
+            value = new TextElement("値");
+            element = new MediaWikiVariable("変数名2", value);
+            Assert.AreEqual("変数名2", element.Variable);
+            Assert.AreSame(value, element.Value);
+        }
+
+        #endregion
+
+        #region プロパティテストケース
+
+        /// <summary>
+        /// Variableプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestVariable()
+        {
+            MediaWikiVariable element = new MediaWikiVariable("変数名");
+
+            Assert.AreEqual("変数名", element.Variable);
+            element.Variable = "test";
+            Assert.AreEqual("test", element.Variable);
+            element.Variable = null;
+            Assert.IsNull(element.Variable);
+        }
+
+        /// <summary>
+        /// Valueプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestValue()
+        {
+            MediaWikiVariable element = new MediaWikiVariable("変数名");
+            IElement value = new TextElement("値");
+
+            Assert.IsNull(element.Value);
+            element.Value = value;
+            Assert.AreSame(value, element.Value);
+            element.Value = null;
+            Assert.IsNull(element.Value);
+        }
+
+        #endregion
+        
+        #region インタフェース実装メソッドテストケース
+
+        /// <summary>
+        /// ToStringメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestToString()
+        {
+            MediaWikiVariable element = new MediaWikiVariable("変数名");
+
+            // 変数名のみ
+            Assert.AreEqual("{{{変数名}}}", element.ToString());
+
+            // 単純な値
+            element.Value = new TextElement("値");
+            Assert.AreEqual("{{{変数名|値}}}", element.ToString());
+
+            // 複雑な値
+            ListElement list = new ListElement();
+            list.Add(new MediaWikiLink("記事名"));
+            list.Add(new MediaWikiTemplate("テンプレート名"));
+            element.Value = list;
+            Assert.AreEqual("{{{変数名|[[記事名]]{{テンプレート名}}}}}", element.ToString());
+        }
+
+        #endregion
+    }
+}
index 8c11f7f..3012a86 100644 (file)
@@ -3,7 +3,7 @@
 //      NUnitテスト支援用クラスソース。</summary>
 //
 // <copyright file="PrivateAccessor.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
index 32aa47b..605ee85 100644 (file)
@@ -1,4 +1,14 @@
-using System.Reflection;
+// ================================================================================================
+// <summary>
+//      Wikipedia翻訳支援ツール(テスト)のアセンブリソース</summary>
+//
+// <copyright file="AssemblyInfo.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
@@ -10,7 +20,7 @@ using System.Runtime.InteropServices;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("")]
 [assembly: AssemblyProduct("Wikipedia 翻訳支援ツール")]
-[assembly: AssemblyCopyright("Copyright (C) Honeplus 2010")]
+[assembly: AssemblyCopyright("Copyright (C) Honeplus 2012")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 
@@ -32,4 +42,4 @@ using System.Runtime.InteropServices;
 // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 
 // 既定値にすることができます:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.80.*")]
+[assembly: AssemblyVersion("1.10.*")]
diff --git a/WptscsTest/Utilities/AppDefaultWebProxyTest.cs b/WptscsTest/Utilities/AppDefaultWebProxyTest.cs
new file mode 100644 (file)
index 0000000..5c1be0b
--- /dev/null
@@ -0,0 +1,126 @@
+// ================================================================================================
+// <summary>
+//      AppDefaultWebProxyのテストクラスソース。</summary>
+//
+// <copyright file="AppDefaultWebProxyTest.cs" company="honeplusのメモ帳">
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
+// <author>
+//      Honeplus</author>
+// ================================================================================================
+
+namespace Honememo.Wptscs.Utilities
+{
+    using System;
+    using System.IO;
+    using NUnit.Framework;
+    using Honememo.Wptscs.Utilities;
+    using Honememo.Wptscs.Properties;
+
+    /// <summary>
+    /// AppDefaultWebProxyのテストクラスです。
+    /// </summary>
+    [TestFixture]
+    public class AppDefaultWebProxyTest
+    {
+        #region 定数
+
+        /// <summary>
+        /// テストデータが格納されているフォルダパス。
+        /// </summary>
+        private static readonly string testFile = "Data\\config.xml";
+
+        #endregion
+
+        #region インタフェース実装プロパティテストケース
+
+        /// <summary>
+        /// UserAgentプロパティテストケース。
+        /// </summary>
+        /// <remarks>アプリ設定部分はアクセス権の関係上試験できず。</remarks>
+        [Test]
+        public void TestUserAgent()
+        {
+            IWebProxy proxy = new AppDefaultWebProxy();
+            //Settings.Default.UserAgent = String.Empty;
+
+            // 初期状態ではアプリ名を元に生成した値
+            Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
+            //Assert.AreEqual(
+            //    String.Format(Settings.Default.DefaultUserAgent, ver.Major, ver.Minor),
+            //    proxy.UserAgent);
+            Assert.AreEqual(
+                String.Format("WikipediaTranslationSupportTool/{0}.{1:D2}", ver.Major, ver.Minor),
+                proxy.UserAgent);
+
+            // アプリ設定時はアプリに格納された設定値
+            //Settings.Default.UserAgent = "test setting useragent";
+            //Assert.AreEqual("test setting useragent", proxy.UserAgent);
+
+            // プロパティ設定時はそれが最優先
+            proxy.UserAgent = "test property useragent";
+            Assert.AreEqual("test property useragent", proxy.UserAgent);
+
+            // 空でも有効
+            proxy.UserAgent = String.Empty;
+            Assert.IsEmpty(proxy.UserAgent);
+
+            // nullなら無効
+            proxy.UserAgent = null;
+            //Assert.AreEqual("test setting useragent", proxy.UserAgent);
+            Assert.IsNotEmpty(proxy.UserAgent);
+        }
+
+        /// <summary>
+        /// Refererプロパティテストケース。
+        /// </summary>
+        /// <remarks>アプリ設定部分はアクセス権の関係上試験できず。</remarks>
+        [Test]
+        public void TestReferer()
+        {
+            IWebProxy proxy = new AppDefaultWebProxy();
+            //Settings.Default.Referer = String.Empty;
+
+            // 初期状態では空
+            Assert.IsEmpty(proxy.Referer);
+
+            // アプリ設定時はアプリに格納された設定値
+            //Settings.Default.Referer = "test setting referer";
+            //Assert.AreEqual("test setting referer", proxy.Referer);
+
+            // プロパティ設定時はそちらが優先
+            proxy.Referer = "test property referer";
+            Assert.AreEqual("test property referer", proxy.Referer);
+
+            // 空でも有効
+            proxy.Referer = String.Empty;
+            Assert.IsEmpty(proxy.Referer);
+
+            // nullなら無効
+            //proxy.Referer = null;
+            //Assert.AreEqual("test setting referer", proxy.Referer);
+        }
+
+        #endregion
+
+        #region インタフェース実装メソッドテストケース
+        
+        /// <summary>
+        /// GetStreamメソッドテストケース。
+        /// </summary>
+        /// <remarks>内容的に難しいため、fileプロトコルのみ確認。</remarks>
+        [Test]
+        public void TestGetStream()
+        {
+            IWebProxy proxy = new AppDefaultWebProxy();
+
+            // テストファイルを読んで例外が発生しなければOKとする
+            UriBuilder b = new UriBuilder("file", "");
+            b.Path = Path.GetFullPath(testFile);
+            using (proxy.GetStream(b.Uri))
+            {
+            }
+        }
+
+        #endregion
+    }
+}
index 81b3613..551c00b 100644 (file)
@@ -3,15 +3,16 @@
 //      FormUtilsのテストクラスソース。</summary>
 //
 // <copyright file="FormUtilsTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Utilities
+namespace Honememo.Wptscs.Utilities
 {
     using System;
     using System.Windows.Forms;
+    using Honememo.Wptscs.Utilities;
     using NUnit.Framework;
 
     /// <summary>
@@ -24,6 +25,8 @@ namespace Honememo.Utilities
     [TestFixture]
     public class FormUtilsTest
     {
+        #region モッククラス
+
         /// <summary>
         /// Websiteテスト用のモッククラスです。
         /// </summary>
@@ -31,8 +34,10 @@ namespace Honememo.Utilities
         {
         }
 
+        #endregion
+
         #region リソース関連テストケース
-        
+
         /// <summary>
         /// ReplaceInvalidFileNameCharsメソッドテストケース。
         /// </summary>
@@ -55,6 +60,10 @@ namespace Honememo.Utilities
             Assert.AreEqual("C__test_test.doc", FormUtils.ReplaceInvalidFileNameChars("C:\\test\\test.doc"));
             Assert.AreEqual("_home_test_test.doc", FormUtils.ReplaceInvalidFileNameChars("/home/test/test.doc"));
             Assert.AreEqual("______", FormUtils.ReplaceInvalidFileNameChars("*?\"<>|"));
+
+            // 一見普通のファイル名に見えるが、&nbsp;由来の半角スペース (u00a0) が含まれており問題を起こす
+            // 通常の半角スペース (u0020) に変換する
+            Assert.AreEqual("Fuji (Spacecraft).xml", FormUtils.ReplaceInvalidFileNameChars("Fuji (Spacecraft).xml"));
         }
 
         #endregion
diff --git a/WptscsTest/Utilities/LazyXmlParserTest.cs b/WptscsTest/Utilities/LazyXmlParserTest.cs
deleted file mode 100644 (file)
index 7dfc711..0000000
+++ /dev/null
@@ -1,334 +0,0 @@
-// ================================================================================================
-// <summary>
-//      LazyXmlParserのテストクラスソース。</summary>
-//
-// <copyright file="LazyXmlParserTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
-// <author>
-//      Honeplus</author>
-// ================================================================================================
-
-namespace Honememo.Utilities
-{
-    using System;
-    using NUnit.Framework;
-
-    /// <summary>
-    /// LazyXmlParserのテストクラスです。
-    /// </summary>
-    [TestFixture]
-    public class LazyXmlParserTest
-    {
-        #region 静的メソッドテストケース
-
-        /// <summary>
-        /// TryParseCommentメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestTryParseComment()
-        {
-            string comment;
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!--test-->", out comment));
-            Assert.AreEqual("<!--test-->", comment);
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!-- test -->", out comment));
-            Assert.AreEqual("<!-- test -->", comment);
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!--test-->-->", out comment));
-            Assert.AreEqual("<!--test-->", comment);
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!--test--", out comment));
-            Assert.AreEqual("<!--test--", comment);
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!--->", out comment));
-            Assert.AreEqual("<!--->", comment);
-            Assert.IsTrue(LazyXmlParser.TryParseComment("<!--\n\ntest\r\n-->", out comment));
-            Assert.AreEqual("<!--\n\ntest\r\n-->", comment);
-            Assert.IsFalse(LazyXmlParser.TryParseComment("<--test-->", out comment));
-            Assert.IsNull(comment);
-            Assert.IsFalse(LazyXmlParser.TryParseComment("<%--test--%>", out comment));
-            Assert.IsNull(comment);
-            Assert.IsFalse(LazyXmlParser.TryParseComment("<! --test-->", out comment));
-            Assert.IsNull(comment);
-        }
-
-        #endregion
-
-        #region 公開メソッドテストケース
-        
-        /// <summary>
-        /// TryParseメソッドテストケース(実例)。
-        /// </summary>
-        [Test]
-        public void TestTryParse()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsTrue(parser.TryParse("<h1>test</h1>", out element));
-            Assert.AreEqual("<h1>test</h1>", element.OuterXml);
-            Assert.AreEqual("h1", element.Name);
-            Assert.AreEqual("test", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<br /><br />test<br />", out element));
-            Assert.AreEqual("<br />", element.OuterXml);
-            Assert.AreEqual("br", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<div id=\"testid\" name=\"testname\"><!--<div id=\"indiv\">test</div>--></div><br />", out element));
-            Assert.AreEqual("<div id=\"testid\" name=\"testname\"><!--<div id=\"indiv\">test</div>--></div>", element.OuterXml);
-            Assert.AreEqual("div", element.Name);
-            Assert.AreEqual("<!--<div id=\"indiv\">test</div>-->", element.InnerXml);
-            Assert.AreEqual(2, element.Attributes.Count);
-            Assert.AreEqual("testid", element.GetAttribute("id"));
-            Assert.AreEqual("testname", element.GetAttribute("name"));
-
-            parser.IsHtml = true;
-            Assert.IsTrue(parser.TryParse("<p>段落1<p>段落2", out element));
-            Assert.AreEqual("<p>", element.OuterXml);
-            Assert.AreEqual("p", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<input type=\"checkbox\" name=\"param\" value=\"test\" checked><label for=\"param\">チェック</label>", out element));
-            Assert.AreEqual("<input type=\"checkbox\" name=\"param\" value=\"test\" checked>", element.OuterXml);
-            Assert.AreEqual("input", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(4, element.Attributes.Count);
-            Assert.AreEqual("checkbox", element.GetAttribute("type"));
-            Assert.AreEqual("param", element.GetAttribute("name"));
-            Assert.AreEqual("test", element.GetAttribute("value"));
-            Assert.IsEmpty(element.GetAttribute("checked"));
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(基本形)。
-        /// </summary>
-        [Test]
-        public void TestTryParseNormal()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsTrue(parser.TryParse("<testtag></testtag>", out element));
-            Assert.AreEqual("<testtag></testtag>", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag2>test value</testtag2>", out element));
-            Assert.AreEqual("<testtag2>test value</testtag2>", element.OuterXml);
-            Assert.AreEqual("testtag2", element.Name);
-            Assert.AreEqual("test value", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag3> test value2 </testtag3>testend", out element));
-            Assert.AreEqual("<testtag3> test value2 </testtag3>", element.OuterXml);
-            Assert.AreEqual("testtag3", element.Name);
-            Assert.AreEqual(" test value2 ", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag4 testattr=\"testvalue\"> test<!-- </testtag4> --> value3 </testtag4>testend", out element));
-            Assert.AreEqual("<testtag4 testattr=\"testvalue\"> test<!-- </testtag4> --> value3 </testtag4>", element.OuterXml);
-            Assert.AreEqual("testtag4", element.Name);
-            Assert.AreEqual(" test<!-- </testtag4> --> value3 ", element.InnerXml);
-            Assert.AreEqual(1, element.Attributes.Count);
-            Assert.AreEqual("testvalue", element.GetAttribute("testattr"));
-            
-            Assert.IsTrue(parser.TryParse("<testtag5 testattr2='testvalue2'><testbody></testtag5 >testend", out element));
-            Assert.AreEqual("<testtag5 testattr2='testvalue2'><testbody></testtag5 >", element.OuterXml);
-            Assert.AreEqual("testtag5", element.Name);
-            Assert.AreEqual("<testbody>", element.InnerXml);
-            Assert.AreEqual(1, element.Attributes.Count);
-            Assert.AreEqual("testvalue2", element.GetAttribute("testattr2"));
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(普通でNGパターン)。
-        /// </summary>
-        [Test]
-        public void TestTryParseNormalNg()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsFalse(parser.TryParse(" <testtag></testtag>", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<!-- comment -->", out element));
-            Assert.IsNull(element);
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(単一のパターン)。
-        /// </summary>
-        [Test]
-        public void TestTryParseSingle()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsTrue(parser.TryParse("<testtag />", out element));
-            Assert.AreEqual("<testtag />", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag2/>", out element));
-            Assert.AreEqual("<testtag2/>", element.OuterXml);
-            Assert.AreEqual("testtag2", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag3   />testtag4 />", out element));
-            Assert.AreEqual("<testtag3   />", element.OuterXml);
-            Assert.AreEqual("testtag3", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag5 testattr=\"testvalue\" />/>", out element));
-            Assert.AreEqual("<testtag5 testattr=\"testvalue\" />", element.OuterXml);
-            Assert.AreEqual("testtag5", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(1, element.Attributes.Count);
-            Assert.AreEqual("testvalue", element.GetAttribute("testattr"));
-
-            Assert.IsTrue(parser.TryParse("<testtag6 testattr1=\"testvalue1\" testattr2=\"testvalue2\"/>/>", out element));
-            Assert.AreEqual("<testtag6 testattr1=\"testvalue1\" testattr2=\"testvalue2\"/>", element.OuterXml);
-            Assert.AreEqual("testtag6", element.Name);
-            Assert.AreEqual(2, element.Attributes.Count);
-            Assert.AreEqual("testvalue1", element.GetAttribute("testattr1"));
-            Assert.AreEqual("testvalue2", element.GetAttribute("testattr2"));
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(不正な構文)。
-        /// </summary>
-        [Test]
-        public void TestTryParseLazy()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsTrue(parser.TryParse("<p>", out element));
-            Assert.AreEqual("<p>", element.OuterXml);
-            Assert.AreEqual("p", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag>test value", out element));
-            Assert.AreEqual("<testtag>test value", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.AreEqual("test value", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag2 testattr=test value>test value</testtag2>", out element));
-            Assert.AreEqual("<testtag2 testattr=test value>test value</testtag2>", element.OuterXml);
-            Assert.AreEqual("testtag2", element.Name);
-            Assert.AreEqual("test value", element.InnerXml);
-            Assert.AreEqual(2, element.Attributes.Count);
-            Assert.AreEqual("test", element.GetAttribute("testattr"));
-            Assert.IsEmpty(element.GetAttribute("value"));
-
-            Assert.IsTrue(parser.TryParse("<testtag3>test value2</ testtag3>testend", out element));
-            Assert.AreEqual("<testtag3>test value2</ testtag3>testend", element.OuterXml);
-            Assert.AreEqual("testtag3", element.Name);
-            Assert.AreEqual("test value2</ testtag3>testend", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-        }
-
-
-        /// <summary>
-        /// TryParseメソッドテストケース(不正でNG)。
-        /// </summary>
-        [Test]
-        public void TestTryParseLazyNg()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser();
-            Assert.IsFalse(parser.TryParse("< testtag></testtag>", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<testtag", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<testtag ", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<testtag /", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<testtag </testtag>", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<testtag testattr=\"testvalue'></testtag>", out element));
-            Assert.IsNull(element);
-            Assert.IsFalse(parser.TryParse("<sub((>4</sub>", out element));
-            Assert.IsNull(element);
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(HTML)。
-        /// </summary>
-        [Test]
-        public void TestTryParseHtml()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser { IsHtml = true };
-            Assert.IsTrue(parser.TryParse("<testtag />", out element));
-            Assert.AreEqual("<testtag />", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<p>", out element));
-            Assert.AreEqual("<p>", element.OuterXml);
-            Assert.AreEqual("p", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag2 />testtag3 />", out element));
-            Assert.AreEqual("<testtag2 />", element.OuterXml);
-            Assert.AreEqual("testtag2", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag4></testtag5>", out element));
-            Assert.AreEqual("<testtag4>", element.OuterXml);
-            Assert.AreEqual("testtag4", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag6 testattr=\"testvalue\">>", out element));
-            Assert.AreEqual("<testtag6 testattr=\"testvalue\">", element.OuterXml);
-            Assert.AreEqual("testtag6", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(1, element.Attributes.Count);
-            Assert.AreEqual("testvalue", element.GetAttribute("testattr"));
-
-            Assert.IsTrue(parser.TryParse("<testtag7 testattr1=\"testvalue1\" testattr2=\"testvalue2\">test</testtag7>", out element));
-            Assert.AreEqual("<testtag7 testattr1=\"testvalue1\" testattr2=\"testvalue2\">test</testtag7>", element.OuterXml);
-            Assert.AreEqual("testtag7", element.Name);
-            Assert.AreEqual(2, element.Attributes.Count);
-            Assert.AreEqual("testvalue1", element.GetAttribute("testattr1"));
-            Assert.AreEqual("testvalue2", element.GetAttribute("testattr2"));
-        }
-
-        /// <summary>
-        /// TryParseメソッドテストケース(大文字小文字)。
-        /// </summary>
-        [Test]
-        public void TestTryParseIgnoreCase()
-        {
-            LazyXmlParser.SimpleElement element;
-            LazyXmlParser parser = new LazyXmlParser { IgnoreCase = false };
-            Assert.IsTrue(parser.TryParse("<testtag></testtag></Testtag>", out element));
-            Assert.AreEqual("<testtag></testtag>", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            Assert.IsTrue(parser.TryParse("<testtag></Testtag></testtag>", out element));
-            Assert.AreEqual("<testtag></Testtag></testtag>", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.AreEqual("</Testtag>", element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-
-            parser.IgnoreCase = true;
-            Assert.IsTrue(parser.TryParse("<testtag></Testtag></testtag>", out element));
-            Assert.AreEqual("<testtag></Testtag>", element.OuterXml);
-            Assert.AreEqual("testtag", element.Name);
-            Assert.IsEmpty(element.InnerXml);
-            Assert.AreEqual(0, element.Attributes.Count);
-        }
-
-        #endregion
-    }
-}
diff --git a/WptscsTest/Utilities/StringUtilsTest.cs b/WptscsTest/Utilities/StringUtilsTest.cs
deleted file mode 100644 (file)
index 24a39f1..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-// ================================================================================================
-// <summary>
-//      StringUtilsのテストクラスソース。</summary>
-//
-// <copyright file="StringUtilsTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
-// <author>
-//      Honeplus</author>
-// ================================================================================================
-
-namespace Honememo.Utilities
-{
-    using System;
-    using System.Text;
-    using NUnit.Framework;
-
-    /// <summary>
-    /// StringUtilsのテストクラスです。
-    /// </summary>
-    [TestFixture]
-    public class StringUtilsTest
-    {
-        #region 初期化メソッドテストケース
-
-        /// <summary>
-        /// DefaultStringメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestDefaultString()
-        {
-            // 引数一つ
-            Assert.AreEqual(String.Empty, StringUtils.DefaultString(null));
-            Assert.AreEqual(String.Empty, StringUtils.DefaultString(String.Empty));
-            Assert.AreEqual(" ", StringUtils.DefaultString(" "));
-            Assert.AreEqual("null以外の文字列", StringUtils.DefaultString("null以外の文字列"));
-
-            // 引数二つ
-            Assert.AreEqual("初期値", StringUtils.DefaultString(null, "初期値"));
-            Assert.AreEqual(String.Empty, StringUtils.DefaultString(String.Empty, "初期値"));
-            Assert.AreEqual(" ", StringUtils.DefaultString(" ", "初期値"));
-            Assert.AreEqual("null以外の文字列", StringUtils.DefaultString("null以外の文字列", "初期値"));
-        }
-
-        #endregion
-
-        #region 文字列チェックテストケース
-
-        /// <summary>
-        /// StartsWithメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestStartsWith()
-        {
-            // null
-            Assert.IsTrue(StringUtils.StartsWith(null, null, 3));
-            Assert.IsFalse(StringUtils.StartsWith(null, "", 2));
-            Assert.IsFalse(StringUtils.StartsWith("", null, 5));
-
-            // 空、文字数
-            Assert.IsFalse(StringUtils.StartsWith("", "", 0));
-            Assert.IsTrue(StringUtils.StartsWith("a", "", 0));
-            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "", 14));
-            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "", 15));
-            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "", -1));
-
-            // 通常
-            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "bc", 1));
-            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "ab", 1));
-            Assert.IsTrue(StringUtils.StartsWith("abcedf0123あいうえお", "あいうえお", 10));
-            Assert.IsFalse(StringUtils.StartsWith("abcedf0123あいうえお", "あいうえおか", 10));
-        }
-
-        /// <summary>
-        /// StartsWithメソッドテストケース(性能試験)。
-        /// </summary>
-        [Test, Timeout(1500)]
-        public void TestStartsWithResponse()
-        {
-            // テストデータとして適当な、ただしある文字が定期的に出現する長い文字列を生成
-            StringBuilder b = new StringBuilder();
-            int span = 0x7D - 0x20;
-            for (int i = 0; i < 100000; i++)
-            {
-                b.Append(Char.ConvertFromUtf32(i % span + 0x20));
-            }
-
-            // 先頭から最後までひたすら実行して時間がかかりすぎないかをチェック
-            string s = b.ToString();
-            for (int i = 0; i < s.Length; i++)
-            {
-                StringUtils.StartsWith(s, "a", i);
-            }
-        }
-
-        #endregion
-    }
-}
similarity index 73%
rename from WptscsTest/Models/MediaWikiPageTest.cs
rename to WptscsTest/Websites/MediaWikiPageTest.cs
index 1f30f4d..d61aa2f 100644 (file)
@@ -3,18 +3,19 @@
 //      MediaWikiPageのテストクラスソース。</summary>
 //
 // <copyright file="MediaWikiPageTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.Collections.Generic;
     using NUnit.Framework;
     using Honememo.Tests;
     using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
 
     /// <summary>
     /// MediaWikiPageのテストクラスです。
@@ -70,42 +71,7 @@ namespace Honememo.Wptscs.Models
         #endregion
 
         // TODO: いっぱい足らない
-
-        #region 公開静的メソッドテストケース
         
-        /// <summary>
-        /// TryParseNowikiメソッドテストケース。
-        /// </summary>
-        [Test]
-        public void TestTryParseNowiki()
-        {
-            string nowiki;
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki>[[test]]</nowiki>", out nowiki));
-            Assert.AreEqual("<nowiki>[[test]]</nowiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<NOWIKI>[[test]]</NOWIKI>", out nowiki));
-            Assert.AreEqual("<NOWIKI>[[test]]</NOWIKI>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<Nowiki>[[test]]</noWiki>", out nowiki));
-            Assert.AreEqual("<Nowiki>[[test]]</noWiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki>[[test]]</nowiki></nowiki>", out nowiki));
-            Assert.AreEqual("<nowiki>[[test]]</nowiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki>[[test]]nowiki", out nowiki));
-            Assert.AreEqual("<nowiki>[[test]]nowiki", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki>\n\n[[test]]\r\n</nowiki>", out nowiki));
-            Assert.AreEqual("<nowiki>\n\n[[test]]\r\n</nowiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki><!--[[test]]--></nowiki>", out nowiki));
-            Assert.AreEqual("<nowiki><!--[[test]]--></nowiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki><!--<nowiki>[[test]]</nowiki>--></nowiki>", out nowiki));
-            Assert.AreEqual("<nowiki><!--<nowiki>[[test]]</nowiki>--></nowiki>", nowiki);
-            Assert.IsTrue(MediaWikiPage.TryParseNowiki("<nowiki><!--[[test]]", out nowiki));
-            Assert.AreEqual("<nowiki><!--[[test]]", nowiki);
-            Assert.IsFalse(MediaWikiPage.TryParseNowiki("<nowik>[[test]]</nowik>", out nowiki));
-            Assert.IsNull(nowiki);
-            Assert.IsFalse(MediaWikiPage.TryParseNowiki("<nowiki[[test]]</nowiki>", out nowiki));
-            Assert.IsNull(nowiki);
-        }
-
-        #endregion
-
         #region 公開インスタンスメソッドテストケース
 
         /// <summary>
@@ -115,7 +81,7 @@ namespace Honememo.Wptscs.Models
         public void TestGetInterWiki()
         {
             // 普通のページ
-            MediaWikiPage page = new MediaWikiPage(new MediaWiki(new Language("en")), "TestTitle", "TestText\n"
+            MediaWikiPage page = new MediaWikiPage(new DummySite(new Language("en")), "TestTitle", "TestText\n"
                 + " [[ja:テストページ]]<nowiki>[[zh:試験]]</nowiki><!--[[ru:test]]-->[[fr:Test_Fr]]");
             Assert.AreEqual("テストページ", page.GetInterWiki("ja"));
             Assert.AreEqual("Test_Fr", page.GetInterWiki("fr"));
@@ -125,6 +91,32 @@ namespace Honememo.Wptscs.Models
         }
 
         /// <summary>
+        /// GetInterWikiメソッドテストケース(通常ページ実データ使用)。
+        /// </summary>
+        [Test, Timeout(20000)]
+        public void TestGetInterWikiDiscoveryChannel()
+        {
+            MediaWikiPage page = (MediaWikiPage)new MockFactory().GetMediaWiki("en").GetPage("Discovery Channel");
+            Assert.AreEqual("ディスカバリーチャンネル", page.GetInterWiki("ja"));
+            Assert.AreEqual("Discovery Channel (Italia)", page.GetInterWiki("it"));
+            Assert.AreEqual("Discovery Channel", page.GetInterWiki("simple"));
+            Assert.AreEqual("Discovery (телеканал)", page.GetInterWiki("ru"));
+            Assert.IsEmpty(page.GetInterWiki("io"));
+        }
+
+        /// <summary>
+        /// GetInterWikiメソッドテストケース(テンプレートページ実データ使用)。
+        /// </summary>
+        [Test, Timeout(20000)]
+        public void TestGetInterWikiPlanetboxBegin()
+        {
+            MediaWikiPage page = (MediaWikiPage)new MockFactory().GetMediaWiki("en").GetPage("Template:Planetbox begin");
+            Assert.AreEqual("Template:Planetbox begin", page.GetInterWiki("ja"));
+            Assert.AreEqual("Шаблон:Planetbox begin", page.GetInterWiki("ru"));
+            Assert.IsEmpty(page.GetInterWiki("zh"));
+        }
+
+        /// <summary>
         /// GetInterWikiメソッドテストケース(Template:Documentation使用ページ)。
         /// </summary>
         [Test]
@@ -132,7 +124,7 @@ namespace Honememo.Wptscs.Models
         {
             // Template:Documentation を使ってるページ
             MediaWiki site = new DummySite(new Language("en"));
-            site.DocumentationTemplate = "Template:Documentation";
+            site.DocumentationTemplates.Add("Template:Documentation");
             site.DocumentationTemplateDefaultPage = "/doc";
             MediaWikiPage page = new MediaWikiPage(site, "Template:Test", "TestText{{Documentation}}");
 
@@ -149,7 +141,7 @@ namespace Honememo.Wptscs.Models
         [Test]
         public void TestIsRedirect()
         {
-            MediaWiki site = new MediaWiki(new Language("en"));
+            MediaWiki site = new DummySite(new Language("en"));
             MediaWikiPage page = new MediaWikiPage(site, "TestTitle", "#REDIRECT [[Test Redirect]]");
             Assert.IsTrue(page.IsRedirect());
             Assert.AreEqual("Test Redirect", page.Redirect.Title);
similarity index 73%
rename from WptscsTest/Models/MediaWikiTest.cs
rename to WptscsTest/Websites/MediaWikiTest.cs
index f8909d6..d2f6fa4 100644 (file)
@@ -3,12 +3,12 @@
 //      MediaWikiのテストクラスソース。</summary>
 //
 // <copyright file="MediaWikiTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2012 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.Collections.Generic;
@@ -19,6 +19,7 @@ namespace Honememo.Wptscs.Models
     using System.Xml.Serialization;
     using NUnit.Framework;
     using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
 
     /// <summary>
     /// MediaWikiのテストクラスです。
@@ -48,7 +49,7 @@ namespace Honememo.Wptscs.Models
             UriBuilder b = new UriBuilder("file", "");
             b.Path = Path.GetFullPath(testDir) + "\\";
             MediaWiki server = new MediaWiki(new Language(language), new Uri(b.Uri, language + "/").ToString());
-            server.ExportPath = "{0}.xml";
+            server.ExportPath = "$1.xml";
             server.NamespacePath = "_api.xml";
             return server;
         }
@@ -140,15 +141,15 @@ namespace Honememo.Wptscs.Models
         {
             MediaWiki site = new MediaWiki(new Language("ja"));
             // デフォルトでは設定ファイルの値が返される
-            Assert.AreEqual("/wiki/Special:Export/{0}", site.ExportPath);
+            Assert.AreEqual("/wiki/Special:Export/$1", site.ExportPath);
             // 値を設定するとその値が返る
             site.ExportPath = "test";
             Assert.AreEqual("test", site.ExportPath);
             // 空またはnullの場合、再び設定ファイルの値が入る
             site.ExportPath = null;
-            Assert.AreEqual("/wiki/Special:Export/{0}", site.ExportPath);
+            Assert.AreEqual("/wiki/Special:Export/$1", site.ExportPath);
             site.ExportPath = String.Empty;
-            Assert.AreEqual("/wiki/Special:Export/{0}", site.ExportPath);
+            Assert.AreEqual("/wiki/Special:Export/$1", site.ExportPath);
         }
 
         /// <summary>
@@ -267,17 +268,19 @@ namespace Honememo.Wptscs.Models
         }
 
         /// <summary>
-        /// DocumentationTemplateプロパティテストケース。
+        /// DocumentationTemplatesプロパティテストケース。
         /// </summary>
         [Test]
-        public void TestDocumentationTemplate()
+        public void TestDocumentationTemplates()
         {
             MediaWiki site = new MediaWiki(new Language("ja"));
             // デフォルトでは空
-            Assert.IsNullOrEmpty(site.DocumentationTemplate);
-            // その値が返る
-            site.DocumentationTemplate = "Template:Documentation";
-            Assert.AreEqual("Template:Documentation", site.DocumentationTemplate);
+            Assert.IsNotNull(site.DocumentationTemplates);
+            Assert.AreEqual(0, site.DocumentationTemplates.Count);
+            // 値を追加
+            site.DocumentationTemplates.Add("Template:Documentation");
+            Assert.AreEqual(1, site.DocumentationTemplates.Count);
+            Assert.AreEqual("Template:Documentation", site.DocumentationTemplates[0]);
         }
 
         /// <summary>
@@ -289,9 +292,27 @@ namespace Honememo.Wptscs.Models
             MediaWiki site = new MediaWiki(new Language("ja"));
             // デフォルトでは空
             Assert.IsNullOrEmpty(site.DocumentationTemplateDefaultPage);
-            // その値が返る
+            // 値を設定するとその値が返る
             site.DocumentationTemplateDefaultPage = "/doc";
             Assert.AreEqual("/doc", site.DocumentationTemplateDefaultPage);
+            site.DocumentationTemplateDefaultPage = null;
+            Assert.IsNullOrEmpty(site.DocumentationTemplateDefaultPage);
+        }
+
+        /// <summary>
+        /// LinkInterwikiFormatプロパティテストケース。
+        /// </summary>
+        [Test]
+        public void TestLinkInterwikiFormat()
+        {
+            MediaWiki site = new MediaWiki(new Language("ja"));
+            // デフォルトでは空
+            Assert.IsNullOrEmpty(site.LinkInterwikiFormat);
+            // 値を設定するとその値が返る
+            site.LinkInterwikiFormat = ("{{仮リンク|$1|$2|$3|label=$4}}");
+            Assert.AreEqual("{{仮リンク|$1|$2|$3|label=$4}}", site.LinkInterwikiFormat);
+            site.LinkInterwikiFormat = null;
+            Assert.IsNullOrEmpty(site.LinkInterwikiFormat);
         }
 
         #endregion
@@ -299,7 +320,7 @@ namespace Honememo.Wptscs.Models
         #region 公開メソッドテストケース
 
         /// <summary>
-        /// GetPageã\83\97ã\83­ã\83\91ã\83\86ã\82£テストケース。
+        /// GetPageã\83¡ã\82½ã\83\83ã\83\89テストケース。
         /// </summary>
         [Test]
         public void TestGetPage()
@@ -313,6 +334,59 @@ namespace Honememo.Wptscs.Models
             Assert.AreEqual(site, page.Website);
         }
 
+        /// <summary>
+        /// IsMagicWordメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestIsMagicWord()
+        {
+            MediaWiki site = new MediaWiki(new Language("en"), "http://example.com");
+            site.MagicWords = new List<string>();
+
+            // 値が設定されていなければ一致しない
+            Assert.IsFalse(site.IsMagicWord("CURRENTYEAR"));
+            Assert.IsFalse(site.IsMagicWord("ns:1"));
+
+            // 値が一致
+            site.MagicWords.Add("CURRENTYEAR");
+            Assert.IsTrue(site.IsMagicWord("CURRENTYEAR"));
+            Assert.IsFalse(site.IsMagicWord("ns:1"));
+
+            // コロンが入るものは、その前の部分までで判定
+            site.MagicWords.Add("ns");
+            Assert.IsTrue(site.IsMagicWord("CURRENTYEAR"));
+            Assert.IsTrue(site.IsMagicWord("ns:1"));
+        }
+
+        /// <summary>
+        /// FormatLinkInterwikiメソッドテストケース。
+        /// </summary>
+        [Test]
+        public void TestFormatLinkInterwiki()
+        {
+            MediaWiki site = new MediaWiki(new Language("en"), "http://example.com");
+
+            // LinkInterwikiFormatが空の場合、nullが返る
+            site.LinkInterwikiFormat = null;
+            Assert.IsNull(site.FormatLinkInterwiki("記事名", "言語", "他言語版記事名", "表示名"));
+            site.LinkInterwikiFormat = String.Empty;
+            Assert.IsNull(site.FormatLinkInterwiki("記事名", "言語", "他言語版記事名", "表示名"));
+
+            // 値が設定されている場合、パラメータを埋め込んで書式化される
+            site.LinkInterwikiFormat = "{{仮リンク|$1|$2|$3|label=$4}}";
+            Assert.AreEqual("{{仮リンク|記事名1|言語1|他言語版記事名1|label=表示名1}}", site.FormatLinkInterwiki("記事名1", "言語1", "他言語版記事名1", "表示名1"));
+            site.LinkInterwikiFormat = "{{日本語版にない記事リンク|$1|$2|$3}}";
+            Assert.AreEqual("{{日本語版にない記事リンク|記事名2|言語2|他言語版記事名2}}", site.FormatLinkInterwiki("記事名2", "言語2", "他言語版記事名2", "表示名2"));
+            site.LinkInterwikiFormat = "[[:$2:$3|$4]]";
+            Assert.AreEqual("[[:言語3:他言語版記事名3|表示名3]]", site.FormatLinkInterwiki("記事名3", "言語3", "他言語版記事名3", "表示名3"));
+            site.LinkInterwikiFormat = "xxx";
+            Assert.AreEqual("xxx", site.FormatLinkInterwiki("記事名", "言語", "他言語版記事名", "表示名"));
+
+            // 値がnull等でも特に制限はない
+            site.LinkInterwikiFormat = "{{仮リンク|$1|$2|$3|label=$4}}";
+            Assert.AreEqual("{{仮リンク||||label=}}", site.FormatLinkInterwiki(null, null, null, null));
+        }
+
         #endregion
 
         #region XMLシリアライズ用メソッドテストケース
@@ -356,7 +430,7 @@ namespace Honememo.Wptscs.Models
             }
 
             // プロパティはデフォルト値の場合出力しないという動作あり
-            Assert.AreEqual("<MediaWiki><Location>http://ja.wikipedia.org</Location><Language Code=\"ja\"><Names /><Bracket /></Language><NamespacePath /><ExportPath /><Redirect /><TemplateNamespace /><CategoryNamespace /><FileNamespace /><MagicWords /></MediaWiki>", b.ToString());
+            Assert.AreEqual("<MediaWiki><Location>http://ja.wikipedia.org</Location><Language Code=\"ja\"><Names /><Bracket /></Language><NamespacePath /><ExportPath /><Redirect /><TemplateNamespace /><CategoryNamespace /><FileNamespace /><MagicWords /><DocumentationTemplates /><LinkInterwikiFormat /></MediaWiki>", b.ToString());
             // TODO: プロパティに値が設定されたパターンを追加すべき
         }
 
similarity index 98%
rename from WptscsTest/Models/PageTest.cs
rename to WptscsTest/Websites/PageTest.cs
index 69b80de..9a9b474 100644 (file)
@@ -3,12 +3,12 @@
 //      Pageのテストクラスソース。</summary>
 //
 // <copyright file="PageTest.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.Collections.Generic;
similarity index 98%
rename from WptscsTest/Models/WebsiteTest.cs
rename to WptscsTest/Websites/WebsiteTest.cs
index 62a1fcf..26bc731 100644 (file)
@@ -3,12 +3,12 @@
 //      Websiteのテストクラスソース。</summary>
 //
 // <copyright file="WebsiteTest.cs.cs" company="honeplusのメモ帳">
-//      Copyright (C) 2010 Honeplus. All rights reserved.</copyright>
+//      Copyright (C) 2011 Honeplus. All rights reserved.</copyright>
 // <author>
 //      Honeplus</author>
 // ================================================================================================
 
-namespace Honememo.Wptscs.Models
+namespace Honememo.Wptscs.Websites
 {
     using System;
     using System.IO;
@@ -18,6 +18,7 @@ namespace Honememo.Wptscs.Models
     using System.Xml.Serialization;
     using NUnit.Framework;
     using Honememo.Utilities;
+    using Honememo.Wptscs.Models;
 
     /// <summary>
     /// Websiteのテストクラスです。
index 0e8c744..2865cf4 100644 (file)
@@ -12,6 +12,7 @@
     <AssemblyName>WptscsTest</AssemblyName>
     <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
     <FileAlignment>512</FileAlignment>
+    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="Logics\MediaWikiTranslatorTest.cs" />
+    <Compile Include="Logics\TranslatorTest.cs" />
     <Compile Include="Models\ConfigTest.cs" />
-    <Compile Include="Models\IgnoreCaseDictionaryTest.cs" />
-    <Compile Include="Models\MediaWikiPageTest.cs" />
-    <Compile Include="Models\PageTest.cs" />
-    <Compile Include="Models\TestingConfig.cs" />
+    <Compile Include="Parsers\MediaWikiLinkTest.cs" />
+    <Compile Include="Parsers\MediaWikiParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiHeadingParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiLinkParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiNowikiParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiTemplateParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiVariableParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiRedirectParserTest.cs" />
+    <Compile Include="Parsers\MediaWikiTemplateTest.cs" />
+    <Compile Include="Parsers\MediaWikiHeadingTest.cs" />
+    <Compile Include="Parsers\MediaWikiVariableTest.cs" />
+    <Compile Include="Websites\MediaWikiPageTest.cs" />
+    <Compile Include="Websites\PageTest.cs" />
+    <Compile Include="Models\MockFoctory.cs" />
     <Compile Include="Models\LanguageTest.cs" />
-    <Compile Include="Models\MediaWikiTest.cs" />
+    <Compile Include="Websites\MediaWikiTest.cs" />
     <Compile Include="Models\TranslationDictionaryTest.cs" />
     <Compile Include="Models\TranslationTableTest.cs" />
-    <Compile Include="Models\WebsiteTest.cs" />
+    <Compile Include="Websites\WebsiteTest.cs" />
     <Compile Include="PrivateAccessor.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Utilities\AppDefaultWebProxyTest.cs" />
     <Compile Include="Utilities\FormUtilsTest.cs" />
-    <Compile Include="Utilities\LazyXmlParserTest.cs" />
-    <Compile Include="Utilities\ObjectUtilsTest.cs" />
-    <Compile Include="Utilities\StringUtilsTest.cs" />
-    <Compile Include="Utilities\ValidateTest.cs" />
-    <Compile Include="Utilities\XmlUtilsTest.cs" />
   </ItemGroup>
   <ItemGroup>
+    <ProjectReference Include="..\HmLib\HmLib.csproj">
+      <Project>{CC7F6106-0C00-427B-9BF2-1EE65448907B}</Project>
+      <Name>HmLib</Name>
+    </ProjectReference>
     <ProjectReference Include="..\wptscs\Wptscs.csproj">
       <Project>{4E20E523-0230-474C-B8F1-1E991360F5F5}</Project>
       <Name>Wptscs</Name>
     <Content Include="Data\MediaWiki\en\.example.xml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Data\MediaWiki\en\Discovery Channel.xml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Data\MediaWiki\en\Fuji %28Spacecraft%29.xml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="Data\MediaWiki\en\Nothing Page.xml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Data\MediaWiki\en\Template_Citation needed.xml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Data\MediaWiki\en\Template_Citation needed_doc.xml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Data\MediaWiki\en\Template_Planetbox begin.xml">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="Data\MediaWiki\en\_api.xml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Content Include="Data\MediaWiki\ja\遠地点.xml">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Data\MediaWiki\result\config.xml">
+      <SubType>Designer</SubType>
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="Data\MediaWiki\result\example.txt">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
     <Content Include="Data\MediaWiki\result\example_定型句なし.txt">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>
+    <Content Include="Data\MediaWiki\result\example_仮リンク有効.txt">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </Content>
     <Content Include="Data\MediaWiki\result\スペースシップツー.txt">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </Content>