public void ExtractSnippetFilterContent2() { // Run the extraction FileSystemSnippetExtractor extractor = new FileSystemSnippetExtractor(); DirectoryInfoBase directoryInfoBase = fileSystem.DirectoryInfo.FromDirectoryName(fileSystem.Path.GetFullPath("Foo")); NodeSnippet snippet = extractor.Extract(directoryInfoBase, "Content2.txt") as NodeSnippet; // Assert Foo Assert.AreEqual("Foo", snippet.Node.Name); Assert.AreEqual(false, snippet.Node.IsLeaf); Assert.AreEqual(2, snippet.Node.Children.Count); // Assert Foo/Content2.txt Node foocontent2 = snippet.Node.Children["Content2.txt"]; Assert.AreEqual("Content2.txt", foocontent2.Name); Assert.AreEqual(true, foocontent2.IsLeaf); Assert.AreEqual(0, foocontent2.Children.Count); // Assert Foo/Bar Node bar = snippet.Node.Children["Bar"]; Assert.AreEqual("Bar", bar.Name); Assert.AreEqual(false, bar.IsLeaf); Assert.AreEqual(2, bar.Children.Count); // Assert Foo/Bar/Content2.txt Node barcontent2 = bar.Children["Content2.txt"]; Assert.AreEqual("Content2.txt", barcontent2.Name); Assert.AreEqual(true, barcontent2.IsLeaf); Assert.AreEqual(0, barcontent2.Children.Count); // Assert Foo/Bar/Foo Node barfoo = bar.Children["Foo"]; Assert.AreEqual("Foo", barfoo.Name); Assert.AreEqual(false, barfoo.IsLeaf); Assert.AreEqual(1, barfoo.Children.Count); // Assert Foo/Bar/Foo/Bar Node barfoobar = barfoo.Children["Bar"]; Assert.AreEqual("Bar", barfoobar.Name); Assert.AreEqual(false, barfoobar.IsLeaf); Assert.AreEqual(1, barfoobar.Children.Count); // Assert Foo/Bar/Foo/Bar/Content2.txt Node barfoobarcontent2 = barfoobar.Children["Content2.txt"]; Assert.AreEqual("Content2.txt", barfoobarcontent2.Name); Assert.AreEqual(true, barfoobarcontent2.IsLeaf); Assert.AreEqual(0, barfoobarcontent2.Children.Count); }
public void ExtractSnippetFilterNotFound() { // Run the extraction FileSystemSnippetExtractor extractor = new FileSystemSnippetExtractor(); DirectoryInfoBase directoryInfoBase = fileSystem.DirectoryInfo.FromDirectoryName(fileSystem.Path.GetFullPath("Foo")); NodeSnippet snippet = extractor.Extract(directoryInfoBase, "NotFound.txt") as NodeSnippet; // Assert Foo Assert.AreEqual("Foo", snippet.Node.Name); Assert.AreEqual(false, snippet.Node.IsLeaf); Assert.AreEqual(0, snippet.Node.Children.Count); }
public void RenderTree() { // Declare files Node file1 = new Node("File1.txt", true); Node file2 = new Node("File2.jpg", true); Node file3 = new Node("File3.cs", true); Node file4 = new Node("File4", true); Node fileA = new Node("A", true); Node fileZ = new Node("Z", true); // Declare folders Node root = new Node("Root", false); Node folder1 = new Node("Folder1", false); Node folder2 = new Node("Folder2", false); Node folder3 = new Node("Folder3", false); // Assign files to folders folder3.Children.Add(file1.Name, file1); folder3.Children.Add(file2.Name, file2); folder1.Children.Add(file3.Name, file3); // Assign folders to folders folder2.Children.Add(folder3.Name, folder3); root.Children.Add(folder1.Name, folder1); root.Children.Add(folder2.Name, folder2); root.Children.Add(file4.Name, file4); root.Children.Add(fileA.Name, fileA); root.Children.Add(fileZ.Name, fileZ); // Create snippet reference System.Collections.Generic.Dictionary <Guid, Extension.Model.Snippet> snippetReference = new System.Collections.Generic.Dictionary <Guid, Extension.Model.Snippet>(); Guid guid = Guid.NewGuid(); snippetReference[guid] = new NodeSnippet(root); // Define formatter this.Formatter = new ProjbookHtmlFormatter("page", this.StreamWriter, CommonMarkSettings.Default, 0, snippetReference, "prefix:"); // Process Block block = new Block(BlockTag.HtmlBlock, 0); string content = "prefix:" + guid; block.StringContent = new StringContent(); block.StringContent.Append(content, 0, content.Length); string output = this.Process(block); // Assert rendering Assert.AreEqual( @"<div class=""filetree""><ul><li data-jstree='{""type"":""folder""}'>Root<ul><li data-jstree='{""type"":""folder""}'>Folder1<ul><li data-jstree='{""type"":""file""}'>File3.cs</li></ul></li></ul><ul><li data-jstree='{""type"":""folder""}'>Folder2<ul><li data-jstree='{""type"":""folder""}'>Folder3<ul><li data-jstree='{""type"":""file""}'>File1.txt</li></ul><ul><li data-jstree='{""type"":""file""}'>File2.jpg</li></ul></li></ul></li></ul><ul><li data-jstree='{""type"":""file""}'>A</li></ul><ul><li data-jstree='{""type"":""file""}'>File4</li></ul><ul><li data-jstree='{""type"":""file""}'>Z</li></ul></li></ul></div>", output); }
public void ExtractSnippetFilterMultiplePattern(string pattern) { // Run the extraction FileSystemSnippetExtractor extractor = new FileSystemSnippetExtractor(); DirectoryInfoBase directoryInfoBase = fileSystem.DirectoryInfo.FromDirectoryName(fileSystem.Path.GetFullPath(".")); NodeSnippet snippet = extractor.Extract(directoryInfoBase, pattern) as NodeSnippet; // Assert children number Assert.AreEqual(1, snippet.Node.Children.Count); // Assert A Node acontent = snippet.Node.Children["A"]; Assert.AreEqual("A", acontent.Name); Assert.AreEqual(false, acontent.IsLeaf); Assert.AreEqual(2, acontent.Children.Count); // Assert A/Content.gif Node contentgif = acontent.Children["Content.gif"]; Assert.AreEqual("Content.gif", contentgif.Name); Assert.AreEqual(true, contentgif.IsLeaf); Assert.AreEqual(0, contentgif.Children.Count); // Assert A/B Node bcontent = acontent.Children["B"]; Assert.AreEqual("B", bcontent.Name); Assert.AreEqual(false, bcontent.IsLeaf); Assert.AreEqual(1, bcontent.Children.Count); // Assert A/B/Content.jpg Node contentjpg = bcontent.Children["Content.jpg"]; Assert.AreEqual("Content.jpg", contentjpg.Name); Assert.AreEqual(true, contentjpg.IsLeaf); Assert.AreEqual(0, contentjpg.Children.Count); }
/// <summary> /// Generates documentation. /// </summary> /// <returns>The generation errors.</returns> public Model.GenerationError[] Generate(Configuration configuration) { // Initialize the list containing all generation errors List <Model.GenerationError> generationError = new List <Model.GenerationError>(); // Ensure output directory exists if (!this.OutputDirectory.Exists) { this.OutputDirectory.Create(); } // Process all pages List <Model.Page> pages = new List <Model.Page>(); foreach (Page page in configuration.Pages) { // Compute the page id used as a tab id and page prefix for bookmarking string pageId = page.Path.Replace(".", string.Empty).Replace("/", string.Empty).Replace("\\", string.Empty); // Load the document Block document; // Process the page string pageFilePath = this.fileSystem.FileInfo.FromFileName(page.FileSystemPath).FullName; using (StreamReader reader = new StreamReader(this.fileSystem.File.Open(pageFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))) { document = CommonMarkConverter.ProcessStage1(reader); } // Process snippet CommonMarkConverter.ProcessStage2(document); Dictionary <Guid, Extension.Model.Snippet> snippetDictionary = new Dictionary <Guid, Extension.Model.Snippet>(); foreach (var node in document.AsEnumerable()) { // Filter fenced code if (node.Block != null && node.Block.Tag == BlockTag.FencedCode) { // Build extraction rule string fencedCode = node.Block.FencedCodeData.Info; // Do not trigger extraction if the fenced code is empty if (string.IsNullOrWhiteSpace(fencedCode)) { continue; } SnippetExtractionRule snippetExtractionRule = SnippetExtractionRule.Parse(fencedCode); // Extract and inject snippet and the factory were able to create an extractor if (null != snippetExtractionRule) { // Cleanup Projbook specific syntax node.Block.FencedCodeData.Info = snippetExtractionRule.Language; // Inject snippet try { // Retrieve the extractor instance ISnippetExtractor snippetExtractor; if (!this.extractorCache.TryGetValue(snippetExtractionRule.TargetPath, out snippetExtractor)) { snippetExtractor = this.snippetExtractorFactory.CreateExtractor(snippetExtractionRule); this.extractorCache[snippetExtractionRule.TargetPath] = snippetExtractor; } // Look for the file in available source directories FileSystemInfoBase fileSystemInfo = null; DirectoryInfoBase[] directoryInfos = null; if (TargetType.FreeText != snippetExtractor.TargetType) { directoryInfos = this.ExtractSourceDirectories(this.CsprojFile); foreach (DirectoryInfoBase directoryInfo in directoryInfos) { // Get directory name string directoryName = directoryInfo.FullName; if (1 == directoryName.Length && Path.DirectorySeparatorChar == directoryName[0]) { directoryName = string.Empty; } // Compute file full path string fullFilePath = this.fileSystem.Path.GetFullPath(this.fileSystem.Path.Combine(directoryName, snippetExtractionRule.TargetPath ?? string.Empty)); switch (snippetExtractor.TargetType) { case TargetType.File: if (this.fileSystem.File.Exists(fullFilePath)) { fileSystemInfo = this.fileSystem.FileInfo.FromFileName(fullFilePath); } break; case TargetType.Folder: if (this.fileSystem.Directory.Exists(fullFilePath)) { fileSystemInfo = this.fileSystem.DirectoryInfo.FromDirectoryName(fullFilePath); } break; } // Stop lookup if the file system info is found if (null != fileSystemInfo) { break; } } } // Raise an error if cannot find the file if (null == fileSystemInfo && TargetType.FreeText != snippetExtractor.TargetType) { // Locate block line int line = this.LocateBlockLine(node.Block, page); // Compute error column: Index of the path in the fenced code + 3 (for the ``` prefix) + 1 (to be 1 based) int column = fencedCode.IndexOf(snippetExtractionRule.TargetPath) + 4; // Report error generationError.Add(new Model.GenerationError( sourceFile: page.Path, message: string.Format("Cannot find target '{0}' in any referenced project ({0})", snippetExtractionRule.TargetPath, string.Join(";", directoryInfos.Select(x => x.FullName))), line: line, column: column)); continue; } // Extract the snippet Extension.Model.Snippet snippet = TargetType.FreeText == snippetExtractor.TargetType ? snippetExtractor.Extract(null, snippetExtractionRule.TargetPath) : snippetExtractor.Extract(fileSystemInfo, snippetExtractionRule.Pattern); // Reference snippet Guid guid = Guid.NewGuid(); snippetDictionary[guid] = snippet; // Inject reference as content StringContent code = new StringContent(); string content = SNIPPET_REFERENCE_PREFIX + guid; code.Append(content, 0, content.Length); node.Block.StringContent = code; // Change tag to html for node snippets NodeSnippet nodeSnippet = snippet as NodeSnippet; if (null != nodeSnippet) { node.Block.Tag = BlockTag.HtmlBlock; } } catch (SnippetExtractionException snippetExtraction) { // Locate block line int line = this.LocateBlockLine(node.Block, page); // Compute error column: Fenced code length - pattern length + 3 (for the ``` prefix) + 1 (to be 1 based) int column = fencedCode.Length - snippetExtractionRule.Pattern.Length + 4; // Report error generationError.Add(new Model.GenerationError( sourceFile: page.Path, message: string.Format("{0}: {1}", snippetExtraction.Message, snippetExtraction.Pattern), line: line, column: column)); } catch (System.Exception exception) { generationError.Add(new Model.GenerationError( sourceFile: page.Path, message: exception.Message, line: 0, column: 0)); } } } } // Write to output ProjbookHtmlFormatter projbookHtmlFormatter = null; MemoryStream documentStream = new MemoryStream(); using (StreamWriter writer = new StreamWriter(documentStream)) { // Setup custom formatter CommonMarkSettings.Default.OutputDelegate = (d, o, s) => (projbookHtmlFormatter = new ProjbookHtmlFormatter(pageId, o, s, configuration.SectionTitleBase, snippetDictionary, SNIPPET_REFERENCE_PREFIX)).WriteDocument(d); // Render CommonMarkConverter.ProcessStage3(document, writer); } // Initialize the pre section content string preSectionContent = string.Empty; // Retrieve page content byte[] pageContent = documentStream.ToArray(); // Set the whole page content if no page break is detected if (projbookHtmlFormatter.PageBreak.Length == 0) { preSectionContent = System.Text.Encoding.UTF8.GetString(pageContent); } // Compute pre section content from the position 0 to the first page break position if (projbookHtmlFormatter.PageBreak.Length > 0 && projbookHtmlFormatter.PageBreak.First().Position > 0) { preSectionContent = this.StringFromByteArray(pageContent, 0, projbookHtmlFormatter.PageBreak.First().Position); } // Build section list List <Model.Section> sections = new List <Model.Section>(); for (int i = 0; i < projbookHtmlFormatter.PageBreak.Length; ++i) { // Retrieve the current page break PageBreakInfo pageBreak = projbookHtmlFormatter.PageBreak[i]; // Extract the content from the current page break to the next one if any string content = null; if (i < projbookHtmlFormatter.PageBreak.Length - 1) { PageBreakInfo nextBreak = projbookHtmlFormatter.PageBreak[1 + i]; content = this.StringFromByteArray(pageContent, pageBreak.Position, nextBreak.Position - pageBreak.Position); } // Otherwise extract the content from the current page break to the end of the content else { content = this.StringFromByteArray(pageContent, pageBreak.Position, pageContent.Length - pageBreak.Position); } // Create a new section and add to the known list sections.Add(new Model.Section( id: pageBreak.Id, level: pageBreak.Level, title: pageBreak.Title, content: content)); } // Add new page pages.Add(new Model.Page( id: pageId, title: page.Title, preSectionContent: preSectionContent, sections: sections.ToArray())); } // Html generation if (configuration.GenerateHtml) { try { string outputFileHtml = this.fileSystem.Path.Combine(this.OutputDirectory.FullName, configuration.OutputHtml); this.GenerateFile(configuration.TemplateHtml, outputFileHtml, configuration, pages); } catch (TemplateParsingException templateParsingException) { generationError.Add(new Model.GenerationError(configuration.TemplateHtml, string.Format("Error during HTML generation: {0}", templateParsingException.Message), templateParsingException.Line, templateParsingException.Column)); } catch (System.Exception exception) { generationError.Add(new Model.GenerationError(configuration.TemplateHtml, string.Format("Error during HTML generation: {0}", exception.Message), 0, 0)); } } // Pdf generation if (configuration.GeneratePdf && !this.SkipPdf) { try { // Generate the pdf template string outputFileHtml = this.fileSystem.Path.Combine(this.OutputDirectory.FullName, configuration.OutputPdf); this.GenerateFile(configuration.TemplatePdf, outputFileHtml, configuration, pages); #if !NOPDF // Compute file names string outputPdf = this.fileSystem.Path.ChangeExtension(configuration.OutputPdf, ".pdf"); string outputFilePdf = this.fileSystem.Path.Combine(this.OutputDirectory.FullName, outputPdf); // Prepare the converter MultiplexingConverter pdfConverter = new MultiplexingConverter(); pdfConverter.ObjectSettings.Page = outputFileHtml; pdfConverter.Error += (s, e) => { generationError.Add(new Model.GenerationError(configuration.TemplatePdf, string.Format("Error during PDF generation: {0}", e.Value), 0, 0)); }; // Prepare file system if abstracted bool requireCopyToFileSystem = !File.Exists(outputFileHtml); try { // File system may be abstracted, this requires to copy the pdf generation file to the actual file system // in order to allow wkhtmltopdf to process the generated html as input file if (requireCopyToFileSystem) { File.WriteAllBytes(outputFileHtml, this.fileSystem.File.ReadAllBytes(outputFileHtml)); } // Run pdf converter using (pdfConverter) using (Stream outputFileStream = this.fileSystem.File.Open(outputFilePdf, FileMode.Create, FileAccess.Write, FileShare.None)) { try { byte[] buffer = pdfConverter.Convert(); outputFileStream.Write(buffer, 0, buffer.Length); } catch { // Ignore generation errors at that level // Errors are handled by the error handling having the best description } } } finally { if (requireCopyToFileSystem && File.Exists(outputFileHtml)) { File.Delete(outputFileHtml); } } #endif } catch (TemplateParsingException templateParsingException) { generationError.Add(new Model.GenerationError(configuration.TemplatePdf, string.Format("Error during PDF generation: {0}", templateParsingException.Message), templateParsingException.Line, templateParsingException.Column)); } catch (System.Exception exception) { if (null != exception.InnerException && INCORRECT_FORMAT_HRESULT == exception.InnerException.HResult) { // Report detailed error message for wrong architecture loading string runningArchitectureProccess = IntPtr.Size == 8 ? "x64" : "x86"; string otherRunningArchitectureProccess = IntPtr.Size != 8 ? "x64" : "x86"; generationError.Add(new Model.GenerationError(configuration.TemplatePdf, string.Format("Error during PDF generation: Could not load wkhtmltopdf for {0}. Try again running as a {1} process.", runningArchitectureProccess, otherRunningArchitectureProccess), 0, 0)); } else { // Report unknown error generationError.Add(new Model.GenerationError(configuration.TemplatePdf, string.Format("Error during PDF generation: {0}", exception.Message), 0, 0)); } } } // Return the generation errors return(generationError.ToArray()); }
/// <summary> /// Specializes block writing for anchor injection. /// For each formatted header we generate an anchor based on the context name, the header content and eventually add an integer suffix in order to prevent conflicts. /// </summary> /// <param name="block">The block to process.</param> /// <param name="isOpening">Define whether the block is opening.</param> /// <param name="isClosing">Defines whether the block is closing.</param> /// <param name="ignoreChildNodes">return whether the processing ignored child nodes.</param> protected override void WriteBlock(Block block, bool isOpening, bool isClosing, out bool ignoreChildNodes) { // Process block content if (null != block.StringContent) { // Read current block content string content = block.StringContent.TakeFromStart(block.StringContent.Length); // Detect snippet reference if (content.StartsWith(snippetReferencePrefix)) { // Fetch matching snippet Extension.Model.Snippet snippet = snippetDictionary[Guid.Parse(content.Substring(snippetReferencePrefix.Length))]; // Render and write plain text snippet PlainTextSnippet plainTextSnippet = snippet as PlainTextSnippet; if (null != plainTextSnippet) { block.StringContent.Replace(plainTextSnippet.Text, 0, plainTextSnippet.Text.Length); } // Render and write node snippet NodeSnippet nodeSnippet = snippet as NodeSnippet; if (null != nodeSnippet) { // Render node as html string renderedNode = Render(nodeSnippet.Node); // Write rendering block.StringContent.Replace(renderedNode, 0, renderedNode.Length); } } } // Filter opening header if (isOpening && null != block && block.Tag == BlockTag.AtxHeading) { // Apply section title base block.Heading = new HeadingData(block.Heading.Level + this.sectionTitleBase); // Retrieve header content string headerContent; if (null != block.InlineContent && null != block.InlineContent.LiteralContent) { // Read the whole content Inline inline = block.InlineContent; StringBuilder stringBuilder = new StringBuilder(); do { stringBuilder.Append(inline.LiteralContent); inline = inline.NextSibling; } while (null != inline); headerContent = stringBuilder.ToString(); } else { headerContent = "unknown"; } // Compute the anchor value string sectionId = headerContent.ToLower(); sectionId = invalidTitleChars.Replace(string.Format("{0}-{1}", this.ContextName, sectionId), "-"); // Detect anchor conflict if (sectionConflict.ContainsKey(sectionId)) { // Append the index sectionId = string.Format("{0}-{1}", sectionId, ++sectionConflict[sectionId]); } // Flush the writer to move the stream position used during page break creation this.writer.Flush(); // Add a new page break this.pageBreak.Add(new PageBreakInfo(sectionId, Math.Max(0, (int)block.Heading.Level), headerContent, this.writer.BaseStream.Position)); // Initialize section conflict sectionConflict[sectionId] = 1; } // Read all paragraph inline strings in order to make table detectable List <string> tableParts = new List <string>(); List <string[]> splittedTableParts = new List <string[]>(); Match[] headerDelimiterMatches = null; if (isOpening && null != block && block.Tag == BlockTag.Paragraph) { Inline inline = block.InlineContent; while (null != inline) { if (inline.Tag == InlineTag.String) { // Read and split line on '|' tableParts.Add(inline.LiteralContent); string line = inline.LiteralContent.Trim(); string[] lineParts = line.Split(new char[] { '|' }, StringSplitOptions.None).Select(x => x.Trim()).ToArray(); // At the third line of the same block, ensure we're processing a table before to continue table parsing if (splittedTableParts.Count == 2) { // If the second line cannot be a header delimiter, break the table parsing if (splittedTableParts[1].Any(x => string.IsNullOrWhiteSpace(x)) || (splittedTableParts[1].Length < 2 && !tableParts[1].Contains('|'))) { break; } // Matches the second line as header delimiter and abort table parsing if the match fail headerDelimiterMatches = splittedTableParts[1].Select(x => dashDelimiter.Match(x)).ToArray(); if (!headerDelimiterMatches.All(x => x.Success)) { break; } } // Define parts boundaries int startPos = 0; int numbber = lineParts.Length; // Ignore the first part if the line starts with '|' if ('|' == line.First()) { ++startPos; --numbber; } // Ignore the last part if the line ends with '|' if ('|' == line.Last()) { --numbber; } // Add the line to splitted parts splittedTableParts.Add(lineParts.Skip(startPos).Take(numbber).ToArray()); } inline = inline.NextSibling; } } // Process table rendering if (null != headerDelimiterMatches && splittedTableParts.Count > 2) { // Remove the delimiter splittedTableParts.RemoveAt(1); // Render table this.Write(@"<table class=""table"">"); for (int i = 0; i < splittedTableParts.Count; ++i) { // Render rows this.Write("<tr>"); // Render cells for (int j = 0; j < splittedTableParts[i].Length; ++j) { // Determine text alignment string style = "text-left"; if (headerDelimiterMatches.Length > j && 0 < i) { Match match = headerDelimiterMatches[j]; if (":" == match.Groups[1].Value && ":" == match.Groups[2].Value) { style = "text-center"; } else if ("" == match.Groups[1].Value && ":" == match.Groups[2].Value) { style = "text-right"; } } // Generate markup string tag = 0 == i ? "th" : "td"; this.Write(string.Format(@"<{0} class=""{1}"">", tag, style)); this.Write(splittedTableParts[i][j]); this.Write(string.Format("</{0}>", tag)); } this.Write("</tr>"); } this.Write("</table>"); // Report rendering finished ignoreChildNodes = true; return; } // Trigger parent rendering for the default html rendering base.WriteBlock(block, isOpening, isClosing, out ignoreChildNodes); }