private static Dictionary<string, PageElement> elementSet = new Dictionary<string, PageElement>();

/// <summary>
/// WikiText -> Elements
/// </summary>
/// <param name="wikitext">&lt; &gt; を中間データとして使用するため、PageElements系以外から呼び出すときはwikitextにHttpUtility.HtmlEncode()の出力を渡すこと。</param>
/// <returns></returns>
public static List<PageElement> Tokenize(PageElement parent, string wikitext, HashSet<Type> targetClasses)
{
	//FIXME:wikitextはエスケープ済みでなければならない仕様
	// Regex.Replaceの仕様上引数を渡せないので、クロージャ内で使える変数を用意
	Type elementClass = null;
	PageElement _elementInstance = null;
	Match _match = null;
	bool matched;

	Func<Match, string> createElement = delegate(Match m)
	{
		matched = true;
		string ret;

		var classes = new HashSet<Type>(targetClasses);
		classes.Remove(elementClass);

		if (elementClass.GetMethod("Merge") != null)
		{
			// 連結可能Element
			Debug.Assert(elementClass.Name != null);
			if (_elementInstance != null && _elementInstance.GetType().Name == elementClass.Name && _match.Index + _match.Length == m.Index)
			{
				// 連結、既存Elementに連結
				elementClass.GetMethod("Merge").Invoke(null, new object[] { parent, m, classes, _elementInstance });
				//_elem = _elem;
				ret = "";
			}
			else
			{
				var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null);
				elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance);
				_elementInstance = elementInstance;
				ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">";
			}
		}
		else
		{
			var elementInstance = (PageElement)Assembly.GetAssembly(elementClass).CreateInstance(elementClass.FullName, false, BindingFlags.CreateInstance, null, new object[] { parent, m, classes }, null, null);
			elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance);
			_elementInstance = null;
			ret = "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">";
		}

		_match = m;
		return ret;
	};

	Func<Match, string> createPlaintextElement = delegate(Match m)
	{
		matched = true;
		// Plains系オブジェクト生成
		//var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetPlainClasses);
		var elementInstance = PageElements.Plain.Construct(parent, m.Groups[0].Value, targetClasses);
		if (elementInstance != null)
		{
			elementSet.Add(elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString(), elementInstance);
			return "<" + elementInstance.GetType().ToString() + ":" + elementInstance.GetHashCode().ToString() + ">";
		}
		else
		{
			return "";
		}
	};

	var elements = new List<PageElement>();
	var plugins = new HashSet<Type>(targetClasses);
	do
	{
		// 置き換えが起きるたびに状況が変わるので、置き換えが起きなくなるまで置き換えを試す
		matched = false;
		// targetClasses Wikitextを消費しない記法で無限再帰するのを防止
		foreach (var plugin in plugins)
		{
			if (plugin.IsSubclassOf(typeof(PageElements.Notation)))
			{
				string pattern = (string)plugin.GetProperty("Pattern").GetGetMethod().Invoke(null, null);

				if (pattern != null)
				{
					elementClass = plugin;
					//// ignoreClassesでも対象Wikitextが短くなっているなら無限再帰を防げる。
					//if (targetClasses.Contains(plugin))
					//    wikitext = Regex.Replace(wikitext, @"(?<=.)" + pattern + @"|" + pattern + @"(?=.)", new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);
					//else
					//    wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);

					// matchedはdelegate内で更新
					wikitext = Regex.Replace(wikitext, pattern, new MatchEvaluator(createElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);
				}
			}
		}
	} while (matched);

	Debug.WriteLine("1 ====================\n" + wikitext + "\n====================\n");

	{
		//TODO:Variables系

	}

	Debug.WriteLine("2 ====================\n" + wikitext + "\n====================\n");

	{
		// Plaintext系 この時点で > ... < 間にあるテキスト全てが対象。
		//FIXME:名前付きグループを使えば3つの正規表現をまとめられる。
		wikitext = Regex.Replace(wikitext, @"^([^<]+)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);
		wikitext = Regex.Replace(wikitext, @"(?<=\>)((?:[^<>]|\n)+?)(?=\<)", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);
		wikitext = Regex.Replace(wikitext, @"([^>]+)$", new MatchEvaluator(createPlaintextElement), RegexOptions.Singleline & RegexOptions.IgnoreCase);
	}

	Debug.Assert(Regex.Replace(wikitext, @"\<[^\>]+?\>", "", RegexOptions.Singleline & RegexOptions.IgnoreCase) == "");

	{
		// Elements構築
		foreach (Match m in Regex.Matches(wikitext, @"\<([^\>]+?)\>", RegexOptions.Singleline & RegexOptions.IgnoreCase))
		{
			elements.Add(elementSet[m.Groups[1].Value]);
		}
	}

	return elements;
}