/// <summary> /// Exports the specified <see cref="TextDocument"/> to an external markdown file. /// </summary> /// <param name="document">The document to export.</param> /// <param name="fileName">Full name of the Maml file to export to. Note that if exporting to multiple Maml files, /// this is the base file name only; the file names will be derived from this name.</param> /// <param name="errors">A list that collects error messages.</param> public void Export(TextDocument document, string fileName, List <MarkdownError> errors = null) { if (null == document) { throw new ArgumentNullException(nameof(document)); } if (string.IsNullOrEmpty(fileName)) { throw new ArgumentNullException(nameof(fileName)); } var basePathName = Path.GetDirectoryName(fileName); if (ExpandChildDocuments) { document = ChildDocumentExpander.ExpandDocumentToNewDocument(document, errors: errors); } if (RenumerateFigures) { document.SourceText = FigureRenumerator.RenumerateFigures(document.SourceText); } // now export the markdown document as Maml file(s) // first parse it with Markdig var pipeline = new MarkdownPipelineBuilder(); pipeline = MarkdownUtilities.UseSupportedExtensions(pipeline); var markdownDocument = Markdig.Markdown.Parse(document.SourceText, pipeline.Build()); var renderer = new OpenXMLRenderer( wordDocumentFileName: OutputFileName, localImages: document.Images, textDocumentFolder: document.Folder ); if (MaximumImageWidth.HasValue) { renderer.MaxImageWidthIn96thInch = MaximumImageWidth.Value.AsValueIn(Altaxo.Units.Length.Inch.Instance) * 96.0; } if (MaximumImageHeight.HasValue) { renderer.MaxImageHeigthIn96thInch = MaximumImageHeight.Value.AsValueIn(Altaxo.Units.Length.Inch.Instance) * 96.0; } if (null != ThemeName) { renderer.ThemeName = ThemeName; } renderer.RemoveOldContentsOfTemplateFile = RemoveOldContentsOfTemplateFile; renderer.ImageResolution = ImageResolutionDpi; renderer.UseAutomaticFigureNumbering = UseAutomaticFigureNumbering; renderer.DoNotFormatFigureLinksAsHyperlinks = DoNotFormatFigureLinksAsHyperlinks; renderer.Render(markdownDocument); }
/// <summary> /// Renumerates all figures, all elements that are enclosed in ^^^ and that have a figure caption. /// The links to those figures are updated, too. /// </summary> /// <param name="documentText">The document text.</param> /// <returns>The modified document text, with renumerated figure captions and updated links.</returns> public static string RenumerateFigures(string documentText) { // first parse the document with markdig var pipeline = new MarkdownPipelineBuilder(); pipeline = MarkdownUtilities.UseSupportedExtensions(pipeline); var builtPipeline = pipeline.Build(); var markdownDocument = Markdig.Markdown.Parse(documentText, builtPipeline); return(RenumerateFigures(markdownDocument, documentText)); }
/// <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> /// Exports the specified <see cref="TextDocument"/> to an external markdown file. /// </summary> /// <param name="document">The document to export.</param> /// <param name="fileName">Full name of the Maml file to export to. Note that if exporting to multiple Maml files, /// this is the base file name only; the file names will be derived from this name.</param> /// <param name="errors">A list that collects error messages.</param> public void Export(TextDocument document, string fileName, List <MarkdownError> errors = null) { if (null == document) { throw new ArgumentNullException(nameof(document)); } if (string.IsNullOrEmpty(fileName)) { throw new ArgumentNullException(nameof(fileName)); } var basePathName = Path.GetDirectoryName(fileName); if (ExpandChildDocuments) { document = ChildDocumentExpander.ExpandDocumentToNewDocument(document, errors: errors); } if (RenumerateFigures) { document.SourceText = FigureRenumerator.RenumerateFigures(document.SourceText); } // remove the old content if (EnableRemoveOldContentsOfContentFolder) { var fullContentFolderName = Path.Combine(basePathName, ContentFolderName); MamlRenderer.RemoveOldContentsOfContentFolder(fullContentFolderName); } // remove old images var fullImageFolderName = Path.Combine(basePathName, ImageFolderName); if (EnableRemoveOldContentsOfImageFolder) { MamlRenderer.RemoveOldContentsOfImageFolder(fullImageFolderName); } if (!Directory.Exists(fullImageFolderName)) { Directory.CreateDirectory(fullImageFolderName); } // First, export the images var(oldToNewImageUrl, listOfReferencedImageFileNames) = ExportImages(document, basePathName); // now export the markdown document as Maml file(s) // first parse it with Markdig var pipeline = new MarkdownPipelineBuilder(); pipeline = MarkdownUtilities.UseSupportedExtensions(pipeline); var markdownDocument = Markdig.Markdown.Parse(document.SourceText, pipeline.Build()); var renderer = new MamlRenderer( projectOrContentFileName: fileName, contentFolderName: ContentFolderName, contentFileNameBase: ContentFileNameBase, imageFolderName: ImageFolderName, splitLevel: SplitLevel, enableHtmlEscape: EnableHtmlEscape, autoOutline: EnableAutoOutline, enableLinkToPreviousSection: EnableLinkToPreviousSection, linkToPreviousSectionLabelText: LinkToPreviousSectionLabelText, enableLinkToNextSection: EnableLinkToNextSection, linkToNextSectionLabelText: LinkToNextSectionLabelText, imagesFullFileNames: listOfReferencedImageFileNames, oldToNewImageUris: oldToNewImageUrl, bodyTextFontFamily: BodyTextFontFamily, bodyTextFontSize: BodyTextFontSize, isIntendedForHelp1File: IsIntendedForHtmlHelp1File ); renderer.Render(markdownDocument); }
/// <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); }