public CardReplacer(Th095Converter parent, bool hideUntriedCards)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    if (hideUntriedCards)
                    {
                        var score = parent.allScoreData.Scores.Find(
                            elem => (elem != null) && elem.LevelScene.Equals(key));
                        if (score == null)
                        {
                            return("??????????");
                        }
                    }

                    return((type == 1) ? SpellCards[key].Enemy.ToLongName() : SpellCards[key].Card);
                });
            }
            public ShotReplacer(Th095Converter parent, string outputFilePath)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    BestShotPair bestshot;
                    if (!string.IsNullOrEmpty(outputFilePath) &&
                        parent.bestshots.TryGetValue(key, out bestshot))
                    {
                        var relativePath = new Uri(outputFilePath)
                                           .MakeRelativeUri(new Uri(bestshot.Path)).OriginalString;
                        var alternativeString = Utils.Format(
                            "ClearData: {0}{3}Slow: {1:F6}%{3}SpellName: {2}",
                            Utils.ToNumberString(bestshot.Header.Score),
                            bestshot.Header.SlowRate,
                            Encoding.Default.GetString(bestshot.Header.CardName).TrimEnd('\0'),
                            Environment.NewLine);
                        return(Utils.Format(
                                   "<img src=\"{0}\" alt=\"{1}\" title=\"{1}\" border=0>",
                                   relativePath,
                                   alternativeString));
                    }
                    else
                    {
                        return(string.Empty);
                    }
                });
            }
            public ScoreReplacer(Th095Converter parent)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    var score = parent.allScoreData.Scores.Find(
                        elem => (elem != null) && elem.LevelScene.Equals(key));

                    switch (type)
                    {
                    case 1:         // high score
                        return((score != null) ? Utils.ToNumberString(score.HighScore) : "0");

                    case 2:         // bestshot score
                        return((score != null) ? Utils.ToNumberString(score.BestshotScore) : "0");

                    case 3:         // num of shots
                        return((score != null) ? Utils.ToNumberString(score.TrialCount) : "0");

                    case 4:         // slow rate
                        return((score != null) ? Utils.Format("{0:F3}%", score.SlowRate2) : "-----%");

                    default:        // unreachable
                        return(match.ToString());
                    }
                });
            }
            public ShotExReplacer(Th095Converter parent, string outputFilePath)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    BestShotPair bestshot;
                    if (!string.IsNullOrEmpty(outputFilePath) &&
                        parent.bestshots.TryGetValue(key, out bestshot))
                    {
                        switch (type)
                        {
                        case 1:         // relative path to the bestshot file
                            return(new Uri(outputFilePath)
                                   .MakeRelativeUri(new Uri(bestshot.Path)).OriginalString);

                        case 2:         // width
                            return(bestshot.Header.Width.ToString(CultureInfo.InvariantCulture));

                        case 3:         // height
                            return(bestshot.Header.Height.ToString(CultureInfo.InvariantCulture));

                        case 4:         // score
                            return(Utils.ToNumberString(bestshot.Header.Score));

                        case 5:         // slow rate
                            return(Utils.Format("{0:F6}%", bestshot.Header.SlowRate));

                        case 6:         // date & time
                            {
                                var score = parent.allScoreData.Scores.Find(
                                    elem => (elem != null) && elem.LevelScene.Equals(key));
                                if (score != null)
                                {
                                    return(new DateTime(1970, 1, 1)
                                           .AddSeconds(score.DateTime).ToLocalTime()
                                           .ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.CurrentCulture));
                                }
                                else
                                {
                                    return("----/--/-- --:--:--");
                                }
                            }

                        default:        // unreachable
                            return(match.ToString());
                        }
                    }
                    else
                    {
                        switch (type)
                        {
                        case 1: return(string.Empty);

                        case 2: return("0");

                        case 3: return("0");

                        case 4: return("--------");

                        case 5: return("-----%");

                        case 6: return("----/--/-- --:--:--");

                        default: return(match.ToString());
                        }
                    }
                });
            }