[英]C# FlowDocument to HTML conversion
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.基本上,我有一个 RichTextBox,我想将它的格式化内容转换为 HTML,以便它可以作为 email 发送。
The method I am currently using does not give any formatting at all:我目前使用的方法根本没有给出任何格式:
string message = new TextRange(messageTextBox.Document.ContentStart,
messageTextBox.Document.ContentEnd).Text;
So I searched around and found this , however, it is over 5 years old and in the comments an MSFT user has commented saying that it is no longer supported - "This sample has been removed from our sample set and is no longer supported"
, and the HTML it generates is in an older format than modern HTML or XHTML which would be better to have.所以我四处搜索,发现 这个,但是,它已经超过 5 年了,在评论中,一位 MSFT 用户评论说它不再受支持 -
"This sample has been removed from our sample set and is no longer supported"
,它生成的 HTML 的格式比现代 HTML 或 XHTML 更旧,后者会更好。
Can anybody show me how I can convert the formatted contents of a RichTextBox to HTML?谁能告诉我如何将 RichTextBox 的格式化内容转换为 HTML?
(So when the email is sent it the recipient sees the email with formatting) (因此,当发送 email 时,收件人会看到带有格式的 email)
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. 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.这不是一个很好的答案,但这是因为任何给定的 FlowDocument 都有大量可能的 HTML 表示。
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.例如,此转换将每个顶级
Section
转换为div
,将每个Paragraph
转换为p
,并将每个Run
转换为span
,其 class 会告诉您它是否为斜体、粗体或下划线,或任何组合以上。 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.这是我为进行转换而编写的值转换器 - 请注意,为了使用值转换器,您还必须修改并实现一个
RichTextBox
版本,它将内容作为依赖属性公开。 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
}
Extended version of XSLT sheet above I thought might help.我认为上面的 XSLT 表的扩展版本可能会有所帮助。 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>
For those who is looking for solution for.Net Core (APS.Net Core) - nuget MarkupConverter did the trick for me.对于那些正在寻找.Net Core (APS.Net Core) 解决方案的人 - nuget MarkupConverter 为我解决了问题。 Add dependency reference
添加依赖引用
<PackageReference Include="MarkupConverter" Version="1.0.6" />
Then use it然后使用它
MarkupConverter.MarkupConverter markupConverter = new MarkupConverter.MarkupConverter();
try
{
message = markupConverter.ConvertXamlToHtml(message);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to parse flowdocument");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.