private static void ApplyFormatting(string formatName, MarkedUpText markedUpText) { foreach (MarkedUpTextRun run in markedUpText._runList) { run.setProperty(formatName); } }
private void AddAllFrom(MarkedUpText m) { foreach (MarkedUpTextRun r in m.Runs) { this._runList.Add(r); } }
private static MarkedUpText ParseXmlRecursive(XmlNode node) { MarkedUpText markedUpText; if ((node.Name == "br") || (node.Name == "span" && (node.Attributes["class"]?.Value ?? "").Equals("bloom-linebreak"))) { // not \r\n or something that might translate to that. See comment in ParseXml() MarkedUpTextRun run = new MarkedUpTextRun("\n"); markedUpText = new MarkedUpText(); markedUpText._runList.Add(run); } else if (!node.HasChildNodes) { MarkedUpTextRun run = new MarkedUpTextRun(node.InnerText); markedUpText = new MarkedUpText(); markedUpText._runList.Add(run); } else { markedUpText = new MarkedUpText(); foreach (XmlNode child in node.ChildNodes) { MarkedUpText markedUpChild = ParseXmlRecursive(child); ApplyFormatting(node.Name, markedUpChild); markedUpText._runList.AddRange(markedUpChild._runList); } } return(markedUpText); }
/// <summary> /// Extract the text and any bold, italic, underline, and/or superscript formatting /// Adds newlines after paragraphs except for the last one /// </summary> public static MarkedUpText ParseXml(string xmlString) { XmlDocument doc = new XmlDocument(); doc.PreserveWhitespace = true; var wrappedXmlString = "<wrapper>" + xmlString + "</wrapper>"; doc.LoadXml(wrappedXmlString); XmlNode root = doc.DocumentElement; MarkedUpText result = new MarkedUpText(); MarkedUpText pending = new MarkedUpText(); //There are no paragraph elements, just keep all whitespace if (((XmlElement)root).GetElementsByTagName("p").Count == 0) { return(ParseXmlRecursive(root)); } foreach (XmlNode x in root.ChildNodes.Cast <XmlNode>()) { if (x.Name == "#whitespace") { continue; } if (string.IsNullOrWhiteSpace(x.InnerText) && x.Name != "p") { if (result.Count > 0) { pending._runList.Add(new MarkedUpTextRun(x.InnerText)); } continue; } result.AddAllFrom(pending); result.AddAllFrom(ParseXmlRecursive(x)); pending = new MarkedUpText(); if (x.Name == "p") { // We want a line break here, but only if something follows...we don't need a blank line at // the end of the cell, which is what Excel will do with a trailing newline. // It's important to use a simple \n here, not something that is (or might be) \r\n. // The latter looks fine in Excel, but when converted to Google Docs the \r shows up // as an explicit \x000D. \n works in both places. pending._runList.Add(new MarkedUpTextRun("\n")); } } return(result); }
public static void WriteSpreadsheet(InternalSpreadsheet spreadsheet, string outputPath, bool retainMarkup, IWebSocketProgress progress = null) { using (var package = new ExcelPackage()) { var worksheet = package.Workbook.Worksheets.Add("BloomBook"); worksheet.DefaultColWidth = languageColumnWidth; for (int i = 1; i <= spreadsheet.StandardLeadingColumns.Length; i++) { worksheet.Column(i).Width = standardLeadingColumnWidth; } var imageSourceColumn = spreadsheet.GetColumnForTag(InternalSpreadsheet.ImageSourceColumnLabel); var imageThumbnailColumn = spreadsheet.GetColumnForTag(InternalSpreadsheet.ImageThumbnailColumnLabel); // Apparently the width is in some approximation of 'characters'. This empirically determined // conversion factor seems to do a pretty good job. worksheet.Column(imageThumbnailColumn + 1).Width = defaultImageWidth / 6.88; int r = 0; foreach (var row in spreadsheet.AllRows()) { r++; for (var c = 0; c < row.Count; c++) { // Enhance: Excel complains about cells that contain pure numbers // but are created as strings. We could possibly tell it that cells // that contain simple numbers can be treated accordingly. // It might be helpful for some uses of the group-on-page-index // if Excel knew to treat them as numbers. var sourceCell = row.GetCell(c); var content = sourceCell.Content; // Parse xml for markdown formatting on language columns, // Display formatting in excel spreadsheet ExcelRange currentCell = worksheet.Cells[r, c + 1]; if (!string.IsNullOrEmpty(sourceCell.Comment)) { // Second arg is supposed to be the author. currentCell.AddComment(sourceCell.Comment, "Bloom"); } if (!retainMarkup && IsWysiwygFormattedColumn(row, c) && IsWysiwygFormattedRow(row)) { MarkedUpText markedUpText = MarkedUpText.ParseXml(content); if (markedUpText.HasFormatting) { currentCell.IsRichText = true; foreach (MarkedUpTextRun run in markedUpText.Runs) { if (!run.Text.Equals("")) { ExcelRichText text = currentCell.RichText.Add(run.Text); text.Bold = run.Bold; text.Italic = run.Italic; text.UnderLine = run.Underlined; if (run.Superscript) { text.VerticalAlign = ExcelVerticalAlignmentFont.Superscript; } } } } else { currentCell.Value = markedUpText.PlainText(); } } else { // Either the retainMarkup flag is set, or this is not book text. It could be header or leading column. // Generally, we just want to blast our cell content into the spreadsheet cell. // However, there are cases where we put an error message in an image thumbnail cell when processing the image path. // We don't want to overwrite these. An easy way to prevent it is to not overwrite any cell that already has content. // Since export is creating a new spreadsheet, cells we want to write will always be empty initially. if (currentCell.Value == null) { currentCell.Value = content; } } //Embed any images in the excel file if (c == imageSourceColumn) { var imageSrc = sourceCell.Content; // if this row has an image source value that is not a header if (imageSrc != "" && !row.IsHeader) { var sheetFolder = Path.GetDirectoryName(outputPath); var imagePath = Path.Combine(sheetFolder, imageSrc); //Images show up in the cell 1 row greater and 1 column greater than assigned //So this will put them in row r, column imageThumbnailColumn+1 like we want var rowHeight = embedImage(imagePath, r - 1, imageThumbnailColumn); worksheet.Row(r).Height = rowHeight * 72 / 96 + 3; //so the image is visible; height seems to be points } } } if (row is HeaderRow) { using (ExcelRange rng = GetRangeForRow(worksheet, r)) rng.Style.Font.Bold = true; } if (row.Hidden) { worksheet.Row(r).Hidden = true; SetBackgroundColorOfRow(worksheet, r, InternalSpreadsheet.HiddenColor); } else if (row.BackgroundColor != default(Color)) { SetBackgroundColorOfRow(worksheet, r, row.BackgroundColor); } } worksheet.Cells[1, 1, r, spreadsheet.ColumnCount].Style.WrapText = true; int embedImage(string imageSrcPath, int rowNum, int colNum) { int finalHeight = 30; // a reasonable default if we don't manage to embed an image. try { using (Image image = Image.FromFile(imageSrcPath)) { string imageName = Path.GetFileNameWithoutExtension(imageSrcPath); var origImageHeight = image.Size.Height; var origImageWidth = image.Size.Width; int finalWidth = defaultImageWidth; finalHeight = (int)(finalWidth * origImageHeight / origImageWidth); var size = new Size(finalWidth, finalHeight); using (Image thumbnail = ImageUtils.ResizeImageIfNecessary(size, image, false)) { var excelImage = worksheet.Drawings.AddPicture(imageName, thumbnail); excelImage.SetPosition(rowNum, 2, colNum, 2); } } } catch (Exception) { string errorText; if (!RobustFile.Exists(imageSrcPath)) { errorText = "Missing"; } else if (Path.GetExtension(imageSrcPath).ToLowerInvariant().Equals(".svg")) { errorText = "Can't display SVG"; } else { errorText = "Bad image file"; } progress?.MessageWithoutLocalizing(errorText + ": " + imageSrcPath); worksheet.Cells[r, imageThumbnailColumn + 1].Value = errorText; } return(Math.Max(finalHeight, 30)); } foreach (var iColumn in spreadsheet.HiddenColumns) { // This is pretty yucky... our internal spreadsheet is all 0-based, but the EPPlus library is all 1-based... var iColumn1Based = iColumn + 1; worksheet.Column(iColumn1Based).Hidden = true; SetBackgroundColorOfColumn(worksheet, iColumn1Based, InternalSpreadsheet.HiddenColor); } try { RobustFile.Delete(outputPath); var xlFile = new FileInfo(outputPath); package.SaveAs(xlFile); } catch (IOException ex) when((ex.HResult & 0x0000FFFF) == 32) //ERROR_SHARING_VIOLATION { Console.WriteLine("Writing Spreadsheet failed. Do you have it open in another program?"); Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); progress?.Message("Spreadsheet.SpreadsheetLocked", "", "Bloom could not write to the spreadsheet because another program has it locked. Do you have it open in another program?", ProgressKind.Error); } catch (Exception ex) { progress?.Message("Spreadsheet.ExportFailed", "", "Export failed: " + ex.Message, ProgressKind.Error); } } }