/// <summary> /// Add a note to the FootNotes part and ensure it exists. /// </summary> /// <param name="description">The description of an acronym, abbreviation, some book references, ...</param> /// <returns>Returns the id of the footnote reference.</returns> private int AddFootnoteReference(string description) { FootnotesPart fpart = mainPart.FootnotesPart; if (fpart == null) { fpart = mainPart.AddNewPart <FootnotesPart>(); } if (fpart.Footnotes == null) { // Insert a new Footnotes reference new Footnotes( new Footnote( new Paragraph( new ParagraphProperties { SpacingBetweenLines = new SpacingBetweenLines() { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto } }, new Run( new SeparatorMark()) ) ) { Type = FootnoteEndnoteValues.Separator, Id = -1 }, new Footnote( new Paragraph( new ParagraphProperties { SpacingBetweenLines = new SpacingBetweenLines() { After = "0", Line = "240", LineRule = LineSpacingRuleValues.Auto } }, new Run( new ContinuationSeparatorMark()) ) ) { Type = FootnoteEndnoteValues.ContinuationSeparator, Id = 0 }).Save(fpart); footnotesRef = 1; } else { // The footnotesRef Id is a required field and should be unique. You can assign yourself some hard-coded // value but that's absolutely not safe. We will loop through the existing Footnote // to retrieve the highest Id. foreach (var fn in fpart.Footnotes.Elements <Footnote>()) { if (fn.Id.HasValue && fn.Id > footnotesRef) { footnotesRef = (int)fn.Id.Value; } } footnotesRef++; } Paragraph p; fpart.Footnotes.Append( new Footnote( p = new Paragraph( new ParagraphProperties { ParagraphStyleId = new ParagraphStyleId() { Val = htmlStyles.GetStyle(htmlStyles.DefaultStyles.FootnoteTextStyle, StyleValues.Paragraph) } }, new Run( new RunProperties { RunStyle = new RunStyle() { Val = htmlStyles.GetStyle(htmlStyles.DefaultStyles.FootnoteReferenceStyle, StyleValues.Character) } }, new FootnoteReferenceMark()), new Run( // Word insert automatically a space before the definition to separate the // reference number with its description new Text(" ") { Space = SpaceProcessingModeValues.Preserve }) ) ) { Id = footnotesRef }); // Description in footnote reference can be plain text or a web protocols/file share (like \\server01) Uri uriReference; Regex linkRegex = new Regex(@"^((https?|ftps?|mailto|file)://|[\\]{2})(?:[\w][\w.-]?)"); if (linkRegex.IsMatch(description) && Uri.TryCreate(description, UriKind.Absolute, out uriReference)) { // when URI references a network server (ex: \\server01), System.IO.Packaging is not resolving the correct URI and this leads // to a bad-formed XML not recognized by Word. To enforce the "original URI", a fresh new instance must be created uriReference = new Uri(uriReference.AbsoluteUri, UriKind.Absolute); HyperlinkRelationship extLink = fpart.AddHyperlinkRelationship(uriReference, true); var h = new Hyperlink( ) { History = true, Id = extLink.Id }; h.Append(new Run( new RunProperties { RunStyle = new RunStyle() { Val = htmlStyles.GetStyle(htmlStyles.DefaultStyles.HyperlinkStyle, StyleValues.Character) } }, new Text(description))); p.Append(h); } else { p.Append(new Run( new Text(description) { Space = SpaceProcessingModeValues.Preserve })); } fpart.Footnotes.Save(); return(footnotesRef); }
/// <summary> /// There is a few attributes shared by a large number of tags. This method will check them for a limited /// number of tags (<p>, <pre>, <div>, <span> and <body>). /// </summary> /// <returns>Returns true if the processing of this tag should generate a new paragraph.</returns> public bool ProcessCommonAttributes(HtmlEnumerator en, IList <OpenXmlElement> styleAttributes) { if (en.Attributes.Count == 0) { return(false); } bool newParagraph = false; List <OpenXmlElement> containerStyleAttributes = new List <OpenXmlElement>(); string attrValue = en.Attributes["lang"]; if (attrValue != null && attrValue.Length > 0) { try { #if !NET_CORE var ci = System.Globalization.CultureInfo.GetCultureInfo(attrValue); #else var ci = new System.Globalization.CultureInfo(attrValue); #endif bool rtl = ci.TextInfo.IsRightToLeft; Languages lang = new Languages() { Val = ci.TwoLetterISOLanguageName }; if (rtl) { lang.Bidi = ci.Name; styleAttributes.Add(new Languages() { Bidi = ci.Name }); // notify table documentStyle.Tables.BeginTag(en.CurrentTag, new TableJustification() { Val = TableRowAlignmentValues.Right }); } containerStyleAttributes.Add(new ParagraphMarkRunProperties(lang)); containerStyleAttributes.Add(new BiDi() { Val = OnOffValue.FromBoolean(rtl) }); } catch (ArgumentException exc) { // lang not valid, ignore it if (Logging.On) { Logging.PrintError($"lang attribute {attrValue} not recognized: " + exc.Message, exc); } } } attrValue = en.StyleAttributes["text-align"]; if (attrValue != null && en.CurrentTag != "<font>") { JustificationValues?align = Converter.ToParagraphAlign(attrValue); if (align.HasValue) { containerStyleAttributes.Add(new Justification { Val = align }); } } // according to w3c, dir should be used in conjonction with lang. But whatever happens, we'll apply the RTL layout attrValue = en.Attributes["dir"]; if (attrValue != null) { if (attrValue.Equals("rtl", StringComparison.OrdinalIgnoreCase)) { styleAttributes.Add(new RightToLeftText()); containerStyleAttributes.Add(new Justification() { Val = JustificationValues.Right }); } else if (attrValue.Equals("ltr", StringComparison.OrdinalIgnoreCase)) { containerStyleAttributes.Add(new Justification() { Val = JustificationValues.Left }); } } // <span> and <font> are considered as semi-container attribute. When converted to OpenXml, there are Runs but not Paragraphs if (en.CurrentTag == "<p>" || en.CurrentTag == "<div>" || en.CurrentTag == "<pre>") { var border = en.StyleAttributes.GetAsBorder("border"); if (!border.IsEmpty) { ParagraphBorders borders = new ParagraphBorders(); if (border.Top.IsValid) { borders.Append( new TopBorder() { Val = border.Top.Style, Color = border.Top.Color.ToHexString(), Size = (uint)border.Top.Width.ValueInPx * 4, Space = 1U }); } if (border.Left.IsValid) { borders.Append( new LeftBorder() { Val = border.Left.Style, Color = border.Left.Color.ToHexString(), Size = (uint)border.Left.Width.ValueInPx * 4, Space = 1U }); } if (border.Bottom.IsValid) { borders.Append( new BottomBorder() { Val = border.Bottom.Style, Color = border.Bottom.Color.ToHexString(), Size = (uint)border.Bottom.Width.ValueInPx * 4, Space = 1U }); } if (border.Right.IsValid) { borders.Append( new RightBorder() { Val = border.Right.Style, Color = border.Right.Color.ToHexString(), Size = (uint)border.Right.Width.ValueInPx * 4, Space = 1U }); } containerStyleAttributes.Add(borders); newParagraph = true; } } else if (en.CurrentTag == "<span>" || en.CurrentTag == "<font>") { // OpenXml limits the border to 4-side of the same color and style. SideBorder border = en.StyleAttributes.GetAsSideBorder("border"); if (border.IsValid) { styleAttributes.Add(new DocumentFormat.OpenXml.Wordprocessing.Border() { Val = border.Style, Color = border.Color.ToHexString(), Size = (uint)border.Width.ValueInPx * 4, Space = 1U }); } } String[] classes = en.Attributes.GetAsClass(); if (classes != null) { for (int i = 0; i < classes.Length; i++) { string className = documentStyle.GetStyle(classes[i], StyleValues.Paragraph, ignoreCase: true); if (className != null) { containerStyleAttributes.Add(new ParagraphStyleId() { Val = className }); newParagraph = true; break; } } } Margin margin = en.StyleAttributes.GetAsMargin("margin"); Indentation indentation = null; if (!margin.IsEmpty) { if (margin.Top.IsFixed || margin.Bottom.IsFixed) { SpacingBetweenLines spacing = new SpacingBetweenLines(); if (margin.Top.IsFixed) { spacing.Before = margin.Top.ValueInDxa.ToString(CultureInfo.InvariantCulture); } if (margin.Bottom.IsFixed) { spacing.After = margin.Bottom.ValueInDxa.ToString(CultureInfo.InvariantCulture); } containerStyleAttributes.Add(spacing); } if (margin.Left.IsFixed || margin.Right.IsFixed) { indentation = new Indentation(); if (margin.Left.IsFixed) { indentation.Left = margin.Left.ValueInDxa.ToString(CultureInfo.InvariantCulture); } if (margin.Right.IsFixed) { indentation.Right = margin.Right.ValueInDxa.ToString(CultureInfo.InvariantCulture); } containerStyleAttributes.Add(indentation); } } // implemented by giorand (feature #13787) Unit textIndent = en.StyleAttributes.GetAsUnit("text-indent"); if (textIndent.IsValid && (en.CurrentTag == "<p>" || en.CurrentTag == "<div>")) { if (indentation == null) { indentation = new Indentation(); } indentation.FirstLine = textIndent.ValueInDxa.ToString(CultureInfo.InvariantCulture); containerStyleAttributes.Add(indentation); } this.BeginTag(en.CurrentTag, containerStyleAttributes); // Process general run styles documentStyle.Runs.ProcessCommonAttributes(en, styleAttributes); return(newParagraph); }
/// <summary> /// Converts some common styling attributes to their OpenXml equivalence. /// </summary> /// <param name="en">The Html parser.</param> /// <param name="styleAttributes">The collection of attributes where to store new discovered attributes.</param> public void ProcessCommonAttributes(HtmlEnumerator en, IList <OpenXmlElement> styleAttributes) { if (en.Attributes.Count == 0) { return; } var colorValue = en.StyleAttributes.GetAsColor("color"); if (colorValue.IsEmpty) { colorValue = en.Attributes.GetAsColor("color"); } if (!colorValue.IsEmpty) { styleAttributes.Add(new Color { Val = colorValue.ToHexString() }); } colorValue = en.StyleAttributes.GetAsColor("background-color"); if (!colorValue.IsEmpty) { // change the way the background-color renders. It now uses Shading instead of Highlight. // Changes brought by Wude on http://html2openxml.codeplex.com/discussions/277570 styleAttributes.Add(new Shading { Val = ShadingPatternValues.Clear, Fill = colorValue.ToHexString() }); } var decorations = Converter.ToTextDecoration(en.StyleAttributes["text-decoration"]); if ((decorations & TextDecoration.Underline) != 0) { styleAttributes.Add(new Underline { Val = UnderlineValues.Single }); } if ((decorations & TextDecoration.LineThrough) != 0) { styleAttributes.Add(new Strike()); } String[] classes = en.Attributes.GetAsClass(); if (classes != null) { for (int i = 0; i < classes.Length; i++) { string className = documentStyle.GetStyle(classes[i], StyleValues.Character, ignoreCase: true); if (className != null) // only one Style can be applied in OpenXml and dealing with inheritance is out of scope { styleAttributes.Add(new RunStyle() { Val = className }); break; } } } HtmlFont font = en.StyleAttributes.GetAsFont("font"); if (!font.IsEmpty) { if (font.Style == FontStyle.Italic) { styleAttributes.Add(new Italic()); } if (font.Weight == FontWeight.Bold || font.Weight == FontWeight.Bolder) { styleAttributes.Add(new Bold()); } if (font.Variant == FontVariant.SmallCaps) { styleAttributes.Add(new SmallCaps()); } if (font.Family != null) { styleAttributes.Add(new RunFonts() { Ascii = font.Family, HighAnsi = font.Family }); } // size are half-point font size if (font.Size.IsFixed) { styleAttributes.Add(new FontSize() { Val = (font.Size.ValueInPoint * 2).ToString(CultureInfo.InvariantCulture) }); } } }
/// <summary> /// Move inside the current tag related to table (td, thead, tr, ...) and converts some common /// attributes to their OpenXml equivalence. /// </summary> /// <param name="en">The Html enumerator positionned on a <i>table (or related)</i> tag.</param> /// <param name="runStyleAttributes">The collection of attributes where to store new discovered attributes.</param> public void ProcessCommonAttributes(HtmlEnumerator en, IList <OpenXmlElement> runStyleAttributes) { List <OpenXmlElement> containerStyleAttributes = new List <OpenXmlElement>(); var colorValue = en.StyleAttributes.GetAsColor("background-color"); if (colorValue.IsEmpty) //We do a first try, if it's empty the background-color, I try it with background { colorValue = en.StyleAttributes.GetAsColor("background"); } // // "background-color" is also handled by RunStyleCollection which duplicate this attribute (bug #13212). // Also apply on <th> (issue #20). // As on 05 Jan 2018, the duplication was due to the wrong argument passed during the td/th processing. // It was the runStyle and not the containerStyle that was provided. The code has been removed as no more useful if (colorValue.IsEmpty) { colorValue = en.Attributes.GetAsColor("bgcolor"); } if (!colorValue.IsEmpty) { containerStyleAttributes.Add( new Shading() { Val = ShadingPatternValues.Clear, Color = "auto", Fill = colorValue.ToHexString() }); } var htmlAlign = en.StyleAttributes["vertical-align"]; if (htmlAlign == null) { htmlAlign = en.Attributes["valign"]; } if (htmlAlign != null) { TableVerticalAlignmentValues?valign = Converter.ToVAlign(htmlAlign); if (valign.HasValue) { containerStyleAttributes.Add(new TableCellVerticalAlignment() { Val = valign }); } } htmlAlign = en.StyleAttributes["text-align"]; if (htmlAlign == null) { htmlAlign = en.Attributes["align"]; } if (htmlAlign != null) { JustificationValues?halign = Converter.ToParagraphAlign(htmlAlign); if (halign.HasValue) { this.BeginTagForParagraph(en.CurrentTag, new KeepNext(), new Justification { Val = halign }); } } // implemented by ddforge String[] classes = en.Attributes.GetAsClass(); if (classes != null) { for (int i = 0; i < classes.Length; i++) { string className = documentStyle.GetStyle(classes[i], StyleValues.Table, ignoreCase: true); if (className != null) // only one Style can be applied in OpenXml and dealing with inheritance is out of scope { containerStyleAttributes.Add(new RunStyle() { Val = className }); break; } } } this.BeginTag(en.CurrentTag, containerStyleAttributes); // Process general run styles documentStyle.Runs.ProcessCommonAttributes(en, runStyleAttributes); }