예제 #1
0
    private static int BugWorkaroundIndent(ref MarkdownParagraph mdp, int level)
    {
        if (!mdp.IsParagraph)
        {
            return(level);
        }
        var p     = mdp as MarkdownParagraph.Paragraph;
        var spans = p.Item;

        if (spans.Count() == 0 || !spans[0].IsLiteral)
        {
            return(level);
        }
        var literal = spans[0] as MarkdownSpan.Literal;

        if (!literal.Item.StartsWith("ceci-n'est-pas-une-indent"))
        {
            return(level);
        }
        //
        var literal2 = MarkdownSpan.NewLiteral(literal.Item.Substring(25));
        var spans2   = Microsoft.FSharp.Collections.FSharpList <MarkdownSpan> .Cons(literal2, spans.Tail);

        var p2 = MarkdownParagraph.NewParagraph(spans2);

        mdp = p2;
        return(0);
    }
예제 #2
0
        IEnumerable <OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
        {
            if (md.IsHeading)
            {
                var mdh   = md as MarkdownParagraph.Heading;
                var level = mdh.Item1;
                var spans = mdh.Item2;
                var sr    = sections[new SectionRef(mdh, filename).Url];
                var props = new ParagraphProperties(new ParagraphStyleId()
                {
                    Val = $"Heading{level}"
                });
                var p = new Paragraph {
                    ParagraphProperties = props
                };
                maxBookmarkId.Value += 1;
                p.AppendChild(new BookmarkStart {
                    Name = sr.BookmarkName, Id = maxBookmarkId.Value.ToString()
                });
                p.Append(Spans2Elements(spans));
                p.AppendChild(new BookmarkEnd {
                    Id = maxBookmarkId.Value.ToString()
                });
                yield return(p);

                Console.WriteLine(new string(' ', level * 4 - 4) + sr.Number + " " + sr.Title);
                yield break;
            }

            else if (md.IsParagraph)
            {
                var mdp   = md as MarkdownParagraph.Paragraph;
                var spans = mdp.Item;
                yield return(new Paragraph(Spans2Elements(spans)));

                yield break;
            }

            else if (md.IsListBlock)
            {
                var mdl  = md as MarkdownParagraph.ListBlock;
                var flat = FlattenList(mdl);

                // Let's figure out what kind of list it is - ordered or unordered? nested?
                var format0 = new[] { "1", "1", "1", "1" };
                foreach (var item in flat)
                {
                    format0[item.Level] = (item.IsBulletOrdered ? "1" : "o");
                }
                var format = string.Join("", format0);

                var numberingPart = wdoc.MainDocumentPart.NumberingDefinitionsPart ?? wdoc.MainDocumentPart.AddNewPart <NumberingDefinitionsPart>("NumberingDefinitionsPart001");
                if (numberingPart.Numbering == null)
                {
                    numberingPart.Numbering = new Numbering();
                }

                Func <int, bool, Level> createLevel;
                createLevel = (level, isOrdered) =>
                {
                    var numformat = NumberFormatValues.Bullet;
                    var levelText = new[] { "·", "o", "·", "o" }[level];
                    if (isOrdered && level == 0)
                    {
                        numformat = NumberFormatValues.Decimal; levelText = "%1.";
                    }
                    if (isOrdered && level == 1)
                    {
                        numformat = NumberFormatValues.LowerLetter; levelText = "%2.";
                    }
                    if (isOrdered && level == 2)
                    {
                        numformat = NumberFormatValues.LowerRoman; levelText = "%3.";
                    }
                    if (isOrdered && level == 3)
                    {
                        numformat = NumberFormatValues.LowerRoman; levelText = "%4.";
                    }
                    var r = new Level {
                        LevelIndex = level
                    };
                    r.Append(new StartNumberingValue {
                        Val = 1
                    });
                    r.Append(new NumberingFormat {
                        Val = numformat
                    });
                    r.Append(new LevelText {
                        Val = levelText
                    });
                    r.Append(new ParagraphProperties(new Indentation {
                        Left = (540 + 360 * level).ToString(), Hanging = "360"
                    }));
                    if (levelText == "·")
                    {
                        r.Append(new NumberingSymbolRunProperties(new RunFonts {
                            Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman"
                        }));
                    }
                    if (levelText == "o")
                    {
                        r.Append(new NumberingSymbolRunProperties(new RunFonts {
                            Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New"
                        }));
                    }
                    return(r);
                };
                var level0 = createLevel(0, format[0] == '1');
                var level1 = createLevel(1, format[1] == '1');
                var level2 = createLevel(2, format[2] == '1');
                var level3 = createLevel(3, format[3] == '1');

                var abstracts = numberingPart.Numbering.OfType <AbstractNum>().Select(an => an.AbstractNumberId.Value).ToList();
                var aid       = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1);
                var aabstract = new AbstractNum(new MultiLevelType()
                {
                    Val = MultiLevelValues.Multilevel
                }, level0, level1, level2, level3)
                {
                    AbstractNumberId = aid
                };
                numberingPart.Numbering.InsertAt(aabstract, 0);

                var instances   = numberingPart.Numbering.OfType <NumberingInstance>().Select(ni => ni.NumberID.Value);
                var nid         = (instances.Count() == 0 ? 1 : instances.Max() + 1);
                var numInstance = new NumberingInstance(new AbstractNumId {
                    Val = aid
                })
                {
                    NumberID = nid
                };
                numberingPart.Numbering.AppendChild(numInstance);

                // We'll also figure out the indentation(for the benefit of those paragraphs that should be
                // indendent with the list but aren't numbered). I'm not sure what the indent comes from.
                // in the docx, each AbstractNum that I created has an indent for each of its levels,
                // defaulted at 900, 1260, 1620, ... but I can't see where in the above code that's created?
                Func <int, string> calcIndent = level => (540 + level * 360).ToString();

                foreach (var item in flat)
                {
                    var content = item.Paragraph;
                    if (content.IsParagraph || content.IsSpan)
                    {
                        var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).Item : (content as MarkdownParagraph.Span).Item);
                        if (item.HasBullet)
                        {
                            yield return new Paragraph(Spans2Elements(spans))
                                   {
                                       ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId {
                                    Val = "ListParagraph"
                                }, new NumberingLevelReference {
                                    Val = item.Level
                                }, new NumberingId {
                                    Val = nid
                                }))
                                   }
                        }
                        ;
                        else
                        {
                            yield return new Paragraph(Spans2Elements(spans))
                                   {
                                       ParagraphProperties = new ParagraphProperties(new Indentation {
                                    Left = calcIndent(item.Level)
                                })
                                   }
                        };
                    }
                    else if (content.IsQuotedBlock || content.IsCodeBlock)
                    {
                        foreach (var p in Paragraph2Paragraphs(content))
                        {
                            var props = p.GetFirstChild <ParagraphProperties>();

                            if (props == null)
                            {
                                props = new ParagraphProperties(); p.InsertAt(props, 0);
                            }
                            var indent = props?.GetFirstChild <Indentation>();
                            if (indent == null)
                            {
                                indent = new Indentation(); props.Append(indent);
                            }
                            indent.Left = calcIndent(item.Level);
                            yield return(p);
                        }
                    }
                    else if (content.IsTableBlock)
                    {
                        foreach (var p in Paragraph2Paragraphs(content))
                        {
                            var table = p as Table;
                            if (table == null)
                            {
                                yield return(p); continue;
                            }
                            var tprops  = table.GetFirstChild <TableProperties>();
                            var tindent = tprops?.GetFirstChild <TableIndentation>();
                            if (tindent == null)
                            {
                                throw new Exception("Ooops! Table is missing indentation");
                            }
                            tindent.Width = int.Parse(calcIndent(item.Level));
                            yield return(table);
                        }
                    }
                    else
                    {
                        throw new Exception("Unexpected item in list");
                    }
                }
            }

            else if (md.IsCodeBlock)
            {
                var mdc  = md as MarkdownParagraph.CodeBlock;
                var code = mdc.Item1;
                var lang = mdc.Item2;
                code = BugWorkaroundDecode(code);
                var runs        = new List <Run>();
                var onFirstLine = true;
                IEnumerable <ColorizedLine> lines;
                if (lang == "csharp" || lang == "c#" || lang == "cs")
                {
                    lines = Colorize.CSharp(code);
                }
                else if (lang == "vb" || lang == "vbnet" || lang == "vb.net")
                {
                    lines = Colorize.VB(code);
                }
                else if (lang == "" || lang == "xml")
                {
                    lines = Colorize.PlainText(code);
                }
                else if (lang == "antlr")
                {
                    lines = Antlr.ColorizeAntlr(code);
                }
                else
                {
                    throw new NotSupportedException($"unrecognized language {lang}");
                }
                foreach (var line in lines)
                {
                    if (onFirstLine)
                    {
                        onFirstLine = false;
                    }
                    else
                    {
                        runs.Add(new Run(new Break()));
                    }
                    foreach (var word in line.Words)
                    {
                        var run   = new Run();
                        var props = new RunProperties();
                        if (word.Red != 0 || word.Green != 0 || word.Blue != 0)
                        {
                            props.Append(new Color {
                                Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}"
                            });
                        }
                        if (word.IsItalic)
                        {
                            props.Append(new Italic());
                        }
                        if (props.HasChildren)
                        {
                            run.Append(props);
                        }
                        run.Append(new Text(word.Text)
                        {
                            Space = SpaceProcessingModeValues.Preserve
                        });
                        runs.Add(run);
                    }
                }
                var style = new ParagraphStyleId {
                    Val = (lang == "antlr" ? "Grammar" : "Code")
                };
                yield return(new Paragraph(runs)
                {
                    ParagraphProperties = new ParagraphProperties(style)
                });
            }

            else if (md.IsQuotedBlock)
            {
                var mdq     = md as MarkdownParagraph.QuotedBlock;
                var quoteds = mdq.Item;
                var kind    = "";
                foreach (var quoted0 in quoteds)
                {
                    var quoted = quoted0;
                    if (quoted.IsParagraph)
                    {
                        var p     = quoted as MarkdownParagraph.Paragraph;
                        var spans = p.Item;
                        if (spans.Any() && spans.First().IsStrong)
                        {
                            var strong = (spans.First() as MarkdownSpan.Strong).Item;
                            if (strong.Any() && strong.First().IsLiteral)
                            {
                                var literal = mdunescape(strong.First() as MarkdownSpan.Literal);
                                if (literal == "Note")
                                {
                                    kind = "AlertText";
                                }
                                else if (literal == "Annotation")
                                {
                                    kind = "Annotation";
                                    yield return(new Paragraph(Span2Elements(spans.Head))
                                    {
                                        ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                                            Val = kind
                                        })
                                    });

                                    if (spans.Tail.Any() && spans.Tail.First().IsLiteral)
                                    {
                                        quoted = MarkdownParagraph.NewParagraph(new Microsoft.FSharp.Collections.FSharpList <MarkdownSpan>(MarkdownSpan.NewLiteral(mdunescape(spans.Tail.First() as MarkdownSpan.Literal).TrimStart()), spans.Tail.Tail));
                                    }
                                    else
                                    {
                                        quoted = MarkdownParagraph.NewParagraph(spans.Tail);
                                    }
                                }
                            }
                        }
                        //
                        foreach (var qp in Paragraph2Paragraphs(quoted))
                        {
                            var qpp = qp as Paragraph;
                            if (qpp != null)
                            {
                                var props = new ParagraphProperties(new ParagraphStyleId()
                                {
                                    Val = kind
                                }); qpp.ParagraphProperties = props;
                            }
                            yield return(qp);
                        }
                    }

                    else if (quoted.IsCodeBlock)
                    {
                        var mdc              = quoted as MarkdownParagraph.CodeBlock;
                        var code             = mdc.Item1;
                        var lang             = mdc.Item2;
                        var ignoredAfterLang = mdc.Item3;
                        var lines            = code.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList();
                        if (string.IsNullOrWhiteSpace(lines.Last()))
                        {
                            lines.RemoveAt(lines.Count - 1);
                        }
                        var run = new Run()
                        {
                            RunProperties = new RunProperties(new RunStyle {
                                Val = "CodeEmbedded"
                            })
                        };
                        foreach (var line in lines)
                        {
                            if (run.ChildElements.Count() > 1)
                            {
                                run.Append(new Break());
                            }
                            run.Append(new Text("    " + line)
                            {
                                Space = SpaceProcessingModeValues.Preserve
                            });
                        }
                        yield return(new Paragraph(run)
                        {
                            ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                                Val = kind
                            })
                        });
                    }

                    else if (quoted.IsListBlock)
                    {
                        if (!(quoted as MarkdownParagraph.ListBlock).Item1.IsOrdered)
                        {
                            throw new NotImplementedException("unordered list inside annotation");
                        }
                        var count = 1;
                        foreach (var qp in Paragraph2Paragraphs(quoted))
                        {
                            var qpp = qp as Paragraph;
                            if (qpp == null)
                            {
                                yield return(qp); continue;
                            }
                            qp.InsertAt(new Run(new Text($"{count}. ")
                            {
                                Space = SpaceProcessingModeValues.Preserve
                            }), 0);
                            count += 1;
                            var props = new ParagraphProperties(new ParagraphStyleId()
                            {
                                Val = kind
                            });
                            qpp.ParagraphProperties = props;
                            yield return(qp);
                        }
                    }
                }
            }

            else if (md.IsTableBlock)
            {
                var mdt    = md as MarkdownParagraph.TableBlock;
                var header = mdt.Item1.Option();
                var align  = mdt.Item2;
                var rows   = mdt.Item3;
                var table  = new Table();
                var tstyle = new TableStyle {
                    Val = "TableGrid"
                };
                var tindent = new TableIndentation {
                    Width = 360, Type = TableWidthUnitValues.Dxa
                };
                var tborders = new TableBorders();
                tborders.TopBorder = new TopBorder {
                    Val = BorderValues.Single
                };
                tborders.BottomBorder = new BottomBorder {
                    Val = BorderValues.Single
                };
                tborders.LeftBorder = new LeftBorder {
                    Val = BorderValues.Single
                };
                tborders.RightBorder = new RightBorder {
                    Val = BorderValues.Single
                };
                tborders.InsideHorizontalBorder = new InsideHorizontalBorder {
                    Val = BorderValues.Single
                };
                tborders.InsideVerticalBorder = new InsideVerticalBorder {
                    Val = BorderValues.Single
                };
                var tcellmar = new TableCellMarginDefault();
                tcellmar.Append();
                table.Append(new TableProperties(tstyle, tindent, tborders));
                var ncols = align.Length;
                for (int irow = -1; irow < rows.Length; irow++)
                {
                    if (irow == -1 && header == null)
                    {
                        continue;
                    }
                    var mdrow = (irow == -1 ? header : rows[irow]);
                    var row   = new TableRow();
                    for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++)
                    {
                        var mdcell = mdrow[icol];
                        var cell   = new TableCell();
                        var pars   = Paragraphs2Paragraphs(mdcell).ToList();
                        for (int ip = 0; ip < pars.Count; ip++)
                        {
                            var p = pars[ip] as Paragraph;
                            if (p == null)
                            {
                                cell.Append(pars[ip]); continue;
                            }
                            var props = new ParagraphProperties(new ParagraphStyleId {
                                Val = "TableCellNormal"
                            });
                            if (align[icol].IsAlignCenter)
                            {
                                props.Append(new Justification {
                                    Val = JustificationValues.Center
                                });
                            }
                            if (align[icol].IsAlignRight)
                            {
                                props.Append(new Justification {
                                    Val = JustificationValues.Right
                                });
                            }
                            p.InsertAt(props, 0);
                            cell.Append(pars[ip]);
                        }
                        if (pars.Count == 0)
                        {
                            cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines {
                                After = "0"
                            }), new Run(new Text(""))));
                        }
                        row.Append(cell);
                    }
                    table.Append(row);
                }
                yield return(new Paragraph(new Run(new Text("")))
                {
                    ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                        Val = "TableLineBefore"
                    })
                });

                yield return(table);

                yield return(new Paragraph(new Run(new Text("")))
                {
                    ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                        Val = "TableLineAfter"
                    })
                });
            }
            else
            {
                yield return(new Paragraph(new Run(new Text($"[{md.GetType().Name}]"))));
            }
        }
예제 #3
0
        IEnumerable <OpenXmlElement> Span2Elements(MarkdownSpan md, bool nestedSpan = false)
        {
            reporter.CurrentSpan = md;
            if (md.IsLiteral)
            {
                var mdl = md as MarkdownSpan.Literal;
                var s   = MarkdownUtilities.UnescapeLiteral(mdl);
                foreach (var r in Literal2Elements(s, nestedSpan))
                {
                    yield return(r);
                }
            }

            else if (md.IsStrong || md.IsEmphasis)
            {
                IEnumerable <MarkdownSpan> spans = (md.IsStrong ? (md as MarkdownSpan.Strong).body : (md as MarkdownSpan.Emphasis).body);

                // Workaround for https://github.com/tpetricek/FSharp.formatting/issues/389 - the markdown parser
                // turns *this_is_it* into a nested Emphasis["this", Emphasis["is"], "it"] instead of Emphasis["this_is_it"]
                // What we'll do is preprocess it into Emphasis["this_is_it"]
                if (md.IsEmphasis)
                {
                    var spans2 = spans.Select(s =>
                    {
                        var _ = "";
                        if (s.IsEmphasis)
                        {
                            s = (s as MarkdownSpan.Emphasis).body.Single();
                            _ = "_";
                        }
                        if (s.IsLiteral)
                        {
                            return(_ + (s as MarkdownSpan.Literal).text + _);
                        }

                        reporter.Error("MD15", $"something odd inside emphasis '{s.GetType().Name}' - only allowed emphasis and literal"); return("");
                    });
                    spans = new List <MarkdownSpan>()
                    {
                        MarkdownSpan.NewLiteral(string.Join("", spans2), FSharpOption <MarkdownRange> .None)
                    };
                }

                // Convention is that ***term*** is used to define a term.
                // That's parsed as Strong, which contains Emphasis, which contains one Literal
                string  literal = null;
                TermRef termdef = null;
                if (!nestedSpan && md.IsStrong && spans.Count() == 1 && spans.First().IsEmphasis)
                {
                    var spans2 = (spans.First() as MarkdownSpan.Emphasis).body;
                    if (spans2.Count() == 1 && spans2.First().IsLiteral)
                    {
                        literal = (spans2.First() as MarkdownSpan.Literal).text;
                        termdef = new TermRef(literal, reporter.Location);
                        if (context.Terms.ContainsKey(literal))
                        {
                            var def = context.Terms[literal];
                            reporter.Warning("MD16", $"Term '{literal}' defined a second time");
                            reporter.Warning("MD16b", $"Here was the previous definition of term '{literal}'", def.Loc);
                        }
                        else
                        {
                            context.Terms.Add(literal, termdef);
                            context.TermKeys.Clear();
                        }
                    }
                }

                // Convention inside our specs is that emphasis only ever contains literals,
                // either to emphasis some human-text or to refer to an ANTLR-production
                ProductionRef prodref = null;
                if (!nestedSpan && md.IsEmphasis && (spans.Count() != 1 || !spans.First().IsLiteral))
                {
                    reporter.Error("MD17", $"something odd inside emphasis");
                }

                if (!nestedSpan && md.IsEmphasis && spans.Count() == 1 && spans.First().IsLiteral)
                {
                    literal = (spans.First() as MarkdownSpan.Literal).text;
                    prodref = productions.FirstOrDefault(pr => pr.Names.Contains(literal));
                    context.Italics.Add(new ItalicUse(literal, prodref != null ? ItalicUse.ItalicUseKind.Production : ItalicUse.ItalicUseKind.Italic, reporter.Location));
                }

                if (prodref != null)
                {
                    var props = new RunProperties(new Color {
                        Val = "6A5ACD"
                    }, new Underline {
                        Val = UnderlineValues.Single
                    });
                    var run = new Run(new Text(literal)
                    {
                        Space = SpaceProcessingModeValues.Preserve
                    })
                    {
                        RunProperties = props
                    };
                    var link = new Hyperlink(run)
                    {
                        Anchor = prodref.BookmarkName
                    };
                    yield return(link);
                }
                else if (termdef != null)
                {
                    context.MaxBookmarkId.Value += 1;
                    yield return(new BookmarkStart {
                        Name = termdef.BookmarkName, Id = context.MaxBookmarkId.Value.ToString()
                    });

                    var props = new RunProperties(new Italic(), new Bold());
                    yield return(new Run(new Text(literal)
                    {
                        Space = SpaceProcessingModeValues.Preserve
                    })
                    {
                        RunProperties = props
                    });

                    yield return(new BookmarkEnd {
                        Id = context.MaxBookmarkId.Value.ToString()
                    });
                }
                else
                {
                    foreach (var e in Spans2Elements(spans, true))
                    {
                        var style = (md.IsStrong ? new Bold() as OpenXmlElement : new Italic());
                        var run   = e as Run;
                        if (run != null)
                        {
                            run.InsertAt(new RunProperties(style), 0);
                        }

                        yield return(e);
                    }
                }
            }

            else if (md.IsInlineCode)
            {
                var mdi  = md as MarkdownSpan.InlineCode;
                var code = mdi.code;

                var txt = new Text(BugWorkaroundDecode(code))
                {
                    Space = SpaceProcessingModeValues.Preserve
                };
                var props = new RunProperties(new RunStyle {
                    Val = "CodeEmbedded"
                });
                var run = new Run(txt)
                {
                    RunProperties = props
                };
                yield return(run);
            }

            else if (md.IsLatexInlineMath)
            {
                var latex = md as MarkdownSpan.LatexInlineMath;
                var code  = latex.code;

                // TODO: Make this look nice - if we actually need it. It's possible that it's only present
                // before subscripts are replaced.
                var txt = new Text(BugWorkaroundDecode(code))
                {
                    Space = SpaceProcessingModeValues.Preserve
                };
                var props = new RunProperties(new RunStyle {
                    Val = "CodeEmbedded"
                });
                var run = new Run(txt)
                {
                    RunProperties = props
                };
                yield return(run);
            }

            else if (md.IsDirectLink || md.IsIndirectLink)
            {
                IEnumerable <MarkdownSpan> spans;
                string url = "", alt = "";
                if (md.IsDirectLink)
                {
                    var mddl = md as MarkdownSpan.DirectLink;
                    spans = mddl.body;
                    url   = mddl.link;
                    alt   = mddl.title.Option();
                }
                else
                {
                    var mdil     = md as MarkdownSpan.IndirectLink;
                    var original = mdil.original;
                    var id       = mdil.key;
                    spans = mdil.body;
                    if (markdownDocument.DefinedLinks.ContainsKey(id))
                    {
                        url = markdownDocument.DefinedLinks[id].Item1;
                        alt = markdownDocument.DefinedLinks[id].Item2.Option();
                    }
                }

                var anchor = "";
                if (spans.Count() == 1 && spans.First().IsLiteral)
                {
                    anchor = MarkdownUtilities.UnescapeLiteral(spans.First() as MarkdownSpan.Literal);
                }
                else if (spans.Count() == 1 && spans.First().IsInlineCode)
                {
                    anchor = (spans.First() as MarkdownSpan.InlineCode).code;
                }
                else
                {
                    reporter.Error("MD18", $"Link anchor must be Literal or InlineCode, not '{md.GetType().Name}'");
                    yield break;
                }

                if (sections.ContainsKey(url))
                {
                    var section = sections[url];
                    // If we're linking to something with a section number, we know what the link text should be.
                    // (There are a few links that aren't to numbered sections, e.g. to "Annex C".)
                    if (section.Number is object)
                    {
                        var expectedAnchor = "§" + section.Number;
                        if (anchor != expectedAnchor)
                        {
                            reporter.Warning("MD19", $"Mismatch: link anchor is '{anchor}', should be '{expectedAnchor}'");
                        }
                    }

                    var txt = new Text(anchor)
                    {
                        Space = SpaceProcessingModeValues.Preserve
                    };
                    var run = new Hyperlink(new Run(txt))
                    {
                        Anchor = section.BookmarkName
                    };
                    yield return(run);
                }
                else if (url.StartsWith("http:") || url.StartsWith("https:"))
                {
                    var style = new RunStyle {
                        Val = "Hyperlink"
                    };
                    var hyperlink = new Hyperlink {
                        DocLocation = url, Tooltip = alt
                    };
                    foreach (var element in Spans2Elements(spans))
                    {
                        var run = element as Run;
                        if (run != null)
                        {
                            run.InsertAt(new RunProperties(style), 0);
                        }

                        hyperlink.AppendChild(run);
                    }
                    yield return(hyperlink);
                }
                else
                {
                    // TODO: Make this report an error unconditionally once the subscript "latex-like" Markdown is removed.
                    if (url != "")
                    {
                        reporter.Error("MD28", $"Hyperlink url '{url}' unrecognized - not a recognized heading, and not http");
                    }
                }
            }

            else if (md.IsHardLineBreak)
            {
                // I've only ever seen this arise from dodgy markdown parsing, so I'll ignore it...
            }

            else
            {
                reporter.Error("MD20", $"Unrecognized markdown element {md.GetType().Name}");
                yield return(new Run(new Text($"[{md.GetType().Name}]")));
            }
        }
예제 #4
0
        IEnumerable <OpenXmlElement> Span2Elements(MarkdownSpan md)
        {
            if (md.IsLiteral)
            {
                var mdl = md as MarkdownSpan.Literal;
                var s   = mdunescape(mdl);
                yield return(new Run(new Text(s)
                {
                    Space = SpaceProcessingModeValues.Preserve
                }));
            }

            else if (md.IsStrong || md.IsEmphasis)
            {
                IEnumerable <MarkdownSpan> spans = (md.IsStrong ? (md as MarkdownSpan.Strong).Item : (md as MarkdownSpan.Emphasis).Item);

                // Workaround for https://github.com/tpetricek/FSharp.formatting/issues/389 - the markdown parser
                // turns *this_is_it* into a nested Emphasis["this", Emphasis["is"], "it"] instead of Emphasis["this_is_it"]
                // What we'll do is preprocess it into Emphasis["this", "_", "is" "_", "it"]
                if (md.IsEmphasis)
                {
                    var spans2 = new List <MarkdownSpan>();
                    foreach (var s in spans)
                    {
                        if (!s.IsEmphasis)
                        {
                            spans2.Add(s); continue;
                        }
                        spans2.Add(MarkdownSpan.NewLiteral("_"));
                        foreach (var ss in (s as MarkdownSpan.Emphasis).Item)
                        {
                            spans2.Add(ss);
                        }
                        spans2.Add(MarkdownSpan.NewLiteral("_"));
                    }
                    spans = spans2;
                }

                foreach (var e in Spans2Elements(spans))
                {
                    var style = (md.IsStrong ? new Bold() as OpenXmlElement : new Italic());
                    var run   = e as Run;
                    if (run != null)
                    {
                        run.InsertAt(new RunProperties(style), 0);
                    }
                    yield return(e);
                }
            }

            else if (md.IsInlineCode)
            {
                var mdi  = md as MarkdownSpan.InlineCode;
                var code = mdi.Item;

                var txt = new Text(BugWorkaroundDecode(code))
                {
                    Space = SpaceProcessingModeValues.Preserve
                };
                var props = new RunProperties(new RunStyle {
                    Val = "CodeEmbedded"
                });
                var run = new Run(txt)
                {
                    RunProperties = props
                };
                yield return(run);
            }

            else if (md.IsDirectLink || md.IsIndirectLink)
            {
                IEnumerable <MarkdownSpan> spans;
                string url = "", alt = "";
                if (md.IsDirectLink)
                {
                    var mddl = md as MarkdownSpan.DirectLink;
                    spans = mddl.Item1;
                    url   = mddl.Item2.Item1;
                    alt   = mddl.Item2.Item2.Option();
                }
                else
                {
                    var mdil     = md as MarkdownSpan.IndirectLink;
                    var original = mdil.Item2;
                    var id       = mdil.Item3;
                    spans = mdil.Item1;
                    if (mddoc.DefinedLinks.ContainsKey(id))
                    {
                        url = mddoc.DefinedLinks[id].Item1;
                        alt = mddoc.DefinedLinks[id].Item2.Option();
                    }
                }

                var anchor = "";
                if (spans.Count() == 1 && spans.First().IsLiteral)
                {
                    anchor = mdunescape(spans.First() as MarkdownSpan.Literal);
                }
                else if (spans.Count() == 1 && spans.First().IsInlineCode)
                {
                    anchor = (spans.First() as MarkdownSpan.InlineCode).Item;
                }
                else
                {
                    throw new NotImplementedException("Link anchor must be Literal or InlineCode, not " + md.ToString());
                }

                if (sections.ContainsKey(url))
                {
                    var section = sections[url];
                    if (anchor != section.Title)
                    {
                        throw new Exception($"Mismatch: link anchor is '{anchor}', should be '{section.Title}'");
                    }
                    var txt = new Text("§" + section.Number)
                    {
                        Space = SpaceProcessingModeValues.Preserve
                    };
                    var run = new Hyperlink(new Run(txt))
                    {
                        Anchor = section.BookmarkName
                    };
                    yield return(run);
                }
                else if (url.StartsWith("http:") || url.StartsWith("https:"))
                {
                    var style = new RunStyle {
                        Val = "Hyperlink"
                    };
                    var hyperlink = new Hyperlink {
                        DocLocation = url, Tooltip = alt
                    };
                    foreach (var element in Spans2Elements(spans))
                    {
                        var run = element as Run;
                        if (run != null)
                        {
                            run.InsertAt(new RunProperties(style), 0);
                        }
                        hyperlink.AppendChild(run);
                    }
                    yield return(hyperlink);
                }
                else
                {
                    throw new Exception("Absent hyperlink in " + md.ToString());
                }
            }

            else if (md.IsHardLineBreak)
            {
                // I've only ever seen this arise from dodgy markdown parsing, so I'll ignore it...
            }

            else
            {
                yield return(new Run(new Text($"[{md.GetType().Name}]")));
            }
        }
예제 #5
0
        IEnumerable <OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md)
        {
            reporter.CurrentParagraph = md;
            if (md.IsHeading)
            {
                var mdh   = md as MarkdownParagraph.Heading;
                var level = mdh.size;
                var spans = mdh.body;
                var sr    = sections[new SectionRef(mdh, filename).Url];
                reporter.CurrentSection = sr;
                var properties = new List <OpenXmlElement>
                {
                    new ParagraphStyleId {
                        Val = $"Heading{level}"
                    }
                };
                if (sr.Number is null)
                {
                    properties.Add(new NumberingProperties(new NumberingLevelReference {
                        Val = 0
                    }, new NumberingId {
                        Val = 0
                    }));
                }
                var props = new ParagraphProperties(properties);
                var p     = new Paragraph {
                    ParagraphProperties = props
                };
                context.MaxBookmarkId.Value += 1;
                p.AppendChild(new BookmarkStart {
                    Name = sr.BookmarkName, Id = context.MaxBookmarkId.Value.ToString()
                });
                p.Append(Span2Elements(MarkdownSpan.NewLiteral(sr.TitleWithoutNumber, FSharpOption <MarkdownRange> .None)));
                p.AppendChild(new BookmarkEnd {
                    Id = context.MaxBookmarkId.Value.ToString()
                });
                yield return(p);

                var    i = sr.Url.IndexOf("#");
                string currentSection = $"{sr.Url.Substring(0, i)} {new string('#', level)} {sr.Title} [{sr.Number}]";
                reporter.Log(currentSection);
                yield break;
            }

            else if (md.IsParagraph)
            {
                var mdp   = md as MarkdownParagraph.Paragraph;
                var spans = mdp.body;
                yield return(new Paragraph(Spans2Elements(spans)));

                yield break;
            }

            else if (md.IsQuotedBlock)
            {
                var mdq = md as MarkdownParagraph.QuotedBlock;
                // TODO: Actually make this a block quote.
                // See https://github.com/ECMA-TC49-TG2/conversion-to-markdown/issues/123
                foreach (var paragraph in mdq.paragraphs.SelectMany(Paragraph2Paragraphs))
                {
                    yield return(paragraph);
                }
                yield break;
            }

            else if (md.IsListBlock)
            {
                var mdl  = md as MarkdownParagraph.ListBlock;
                var flat = FlattenList(mdl);

                // Let's figure out what kind of list it is - ordered or unordered? nested?
                var format0 = new[] { "1", "1", "1", "1" };
                foreach (var item in flat)
                {
                    format0[item.Level] = (item.IsBulletOrdered ? "1" : "o");
                }

                var format = string.Join("", format0);

                var numberingPart = wordDocument.MainDocumentPart.NumberingDefinitionsPart ?? wordDocument.MainDocumentPart.AddNewPart <NumberingDefinitionsPart>("NumberingDefinitionsPart001");
                if (numberingPart.Numbering == null)
                {
                    numberingPart.Numbering = new Numbering();
                }

                Func <int, bool, Level> createLevel;
                createLevel = (level, isOrdered) =>
                {
                    var numformat = NumberFormatValues.Bullet;
                    var levelText = new[] { "·", "o", "·", "o" }[level];
                    if (isOrdered && level == 0)
                    {
                        numformat = NumberFormatValues.Decimal; levelText = "%1.";
                    }
                    if (isOrdered && level == 1)
                    {
                        numformat = NumberFormatValues.LowerLetter; levelText = "%2.";
                    }
                    if (isOrdered && level == 2)
                    {
                        numformat = NumberFormatValues.LowerRoman; levelText = "%3.";
                    }
                    if (isOrdered && level == 3)
                    {
                        numformat = NumberFormatValues.LowerRoman; levelText = "%4.";
                    }
                    var r = new Level {
                        LevelIndex = level
                    };
                    r.Append(new StartNumberingValue {
                        Val = 1
                    });
                    r.Append(new NumberingFormat {
                        Val = numformat
                    });
                    r.Append(new LevelText {
                        Val = levelText
                    });
                    r.Append(new ParagraphProperties(new Indentation {
                        Left = (540 + 360 * level).ToString(), Hanging = "360"
                    }));
                    if (levelText == "·")
                    {
                        r.Append(new NumberingSymbolRunProperties(new RunFonts {
                            Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman"
                        }));
                    }

                    if (levelText == "o")
                    {
                        r.Append(new NumberingSymbolRunProperties(new RunFonts {
                            Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New"
                        }));
                    }

                    return(r);
                };
                var level0 = createLevel(0, format[0] == '1');
                var level1 = createLevel(1, format[1] == '1');
                var level2 = createLevel(2, format[2] == '1');
                var level3 = createLevel(3, format[3] == '1');

                var abstracts = numberingPart.Numbering.OfType <AbstractNum>().Select(an => an.AbstractNumberId.Value).ToList();
                var aid       = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1);
                var aabstract = new AbstractNum(new MultiLevelType()
                {
                    Val = MultiLevelValues.Multilevel
                }, level0, level1, level2, level3)
                {
                    AbstractNumberId = aid
                };
                numberingPart.Numbering.InsertAt(aabstract, 0);

                var instances   = numberingPart.Numbering.OfType <NumberingInstance>().Select(ni => ni.NumberID.Value);
                var nid         = (instances.Count() == 0 ? 1 : instances.Max() + 1);
                var numInstance = new NumberingInstance(new AbstractNumId {
                    Val = aid
                })
                {
                    NumberID = nid
                };
                numberingPart.Numbering.AppendChild(numInstance);

                // We'll also figure out the indentation(for the benefit of those paragraphs that should be
                // indendent with the list but aren't numbered). I'm not sure what the indent comes from.
                // in the docx, each AbstractNum that I created has an indent for each of its levels,
                // defaulted at 900, 1260, 1620, ... but I can't see where in the above code that's created?
                Func <int, string> calcIndent = level => (540 + level * 360).ToString();

                foreach (var item in flat)
                {
                    var content = item.Paragraph;
                    if (content.IsParagraph || content.IsSpan)
                    {
                        var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).body : (content as MarkdownParagraph.Span).body);
                        if (item.HasBullet)
                        {
                            yield return(new Paragraph(Spans2Elements(spans))
                            {
                                ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId {
                                    Val = "ListParagraph"
                                }, new NumberingLevelReference {
                                    Val = item.Level
                                }, new NumberingId {
                                    Val = nid
                                }))
                            });
                        }
                        else
                        {
                            yield return(new Paragraph(Spans2Elements(spans))
                            {
                                ParagraphProperties = new ParagraphProperties(new Indentation {
                                    Left = calcIndent(item.Level)
                                })
                            });
                        }
                    }
                    else if (content.IsQuotedBlock || content.IsCodeBlock)
                    {
                        foreach (var p in Paragraph2Paragraphs(content))
                        {
                            var props = p.GetFirstChild <ParagraphProperties>();
                            if (props == null)
                            {
                                props = new ParagraphProperties();
                                p.InsertAt(props, 0);
                            }
                            var indent = props?.GetFirstChild <Indentation>();
                            if (indent == null)
                            {
                                indent = new Indentation();
                                props.Append(indent);
                            }
                            indent.Left = calcIndent(item.Level);
                            yield return(p);
                        }
                    }
                    else if (content.IsTableBlock)
                    {
                        foreach (var p in Paragraph2Paragraphs(content))
                        {
                            var table = p as Table;
                            if (table == null)
                            {
                                yield return(p);

                                continue;
                            }
                            var tprops  = table.GetFirstChild <TableProperties>();
                            var tindent = tprops?.GetFirstChild <TableIndentation>();
                            if (tindent == null)
                            {
                                throw new Exception("Ooops! Table is missing indentation");
                            }

                            tindent.Width = int.Parse(calcIndent(item.Level));
                            yield return(table);
                        }
                    }
                    else
                    {
                        reporter.Error("MD08", $"Unexpected item in list '{content.GetType().Name}'");
                    }
                }
            }

            else if (md.IsCodeBlock)
            {
                var mdc  = md as MarkdownParagraph.CodeBlock;
                var code = mdc.code;
                var lang = mdc.language;
                code = BugWorkaroundDecode(code);
                var runs        = new List <Run>();
                var onFirstLine = true;
                IEnumerable <ColorizedLine> lines;
                switch (lang)
                {
                case "csharp":
                case "c#":
                case "cs":
                    lines = Colorize.CSharp(code);
                    break;

                case "vb":
                case "vbnet":
                case "vb.net":
                    lines = Colorize.VB(code);
                    break;

                case "":
                case "console":
                case "xml":
                    lines = Colorize.PlainText(code);
                    break;

                case "ANTLR":
                case "antlr":
                    lines = Antlr.ColorizeAntlr(code);
                    break;

                default:
                    reporter.Error("MD09", $"unrecognized language {lang}");
                    lines = Colorize.PlainText(code);
                    break;
                }

                foreach (var line in lines)
                {
                    if (onFirstLine)
                    {
                        onFirstLine = false;
                    }
                    else
                    {
                        runs.Add(new Run(new Break()));
                    }

                    foreach (var word in line.Words)
                    {
                        var run   = new Run();
                        var props = new RunProperties();
                        if (word.Red != 0 || word.Green != 0 || word.Blue != 0)
                        {
                            props.Append(new Color {
                                Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}"
                            });
                        }

                        if (word.IsItalic)
                        {
                            props.Append(new Italic());
                        }

                        if (props.HasChildren)
                        {
                            run.Append(props);
                        }

                        run.Append(new Text(word.Text)
                        {
                            Space = SpaceProcessingModeValues.Preserve
                        });
                        runs.Add(run);
                    }
                }
                if (lang == "antlr")
                {
                    var p = new Paragraph()
                    {
                        ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                            Val = "Grammar"
                        })
                    };
                    var prodref = productions.Single(prod => prod.Code == code);
                    context.MaxBookmarkId.Value += 1;
                    p.AppendChild(new BookmarkStart {
                        Name = prodref.BookmarkName, Id = context.MaxBookmarkId.Value.ToString()
                    });
                    p.Append(runs);
                    p.AppendChild(new BookmarkEnd {
                        Id = context.MaxBookmarkId.Value.ToString()
                    });
                    yield return(p);
                }
                else
                {
                    var p = new Paragraph()
                    {
                        ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                            Val = "Code"
                        })
                    };
                    p.Append(runs);
                    yield return(p);
                }
            }

            else if (md.IsTableBlock)
            {
                var mdt    = md as MarkdownParagraph.TableBlock;
                var header = mdt.headers.Option();
                var align  = mdt.alignments;
                var rows   = mdt.rows;
                var table  = new Table();
                if (header == null)
                {
                    reporter.Error("MD10", "Github requires all tables to have header rows");
                }

                if (!header.Any(cell => cell.Length > 0))
                {
                    header = null; // even if Github requires an empty header, we can at least cull it from Docx
                }

                var tstyle = new TableStyle {
                    Val = "TableGrid"
                };
                var tindent = new TableIndentation {
                    Width = 360, Type = TableWidthUnitValues.Dxa
                };
                var tborders = new TableBorders();
                tborders.TopBorder = new TopBorder {
                    Val = BorderValues.Single
                };
                tborders.BottomBorder = new BottomBorder {
                    Val = BorderValues.Single
                };
                tborders.LeftBorder = new LeftBorder {
                    Val = BorderValues.Single
                };
                tborders.RightBorder = new RightBorder {
                    Val = BorderValues.Single
                };
                tborders.InsideHorizontalBorder = new InsideHorizontalBorder {
                    Val = BorderValues.Single
                };
                tborders.InsideVerticalBorder = new InsideVerticalBorder {
                    Val = BorderValues.Single
                };
                var tcellmar = new TableCellMarginDefault();
                tcellmar.Append();
                table.Append(new TableProperties(tstyle, tindent, tborders));
                var ncols = align.Length;
                for (int irow = -1; irow < rows.Length; irow++)
                {
                    if (irow == -1 && header == null)
                    {
                        continue;
                    }

                    var mdrow = (irow == -1 ? header : rows[irow]);
                    var row   = new TableRow();
                    for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++)
                    {
                        var mdcell = mdrow[icol];
                        var cell   = new TableCell();
                        var pars   = Paragraphs2Paragraphs(mdcell).ToList();
                        for (int ip = 0; ip < pars.Count; ip++)
                        {
                            var p = pars[ip] as Paragraph;
                            if (p == null)
                            {
                                cell.Append(pars[ip]);
                                continue;
                            }
                            var props = new ParagraphProperties(new ParagraphStyleId {
                                Val = "TableCellNormal"
                            });
                            if (align[icol].IsAlignCenter)
                            {
                                props.Append(new Justification {
                                    Val = JustificationValues.Center
                                });
                            }

                            if (align[icol].IsAlignRight)
                            {
                                props.Append(new Justification {
                                    Val = JustificationValues.Right
                                });
                            }

                            p.InsertAt(props, 0);
                            cell.Append(pars[ip]);
                        }
                        if (pars.Count == 0)
                        {
                            cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines {
                                After = "0"
                            }), new Run(new Text(""))));
                        }

                        row.Append(cell);
                    }
                    table.Append(row);
                }
                yield return(new Paragraph(new Run(new Text("")))
                {
                    ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                        Val = "TableLineBefore"
                    })
                });

                yield return(table);

                yield return(new Paragraph(new Run(new Text("")))
                {
                    ParagraphProperties = new ParagraphProperties(new ParagraphStyleId {
                        Val = "TableLineAfter"
                    })
                });
            }
            else
            {
                reporter.Error("MD11", $"Unrecognized markdown element {md.GetType().Name}");
                yield return(new Paragraph(new Run(new Text($"[{md.GetType().Name}]"))));
            }
        }