private List <XAttribute> GetPbInfo(PageBreakInfo item) { return(new List <XAttribute>() { new XAttribute("pb-id", item.PageBreakXmlId), new XAttribute("pb", item.PageBreak) }); }
/// <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()); }