Example #1
0
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Invoked by the ProgressDialog OnShown callback
        private async Task Execute(UI.ProgressDialog progress, CancellationToken token)
        {
            logger.Start();
            logger.StartClock();

            using (var one = new OneNote())
            {
                var hierarchy = await GetHierarchy(one);

                var ns = one.GetNamespace(hierarchy);

                var pageList = hierarchy.Descendants(ns + "Page")
                               .Where(e => e.Attribute("isInRecycleBin") == null);

                var pageCount = pageList.Count();
                progress.SetMaximum(pageCount);
                progress.SetMessage($"Scanning {pageCount} pages");

                // OneNote likes to inject \n\r before the href attribute so match any spaces
                var editor = new Regex(
                    $"(<a\\s+href=[^>]+{keys}[^>]+>)([^<]*)(</a>)",
                    RegexOptions.Compiled | RegexOptions.Multiline);

                foreach (var item in pageList)
                {
                    progress.Increment();
                    progress.SetMessage(item.Attribute("name").Value);

                    var xml = one.GetPageXml(item.Attribute("ID").Value, OneNote.PageDetail.Basic);

                    // initial string scan before instantiating entire XElement DOM
                    if (xml.Contains(keys))
                    {
                        var page = new Page(XElement.Parse(xml));

                        var blocks = page.Root.DescendantNodes().OfType <XCData>()
                                     .Where(n => n.Value.Contains(keys))
                                     .ToList();

                        foreach (var block in blocks)
                        {
                            block.Value = editor.Replace(block.Value, $"$1{title}$3");
                        }

                        await one.Update(page);

                        updates++;
                    }

                    if (token.IsCancellationRequested)
                    {
                        logger.WriteLine("cancelled");
                        break;
                    }
                }
            }

            logger.WriteTime("refresh complete");
            logger.End();
        }
Example #2
0
        private void ExportMany(OneNote one, List <string> pageIDs)
        {
            OneNote.ExportFormat format;
            string path;

            using (var dialog = new ExportDialog(pageIDs.Count))
            {
                if (dialog.ShowDialog(owner) != DialogResult.OK)
                {
                    return;
                }

                path   = dialog.FolderPath;
                format = dialog.Format;
            }

            string ext = null;

            switch (format)
            {
            case OneNote.ExportFormat.HTML: ext = ".htm"; break;

            case OneNote.ExportFormat.PDF: ext = ".pdf"; break;

            case OneNote.ExportFormat.Word: ext = ".docx"; break;

            case OneNote.ExportFormat.XML: ext = ".xml"; break;

            case OneNote.ExportFormat.OneNote: ext = ".one"; break;
            }

            string formatName = format.ToString();

            using (var progress = new UI.ProgressDialog())
            {
                progress.SetMaximum(pageIDs.Count);
                progress.Show(owner);

                foreach (var pageID in pageIDs)
                {
                    var page     = one.GetPage(pageID, OneNote.PageDetail.BinaryData);
                    var filename = Path.Combine(path, page.Title.Replace(' ', '_') + ext);

                    progress.SetMessage(filename);
                    progress.Increment();

                    if (format == OneNote.ExportFormat.XML)
                    {
                        SaveAsXML(page.Root, filename);
                    }
                    else
                    {
                        SaveAs(one, page.PageId, filename, format, formatName);
                    }
                }
            }

            UIHelper.ShowMessage(string.Format(Resx.SaveAsMany_Success, pageIDs.Count, path));
        }
        public override async Task Execute(params object[] args)
        {
            logger.StartClock();

            using (var one = new OneNote())
            {
                var section = one.GetSection();
                if (section != null)
                {
                    var ns = one.GetNamespace(section);

                    var pageIds = section.Elements(ns + "Page")
                                  .Select(e => e.Attribute("ID").Value)
                                  .ToList();

                    using (var progress = new UI.ProgressDialog())
                    {
                        progress.SetMaximum(pageIds.Count);
                        progress.Show(owner);

                        foreach (var pageId in pageIds)
                        {
                            var page = one.GetPage(pageId, OneNote.PageDetail.Basic);
                            var name = page.Root.Attribute("name").Value;

                            progress.SetMessage(string.Format(
                                                    Properties.Resources.RemovingPageNumber_Message, name));

                            progress.Increment();

                            if (string.IsNullOrEmpty(name))
                            {
                                continue;
                            }

                            if (RemoveNumbering(name, out string clean))
                            {
                                page.Root
                                .Element(ns + "Title")
                                .Element(ns + "OE")
                                .Element(ns + "T")
                                .GetCData().Value = clean;

                                await one.Update(page);
                            }
                        }
                    }
                }
            }

            logger.StopClock();
            logger.WriteTime("removed page numbering");
        }
Example #4
0
        private async Task DownloadSelectedSubpages(
            UI.ProgressDialog progress, CancellationToken token)
        {
            var updated = false;

            foreach (var selection in selections)
            {
                progress.SetMessage(selection.Address);
                //logger.WriteLine($"fetching {selection.Address}");

                var page = await importer
                           .ImportSubpage(one, parentPage, new Uri(selection.Address), token);

                if (page != null)
                {
                    var pageUri = one.GetHyperlink(page.PageId, string.Empty);

                    // redirect primary reference on parent page to our new subpage
                    PatchCData(selection.CData, selection.Address, selection.Text, pageUri);

                    // redirect duplicate references on parent page
                    if (selection.RefCount > 1)
                    {
                        var ns    = parentPage.Namespace;
                        var regex = new Regex($@"<a\s+href=""{Regex.Escape(selection.Address)}""");

                        var cdatas = parentPage.Root.Elements(ns + "Outline")
                                     .DescendantNodes().OfType <XCData>()
                                     .Where(d => d != selection.CData && regex.IsMatch(d.Value));

                        foreach (var cdata in cdatas)
                        {
                            PatchCData(cdata, selection.Address, selection.Text, pageUri);
                        }
                    }

                    updated = true;
                }

                progress.Increment();
            }

            if (updated)
            {
                await one.Update(parentPage);

                await one.NavigateTo(parentPage.PageId);
            }
        }
Example #5
0
        private Dictionary <string, string> GetHyperlinks(
            UI.ProgressDialog progress, CancellationToken token)
        {
            var catalog = fullCatalog ? OneNote.Scope.Notebooks : scope;

            return(one.BuildHyperlinkCache(catalog, token,
                                           (count) =>
            {
                progress.SetMaximum(count);
                progress.SetMessage($"Scanning {count} page references");
            },
                                           () =>
            {
                progress.Increment();
            }));
        }
Example #6
0
        private async Task CopyPages(XElement root, XElement clone, OneNote one, XNamespace ns)
        {
            var cloneID = clone.Attribute("ID").Value;

            foreach (var element in root.Elements(ns + "Page"))
            {
                // get the page to copy
                var page = one.GetPage(element.Attribute("ID").Value);
                progress.SetMessage(page.Title);

                // create a new page to get a new ID
                one.CreatePage(cloneID, out var newPageId);

                // set the page ID to the new page's ID
                page.Root.Attribute("ID").Value = newPageId;

                // remove all objectID values and let OneNote generate new IDs
                page.Root.Descendants().Attributes("objectID").Remove();

                await one.Update(page);

                progress.Increment();
            }

            // recurse...

            // NOTE that these find target sections by name, so the names must be unique otherwise
            // this will copy all pages into the first occurance with a matching name!

            foreach (var section in root.Elements(ns + "SectionGroup").Elements(ns + "Section"))
            {
                var cloneSection = clone.Elements(ns + "SectionGroup").Elements(ns + "Section")
                                   .FirstOrDefault(e => e.Attribute("name").Value == section.Attribute("name").Value);

                await CopyPages(section, cloneSection, one, ns);
            }

            foreach (var section in root.Elements(ns + "Section"))
            {
                var cloneSection = clone.Elements(ns + "Section")
                                   .FirstOrDefault(e => e.Attribute("name").Value == section.Attribute("name").Value);

                await CopyPages(section, cloneSection, one, ns);
            }
        }
Example #7
0
        private async Task <Dictionary <string, OneNote.HyperlinkInfo> > GetHyperlinks(
            UI.ProgressDialog progress, CancellationToken token)
        {
            var catalog = fullCatalog ? OneNote.Scope.Notebooks : scope;

            return(await one.BuildHyperlinkMap(catalog, token,
                                               async (count) =>
            {
                progress.SetMaximum(count);
                progress.SetMessage($"Scanning {count} page references");
                await Task.Yield();
            },
                                               async() =>
            {
                progress.Increment();
                await Task.Yield();
            }));
        }
Example #8
0
        public async Task IndexPages(List <string> pageIds)
        {
            string indexId = null;

            using (var progress = new UI.ProgressDialog())
            {
                progress.SetMaximum(pageIds.Count);
                progress.Show(owner);

                // create a new page to get a new ID
                one.CreatePage(sectionId, out indexId);
                var indexPage = one.GetPage(indexId);

                indexPage.Title = "Page Index";

                var container = indexPage.EnsureContentContainer();

                foreach (var pageId in pageIds)
                {
                    // get the page to copy
                    var page = one.GetPage(pageId);
                    var ns   = page.Namespace;

                    progress.SetMessage(page.Title);
                    progress.Increment();

                    var link = one.GetHyperlink(page.PageId, string.Empty);

                    container.Add(new XElement(ns + "OE",
                                               new XElement(ns + "T",
                                                            new XCData($"<a href=\"{link}\">{page.Title}</a>"))
                                               ));
                }

                await one.Update(indexPage);
            }

            // navigate after progress dialog is closed otherwise it will hang!
            if (indexId != null)
            {
                await one.NavigateTo(indexId);
            }
        }
Example #9
0
        public async Task BuildHyperlinkMap(
            OneNote.Scope scope, UI.ProgressDialog progress, CancellationToken token)
        {
            logger.WriteLine("building hyperlink map");

            map = await one.BuildHyperlinkMap(
                scope,
                token,
                async (count) =>
            {
                progress.SetMaximum(count);
                progress.SetMessage($"Scanning {count} page references");
                await Task.Yield();
            },
                async() =>
            {
                progress.Increment();
                await Task.Yield();
            });
        }
Example #10
0
        public async Task CopyPages(List <string> pageIds)
        {
            string lastId = null;

            using (var progress = new UI.ProgressDialog())
            {
                progress.SetMaximum(pageIds.Count);
                progress.Show(owner);

                foreach (var pageId in pageIds)
                {
                    if (one.GetParent(pageId) == sectionId)
                    {
                        continue;
                    }

                    // get the page to copy
                    var page = one.GetPage(pageId);
                    progress.SetMessage(page.Title);

                    // create a new page to get a new ID
                    one.CreatePage(sectionId, out var newPageId);

                    // set the page ID to the new page's ID
                    page.Root.Attribute("ID").Value = newPageId;
                    // remove all objectID values and let OneNote generate new IDs
                    page.Root.Descendants().Attributes("objectID").Remove();
                    await one.Update(page);

                    lastId = newPageId;

                    progress.Increment();
                }
            }

            // navigate after progress dialog is closed otherwise it will hang!
            if (lastId != null)
            {
                await one.NavigateTo(lastId);
            }
        }
Example #11
0
        private async Task Toggle(bool pageOnly, bool showTimestamps)
        {
            using (var one = new OneNote())
            {
                if (pageOnly)
                {
                    var page = one.GetPage();
                    await SetTimestampVisibility(one, page, showTimestamps);
                }
                else
                {
                    var section = one.GetSection();
                    if (section != null)
                    {
                        var ns = one.GetNamespace(section);

                        var pageIds = section.Elements(ns + "Page")
                                      .Select(e => e.Attribute("ID").Value)
                                      .ToList();

                        using (var progress = new UI.ProgressDialog())
                        {
                            progress.SetMaximum(pageIds.Count);
                            progress.Show(owner);

                            foreach (var pageId in pageIds)
                            {
                                var page = one.GetPage(pageId, OneNote.PageDetail.Basic);
                                var name = page.Root.Attribute("name").Value;

                                progress.SetMessage(name);
                                progress.Increment();

                                await SetTimestampVisibility(one, page, showTimestamps);
                            }
                        }
                    }
                }
            }
        }
Example #12
0
        private async Task <int> ApplyNumbering(
            List <PageBasics> pages, int index, int level, bool numeric, string prefix)
        {
            int counter = 1;

            while (index < pages.Count && pages[index].Level == level)
            {
                var page = one.GetPage(pages[index].ID, OneNote.PageDetail.Basic);

                var cdata = page.Root.Element(ns + "Title")
                            .Element(ns + "OE")
                            .Element(ns + "T")
                            .GetCData();

                progress.SetMessage(string.Format(Properties.Resources.NumberingPage_Message, pages[index].Name));
                progress.Increment();

                var text = cdata.Value;
                if (cleaner != null)
                {
                    cleaner.RemoveNumbering(cdata.Value, out text);
                }

                cdata.Value = BuildPrefix(counter, numeric, level, prefix) + " " + text;
                await one.Update(page);

                index++;
                counter++;

                if (index < pages.Count && pages[index].Level > level)
                {
                    index = await ApplyNumbering(
                        pages, index, pages[index].Level,
                        numeric, $"{prefix}{counter - 1}.");
                }
            }

            return(index);
        }
Example #13
0
        private async Task Archive(UI.ProgressDialog progress, XElement root, string path)
        {
            foreach (var element in root.Elements())
            {
                if (element.Name.LocalName == "Page")
                {
                    var page = one.GetPage(
                        element.Attribute("ID").Value, OneNote.PageDetail.BinaryData);

                    progress.SetMessage($"Archiving {page.Title}");
                    progress.Increment();

                    await ArchivePage(element, page, path);
                }
                else
                {
                    // SectionGroup or Section

                    element.ReadAttributeValue("locked", out bool locked, false);
                    if (locked)
                    {
                        continue;
                    }

                    element.ReadAttributeValue("isRecycleBin", out bool recycle, false);
                    if (recycle)
                    {
                        continue;
                    }

                    // append name of Section/Group to path to build zip folder path
                    var name = element.Attribute("name").Value;

                    await Archive(progress, element, Path.Combine(path, name));
                }
            }
        }
Example #14
0
        private void Export(List <string> pageIDs)
        {
            OneNote.ExportFormat format;
            string path;
            bool   withAttachments;
            bool   useUnderscores;

            // dialog...

            using (var dialog = new ExportDialog(pageIDs.Count))
            {
                if (dialog.ShowDialog(owner) != DialogResult.OK)
                {
                    return;
                }

                path            = dialog.FolderPath;
                format          = dialog.Format;
                withAttachments = dialog.WithAttachments;
                useUnderscores  = dialog.UseUnderscores;
            }

            // prepare...

            string ext = null;

            switch (format)
            {
            case OneNote.ExportFormat.HTML: ext = ".htm"; break;

            case OneNote.ExportFormat.PDF: ext = ".pdf"; break;

            case OneNote.ExportFormat.Word: ext = ".docx"; break;

            case OneNote.ExportFormat.XML: ext = ".xml"; break;

            case OneNote.ExportFormat.Markdown: ext = ".md"; break;

            case OneNote.ExportFormat.OneNote: ext = ".one"; break;
            }

            // export...

            using (var progress = new UI.ProgressDialog())
            {
                progress.SetMaximum(pageIDs.Count);
                progress.Show(owner);

                var archivist = new Archivist(one);

                foreach (var pageID in pageIDs)
                {
                    var page = one.GetPage(pageID, OneNote.PageDetail.BinaryData);

                    var title = useUnderscores
                                                ? PathFactory.CleanFileName(page.Title).Replace(' ', '_')
                                                : page.Title;

                    var filename = Path.Combine(path, title + ext);

                    progress.SetMessage(filename);
                    progress.Increment();

                    if (format == OneNote.ExportFormat.HTML)
                    {
                        if (withAttachments)
                        {
                            archivist.ExportHTML(page, ref filename);
                        }
                        else
                        {
                            archivist.Export(page.PageId, filename, OneNote.ExportFormat.HTML);
                        }
                    }
                    else if (format == OneNote.ExportFormat.XML)
                    {
                        archivist.ExportXML(page.Root, filename, withAttachments);
                    }
                    else if (format == OneNote.ExportFormat.Markdown)
                    {
                        archivist.ExportMarkdown(page, filename, withAttachments);
                    }
                    else
                    {
                        archivist.Export(page.PageId, filename, format, withAttachments);
                    }
                }
            }

            SaveDefaultPath(path);

            UIHelper.ShowMessage(string.Format(Resx.SaveAsMany_Success, pageIDs.Count, path));
        }
Example #15
0
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Invoked by the ProgressDialog OnShown callback
        private async Task Execute(UI.ProgressDialog progress, CancellationToken token)
        {
            logger.Start();
            logger.StartClock();

            using (one = new OneNote())
            {
                if (page == null)
                {
                    page = one.GetPage();
                    ns   = page.Namespace;
                }

                PageNamespace.Set(ns);

                var title = Unstamp(page.Title);

                string startId = string.Empty;
                switch (scope)
                {
                case OneNote.Scope.Sections: startId = one.CurrentNotebookId; break;

                case OneNote.Scope.Pages: startId = one.CurrentSectionId; break;
                }

                logger.WriteLine($"searching for '{title}'");
                var results = one.Search(startId, title, unindexed);

                if (token.IsCancellationRequested)
                {
                    logger.WriteLine("cancelled");
                    return;
                }

                //logger.WriteLine(results);

                var referals = FlattenPages(results, page.PageId);

                var total = referals.Count();
                if (total == 0)
                {
                    UIHelper.ShowInfo(Resx.LinkReferencesCommand_noref);
                    return;
                }

                // initialize search-and-replace editor...

                var whatText = $@"\b{SearchAndReplaceEditor.EscapeEscapes(title)}\b";
                var pageLink = one.GetHyperlink(page.PageId, string.Empty);

                var withElement = new XElement("A",
                                               new XAttribute("href", pageLink),
                                               page.Title
                                               );

                var editor = new SearchAndReplaceEditor(whatText, withElement,
                                                        useRegex: true,
                                                        caseSensitive: false
                                                        );

                // process pages...

                progress.SetMaximum(total);
                progress.SetMessage($"Linking for {total} pages");

                var updates = 0;
                foreach (var referal in referals)
                {
                    progress.Increment();
                    progress.SetMessage(referal.Attribute(NameAttr).Value);

                    var refpage = one.GetPage(referal.Attribute("ID").Value, OneNote.PageDetail.Basic);

                    logger.WriteLine($"searching for '{whatText}' on {refpage.Title}");

                    var count = editor.SearchAndReplace(refpage);
                    if (count > 0)
                    {
                        await one.Update(refpage);

                        referal.SetAttributeValue(LinkedAttr, "true");
                        referal.SetAttributeValue(SynopsisAttr, GetSynopsis(refpage));
                        updates++;
                    }
                    else
                    {
                        logger.WriteLine($"search not found on {referal.Attribute(NameAttr).Value}");
                    }

                    if (token.IsCancellationRequested)
                    {
                        logger.WriteLine("cancelled");
                        break;
                    }
                }

                if (updates > 0)
                {
                    // even if cancellation is request, must update page with referals that were
                    // modified, otherwise, there will be referal pages that link to this page
                    // without this page referring back!

                    AppendReferalBlock(page, referals);
                    await one.Update(page);
                }
            }

            logger.WriteTime("linking complete");
            logger.End();
        }
Example #16
0
        public async Task MovePages(List <string> pageIds)
        {
            var sections = new Dictionary <string, XElement>();
            var section  = one.GetSection(sectionId);
            var ns       = one.GetNamespace(section);

            var updated = false;

            using (var progress = new UI.ProgressDialog())
            {
                progress.SetMaximum(pageIds.Count);
                progress.Show(owner);

                foreach (var pageId in pageIds)
                {
                    // find the section that currently owns the page
                    var parentId = one.GetParent(pageId);
                    if (parentId == sectionId)
                    {
                        continue;
                    }

                    // load the owning section
                    XElement parent;
                    if (sections.ContainsKey(parentId))
                    {
                        parent = sections[parentId];
                    }
                    else
                    {
                        parent = one.GetSection(parentId);
                        sections.Add(parentId, parent);
                    }

                    // get the Page reference within the owing section
                    var element = parent.Elements(ns + "Page")
                                  .FirstOrDefault(e => e.Attribute("ID").Value == pageId);

                    if (element != null)
                    {
                        progress.SetMessage(element.Attribute("name").Value);

                        // remove page from current owner
                        element.Remove();

                        // remove misc attributes; OneNote will recreate them
                        element.Attributes()
                        .Where(a => a.Name != "ID" && a.Name != "name")
                        .Remove();

                        // add page to target section
                        section.Add(element);

                        updated = true;
                    }

                    progress.Increment();
                }
            }

            // updated at least one
            if (updated)
            {
                // update each source section
                foreach (var s in sections.Values)
                {
                    one.UpdateHierarchy(s);
                }

                sections.Clear();

                // update target section
                one.UpdateHierarchy(section);

                // navigate after progress dialog is closed otherwise it will hang!
                await one.NavigateTo(sectionId);
            }
        }
Example #17
0
        // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        // Invoked by the ProgressDialog OnShown callback
        private async Task Execute(UI.ProgressDialog progress, CancellationToken token)
        {
            logger.Start();
            logger.StartClock();

            using (one = new OneNote())
            {
                var hierarchy = GetHierarchy();

                if (token.IsCancellationRequested)
                {
                    logger.WriteLine("cancelled");
                    return;
                }

                try
                {
                    var hyperlinks = await GetHyperlinks(progress, token);

                    if (token.IsCancellationRequested)
                    {
                        logger.WriteLine("cancelled");
                        return;
                    }

                    var elements = hierarchy.Descendants(ns + "Page").ToList();
                    var titles   = new Dictionary <string, string>();

                    logger.WriteLine($"building map for {elements.Count} pages");
                    progress.SetMaximum(elements.Count);
                    progress.SetMessage($"Building map for {elements.Count} pages");

                    foreach (var element in elements)
                    {
                        progress.Increment();

                        if (token.IsCancellationRequested)
                        {
                            return;
                        }

                        var parentId = element.Attribute("ID").Value;
                        var xml      = one.GetPageXml(parentId);
                        var matches  = regex.Matches(xml);

                        if (matches.Count == 0)
                        {
                            element.Remove();
                            continue;
                        }

                        var parent = new Page(XElement.Parse(xml));

                        var name = element.Parent.Attribute("name").Value;
                        progress.SetMessage($"Scanning {name}/{parent.Title}");

                        // prevent duplicates
                        var refs = new List <string>();

                        foreach (Match match in matches)
                        {
                            var pid = match.Groups[1].Value;
                            if (refs.Contains(pid))
                            {
                                // already captured this pid
                                continue;
                            }

                            if (!hyperlinks.ContainsKey(pid))
                            {
                                logger.WriteLine($"not found in scope: {pid} on {name}/{parent.Title}");
                                continue;
                            }

                            var hyperlink = hyperlinks[pid];
                            if (hyperlink.PageID != parentId)
                            {
                                string title;
                                if (titles.ContainsKey(hyperlink.PageID))
                                {
                                    title = titles[hyperlink.PageID];
                                }
                                else
                                {
                                    var p = one.GetPage(hyperlink.PageID, OneNote.PageDetail.Basic);
                                    title = p.Title;
                                    titles.Add(hyperlink.PageID, title);
                                }

                                element.Add(new XElement("Ref",
                                                         new XAttribute("title", title),
                                                         new XAttribute("ID", hyperlink.PageID)
                                                         ));

                                //logger.WriteLine($" - {title}");

                                refs.Add(pid);
                            }
                        }
                    }

                    if (titles.Count == 0)
                    {
                        UIHelper.ShowMessage("No linked pages were found");
                        return;
                    }

                    if (token.IsCancellationRequested)
                    {
                        return;
                    }

                    Prune(hierarchy);
                    await BuildMapPage(hierarchy);
                }
                catch (Exception exc)
                {
                    logger.WriteLine(exc);
                }
            }

            logger.WriteTime("map complete");
            logger.End();
        }
Example #18
0
        private void ReportNotebooks(XElement container, XElement notebooks)
        {
            progress.SetMessage("Notebooks...");
            progress.Increment();

            var backupUri = new Uri(backupPath).AbsoluteUri;
            var folderUri = new Uri(defaultPath).AbsoluteUri;

            container.Add(
                new Paragraph("Summary").SetQuickStyle(heading1Index),
                new Paragraph(Resx.AnalyzeCommand_SummarySummary),
                new Paragraph(new ContentList(ns,
                                              new Bullet($"<span style='font-style:italic'>Default location</span>: <a href=\"{folderUri}\">{defaultPath}</a>"),
                                              new Bullet($"<span style='font-style:italic'>Backup location</span>: <a href=\"{backupUri}\">{backupPath}</a>")
                                              )),
                new Paragraph(string.Empty)
                );

            var table = new Table(ns, 1, 4)
            {
                HasHeaderRow   = true,
                BordersVisible = true
            };

            table.SetColumnWidth(0, 120);
            table.SetColumnWidth(1, 70);
            table.SetColumnWidth(2, 70);
            table.SetColumnWidth(3, 70);

            var row = table[0];

            row.SetShading(HeaderShading);
            row[0].SetContent(new Paragraph("Notebook").SetStyle(HeaderCss));
            row[1].SetContent(new Paragraph("Backups").SetStyle(HeaderCss).SetAlignment("center"));
            row[2].SetContent(new Paragraph("RecycleBin").SetStyle(HeaderCss).SetAlignment("center"));
            row[3].SetContent(new Paragraph("Total").SetStyle(HeaderCss).SetAlignment("center"));

            long total = 0;

            foreach (var notebook in notebooks.Elements(ns + "Notebook"))
            {
                row = table.AddRow();

                var name   = notebook.Attribute("name").Value;
                var remote = notebook.Attribute("path").Value.Contains("https://");

                row[0].SetContent(remote ? $"{name} {Cloud}" : name);

                var path = Path.Combine(remote ? backupPath : defaultPath, name);
                if (Directory.Exists(path))
                {
                    var dir  = new DirectoryInfo(path);
                    var size = dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(f => f.Length);

                    var repath = Path.Combine(path, RecycleBin);
                    if (Directory.Exists(repath))
                    {
                        dir = new DirectoryInfo(repath);
                        var relength = dir.EnumerateFiles("*.*", SearchOption.AllDirectories).Sum(f => f.Length);

                        row[1].SetContent(new Paragraph((size - relength).ToBytes(1)).SetAlignment("right"));
                        row[2].SetContent(new Paragraph(relength.ToBytes(1)).SetAlignment("right"));
                        row[3].SetContent(new Paragraph(size.ToBytes(1)).SetAlignment("right"));
                    }

                    total += size;
                }
            }

            if (total > 0)
            {
                row = table.AddRow();
                row[3].SetContent(new Paragraph(total.ToBytes(1)).SetAlignment("right"));
            }

            container.Add(
                new Paragraph(table.Root),
                new Paragraph(string.Empty)
                );
        }