Exemplo n.º 1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="CopySaveOptionsWindow"/> class.
 /// </summary>
 /// <param name="pto">The pto.</param>
 /// <param name="plan">The p.</param>
 /// <param name="isForCopy">if set to <c>true</c> [is for copy].</param>
 public CopySaveOptionsWindow(PlanExportSettings pto, Plan plan, bool isForCopy)
     : this()
 {
     m_planTextOptions = pto;
     m_plan = plan;
     m_isForCopy = isForCopy;
 }
Exemplo n.º 2
0
        public PrintOptionsDialog(PlanExportSettings pto, PrintDocument doc)
        {
            InitializeComponent();

            int index;
            string curPrinter = doc.PrinterSettings.PrinterName;

            m_pto = pto;

            foreach (String printer in PrinterSettings.InstalledPrinters)
            {
                index = comboPrinters.Items.Add(printer);

                doc.PrinterSettings.PrinterName = printer;
                if (doc.PrinterSettings.IsDefaultPrinter)
                    comboPrinters.SelectedIndex = index;
            }

            // If this dialog is cancelled, we dont want the name of the printer to have changed
            doc.PrinterSettings.PrinterName = curPrinter;

            EntryFinishDate = pto.EntryFinishDate;
            EntryNumber = pto.EntryNumber;
            EntryStartDate = pto.EntryStartDate;
            EntryTrainingTimes = pto.EntryTrainingTimes;
            EntryNotes = pto.EntryNotes;
            FooterCount = pto.FooterCount;
            FooterDate = pto.FooterDate;
            FooterTotalTime = pto.FooterTotalTime;
            IncludeHeader = pto.IncludeHeader;
        }
Exemplo n.º 3
0
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="plan">The plan.</param>
        private PlanPrinter(Plan plan)
        {
            m_plan = plan;
            m_plan.UpdateStatistics();

            m_character = (Character)plan.Character;
            m_settings = Settings.Exportation.PlanToText;

            m_font = FontFactory.GetFont("Arial", 10);
            m_boldFont = FontFactory.GetFont("Arial", 10, FontStyle.Bold | FontStyle.Underline);
        }
Exemplo n.º 4
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ExportationSettings"/> class.
 /// </summary>
 public ExportationSettings()
 {
     PlanToText = new PlanExportSettings();
 }
Exemplo n.º 5
0
        /// <summary>
        /// Exports the plan under a text format.
        /// </summary>
        /// <param name="planToExport"></param>
        /// <param name="settings"></param>
        /// <param name="exportActions"></param>
        /// <returns></returns>
        public static string ExportAsText(Plan planToExport, PlanExportSettings settings, ExportPlanEntryActions exportActions)
        {
            var plan = new PlanScratchpad(planToExport.Character, planToExport);
            plan.Sort(planToExport.SortingPreferences);
            plan.UpdateStatistics();

            var builder = new StringBuilder();
            var timeFormat = DescriptiveTextOptions.FullText | DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.SpaceText;
            var character = (Character)plan.Character;

            // Initialize constants
            string lineFeed = Environment.NewLine;
            string boldStart = String.Empty;
            string boldEnd = String.Empty;

            switch (settings.Markup)
            {
                default:
                    break;
                case MarkupType.Forum:
                    boldStart = "[b]";
                    boldEnd = "[/b]";
                    break;
                case MarkupType.Html:
                    lineFeed = String.Format(CultureInfo.InvariantCulture, "<br />{0}", Environment.NewLine);
                    boldStart = "<b>";
                    boldEnd = "</b>";
                    break;
            }

            // Header
            if (settings.IncludeHeader)
            {
                builder.Append(boldStart);
                if (settings.ShoppingList)
                {
                    builder.AppendFormat(CultureConstants.DefaultCulture, "Shopping list for {0}", character.Name);
                }
                else
                {
                    builder.AppendFormat(CultureConstants.DefaultCulture, "Skill plan for {0}", character.Name);
                }
                builder.Append(boldEnd);
                builder.Append(lineFeed);
                builder.Append(lineFeed);
            }

            // Scroll through entries
            int index = 0;
            DateTime endTime = DateTime.Now;
            foreach (PlanEntry entry in plan)
            {
                // Remapping point
                if (!settings.ShoppingList && entry.Remapping != null)
                {
                    builder.AppendFormat(CultureConstants.DefaultCulture, "***{0}***", entry.Remapping);
                    builder.Append(lineFeed);
                }

                // Skip is we're only build a shopping list
                bool shoppingListCandidate = !(entry.CharacterSkill.IsKnown || entry.Level != 1 || entry.CharacterSkill.IsOwned);
                if (settings.ShoppingList && !shoppingListCandidate) 
                    continue;

                // Entry's index
                index++;
                if (settings.EntryNumber)
                    builder.AppendFormat(CultureConstants.DefaultCulture, "{0}. ", index);

                // Name
                builder.Append(boldStart);

                if (settings.Markup.Equals(MarkupType.Html))
                {
                    if (!settings.ShoppingList)
                    {
                        builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showInfo({0})\">", entry.Skill.ID);
                    }
                    else
                    {
                        builder.AppendFormat(CultureConstants.DefaultCulture, "<a href=\"\" onclick=\"CCPEVE.showMarketDetails({0})\">", entry.Skill.ID);
                    }
                }
                builder.Append(entry.Skill.Name);

                if (settings.Markup == MarkupType.Html)
                    builder.Append("</a>");

                if (!settings.ShoppingList)
                    builder.AppendFormat(CultureConstants.DefaultCulture, " {0}", Skill.GetRomanForInt(entry.Level));

                builder.Append(boldEnd);

                // Training time
                if (settings.EntryTrainingTimes || settings.EntryStartDate || settings.EntryFinishDate || (settings.EntryCost && shoppingListCandidate))
                {
                    builder.Append(" (");
                    bool needComma = false;

                    // Training time
                    if (settings.EntryTrainingTimes)
                    {
                        needComma = true;
                        builder.Append(entry.TrainingTime.ToDescriptiveText(timeFormat));
                    }

                    // Training start date
                    if (settings.EntryStartDate)
                    {
                        if (needComma)
                            builder.Append("; ");

                        needComma = true;

                        builder.AppendFormat(CultureConstants.DefaultCulture, "Start: {0}", entry.StartTime);
                    }

                    // Training end date
                    if (settings.EntryFinishDate)
                    {
                        if (needComma)
                            builder.Append("; ");

                        needComma = true;

                        builder.AppendFormat(CultureConstants.DefaultCulture, "Finish: {0}", entry.EndTime);
                    }

                    // Skill cost
                    if (settings.EntryCost && shoppingListCandidate)
                    {
                        if (needComma)
                            builder.Append("; ");

                        needComma = true;

                        builder.AppendFormat(CultureConstants.DefaultCulture, "{0} ISK",  entry.Skill.FormattedCost);
                    }

                    builder.Append(')');
                }

                if (exportActions != null)
                    exportActions(builder, entry, settings);

                builder.Append(lineFeed);

                // End time
                endTime = entry.EndTime;
            }

            // Footer
            if (settings.FooterCount || settings.FooterTotalTime || settings.FooterDate || settings.FooterCost)
            {
                builder.AppendLine(lineFeed);
                bool needComma = false;

                // Skills count
                if (settings.FooterCount)
                {
                    builder.AppendFormat(CultureConstants.DefaultCulture, "{0}{1}{2}", boldStart, index, boldEnd);
                    builder.Append((index == 1 ? " skill" : " skills"));
                    needComma = true;
                }

                // Plan's training duration
                if (settings.FooterTotalTime)
                {
                    if (needComma)
                        builder.Append("; ");

                    needComma = true;

                    builder.AppendFormat(CultureConstants.DefaultCulture, "Total time: {0}{1}{2}",
                        boldStart, plan.GetTotalTime(null, true).ToDescriptiveText(timeFormat), boldEnd);
                }

                // End training date
                if (settings.FooterDate)
                {
                    if (needComma)
                        builder.Append("; ");

                    needComma = true;

                    builder.AppendFormat(CultureConstants.DefaultCulture, "Completion: {0}{1}{2}", boldStart, endTime, boldEnd);
                }

                // Total books cost
                if (settings.FooterCost)
                {
                    if (needComma)
                        builder.Append("; ");

                    needComma = true;

                    string formattedIsk = String.Format(CultureConstants.TidyInteger, "{0:n}", plan.NotKnownSkillBooksCost);
                    builder.AppendFormat(CultureConstants.DefaultCulture, "Cost: {0}{1}{2}", boldStart, formattedIsk, boldEnd);
                }

                // Warning about skill costs
                builder.Append(lineFeed);
                if (settings.FooterCost || settings.EntryCost)
                    builder.Append("N.B. Skill costs are based on CCP's database and are indicative only");
            }

            // Returns the text representation.
            return builder.ToString();
        }
Exemplo n.º 6
0
 /// <summary>
 /// Exports the plan under a text format.
 /// </summary>
 /// <param name="planToExport"></param>
 /// <param name="settings"></param>
 /// <returns></returns>
 public static string ExportAsText(Plan planToExport, PlanExportSettings settings)
 {
     return ExportAsText(planToExport, settings, null);
 }
Exemplo n.º 7
0
        /// <summary>
        /// Exports the plan under a text format.
        /// </summary>
        /// <param name="planToExport">The plan to export.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="exportActions">The export actions.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">
        /// planToExport
        /// or
        /// settings
        /// </exception>
        /// <exception cref="System.NotImplementedException"></exception>
        /// <exception cref="System.ArgumentNullException">planToExport or settings</exception>
        public static string ExportAsText(Plan planToExport, PlanExportSettings settings,
            Action<StringBuilder, PlanEntry, PlanExportSettings> exportActions = null)
        {
            planToExport.ThrowIfNull(nameof(planToExport));

            settings.ThrowIfNull(nameof(settings));

            PlanScratchpad plan = new PlanScratchpad(planToExport.Character, planToExport);
            plan.Sort(planToExport.SortingPreferences);
            plan.UpdateStatistics();

            StringBuilder builder = new StringBuilder();
            Character character = (Character)plan.Character;

            // Initialize constants
            string lineFeed = Environment.NewLine;
            string boldStart = String.Empty;
            string boldEnd = String.Empty;

            switch (settings.Markup)
            {
                case MarkupType.Forum:
                    boldStart = "[b]";
                    boldEnd = "[/b]";
                    break;
                case MarkupType.Html:
                    lineFeed = $"<br />{Environment.NewLine}";
                    boldStart = "<b>";
                    boldEnd = "</b>";
                    break;
                case MarkupType.Undefined:
                case MarkupType.None:
                    break;
                default:
                    throw new NotImplementedException();
            }

            // Header
            if (settings.IncludeHeader)
            {
                builder.Append(boldStart)
                    .Append($"{(settings.ShoppingList ? "Shopping list " : "Skill plan ")} for {character.Name}")
                    .Append(boldEnd)
                    .Append(lineFeed)
                    .Append(lineFeed);
            }

            // Scroll through entries
            int index = 0;
            DateTime endTime = DateTime.Now;
            foreach (PlanEntry entry in plan)
            {
                // Skip is we're only build a shopping list
                bool shoppingListCandidate = !(entry.CharacterSkill.IsKnown || entry.Level != 1 || entry.CharacterSkill.IsOwned);
                if (settings.ShoppingList && !shoppingListCandidate)
                    continue;

                // Remapping point
                if (!settings.ShoppingList && (entry.Remapping != null) && settings.RemappingPoints)
                {
                    builder
                        .Append($"***{entry.Remapping}***")
                        .Append(lineFeed);
                }

                // Entry's index
                index++;
                if (settings.EntryNumber)
                    builder.Append($"{index}. ");

                // Name
                builder.Append(boldStart);
                AddName(settings, entry, builder);
                builder.Append(boldEnd);

                // Training time
                AddTrainingTime(settings, shoppingListCandidate, entry, builder);

                exportActions?.Invoke(builder, entry, settings);

                builder.Append(lineFeed);

                // End time
                endTime = entry.EndTime;
            }

            // Footer
            AddFooter(settings, boldEnd, index, endTime, builder, lineFeed, plan, boldStart);

            // Returns the text representation.
            return builder.ToString().TrimEnd(Environment.NewLine.ToCharArray());
        }
Exemplo n.º 8
0
        /// <summary>
        /// Adds the training time.
        /// </summary>
        /// <param name="settings">The settings.</param>
        /// <param name="shoppingListCandidate">if set to <c>true</c> [shopping list candidate].</param>
        /// <param name="entry">The entry.</param>
        /// <param name="builder">The builder.</param>
        private static void AddTrainingTime(PlanExportSettings settings, bool shoppingListCandidate, PlanEntry entry, StringBuilder builder)
        {
            if (!settings.EntryTrainingTimes && !settings.EntryStartDate && !settings.EntryFinishDate &&
                (!settings.EntryCost || !shoppingListCandidate))
                return;

            const DescriptiveTextOptions TimeFormat = DescriptiveTextOptions.FullText
                                                      | DescriptiveTextOptions.IncludeCommas
                                                      | DescriptiveTextOptions.SpaceText;

            builder.Append(" (");
            bool needComma = false;

            // Training time
            if (settings.EntryTrainingTimes)
            {
                needComma = true;
                builder.Append(entry.TrainingTime.ToDescriptiveText(TimeFormat));
            }

            // Training start date
            if (settings.EntryStartDate)
            {
                if (needComma)
                    builder.Append("; ");

                needComma = true;

                builder.Append($"Start: {entry.StartTime.ToUniversalTime().DateTimeToTimeString()} UTC");
            }

            // Training end date
            if (settings.EntryFinishDate)
            {
                if (needComma)
                    builder.Append("; ");

                needComma = true;

                builder.Append($"Finish: {entry.EndTime.ToUniversalTime().DateTimeToTimeString()} UTC");
            }

            // Skill cost
            if (settings.EntryCost && shoppingListCandidate)
            {
                if (needComma)
                    builder.Append("; ");

                builder.Append(FormattableString.Invariant($"Cost: {entry.Skill.Cost:N0} ISK"));
            }

            builder.Append(')');
        }
Exemplo n.º 9
0
        /// <summary>
        /// Adds the name.
        /// </summary>
        /// <param name="settings">The settings.</param>
        /// <param name="entry">The entry.</param>
        /// <param name="builder">The builder.</param>
        private static void AddName(PlanExportSettings settings, ISkillLevel entry, StringBuilder builder)
        {
            if (settings.Markup == MarkupType.Html)
            {
                builder.Append("<a href=\"\" onclick=\"CCPEVE.show" +
                               $"{(!settings.ShoppingList ? "Info" : "MarketDetails")}({entry.Skill.ID})\">");
            }
            builder.Append(entry.Skill.Name);

            if (settings.Markup == MarkupType.Html)
                builder.Append("</a>");

            if (!settings.ShoppingList)
                builder.Append($" {Skill.GetRomanFromInt(entry.Level)}");
        }
Exemplo n.º 10
0
        /// <summary>
        /// Adds the footer.
        /// </summary>
        /// <param name="settings">The settings.</param>
        /// <param name="boldEnd">The bold end.</param>
        /// <param name="index">The index.</param>
        /// <param name="endTime">The end time.</param>
        /// <param name="builder">The builder.</param>
        /// <param name="lineFeed">The line feed.</param>
        /// <param name="plan">The plan.</param>
        /// <param name="boldStart">The bold start.</param>
        private static void AddFooter(PlanExportSettings settings, string boldEnd, int index, DateTime endTime, StringBuilder builder,
            string lineFeed, BasePlan plan, string boldStart)
        {
            if (!settings.FooterCount && !settings.FooterTotalTime && !settings.FooterDate && !settings.FooterCost)
                return;

            builder.AppendLine(lineFeed);
            bool needComma = false;

            // Skills count
            if (settings.FooterCount)
            {
                builder
                    .Append($"{boldStart}{plan.GetUniqueSkillsCount()}{boldEnd} " +
                            $"unique skill{(plan.GetUniqueSkillsCount() == 1 ? String.Empty : "s")}, ")
                    .Append($"{boldStart}{index}{boldEnd} skill level{(index == 1 ? String.Empty : "s")}");

                needComma = true;
            }

            // Plan's training duration
            if (settings.FooterTotalTime)
            {
                const DescriptiveTextOptions TimeFormat =
                    DescriptiveTextOptions.FullText | DescriptiveTextOptions.IncludeCommas | DescriptiveTextOptions.SpaceText;

                if (needComma)
                    builder.Append("; ");

                needComma = true;

                builder.Append($"Total time: {boldStart}{plan.TotalTrainingTime.ToDescriptiveText(TimeFormat)}{boldEnd}");
            }

            // End training date
            if (settings.FooterDate)
            {
                if (needComma)
                    builder.Append("; ");

                needComma = true;

                builder.Append($"Completion: {boldStart}{endTime.ToUniversalTime().DateTimeToTimeString()}{boldEnd} UTC");
            }

            // Total books cost
            if (settings.FooterCost)
            {
                if (needComma)
                    builder.Append("; ");

                string formattedIsk = FormattableString.Invariant($"{plan.NotKnownSkillBooksCost:N0}");
                builder.Append($"Cost: {boldStart}{formattedIsk}{boldEnd} ISK");
            }

            // Warning about skill costs
            builder.Append(lineFeed);
            if (settings.FooterCost || settings.EntryCost)
                builder.Append("N.B. Skill costs are based on CCP's database and are indicative only");
        }
Exemplo n.º 11
0
        /// <summary>
        /// Outputs a plan or shopping list for a given character to a stream writer.
        /// </summary>
        /// <param name="context">context of the request</param>
        /// <param name="requestPath">url of the request</param>
        /// <param name="sw">stream writer to output to</param>
        /// <param name="character">character to use</param>
        private static void GeneratePlanOrShoppingOutput(string context, string requestPath, TextWriter sw, Character character)
        {
            sw.WriteLine("<h1>Hello, {0}</h1>", HttpUtility.HtmlEncode(character.Name));
            sw.WriteLine("<a href=\"/characters\">List all characters</a><hr/>");
            sw.WriteLine("<a href=\"{0}\">Character overview</a>", context);

            Regex regex =
                new Regex(
                    @"\/(owned\/(?'skillId'[^\/]+)\/(?'markOwned'[^\/]+)\/)?(?'requestType'shopping|plan)\/(?'planName'[^\/]+)(.*)",
                    RegexOptions.CultureInvariant | RegexOptions.Compiled);
            Match match = regex.Match(requestPath);

            if (match.Success)
            {
                string requestType = match.Groups["requestType"].Value;
                bool shopping = requestType.Equals("shopping", StringComparison.OrdinalIgnoreCase);
                string planName = HttpUtility.UrlDecode(match.Groups["planName"].Value);

                int skillId;
                bool setAsOwned;
                if (match.Groups["skillId"].Success &&
                    match.Groups["markOwned"].Success &&
                    Int32.TryParse(match.Groups["skillId"].Value, out skillId) &&
                    Boolean.TryParse(match.Groups["markOwned"].Value, out setAsOwned))
                {
                    Skill skill = character.Skills.FirstOrDefault(x => x.ID == skillId);
                    if (skill != null)
                    {
                        sw.WriteLine("<h2>Skillbook shopping result</h2>");
                        skill.IsOwned = setAsOwned;
                        sw.WriteLine("<a href=\"\" onclick=\"CCPEVE.showInfo({0})\">{1}</a> is now marked as {2} owned.", skill.ID,
                            HttpUtility.HtmlEncode(skill.Name), skill.IsOwned ? String.Empty : "not");
                    }
                    else
                    {
                        // Display an error message
                        sw.WriteLine("<h2>Error Message</h2>");
                        sw.WriteLine("Skill with id '{0}' could not be found", skillId);
                    }
                    sw.WriteLine("<hr/>");
                }

                Plan plan = character.Plans[planName];
                if (plan == null)
                {
                    // Display an error message
                    sw.WriteLine("<h2>Error Message</h2>");
                    sw.WriteLine("A plan named \"{0}\" does not exist.", HttpUtility.HtmlEncode(planName));
                }
                else
                {
                    sw.WriteLine("<h2>Plan: {0}</h2>", HttpUtility.HtmlEncode(plan.Name));

                    PlanExportSettings x = new PlanExportSettings
                    {
                        // Only if not shopping
                        EntryTrainingTimes = !shopping,
                        // Only if not shopping
                        EntryStartDate = !shopping,
                        // Only if not shopping
                        EntryFinishDate = !shopping,
                        // Only if not shopping
                        FooterTotalTime = !shopping,
                        // Only if not shopping
                        FooterDate = !shopping,
                        FooterCount = true,
                        ShoppingList = shopping,
                        EntryCost = true,
                        FooterCost = true,
                        Markup = MarkupType.Html
                    };

                    sw.Write(PlanIOHelper.ExportAsText(plan, x, ExportActions(context, requestType, plan)));
                }
            }
            else
            {
                sw.WriteLine("<h2>Error Message</h2>");
                sw.WriteLine("Invalid request");
            }

            sw.WriteLine("<br/><br/><a href=\"{0}\">Character overview</a>", context);
            sw.WriteLine("<hr/><a href=\"/characters\">List all characters</a>");
        }
Exemplo n.º 12
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ExportationSettings"/> class.
 /// </summary>
 public ExportationSettings()
 {
     PlanToText = new PlanExportSettings();
 }
Exemplo n.º 13
0
        /// <summary>
        /// Outputs a plan or shopping list for a given character to a stream writer
        /// </summary>
        /// <param name="context">context of the request</param>
        /// <param name="requestUrl">url of the request</param>
        /// <param name="sw">stream writer to output to</param>
        /// <param name="character">character to use</param>
        private static void GeneratePlanOrShoppingOutput(string context, string requestUrl, StreamWriter sw, Character character)
        {
            WriteDocumentHeader(sw);
            sw.WriteLine("<h1>Hello, {0}</h1>", HttpUtility.HtmlEncode(character.Name));
            sw.WriteLine("<a href=\"/characters\">List all characters</a><hr/>");
            sw.WriteLine("<a href=\"{0}\">Character overview</a>", context);

            var regex = new Regex(@"\/(owned\/(?'skillId'[^\/]+)\/(?'markOwned'[^\/]+)\/)?(?'requestType'shopping|plan)\/(?'planName'[^\/]+)(.*)", RegexOptions.CultureInvariant | RegexOptions.Compiled);
            var match = regex.Match(requestUrl);

            if (match.Success)
            {
                var requestType = match.Groups["requestType"].Value;
                var shopping = requestType.Equals("shopping", StringComparison.OrdinalIgnoreCase);
                var planName = HttpUtility.UrlDecode(match.Groups["planName"].Value);

                int skillId;
                bool setAsOwned;
                if (match.Groups["skillId"].Success &&
                    match.Groups["markOwned"].Success &&
                    Int32.TryParse(match.Groups["skillId"].Value, out skillId) &&
                    Boolean.TryParse(match.Groups["markOwned"].Value, out setAsOwned))
                {
                    var skill = character.Skills.FirstOrDefault(x => x.ID == skillId);
                    if (skill != null)
                    {
                        sw.WriteLine("<h2>Skillbook shopping result</h2>");
                        Dispatcher.Invoke(() => skill.IsOwned = setAsOwned);
                        sw.WriteLine("<a href=\"\" onclick=\"CCPEVE.showInfo({0})\">{1}</a> is now marked as {2} owned.", skill.ID, HttpUtility.HtmlEncode(skill.Name), skill.IsOwned ? String.Empty : "not");
                    }
                    else
                    {
                        // Display an error message
                        sw.WriteLine("<h2>Error Message</h2>");
                        sw.WriteLine("Skill with id '{0}' could not be found", skillId);
                    }
                    sw.WriteLine("<hr/>");
                }

                Plan p = character.Plans[planName];
                if (p == null)
                {
                    // Display an error message
                    sw.WriteLine("<h2>Error Message</h2>");
                    sw.WriteLine("A plan named \"{0}\" does not exist.", HttpUtility.HtmlEncode(planName));
                }
                else
                {
                    sw.WriteLine("<h2>Plan: {0}</h2>", HttpUtility.HtmlEncode(p.Name));

                    PlanExportSettings x = new PlanExportSettings();
                    x.EntryTrainingTimes = !shopping; // only if not shopping
                    x.EntryStartDate = !shopping; // only if not shopping
                    x.EntryFinishDate = !shopping; // only if not shopping
                    x.FooterTotalTime = !shopping; // only if not shopping
                    x.FooterCount = true;
                    x.FooterDate = !shopping; // only if not shopping
                    x.ShoppingList = shopping;
                    x.EntryCost = true;
                    x.FooterCost = true;
                    x.Markup = MarkupType.Html;
                    sw.Write(PlanExporter.ExportAsText(p, x, (builder, entry, settings) =>
                    {
                        if (settings.Markup != MarkupType.Html)
                            return;

                        // Skill is known
                        if (entry.CharacterSkill.IsKnown || entry.Level != 1)
                            return;

                        builder.AppendFormat(CultureConstants.DefaultCulture, " <a href='{0}/owned/{1}/{2}/{4}/{5}'>{3}</a>", 
                                             context,
                                             entry.Skill.ID,
                                             !entry.CharacterSkill.IsOwned,
                                             HttpUtility.HtmlEncode(!entry.CharacterSkill.IsOwned ? "Mark as owned" : "Mark as not owned"),
                                             requestType,
                                             HttpUtility.HtmlEncode(p.Name));
                    }));
                }
            }
            else
            {
                sw.WriteLine("<h2>Error Message</h2>");
                sw.WriteLine("Invalid request");
            }

            sw.WriteLine("<br/><br/><a href=\"{0}\">Character overview</a>", context);
            sw.WriteLine("<hr/><a href=\"/characters\">List all characters</a>");
            WriteDocumentFooter(sw);
        }