Exemple #1
0
 private static void ApplyFormatting(string formatName, MarkedUpText markedUpText)
 {
     foreach (MarkedUpTextRun run in markedUpText._runList)
     {
         run.setProperty(formatName);
     }
 }
Exemple #2
0
 private void AddAllFrom(MarkedUpText m)
 {
     foreach (MarkedUpTextRun r in m.Runs)
     {
         this._runList.Add(r);
     }
 }
Exemple #3
0
        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);
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
        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);
                }
            }
        }