/// <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);
        }
        internal static ConverterMapping ParseLineForCardAndQuantity(string line)
        {
            // Is the line a Comment?
            if (RegexMatch_Comment(line) != null)
            {
                return(null);
            }

            // The line is a regular main deck Card/Quantity entry, without any Set info?  Record it
            ConverterMapping potentialRegularCard = RegexMatch_RegularCard(line);

            if (potentialRegularCard != null)
            {
                return(potentialRegularCard);
            }

            ConverterMapping potentialRegularCardQuantityAfterName = RegexMatch_RegularCardQuantityAfterName(line);

            if (potentialRegularCardQuantityAfterName != null)
            {
                return(potentialRegularCardQuantityAfterName);
            }

            // The line is a main deck Card/Quantity entry with Set info?  Record it
            ConverterMapping potentialMWSMainDeckCard = File.MWS.RegexMatch_MWSMainDeckCard(line);

            if (potentialMWSMainDeckCard != null)
            {
                return(potentialMWSMainDeckCard);
            }

            // The line is not a valid card entry
            return(null);
        }
Ejemplo n.º 3
0
        /// <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));
            }
        }
        /// <summary>
        /// Initializes a new instance of the InlineDialogPage_ChooseAnotherCardVM class.
        /// </summary>
        /// <param name="converterMapping">The Converter Mapping which contains the parsed card name to match to</param>
        /// <param name="converterGame">The Converter Game to be used when searching for another card</param>
        public InlineDialogPage_ChooseAnotherCardVM(ConverterMapping converterMapping, ConverterGame converterGame)
        {
            if (converterGame == null)
            {
                throw new ArgumentNullException("converterGame");
            }

            this._ConverterGame   = converterGame;
            this.ConverterMapping = converterMapping;
        }
Ejemplo n.º 5
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);
            }
        }
Ejemplo n.º 6
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);
        }
        /// <summary>
        /// Handles the MouseEnter event on the Compare Cards DataGridRow
        /// </summary>
        /// <param name="sender">DataGridRow the mouse is now hovering over</param>
        /// <param name="e">The parameter is not used.</param>
        private void ConverterDataGridRow_MouseEnter(object sender, MouseEventArgs e)
        {
            DataGridRow dataGridRow = sender as DataGridRow;

            if (dataGridRow != null)
            {
                ConverterMapping converterMapping = dataGridRow.DataContext as ConverterMapping;
                this.MouseOverConverterCard
                (
                    converterMapping != null ?
                    converterMapping.SelectedOCTGNCard :
                    null
                );
            }
        }
Ejemplo n.º 8
0
        private static void VerifyConverterMappingsAreIdentical(ConverterMapping converterMapping, ConverterMapping otherConverterMapping)
        {
            Assert.AreEqual(converterMapping.CardName, otherConverterMapping.CardName);
            if (!string.IsNullOrWhiteSpace(converterMapping.CardSet) && !string.IsNullOrWhiteSpace(otherConverterMapping.CardSet))
            {
                Assert.AreEqual(converterMapping.CardSet, otherConverterMapping.CardSet);
            }
            Assert.AreEqual(converterMapping.Quantity, otherConverterMapping.Quantity);
            if (converterMapping.SelectedOCTGNCard != null)
            {
                VerifyConverterCardsAreIdentical(converterMapping.SelectedOCTGNCard, otherConverterMapping.SelectedOCTGNCard);
            }
            Assert.AreEqual(converterMapping.PotentialOCTGNCards.Count, otherConverterMapping.PotentialOCTGNCards.Count);

            foreach (ConverterCard potentialConverterCard in converterMapping.PotentialOCTGNCards)
            {
                ConverterCard otherPotentialConverterCard = otherConverterMapping.PotentialOCTGNCards.First(poc => poc.CardID == potentialConverterCard.CardID);
                VerifyConverterCardsAreIdentical(potentialConverterCard, otherPotentialConverterCard);
            }

            Assert.AreEqual(converterMapping.CardName, otherConverterMapping.CardName);
        }
        /// <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);
        }
        /// <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;
        }