diff --git a/OneMore/Commands/Clean/RemoveTagsCommand.cs b/OneMore/Commands/Clean/RemoveTagsCommand.cs index 1a03ecb009..5362c908ff 100644 --- a/OneMore/Commands/Clean/RemoveTagsCommand.cs +++ b/OneMore/Commands/Clean/RemoveTagsCommand.cs @@ -1,9 +1,10 @@ //************************************************************************************************ -// Copyright © 2021 Steven M Cohn. All rights reserved. +// Copyright © 2021 Steven M Cohn. All rights reserved. //************************************************************************************************ namespace River.OneMoreAddIn.Commands { + using River.OneMoreAddIn.Models; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; @@ -31,7 +32,7 @@ public override async Task Execute(params object[] args) var updated = false; var reminders = new ReminderSerializer().LoadReminders(page); - var tagdefs = page.GetTagDefMap().Select(m => m.TagDef); + var tagdefs = TagMapper.GetTagDefs(page); foreach (var tag in tags) { diff --git a/OneMore/Commands/References/EmbedSubpageCommand.cs b/OneMore/Commands/References/EmbedSubpageCommand.cs index ed17ee35d6..0fc6fe92a1 100644 --- a/OneMore/Commands/References/EmbedSubpageCommand.cs +++ b/OneMore/Commands/References/EmbedSubpageCommand.cs @@ -5,6 +5,7 @@ namespace River.OneMoreAddIn.Commands { using OneMoreAddIn.Models; + using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -65,6 +66,8 @@ public override async Task Execute(params object[] args) var sourceId = args.Length > 1 ? args[1] as string : null; var linkId = args.Length > 2 ? args[2] as string : null; await UpdateContent(sourceId, linkId); + // prevent replay + IsCancelled = true; return; } @@ -72,57 +75,88 @@ public override async Task Execute(params object[] args) } - //======================================================================================== - // Update... + private async Task EmbedContent() + { + await using var o = new OneNote(); + o.SelectLocation( + Resx.EmbedSubpageCommand_Select, + Resx.EmbedSubpageCommand_SelectIntro, + OneNote.Scope.Pages, Callback); + } - private async Task UpdateContent(string sourceId, string linkId) + + private async Task Callback(string sourceId) { - await using (one = new OneNote(out page, out ns)) + if (string.IsNullOrEmpty(sourceId)) { - // find all embedded sections... - - var metas = page.Root.Descendants(ns + "Meta") - .Where(e => e.Attribute("name").Value == EmbeddedMetaName); + // cancelled + return; + } - if (!string.IsNullOrEmpty(sourceId)) + await using (one = new OneNote(out page, out ns)) + { + var source = await GetSource(sourceId, null); + if (source is null || !source.Snippets.Any()) { - // refine filter by source pageId - metas = metas.Where(e => e.Attribute("content").Value == sourceId); + return; } - if (!metas.Any()) + page.EnsureContentContainer(); + + var container = page.Root.Elements(ns + "Outline") + .Descendants(ns + "T") + .Where(e => e.Attribute("selected")?.Value == "all") + .Ancestors(ns + "OEChildren") + .FirstOrDefault(); + + if (container is null) { - ShowError(Resx.EmbedSubpageCommand_NoEmbedded); + ShowError(Resx.Error_BodyContext); return; } - // updated each section... + var table = new Table(ns, 1, 1) + { + BordersVisible = true, + }; - var updated = false; - foreach (var meta in metas) + // is cursor positioned in an existing table cell? + XElement hostCell = container.Parent; + while (hostCell != null && hostCell.Name.LocalName != "Cell") { - var tableRoot = meta.ElementsAfterSelf(ns + "Table").FirstOrDefault(); - if (tableRoot is not null) - { - sourceId = meta.Attribute("content").Value; - var source = await GetSource(sourceId, linkId); - if (source is null || !source.Snippets.Any()) - { - // error reading source - ShowError(Resx.EmbedSubpageCommand_NoEmbedded); - return; - } + hostCell = hostCell.Parent; + } - var table = new Table(tableRoot); - FillCell(table[0][0], source.Snippets, source.Page); - updated = true; - } + if (hostCell is null) + { + // set width to width of source page outline + var width = source.Outline.GetWidth(); + table.SetColumnWidth(0, width == 0 ? 500 : width); } - if (updated) + try { - await one.Update(page); + FillCell(table[0][0], source.Snippets, source.Page); + } + catch (Exception exc) + { + logger.WriteLine("error in FillCell", exc); + } + + try + { + var editor = new PageEditor(page); + editor.AddNextParagraph(new Paragraph( + new Meta(EmbeddedMetaName, source.Page.PageId), + table.Root + )); + } + catch (Exception exc) + { + logger.WriteLine("error adding paragraph", exc); } + + await one.Update(page); } } @@ -164,12 +198,11 @@ private async Task GetSource(string sourceId, string linkId) var outRoot = source.BodyOutlines.FirstOrDefault(); if (outRoot is null) { + var schema = new PageSchema(); + // couldn't find an Outline but page may contain other valid content items - var child = source.Root.Elements().FirstOrDefault(e => - e.Name.LocalName == "Image" || - e.Name.LocalName == "InkDrawing" || - e.Name.LocalName == "InsertedFile" || - e.Name.LocalName == "MediaFile"); + var child = source.Root.Elements() + .FirstOrDefault(e => e.Name.LocalName.In(schema.OeContent)); if (child is not null) { @@ -219,7 +252,9 @@ private void FillCell(TableCell cell, IEnumerable snippets, Page sourc var match = Regex.Match(link, @"page-id=({[^}]+?})"); var linkId = match.Success ? match.Groups[1].Value : string.Empty; - var tagmap = page.MergeTagDefs(source); + var mapper = new TagMapper(page); + mapper.MergeTagDefsFrom(source); + var quickmap = page.MergeQuickStyles(source); var citationIndex = page.GetQuickStyle(Styles.StandardStyles.Citation).Index; @@ -238,7 +273,7 @@ private void FillCell(TableCell cell, IEnumerable snippets, Page sourc foreach (var snippet in snippets) { page.ApplyStyleMapping(quickmap, snippet); - page.ApplyTagDefMapping(tagmap, snippet); + mapper.RemapTags(snippet); cell.Root.Add(snippet); } @@ -246,76 +281,56 @@ private void FillCell(TableCell cell, IEnumerable snippets, Page sourc //======================================================================================== - // Embed... - - private async Task EmbedContent() - { - await using var o = new OneNote(); - o.SelectLocation( - Resx.EmbedSubpageCommand_Select, - Resx.EmbedSubpageCommand_SelectIntro, - OneNote.Scope.Pages, Callback); - } - + // Update... - private async Task Callback(string sourceId) + private async Task UpdateContent(string sourceId, string linkId) { - if (string.IsNullOrEmpty(sourceId)) - { - // cancelled - return; - } - await using (one = new OneNote(out page, out ns)) { - var source = await GetSource(sourceId, null); - if (source is null || !source.Snippets.Any()) - { - return; - } + // find all embedded sections... - page.EnsureContentContainer(); + var metas = page.Root.Descendants(ns + "Meta") + .Where(e => e.Attribute("name").Value == EmbeddedMetaName); - var container = page.Root.Elements(ns + "Outline") - .Descendants(ns + "T") - .Where(e => e.Attribute("selected")?.Value == "all") - .Ancestors(ns + "OEChildren") - .FirstOrDefault(); + if (!string.IsNullOrEmpty(sourceId)) + { + // refine filter by source pageId + metas = metas.Where(e => e.Attribute("content").Value == sourceId); + } - if (container is null) + if (!metas.Any()) { - ShowError(Resx.Error_BodyContext); + ShowError(Resx.EmbedSubpageCommand_NoEmbedded); return; } - var table = new Table(ns, 1, 1) - { - BordersVisible = true, - }; + // updated each section... - // is cursor positioned in an existing table cell? - XElement hostCell = container.Parent; - while (hostCell != null && hostCell.Name.LocalName != "Cell") + var updated = false; + foreach (var meta in metas) { - hostCell = hostCell.Parent; + var tableRoot = meta.ElementsAfterSelf(ns + "Table").FirstOrDefault(); + if (tableRoot is not null) + { + sourceId = meta.Attribute("content").Value; + var source = await GetSource(sourceId, linkId); + if (source is null || !source.Snippets.Any()) + { + // error reading source + ShowError(Resx.EmbedSubpageCommand_NoEmbedded); + return; + } + + var table = new Table(tableRoot); + FillCell(table[0][0], source.Snippets, source.Page); + updated = true; + } } - if (hostCell is null) + if (updated) { - // set width to width of source page outline - var width = source.Outline.GetWidth(); - table.SetColumnWidth(0, width == 0 ? 500 : width); + await one.Update(page); } - - FillCell(table[0][0], source.Snippets, source.Page); - - var editor = new PageEditor(page); - editor.AddNextParagraph(new Paragraph( - new Meta(EmbeddedMetaName, source.Page.PageId), - table.Root - )); - - await one.Update(page); } } } diff --git a/OneMore/Commands/References/EmbedSubpageProxy.cs b/OneMore/Commands/References/EmbedSubpageProxy.cs index cbc02b5f13..73b16894f1 100644 --- a/OneMore/Commands/References/EmbedSubpageProxy.cs +++ b/OneMore/Commands/References/EmbedSubpageProxy.cs @@ -16,6 +16,7 @@ internal class EmbedSubpageProxy : Command { public EmbedSubpageProxy() { + IsCancelled = true; } diff --git a/OneMore/Commands/Tables/Formulas/Processor.cs b/OneMore/Commands/Tables/Formulas/Processor.cs index 06b08496e9..ce37cb8fe9 100644 --- a/OneMore/Commands/Tables/Formulas/Processor.cs +++ b/OneMore/Commands/Tables/Formulas/Processor.cs @@ -95,7 +95,7 @@ private void GetCellValue(object sender, GetCellValueEventArgs e) var index = tagx.Attribute("index").Value; if (index != null) { - tags ??= DiscoverTags(); + tags ??= DiscoverToDoTags(); var tag = tags.Find(t => t.Index == index); if (tag != null) @@ -121,7 +121,7 @@ private void GetCellValue(object sender, GetCellValueEventArgs e) } - private List DiscoverTags() + private List DiscoverToDoTags() { var pageElement = table.Root.Ancestors().FirstOrDefault(e => e.Name.LocalName == "Page"); if (pageElement == null) @@ -130,9 +130,7 @@ private List DiscoverTags() } var page = new Page(pageElement); - var map = page.GetTagDefMap(); - var list = map.Where(m => m.TagDef.IsToDo()).Select(m => m.TagDef).ToList(); - return list; + return TagMapper.GetTagDefs(page).Where(d => d.IsToDo()).ToList(); } diff --git a/OneMore/Models/Page.cs b/OneMore/Models/Page.cs index d331a614a4..0157bcb2bc 100644 --- a/OneMore/Models/Page.cs +++ b/OneMore/Models/Page.cs @@ -35,7 +35,7 @@ internal partial class Page /// public Page(XElement root) { - if (root != null) + if (root is not null) { Namespace = root.GetNamespaceOfPrefix(OneNote.Prefix); PageId = root.Attribute("ID")?.Value; @@ -67,7 +67,7 @@ private static void ComputeHashes(XElement root) child.Name.LocalName != "Meta") { var att = child.Attribute(HashAttributeName); - if (att == null) + if (att is null) { child.Add(new XAttribute( HashAttributeName, @@ -93,7 +93,7 @@ public void OptimizeForSave(bool keep) foreach (var child in Root.Elements().ToList()) { var att = child.Attribute(HashAttributeName); - if (att != null) + if (att is not null) { att.Remove(); @@ -120,7 +120,7 @@ public void OptimizeForSave(bool keep) .Any(m => m.Attribute("name").Value.Equals(MetaNames.TaggingBank))); - public bool IsValid => Root != null; + public bool IsValid => Root is not null; /// @@ -207,7 +207,7 @@ public void AddHtmlContent(string html) public void AddQuickStyleDef(XElement def) { var tagdef = Root.Elements(Namespace + "TagDef").LastOrDefault(); - if (tagdef == null) + if (tagdef is null) { Root.AddFirst(def); } @@ -233,7 +233,7 @@ public string AddTagDef(string symbol, string name, int tagType = 0) if (tags?.Any() == true) { var tag = tags.FirstOrDefault(e => e.Attribute("symbol").Value == symbol); - if (tag != null) + if (tag is not null) { return tag.Attribute("index").Value; } @@ -260,7 +260,7 @@ public void AddTagDef(TagDef tagdef) if (tags?.Any() == true) { var tag = tags.FirstOrDefault(e => e.Attribute("symbol").Value == tagdef.Symbol); - if (tag != null) + if (tag is not null) { return; } @@ -303,34 +303,6 @@ public void ApplyStyleMapping(List mapping, XElement outline) } - /// - /// Apply the given tagdef mappings to all descendants of the specified outline - /// - /// - /// - public void ApplyTagDefMapping(List mapping, XElement outline) - { - // reverse sort the indexes so logic doesn't overwrite subsequent index references - foreach (var map in mapping.OrderByDescending(s => s.TagDef.IndexValue)) - { - if (map.OriginalIndex != map.TagDef.Index) - { - // apply new index to child outline elements - var elements = outline.Descendants(Namespace + "Tag") - .Where(e => e.Attribute("index")?.Value == map.OriginalIndex); - - if (elements.Any()) - { - foreach (var element in elements) - { - element.Attribute("index").Value = map.TagDef.Index; - } - } - } - } - } - - /// /// Used by the ribbon to enable/disable items based on whether the focus is currently /// on the page body or elsewhere such as the title. @@ -392,7 +364,7 @@ public XElement EnsureContentContainer(bool last = true) ? Root.Elements(Namespace + "Outline").LastOrDefault() : Root.Elements(Namespace + "Outline").FirstOrDefault(); - if (outline == null) + if (outline is null) { container = new XElement(Namespace + "OEChildren"); outline = new XElement(Namespace + "Outline", container); @@ -404,7 +376,7 @@ public XElement EnsureContentContainer(bool last = true) ? outline.Elements(Namespace + "OEChildren").LastOrDefault() : outline.Elements(Namespace + "OEChildren").FirstOrDefault(); - if (container == null) + if (container is null) { container = new XElement(Namespace + "OEChildren"); outline.Add(container); @@ -413,7 +385,7 @@ public XElement EnsureContentContainer(bool last = true) // check Outline size var size = outline.Elements(Namespace + "Size").FirstOrDefault(); - if (size == null) + if (size is null) { // this size is close to OneNote defaults when a new Outline is created outline.AddFirst(new XElement(Namespace + "Size", @@ -452,13 +424,13 @@ public void EnsurePageWidth( .Elements(Namespace + "Size") .FirstOrDefault(); - if (element == null) + if (element is null) { return; } var attr = element.Attribute("width"); - if (attr != null) + if (attr is not null) { var outlinePoints = double.Parse(attr.Value, CultureInfo.InvariantCulture); @@ -474,7 +446,7 @@ public void EnsurePageWidth( attr.Value = stringPoints.ToString("#0.00", CultureInfo.InvariantCulture); // must include isSetByUser or width doesn't take effect! - if (element.Attribute("isSetByUser") == null) + if (element.Attribute("isSetByUser") is null) { element.Add(new XAttribute("isSetByUser", "true")); } @@ -511,12 +483,12 @@ public Style GetQuickStyle(StandardStyles key) .Select(p => new Style(new QuickStyleDef(p))) .FirstOrDefault(); - if (style == null) + if (style is null) { var quick = key.GetDefaults(); var sibling = Root.Elements(Namespace + "QuickStyleDef").LastOrDefault(); - if (sibling == null) + if (sibling is null) { quick.Index = 0; Root.AddFirst(quick.ToElement(Namespace)); @@ -647,7 +619,7 @@ public string GetTagDefIndex(string symbol) var tag = Root.Elements(Namespace + "TagDef") .FirstOrDefault(e => e.Attribute("symbol").Value == symbol); - if (tag != null) + if (tag is not null) { return tag.Attribute("index").Value; } @@ -666,7 +638,7 @@ public string GetTagDefSymbol(string index) var tag = Root.Elements(Namespace + "TagDef") .FirstOrDefault(e => e.Attribute("index").Value == index); - if (tag != null) + if (tag is not null) { return tag.Attribute("symbol").Value; } @@ -675,19 +647,6 @@ public string GetTagDefSymbol(string index) } - /// - /// Gets the TagDef mappings for the current page. Used to copy or merge - /// content on this page - /// - /// - public List GetTagDefMap() - { - return Root.Elements(Namespace + "TagDef") - .Select(e => new TagDefMapping(e)) - .ToList(); - } - - /// /// Determines if the page has an active, incomplete media file which could be /// either video or audio. @@ -715,7 +674,7 @@ public bool HasActiveMedia() .Elements(Namespace + "MediaReference") .FirstOrDefault(e => e.Attribute("mediaID").Value == mediaID); - if (file == null) + if (file is null) { // MediaFile element exists only after recording has stopped return true; @@ -758,7 +717,7 @@ public List MergeQuickStyles(Page sourcePage) foreach (var source in sourcemap) { var quick = map.Find(q => q.Style.Equals(source.Style)); - if (quick == null) + if (quick is null) { // no match so add it and set index to maxIndex+1 // O(n) is OK here; there should only be a few @@ -779,45 +738,6 @@ public List MergeQuickStyles(Page sourcePage) } - /// - /// Merges the TagDefs from a source page with the TagDefs on the current page, - /// adjusting index values to avoid collisions with pre-existing definitions - /// - /// - /// The page from which to copy TagDefs into this page. The value of the index - /// attribute of the TagDefs are updated for each definition - /// - public List MergeTagDefs(Page sourcePage) - { - var sourcemap = sourcePage.GetTagDefMap(); - var map = GetTagDefMap(); - - var index = map.Any() ? map.Max(t => t.TagDef.IndexValue) + 1 : 0; - - foreach (var source in sourcemap) - { - var tagdef = map.Find(t => t.TagDef.Equals(source.TagDef)); - if (tagdef == null) - { - // no match so add it and set index to maxIndex+1 - // O(n) is OK here; there should only be a few - source.TagDef.IndexValue = index++; - - source.Element = new XElement(source.Element); - source.Element.Attribute("index").Value = source.TagDef.Index; - - map.Add(source); - AddTagDef(source.TagDef); - } - - // else if found then the index may differ but keep it so it can be mapped - // to content later... - } - - return map; - } - - /// /// Adds a Meta element to the page (in the proper schema sequence) with the /// specified name and value. @@ -829,7 +749,7 @@ public void SetMeta(string name, string value) var meta = Root.Elements(Namespace + "Meta") .FirstOrDefault(e => e.Attribute("name").Value == name); - if (meta == null) + if (meta is null) { meta = new XElement(Namespace + "Meta", new XAttribute("name", name), @@ -838,13 +758,13 @@ public void SetMeta(string name, string value) // add into schema sequence... var after = Root.Elements(Namespace + "XPSFile").LastOrDefault(); - if (after == null) + if (after is null) { after = Root.Elements(Namespace + "QuickStyleDef").LastOrDefault(); after ??= Root.Elements(Namespace + "TagDef").LastOrDefault(); } - if (after == null) + if (after is null) { Root.AddFirst(meta); } @@ -870,7 +790,7 @@ public void SetTitle(string title) PageNamespace.Set(ns); var block = Root.Elements(ns + "Title").FirstOrDefault(); - if (block == null) + if (block is null) { var style = GetQuickStyle(StandardStyles.PageTitle); block = new XElement(ns + "Title", diff --git a/OneMore/Models/PageEditor.cs b/OneMore/Models/PageEditor.cs index cba76c9ed6..b12953e05c 100644 --- a/OneMore/Models/PageEditor.cs +++ b/OneMore/Models/PageEditor.cs @@ -369,7 +369,6 @@ public bool InsertOrReplace(XElement content, bool above = true) /// document with a selection range. /// /// - /// The page root node /// The content to insert public SelectionScope ReplaceSelectedWith(XElement content) { diff --git a/OneMore/Models/TagDef.cs b/OneMore/Models/TagDef.cs index 568840c5f0..81e4c6fee9 100644 --- a/OneMore/Models/TagDef.cs +++ b/OneMore/Models/TagDef.cs @@ -29,17 +29,23 @@ public TagDef(XElement original) : base(original.GetNamespaceOfPrefix(OneNote.Prefix) + "TagDef", original.Attributes()) { - Index = Attribute("index").Value; + ElementRef = original; IndexValue = int.Parse(original.Attribute("index").Value); Type = int.Parse(Attribute("type").Value); hashcode = original.GetHashCode(); } - public string Index { get; private set; } + /// + /// Dynamic, indicates the order in which the tagdefs were added to the page + /// + public string Index => Attribute("index").Value; - public int Type { get; private set; } + /// + /// Gets the original XElement reference. + /// + public XElement ElementRef { get; private set; } public int IndexValue @@ -54,9 +60,23 @@ public int IndexValue } + /// + /// Static, indicates the specific glyph used for the tag + /// public string Symbol => Attribute("symbol").Value; + /// + /// Dynamic, can differ page to page, seems meaningless?? + /// + public int Type { get; private set; } + + + /// + /// Compares by Symbol + /// + /// other instance + /// True if the Symbols are equal public override bool Equals(object obj) { if (obj is XElement other) diff --git a/OneMore/Models/TagDefMapping.cs b/OneMore/Models/TagDefMapping.cs deleted file mode 100644 index 3fd79ffe4b..0000000000 --- a/OneMore/Models/TagDefMapping.cs +++ /dev/null @@ -1,47 +0,0 @@ -//************************************************************************************************ -// Copyright © 2021 Steven M Cohn. All rights reserved. -//************************************************************************************************ - -namespace River.OneMoreAddIn.Models -{ - using System.Xml.Linq; - - - /// - /// Used to copy and merge TagDefs from one page to another, tracking index changes - /// from page to page so they can be applied to content when pages are merges - /// - internal class TagDefMapping - { - /// - /// Gets or sets the TagDef element. The setter is used to deep-clone the - /// original so the source page is not affected when merging styles - /// - public XElement Element { get; set; } - - - /// - /// Gets or sets a smart copy of the original TagDef element - /// - public TagDef TagDef { get; set; } - - - /// - /// Gets the index value of this quick style on the source page before it was - /// mapped to the target page - /// - public string OriginalIndex { get; private set; } - - - /// - /// Initializes a new instance from the given TagDef element - /// - /// - public TagDefMapping(XElement element) - { - Element = element; - TagDef = new TagDef(element); - OriginalIndex = TagDef.Index; - } - } -} diff --git a/OneMore/Models/TagMapper.cs b/OneMore/Models/TagMapper.cs new file mode 100644 index 0000000000..65ca2c72bf --- /dev/null +++ b/OneMore/Models/TagMapper.cs @@ -0,0 +1,158 @@ +//************************************************************************************************ +// Copyright © 2021 Steven M Cohn. All rights reserved. +//************************************************************************************************ + +namespace River.OneMoreAddIn.Models +{ + using System.Collections.Generic; + using System.Linq; + using System.Xml.Linq; + + + /// + /// Manages Tags and TagDefs on a page and merges TagDefs from one page to another. + /// Originally part of the Page class. + /// + internal class TagMapper + { + #region class Mapping + private sealed class Mapping + { + public TagDef TagDef { get; private set; } + + public List SourceIndex { get; set; } + + public int TargetIndex { get; set; } + + public Mapping(XElement element) + : this(new TagDef(element)) + { + } + + public Mapping(TagDef tagdef) + { + TagDef = tagdef; + SourceIndex = new List { tagdef.IndexValue }; + TargetIndex = tagdef.IndexValue; + } + } + #endregion + + private readonly Page page; + private readonly XNamespace ns; + private readonly List map; + + + /// + /// Initializes a new mapper for the given target page + /// + /// The target page into which a merge will occur + public TagMapper(Page targetPage) + { + page = targetPage; + ns = page.Root.GetNamespaceOfPrefix(OneNote.Prefix); + map = BuildMap(page); + } + + + private List BuildMap(Page page) + { + return page.Root + .Elements(ns + "TagDef") + .Select(e => new Mapping(e)).ToList(); + } + + + /// + /// Merges the TagDefs from a source page with the TagDefs on the current page, + /// adjusting index values to avoid collisions with pre-existing definitions + /// + /// + /// The page from which to copy TagDefs into this page. The value of the index + /// attribute of the TagDefs are updated for each definition + /// + public void MergeTagDefsFrom(Page sourcePage) + { + // next available index for new tagdefs + var index = map.Any() ? map.Max(t => t.TargetIndex) + 1 : 0; + + var sourcedefs = GetTagDefs(sourcePage); + + // resolve source tagdefs with target tagdefs + foreach (var source in sourcedefs) + { + var mapping = map.Find(m => m.TagDef.Symbol == source.Symbol); + if (mapping is null) + { + // no match so add it and set index to maxIndex+1 + // O(n) is OK here; there should only be a few + var sourceIndex = source.IndexValue; + source.IndexValue = index++; + + mapping = new Mapping(source) + { + SourceIndex = new List { sourceIndex } + }; + + map.Add(mapping); + page.AddTagDef(source); + } + else + { + // OneNote allows multiple TagDefs for the same symbol but having different + // index values, so we need to accumulate them in a list so we can then + // resolve each later in RemapTags... + + if (!mapping.SourceIndex.Contains(source.IndexValue)) + { + mapping.SourceIndex.Add(source.IndexValue); + } + } + } + } + + + /// + /// Apply the current map to all Tags within the given content branch + /// + /// + public void RemapTags(XElement content) + { + // reverse sort the indexes so logic doesn't overwrite subsequent index references + foreach (var mapping in map.OrderByDescending(s => s.TargetIndex)) + { + var targetIndex = mapping.TargetIndex.ToString(); + + // lookup Tag index value in map, for any Mapping that has this index value + // in its list of index values... + + var tags = content.Descendants(ns + "Tag") + .Where(e => e.Attribute("index") is XAttribute a && + int.Parse(a.Value) is int v && + mapping.SourceIndex.Contains(v)); + + if (tags.Any()) + { + // apply new index to child outline elements + foreach (var tag in tags) + { + tag.Attribute("index").Value = targetIndex; + } + } + } + } + + + /// + /// Get a list of TagDefs on the given page. + /// + /// + /// + public static List GetTagDefs(Page page) + { + return page.Root + .Elements(page.Namespace + "TagDef") + .Select(e => new TagDef(e)).ToList(); + } + } +} diff --git a/OneMore/OneMore.csproj b/OneMore/OneMore.csproj index 0b0d374f81..35dc58d0f7 100644 --- a/OneMore/OneMore.csproj +++ b/OneMore/OneMore.csproj @@ -346,6 +346,7 @@ + True True @@ -733,7 +734,6 @@ - diff --git a/OneMore/OneNote.cs b/OneMore/OneNote.cs index 00514fc075..9934480c56 100644 --- a/OneMore/OneNote.cs +++ b/OneMore/OneNote.cs @@ -1200,7 +1200,14 @@ public FilingCallback(SelectLocationCallback usercb) public void OnDialogClosed(IQuickFilingDialog dialog) { - userCallback(dialog.SelectedItem); + try + { + userCallback(dialog.SelectedItem); + } + catch (Exception exc) + { + Logger.Current.WriteLine("error returned from FilingCallback", exc); + } } }