/// <summary> /// Gets a list of all referenced image Urls. /// We use this only in the serialization code to serialize only those local images which are referenced in the markdown. /// </summary> /// <returns>A new list containing all image Urls together with the begin and and of the Url span.</returns> public static List <(string Url, int urlSpanStart, int urlSpanEnd)> GetReferencedImageUrls(MarkdownDocument markdownDocument) { var list = new List <(string Url, int urlSpanStart, int urlSpanEnd)>(); foreach (var mdo in MarkdownUtilities.EnumerateAllMarkdownObjectsRecursively(markdownDocument)) { if (mdo is LinkInline link) { if (link.IsImage && link.UrlSpan.HasValue) { list.Add((link.Url, link.UrlSpan.Value.Start, link.UrlSpan.Value.End)); } } } return(list); }
/// <summary> /// Determines whether the given <paramref name="url"/> points to anywhere inside the given Markdig <paramref name="element"/>. /// </summary> /// <param name="url">The URL.</param> /// <param name="element">The Markdig element.</param> /// <returns> /// <c>true</c> if the given <paramref name="url"/> points to anywhere inside the given Markdig <paramref name="element"/>.; otherwise, <c>false</c>. /// </returns> public static bool IsLinkInElement(string url, Markdig.Syntax.MarkdownObject element) { if (url.StartsWith("#")) { url = url.Substring(1); } foreach (var child in MarkdownUtilities.EnumerateAllMarkdownObjectsRecursively(element)) { var attr = (Markdig.Renderers.Html.HtmlAttributes)child.GetData(typeof(Markdig.Renderers.Html.HtmlAttributes)); string uniqueAddress = attr?.Id; // this header has a user defined address if (uniqueAddress == url) { return(true); } } return(false); }
/// <summary> /// Expands the document to include child documents directly. This process is recursively, i.e. if the child documents contain child-child documents, /// they are expanded, too. /// </summary> /// <param name="textDocument">The original text document. This document is not changed during the expansion.</param> /// <param name="convertGraphsToImages">If true, links to Altaxo graphs will be converted to images. If false, the links to the graphs were kept, but the path to the graphs is changed appropriately.</param> /// <param name="newPath">Folder path of the final document that is the target of the expansion process.</param> /// <param name="recursionLevel">The recursion level. Start with 0 here.</param> /// <param name="errors">A list that collects error messages.</param> /// <returns>A new <see cref="TextDocument"/>. This text document contains the expanded markdown text. In addition, all Altaxo graphs are converted to local images.</returns> /// <remarks>Since finding Altaxo graphs embedded in the markdown is depended on the context (location of the TextDocument and location of the graph), /// and we somewhat loose this context during the expansion, we convert the graphs to local images before we insert the document into the master document.</remarks> private static TextDocument ExpandDocumentToNewDocument(TextDocument textDocument, bool convertGraphsToImages, string newPath, int recursionLevel = 0, List <MarkdownError> errors = null) { var resultDocument = new TextDocument(); resultDocument.AddImagesFrom(textDocument); resultDocument.Name = textDocument.Name; // first parse the document with markdig var pipeline = new MarkdownPipelineBuilder(); pipeline = MarkdownUtilities.UseSupportedExtensions(pipeline); var builtPipeline = pipeline.Build(); var markdownDocument = Markdig.Markdown.Parse(textDocument.SourceText, builtPipeline); var markdownToProcess = new List <MarkdownObject>(); foreach (var mdo in MarkdownUtilities.EnumerateAllMarkdownObjectsRecursively(markdownDocument)) { if (mdo is LinkInline link) { if (link.Url.ToLowerInvariant().StartsWith(ImagePretext.GraphRelativePathPretext)) { markdownToProcess.Add(mdo); } } else if (mdo is CodeBlock blk) { var attr = (Markdig.Renderers.Html.HtmlAttributes)mdo.GetData(typeof(Markdig.Renderers.Html.HtmlAttributes)); if (attr != null && attr.Properties != null && attr.Properties.Count >= 2 && attr.Properties[0].Key == "Altaxo" && attr.Properties[1].Key == "child") { var childDoc = attr.Properties[1].Value; if (null != childDoc) { markdownToProcess.Add(mdo); } } } } // now we process the list backwards and change the source var documentAsStringBuilder = new StringBuilder(textDocument.SourceText); var imageStreamProvider = new ImageStreamProvider(); markdownToProcess.Reverse(); // we start from the end of the document, in order not to change the positions of unprocessed markdown foreach (var mdo in markdownToProcess) { if (mdo is LinkInline link) { if (convertGraphsToImages) // convert links to graphs to images { using (var stream = new System.IO.MemoryStream()) { var streamResult = imageStreamProvider.GetImageStream(stream, link.Url, 300, Altaxo.Main.ProjectFolder.GetFolderPart(textDocument.Name), textDocument.Images); if (null == streamResult.ErrorMessage) { stream.Seek(0, System.IO.SeekOrigin.Begin); var proxy = MemoryStreamImageProxy.FromStream(stream, streamResult.Extension); resultDocument.AddImage(proxy); documentAsStringBuilder.Remove(link.UrlSpan.Value.Start, link.UrlSpan.Value.Length); documentAsStringBuilder.Insert(link.UrlSpan.Value.Start, "local:" + proxy.ContentHash); } } } else // keep link to graphs, but change their path { var newUrl = ConvertGraphUrl(link.Url, textDocument.Name, newPath); if (newUrl != link.Url) { documentAsStringBuilder.Remove(link.UrlSpan.Value.Start, link.UrlSpan.Value.Length); documentAsStringBuilder.Insert(link.UrlSpan.Value.Start, newUrl); } } } else if (mdo is CodeBlock blk) { var attr = (Markdig.Renderers.Html.HtmlAttributes)mdo.GetData(typeof(Markdig.Renderers.Html.HtmlAttributes)); var childDocName = attr.Properties[1].Value; // first, we assume a relative name var fullName = Altaxo.Main.ProjectFolder.GetFolderPart(textDocument.Name) + childDocName; var success = Current.Project.TextDocumentCollection.TryGetValue(fullName, out var childTextDocument); if (!success) // relative name failed, we try it with the unmodified (absolute) name { success = Current.Project.TextDocumentCollection.TryGetValue(childDocName, out childTextDocument); } if (success) { var expandedChild = ExpandDocumentToNewDocument(childTextDocument, convertGraphsToImages, newPath, recursionLevel + 1, errors); // exchange the source text documentAsStringBuilder.Remove(mdo.Span.Start, mdo.Span.Length); documentAsStringBuilder.Insert(mdo.Span.Start, expandedChild.SourceText); // insert images resultDocument.AddImagesFrom(expandedChild); } else if (null != errors) // report an error { var error = new MarkdownError() { AltaxoDocumentName = textDocument.Name, LineNumber = blk.Line, ColumnNumber = blk.Column, ErrorMessage = string.Format("Could not expand child document \"{0}\" because this name could not be resolved!", childDocName) }; errors.Add(error); } } } resultDocument.SourceText = documentAsStringBuilder.ToString(); if (0 == recursionLevel) // if we are about to return the master document, we must restore the list of referenced image Urls { markdownDocument = Markdig.Markdown.Parse(resultDocument.SourceText, builtPipeline); resultDocument.ReferencedImageUrls = MarkdownUtilities.GetReferencedImageUrls(markdownDocument); } return(resultDocument); }