/// <summary>
        /// Reads each line in each section, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="sectionLines">A collection of deck sections with Item1 as Name, and Item2 as a collection of lines of text which are cards</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        internal static ConverterDeck ConvertDeckWithSeparateSections(Dictionary <string, IEnumerable <string> > sectionLines, IEnumerable <string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            foreach (KeyValuePair <string, IEnumerable <string> > section in sectionLines)
            {
                ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(section.Key, StringComparison.InvariantCultureIgnoreCase));
                foreach (string line in section.Value)
                {
                    // The line is the Deck Name?  Record it.
                    string potentialDeckName = TextConverter.RegexMatch_DeckName(line);

                    if (potentialDeckName != null)
                    {
                        converterDeck.DeckName = potentialDeckName;
                    }
                    else
                    {
                        ConverterMapping potentialCard = TextConverter.ParseLineForCardAndQuantity(line);

                        if (potentialCard != null)
                        {
                            converterSection.AddConverterMapping(potentialCard);
                        }
                        else
                        {
                            converterSection.AddIncorrectlyFormattedLine(line);
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return(converterDeck);
        }
        /// <summary>
        /// Adds a Mage Stats card to accompany the Mage card, since it is required in OCTGN
        /// </summary>
        private static void AddMageStatsCard(ConverterDeck converterDeck)
        {
            ConverterSection mageSection = converterDeck.ConverterSections.First(s => s.SectionName.Equals("Mage", StringComparison.InvariantCultureIgnoreCase));

            if (mageSection.SectionMappings.Any(sm => sm.CardName.EndsWith(" Stats")))
            {
                // The corresponding Stats card already appears to be added
                return;
            }

            ConverterMapping firstMage = mageSection.SectionMappings.FirstOrDefault();

            if (firstMage != null)
            {
                string mageName = firstMage.CardName;

                if (MW.RegexMatch_WizardSubtype(mageName))
                {
                    // No matter what the subtype of Wizard is, it should have a 'Wizard Stats' card
                    mageName = "Wizard";
                }

                mageSection.AddConverterMapping(new ConverterMapping(mageName + " Stats", string.Empty, 1));
            }
        }
Exemple #3
0
        private static void VerifyConverterSectionsAreIdentical(ConverterSection converterSection, ConverterSection otherConverterSection)
        {
            Assert.AreEqual(converterSection.SectionCount, otherConverterSection.SectionCount);
            Assert.AreEqual(converterSection.SectionMappings.Count, otherConverterSection.SectionMappings.Count);

            foreach (ConverterMapping converterMapping in converterSection.SectionMappings)
            {
                ConverterMapping otherConverterMapping = otherConverterSection.SectionMappings.First(sm => sm.CardName == converterMapping.CardName);
                VerifyConverterMappingsAreIdentical(converterMapping, otherConverterMapping);
            }
        }
Exemple #4
0
        /// <summary>
        /// Reads the lines which has Sideboard cards denoted on each line, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="fullPathName">The full path name of the Deck file to convert</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        public static ConverterDeck ConvertMTGDeckWithSideBoardCardsListedEachLine(IEnumerable <string> lines, IEnumerable <string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            ConverterSection mtgMainDeckConverterSection  = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Main", StringComparison.InvariantCultureIgnoreCase));
            ConverterSection mtgSideboardConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Sideboard", StringComparison.InvariantCultureIgnoreCase));

            foreach (string line in lines)
            {
                // The line is the Deck Name?  Record it.
                string potentialDeckName = TextConverter.RegexMatch_DeckName(line);

                if (potentialDeckName != null)
                {
                    converterDeck.DeckName = potentialDeckName;
                }
                else
                {
                    // Ordering: The most specific pattern is listed first, and each more generalized pattern follows
                    if (RegexMatch_MWSSideBoardCard(line) != null)
                    {
                        // The line is a MWS sideboard "SB: Quantity [SET] Card" entry
                        mtgSideboardConverterSection.AddConverterMapping(RegexMatch_MWSSideBoardCard(line));
                    }
                    else if (RegexMatch_MWSMainDeckCard(line) != null)
                    {
                        // The line is a MWS main deck "Quantity [SET] Card" entry
                        mtgMainDeckConverterSection.AddConverterMapping(RegexMatch_MWSMainDeckCard(line));
                    }
                    else if (RegexMatch_RegularMTGSideBoardCard(line) != null)
                    {
                        // The line is a regular sideboard "Quantity Card" entry, without any Set info
                        mtgSideboardConverterSection.AddConverterMapping(RegexMatch_RegularMTGSideBoardCard(line));
                    }
                    else
                    {
                        ConverterMapping potentialCard = TextConverter.ParseLineForCardAndQuantity(line);
                        if (potentialCard != null)
                        {
                            mtgMainDeckConverterSection.AddConverterMapping(potentialCard);
                        }
                        else
                        {
                            // The line is not a valid card entry
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return(converterDeck);
        }
        public void SerializeAndDeserializeAType()
        {
            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();
            section.Type = typeof(Exception);
            config.Sections.Add(sectionName, section);
            config.Save();

            config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            Assert.AreEqual(section.Type, typeof(Exception));
        }
        public void SerializeAndDeserialzieANullByteArray()
        {
            SysConfig.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();
            section.ByteArray = null;
            config.Sections.Add(sectionName, section);
            config.Save();

            config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            Assert.IsNull(section.ByteArray);
        }
        public void SerializeAndDeserializeAType()
        {
            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();

            section.Type = typeof(Exception);
            config.Sections.Add(sectionName, section);
            config.Save();
            config  = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            Assert.AreEqual(section.Type, typeof(Exception));
        }
        public void SerializeAndDeserialzieANullByteArray()
        {
            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();

            section.ByteArray = null;
            config.Sections.Add(sectionName, section);
            config.Save();
            config  = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            Assert.IsNull(section.ByteArray);
        }
        public void SerializeAndDeserializeAByteArray()
        {
            SysConfig.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();
            section.ByteArray = new byte[] { 1, 2, 3, 4 };
            config.Sections.Add(sectionName, section);
            config.Save();

            config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            byte actual = 1;
            Assert.AreEqual(section.ByteArray[0], actual);
        }
Exemple #10
0
        /// <summary>
        /// Reads the lines of text which is in the SpellBookBuilder Text format, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="lines">The lines of text of the Deck file to convert</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        public ConverterDeck Convert(IEnumerable <string> lines, IEnumerable <string> deckSectionNames)
        {
            string deckName = lines.FirstOrDefault(l => SpellBookBuilderText.RegexMatch_DeckName(l) != null);

            string mage;

            try
            {
                mage = (from l in lines select SpellBookBuilderText.RegexMatch_Mage(l)).First(m => m != null);
            }
            catch (Exception e)
            {
                e.Data.Add("Description", "Could not find a Mage in this deck.");
                throw;
            }

            List <ParsedCard> cards = new List <ParsedCard>();

            foreach (string line in lines)
            {
                ParsedCard potentialCard = SpellBookBuilderText.RegexMatch_SBBTCard(line);
                if (potentialCard != null)
                {
                    cards.Add(potentialCard);
                }
            }

            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            // Insert the Deck Name
            if (deckName != null)
            {
                converterDeck.DeckName = deckName;
            }

            // Add the Mage with quantity of 1
            ConverterSection mageSection = converterDeck.ConverterSections.First(s => s.SectionName.Equals("Mage", StringComparison.InvariantCultureIgnoreCase));

            mageSection.AddConverterMapping(new ConverterMapping(mage, string.Empty, 1));

            // Add each other card with quantity to the corresponding Section
            foreach (ParsedCard card in cards)
            {
                ConverterSection correspondingSection = converterDeck.ConverterSections.First(s => s.SectionName.Equals(card.Section, StringComparison.InvariantCultureIgnoreCase));
                correspondingSection.AddConverterMapping(new ConverterMapping(card.Name, string.Empty, card.Quantity));
            }

            return(converterDeck);
        }
        public void SerializeAndDeserializeAByteArray()
        {
            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            ConverterSection section = new ConverterSection();

            section.ByteArray = new byte[] { 1, 2, 3, 4 };
            config.Sections.Add(sectionName, section);
            config.Save();
            config  = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            section = config.Sections[sectionName] as ConverterSection;
            Assert.IsNotNull(section);
            byte actual = 1;

            Assert.AreEqual(section.ByteArray[0], actual);
        }
Exemple #12
0
        /// <summary>
        /// Reads the OCTGN format file, and returns a ConverterDeck which is populated with all cards.
        /// </summary>
        /// <param name="fullPathName">The full path name of the Deck file to convert</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        /// <remarks>This will purposely ignore the Guids, which may help importing from OCTGN2 or other unofficial sets</remarks>
        public override ConverterDeck Convert(string fullPathName, IEnumerable <string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            using (XmlTextReader reader = new XmlTextReader(fullPathName))
            {
                reader.WhitespaceHandling = WhitespaceHandling.None;

                XmlDocument xd = new XmlDocument();
                xd.Load(reader);

                XmlNode xnodDE = xd.DocumentElement;

                if (xnodDE.Name != "deck")
                {
                    throw new InvalidOperationException("File is not a Octgn Deck");
                }

                foreach (XmlNode child in xnodDE.ChildNodes)
                {
                    if (child.Name.Equals("section", StringComparison.InvariantCultureIgnoreCase))
                    {
                        ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(child.Attributes.GetNamedItem("name").Value, StringComparison.InvariantCultureIgnoreCase));
                        foreach (XmlNode cardXmlNode in child.ChildNodes)
                        {
                            converterSection.AddConverterMapping
                            (
                                new ConverterMapping
                                (
                                    cardXmlNode.InnerText,
                                    string.Empty,
                                    int.Parse(cardXmlNode.Attributes.GetNamedItem("qty").Value)
                                )
                            );
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return(converterDeck);
        }
        /// <summary>
        /// Converts a LoTR URL from cardgamedb.com into a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="url">The URL of the Deck</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <param name="convertGenericFileFunc">
        /// Function to convert a collection of lines from a deck file into a ConverterDeck.
        /// Used when downloading a Deck File from a webpage instead of scraping.
        /// </param>
        /// <returns>A ConverterDeck which is populated with all cards and deck name</returns>
        public override ConverterDeck Convert(
            string url,
            IEnumerable <string> deckSectionNames,
            Func <IEnumerable <string>, IEnumerable <string>, ConverterDeck> convertGenericFileFunc)
        {
            object htmlWebInstance           = HtmlAgilityPackWrapper.HtmlWeb_CreateInstance();
            object htmlDocumentInstance      = HtmlAgilityPackWrapper.HtmlWeb_InvokeMethod_Load(htmlWebInstance, url);
            object htmlDocument_DocumentNode = HtmlAgilityPackWrapper.HtmlDocument_GetProperty_DocumentNode(htmlDocumentInstance);

            // Find the block of javascript that contains the variable 'viewType'
            object rawDeckJavascriptNode = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(htmlDocument_DocumentNode, "//script[contains(text(), 'var viewType =')]");
            string rawDeckJavascriptText = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(rawDeckJavascriptNode);

            string[] rawDeckJavascriptLines = rawDeckJavascriptText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);

            dynamic rawDeckJSON = null;

            string viewTypeLine = rawDeckJavascriptLines.FirstOrDefault(l => l.Contains("var viewType ="));

            if (viewTypeLine == null)
            {
                throw new InvalidOperationException("Could not find the javascript variable 'viewType'");
            }
            else if (viewTypeLine.Contains("submitted"))
            {
                // Since viewType is 'submitted', the deck is published.  In this case, the JSON data the deck is embedded on this page in the javascript variable 'rawdeck'

                string rawDeckLine = rawDeckJavascriptLines.First(l => l.Contains("var rawdeck ="));

                // Trim everything except the JSON
                int    openingCurlyBraceIndex = rawDeckLine.IndexOf('{');
                int    closingCurlyBraceIndex = rawDeckLine.LastIndexOf('}');
                string rawDeckJSONString      = rawDeckLine.Substring(openingCurlyBraceIndex, closingCurlyBraceIndex - openingCurlyBraceIndex + 1);
                rawDeckJSON = JsonConvert.DeserializeObject(rawDeckJSONString);
            }
            else if (viewTypeLine.Contains("share"))
            {
                // Since viewType is 'share', the deck is personal.  In this case, the JSON is not embedded on this page and must be fetched via a form POST

                Dictionary <string, string> urlParams = WebpageConverter.GetParams(url);

                // Find the block of javascript that contains the 'ipb.vars' dictionary variable
                object   rawIPBVarsJavascriptNode  = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(htmlDocument_DocumentNode, "//script[contains(text(), 'ipb.vars[')]");
                string   rawIPBVarsJavascriptText  = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(rawIPBVarsJavascriptNode);
                string[] rawIPBVarsJavascriptLines = rawIPBVarsJavascriptText.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);

                // Extract the 'secure_hash' value from IPBVar
                string secureHashLine = rawIPBVarsJavascriptLines.First(l => l.Contains(@"ipb.vars['secure_hash']"));
                string secure_hash    = secureHashLine.Substring(0, secureHashLine.LastIndexOf('\''));
                secure_hash = secure_hash.Substring(secure_hash.LastIndexOf('\'') + 1);

                // Extract the 'secure_hash' value from IPBVar
                string baseURLLine = rawIPBVarsJavascriptLines.First(l => l.Contains(@"ipb.vars['base_url']"));
                string base_url    = baseURLLine.Substring(0, baseURLLine.LastIndexOf('\''));
                base_url = base_url.Substring(base_url.LastIndexOf('\'') + 1);

                // These variables are all submitted with the form.  They are added via javascript in lotrdecksection.js
                System.Collections.Specialized.NameValueCollection outgoingQueryString = System.Web.HttpUtility.ParseQueryString(String.Empty);
                outgoingQueryString.Add("dguid", urlParams["deck"]);
                outgoingQueryString.Add("fPage", "deckshare");
                outgoingQueryString.Add("fgame", "lordoftherings");
                outgoingQueryString.Add("md5check", secure_hash);
                outgoingQueryString.Add("pid", urlParams["p"]);
                string postData = outgoingQueryString.ToString();

                ASCIIEncoding ascii     = new ASCIIEncoding();
                byte[]        postBytes = ascii.GetBytes(postData.ToString());

                string requestString = base_url + "app=ccs&module=ajax&section=lotrdeckbuilder&do=share";
                System.Net.HttpWebRequest request = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(requestString);
                request.Method        = "POST";
                request.Accept        = "application/json";
                request.ContentType   = "application/x-www-form-urlencoded";
                request.ContentLength = postBytes.Length;
                request.Host          = "www.cardgamedb.com";
                request.Referer       = url;
                request.UserAgent     = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0";

                // add post data to request
                System.IO.Stream postStream = request.GetRequestStream();
                postStream.Write(postBytes, 0, postBytes.Length);
                postStream.Flush();
                postStream.Close();

                System.Net.WebResponse response   = request.GetResponse();
                System.IO.Stream       dataStream = response.GetResponseStream();
                System.IO.StreamReader reader     = new System.IO.StreamReader(dataStream);
                string responseFromServer         = System.Web.HttpUtility.HtmlDecode(reader.ReadToEnd());
                reader.Close();
                response.Close();
                rawDeckJSON = JsonConvert.DeserializeObject(responseFromServer);

                // When JSON is retrieved this way, the deck contents is stored in a sub-variable
                rawDeckJSON = rawDeckJSON.deckContents;
            }
            else
            {
                throw new InvalidOperationException("The javascript variable 'viewType' was not an expected value (" + viewTypeLine + ")");
            }

            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            foreach (dynamic card in rawDeckJSON.hero)
            {
                ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("hero", StringComparison.InvariantCultureIgnoreCase));
                ConverterMapping converterMapping = new ConverterMapping(
                    (string)card.name,
                    (string)card.setname,
                    int.Parse((string)card.quantity));
                converterSection.AddConverterMapping(converterMapping);
            }

            foreach (dynamic card in rawDeckJSON.cards)
            {
                ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals((string)card.type, StringComparison.InvariantCultureIgnoreCase));
                ConverterMapping converterMapping = new ConverterMapping(
                    (string)card.name,
                    (string)card.setname,
                    int.Parse((string)card.quantity));
                converterSection.AddConverterMapping(converterMapping);
            }

            return(converterDeck);
        }
        /// <summary>
        /// Converts a MW URL from arcanewonders.com into a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="url">The URL of the Deck</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <param name="convertGenericFileFunc">
        /// Function to convert a collection of lines from a deck file into a ConverterDeck.
        /// Used when downloading a Deck File from a webpage instead of scraping.
        /// </param>
        /// <returns>A ConverterDeck which is populated with all cards and deck name</returns>
        public override ConverterDeck Convert(
            string url,
            IEnumerable <string> deckSectionNames,
            Func <IEnumerable <string>, IEnumerable <string>, ConverterDeck> convertGenericFileFunc)
        {
            object htmlWebInstance           = HtmlAgilityPackWrapper.HtmlWeb_CreateInstance();
            object htmlDocumentInstance      = HtmlAgilityPackWrapper.HtmlWeb_InvokeMethod_Load(htmlWebInstance, url);
            object htmlDocument_DocumentNode = HtmlAgilityPackWrapper.HtmlDocument_GetProperty_DocumentNode(htmlDocumentInstance);

            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            // Find the div with id 'spellbook' because all deck data is contained inside it
            object spellbookDiv = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(htmlDocument_DocumentNode, "//div[@id='spellbook']");

            if (spellbookDiv == null)
            {
                throw new InvalidOperationException("Could not find the html div 'spellbook', are you sure this webpage has a deck?");
            }

            // Insert the Deck Name if available
            object spellbooknameSpan = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(spellbookDiv, "//span[@id='spellbookname']");

            if (spellbooknameSpan != null)
            {
                converterDeck.DeckName = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(spellbooknameSpan);
            }

            // Convert the Mage card
            object firstMageSpan = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectNodes(spellbookDiv, "//span[@id='mage']").FirstOrDefault();

            if (firstMageSpan != null)
            {
                ConverterSection mageSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Mage", StringComparison.InvariantCultureIgnoreCase));
                mageSection.AddConverterMapping(new ConverterMapping(
                                                    HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(firstMageSpan),
                                                    string.Empty,
                                                    1));
            }

            // The div 'spells' contains all the cards, grouped by spell class
            object spellsDiv = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(spellbookDiv, "//div[@id='spells']");

            // Get a collection of all the span nodes inside the 'spells' div
            IEnumerable <object> cardAndSectionSpans = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectNodes(spellsDiv, "span");

            ConverterSection currentConverterSection = null;

            foreach (object cardOrSectionSpan in cardAndSectionSpans)
            {
                // get the class name of the span
                IEnumerable <object> attributes = HtmlAgilityPackWrapper.HtmlNode_GetProperty_Attributes(cardOrSectionSpan);
                string className = string.Empty;
                foreach (object attribute in attributes)
                {
                    if (HtmlAgilityPackWrapper.HtmlAttribute_GetProperty_Name(attribute).Equals("class", StringComparison.InvariantCultureIgnoreCase))
                    {
                        className = HtmlAgilityPackWrapper.HtmlAttribute_GetProperty_Value(attribute);
                        break;
                    }
                }

                // If the class name of the span is 'spellClass', then it denotes a Deck Section.  Find the corresponding ConverterSection
                if (className.Equals("spellClass", StringComparison.InvariantCultureIgnoreCase))
                {
                    string currentSpellClass = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(cardOrSectionSpan);
                    currentConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(currentSpellClass, StringComparison.InvariantCultureIgnoreCase));
                }
                else
                {
                    // This span contains a card and quantity, so parse it
                    string           quantityAndCardString = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(cardOrSectionSpan);
                    ConverterMapping converterMapping      = TextConverter.RegexMatch_RegularCard(quantityAndCardString);
                    currentConverterSection.AddConverterMapping(converterMapping);
                }
            }

            return(converterDeck);
        }