C# FlowDocument to HTML conversion

前端 未结 2 1316
既然无缘
既然无缘 2020-12-30 11:42

Basically, I have a RichTextBox and I want to convert the formatted contents of it to HTML so it can be sent as an email.

The method I am currently using does not gi

相关标签:
2条回答
  • 2020-12-30 12:07

    The general technique is to use a XamlWriter to convert the FlowDocument content to a stream of XML, and then to use an XSLT transform to convert the XML to HTML. That's not much of an answer, but that's because there's a huge range of possible HTML representations of any given FlowDocument.

    This transform, for instance, converts every top-level Section to a div, every Paragraph to a p, and every Run to a span whose class tells you whether or not it's italicized, bold-faced, or underlined, or any combination of the above. It was useful for the purpose I wrote it for, but to call it a lossy transformation is an understatement:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        exclude-result-prefixes="msxsl x">
    
      <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
      <xsl:template match="x:Section[not(parent::x:Section)]">
        <div>
          <xsl:apply-templates select="node()"/>
        </div>
      </xsl:template>
    
      <xsl:template match="x:Section">
        <xsl:apply-templates select="node()"/>
      </xsl:template>
    
      <xsl:template match="x:Paragraph">
        <p>
          <xsl:apply-templates select="node()"/>
        </p>
      </xsl:template>
    
      <xsl:template match="x:Run">
        <xsl:variable name="class">
          <xsl:if test="@FontStyle='Italic'">
            <xsl:text>i </xsl:text>
          </xsl:if>
          <xsl:if test="@FontWeight='Bold'">
            <xsl:text>b </xsl:text>
          </xsl:if>
          <xsl:if test="contains(@TextDecorations, 'Underline')">
            <xsl:text>u </xsl:text>
          </xsl:if>
        </xsl:variable>
        <span>
          <xsl:if test="normalize-space($class) != ''">
            <xsl:attribute name="class">
              <xsl:value-of select="normalize-space($class)"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
        </span>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Here's a value converter I wrote to do the conversion - note that in order to use the value converter, you also have to hack around and implement a version of RichTextBox that exposes the content as a dependency property. Really this whole project was a pain.

    public class FlowDocumentToHtmlConverter : IValueConverter
    {
        private static XslCompiledTransform ToHtmlTransform;
        private static XslCompiledTransform ToXamlTransform;
    
        public FlowDocumentToHtmlConverter()
        {
            if (ToHtmlTransform == null)
            {
                ToHtmlTransform = LoadTransformResource("Converters/FlowDocumentToXhtml.xslt");
            }
            if (ToXamlTransform == null)
            {
                ToXamlTransform = LoadTransformResource("Converters/XhtmlToFlowDocument.xslt");
            }
        }
        private static XslCompiledTransform LoadTransformResource(string path)
        {
            Uri uri = new Uri(path, UriKind.Relative);
            XmlReader xr = XmlReader.Create(Application.GetResourceStream(uri).Stream);
            XslCompiledTransform xslt = new XslCompiledTransform();
            xslt.Load(xr);
            return xslt;
        }
    
        #region IValueConverter Members
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (!(value is FlowDocument))
            {
                return null;
            }
            if (targetType == typeof(FlowDocument))
            {
                return value;
            }
    
            if (targetType != typeof(string))
            {
                throw new InvalidOperationException(
                    "FlowDocumentToHtmlConverter can only convert back from a FlowDocument to a string.");
            }
    
            FlowDocument d = (FlowDocument)value;
    
            using (MemoryStream ms = new MemoryStream())
            {
                // write XAML out to a MemoryStream
                TextRange tr = new TextRange(
                    d.ContentStart,
                    d.ContentEnd);
                tr.Save(ms, DataFormats.Xaml);
                ms.Seek(0, SeekOrigin.Begin);
    
                // transform the contents of the MemoryStream to HTML
                StringBuilder sb = new StringBuilder();
                using (StringWriter sw = new StringWriter(sb))
                {
                    XmlWriterSettings xws = new XmlWriterSettings();
                    xws.OmitXmlDeclaration = true;
                    XmlReader xr = XmlReader.Create(ms);
                    XmlWriter xw = XmlWriter.Create(sw, xws);
                    ToHtmlTransform.Transform(xr, xw);
                }
                return sb.ToString();
            }
        }
    
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value == null)
            {
                return new FlowDocument();
            }
            if (value is FlowDocument)
            {
                return value;
            }
            if (targetType != typeof(FlowDocument))
            {
                throw new InvalidOperationException(
                    "FlowDocumentToHtmlConverter can only convert to a FlowDocument.");
            }
            if (!(value is string))
            {
                throw new InvalidOperationException(
                    "FlowDocumentToHtmlConverter can only convert from a string or FlowDocument.");
            }
    
            string s = (string)value;
    
            FlowDocument d;
    
            using (MemoryStream ms = new MemoryStream())
            using (StringReader sr = new StringReader(s))
            {
                XmlWriterSettings xws = new XmlWriterSettings();
                xws.OmitXmlDeclaration = true;
                using (XmlReader xr = XmlReader.Create(sr))
                using (XmlWriter xw = XmlWriter.Create(ms, xws))
                {
                    ToXamlTransform.Transform(xr, xw);
                }
                ms.Seek(0, SeekOrigin.Begin);
    
                d = XamlReader.Load(ms) as FlowDocument;
            }
            XamlWriter.Save(d, Console.Out);
            return d;
        }
    
        #endregion
    }
    
    0 讨论(0)
  • 2020-12-30 12:13

    Extended version of XSLT sheet above I thought might help. Not perfect but slightly more comprehensive. Extension of an extension of another answer referencing this one.

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet
        version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:msxsl="urn:schemas-microsoft-com:xslt"
        exclude-result-prefixes="msxsl x">
    
      <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
    
      <!--<xsl:template match="x:Section[not(parent::x:Section)]">
        <div>
          <xsl:apply-templates select="node()"/>
        </div>
      </xsl:template>-->
    
      <xsl:template match="x:Section[not(parent::x:Section)]">
        <xsl:variable name="style">
          <xsl:if test="@FontStyle='Italic'">
            <xsl:text>font-style:italic;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontWeight='Bold'">
            <xsl:text>font-weight:bold;</xsl:text>
          </xsl:if>
          <xsl:if test="contains(@TextDecorations, 'Underline')">
            <xsl:text>text-decoration:underline;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontSize != ''">
            <xsl:text>font-size:</xsl:text>
            <xsl:value-of select="@FontSize" />
            <xsl:text>pt;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontFamily != ''">
            <xsl:text>font-family:</xsl:text>
            <xsl:value-of select="@FontFamily" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="concat(substring(@Foreground, 1, 1), substring(@Foreground, 4))" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground-Color != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="@Foreground-Color"/>
            <xsl:text>;</xsl:text>
          </xsl:if>
        </xsl:variable>
        <div>
          <xsl:if test="normalize-space($style) != ''">
            <xsl:attribute name="style">
              <xsl:value-of select="normalize-space($style)"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
          <xsl:apply-templates select="node()"/>
        </div>
      </xsl:template>
    
    
      <xsl:template match="x:Section">
        <xsl:apply-templates select="node()"/>
      </xsl:template>
    
      <xsl:template match="x:Paragraph">
        <xsl:variable name="style">
          <xsl:if test="@FontStyle='Italic'">
            <xsl:text>font-style:italic;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontWeight='Bold'">
            <xsl:text>font-weight:bold;</xsl:text>
          </xsl:if>
          <xsl:if test="contains(@TextDecorations, 'Underline')">
            <xsl:text>text-decoration:underline;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontSize != ''">
            <xsl:text>font-size:</xsl:text>
            <xsl:value-of select="@FontSize" />
            <xsl:text>pt;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontFamily != ''">
            <xsl:text>font-family:</xsl:text>
            <xsl:value-of select="@FontFamily" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="concat(substring(@Foreground, 1, 1), substring(@Foreground, 4))" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground-Color != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="@Foreground-Color"/>
            <xsl:text>;</xsl:text>
          </xsl:if>
        </xsl:variable>
        <p>
          <xsl:if test="normalize-space($style) != ''">
            <xsl:attribute name="style">
              <xsl:value-of select="normalize-space($style)"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
          <xsl:apply-templates select="node()"/>
        </p>
      </xsl:template>
    
      <xsl:template match="x:Span">
        <xsl:variable name="style">
          <xsl:if test="@FontStyle='Italic'">
            <xsl:text>font-style:italic;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontWeight='Bold'">
            <xsl:text>font-weight:bold;</xsl:text>
          </xsl:if>
          <xsl:if test="contains(@TextDecorations, 'Underline')">
            <xsl:text>text-decoration:underline;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontSize != ''">
            <xsl:text>font-size:</xsl:text>
            <xsl:value-of select="@FontSize" />
            <xsl:text>pt;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontFamily != ''">
            <xsl:text>font-family:</xsl:text>
            <xsl:value-of select="@FontFamily" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="concat(substring(@Foreground, 1, 1), substring(@Foreground, 4))" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground-Color != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="@Foreground-Color"/>
            <xsl:text>;</xsl:text>
          </xsl:if>
        </xsl:variable>
        <span>
          <xsl:if test="normalize-space($style) != ''">
            <xsl:attribute name="style">
              <xsl:value-of select="normalize-space($style)"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
          <xsl:apply-templates select="node()"/>
        </span>
      </xsl:template>
    
    
      <xsl:template match="x:Run">
        <xsl:variable name="style">
          <xsl:if test="@FontStyle='Italic'">
            <xsl:text>font-style:italic;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontWeight='Bold'">
            <xsl:text>font-weight:bold;</xsl:text>
          </xsl:if>
          <xsl:if test="contains(@TextDecorations, 'Underline')">
            <xsl:text>text-decoration:underline;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontSize != ''">
            <xsl:text>font-size:</xsl:text>
            <xsl:value-of select="@FontSize" />
            <xsl:text>pt;</xsl:text>
          </xsl:if>
          <xsl:if test="@FontFamily != ''">
            <xsl:text>font-family:</xsl:text>
            <xsl:value-of select="@FontFamily" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="concat(substring(@Foreground, 1, 1), substring(@Foreground, 4))" />
            <xsl:text>;</xsl:text>
          </xsl:if>
          <xsl:if test="@Foreground-Color != ''">
            <xsl:text>color:</xsl:text>
            <xsl:value-of select="@Foreground-Color"/>
            <xsl:text>;</xsl:text>
          </xsl:if>
        </xsl:variable>
        <span>
          <xsl:if test="normalize-space($style) != ''">
            <xsl:attribute name="style">
              <xsl:value-of select="normalize-space($style)"/>
            </xsl:attribute>
          </xsl:if>
          <xsl:value-of select="text()"/>
          <xsl:apply-templates select="node()"/>
        </span>
      </xsl:template>
    </xsl:stylesheet>
    
    0 讨论(0)
提交回复
热议问题