如何使用 iText 将带有图像和超链接的 HTML 转换为 PDF?
How can I use iText to convert HTML with images and hyperlinks to PDF?
我正在尝试在使用 MVC, and web forms. The <img>
and <a>
elements have absolute and relative URLs, and some of the <img>
elements are base64 的 ASP.NET
Web 应用程序中使用 iTextSharp 将 HTML
转换为 PDF
。 SO 和 Google 搜索结果中的典型答案使用通用 HTML
到 PDF
代码 XMLWorkerHelper
看起来像这样:
using (var stringReader = new StringReader(xHtml))
{
using (Document document = new Document())
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
XMLWorkerHelper.GetInstance().ParseXHtml(
writer, document, stringReader
);
}
}
所以样本 HTML
是这样的:
<div>
<h3>HTML Works, but Broken in Converted PDF</h3>
<div>Relative local <img>: <img src='./../content/images/kuujinbo_320-30.gif' /></div>
<div>
Base64 <img>:
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' />
</div>
<div><a href='/somePage.html'>Relative local hyperlink, broken in PDF</a></div>
<div>
生成的 PDF:(1) 缺少所有图像,并且 (2) 所有具有相对 URL 的超链接都是损坏并使用 file URI scheme (file///XXX...
) 而不是指向正确的网站。
这里的一些答案和 Google 搜索中的其他答案建议将相对 URL 替换为绝对 URL,这对于一次性案例来说完全可以接受。但是,对于这个问题,用硬编码字符串全局替换所有 <img src>
和 <a href>
属性是 不可接受的 ,所以请不要 post 回答像那样,因为它会相应地被否决。
我正在寻找适用于 许多 驻留在测试、开发和生产环境中的不同 Web 应用程序的解决方案。
开箱即用 XMLWorker
only understands absolute URIs, so the described issues are expected behavior. The parser can't automagically deduce URI schemes 或没有一些附加信息的路径。
实施 ILinkProvider fixes the broken hyperlink problem, and implementing an IImageProvider fixes the broken image problem. Since both implementations must perform URI resolution,这是第一步。下面的助手 class 做到了这一点,并且还尝试使 Web (ASP.NET
) 上下文调用(示例如下)尽可能简单:
// resolve URIs for LinkProvider & ImageProvider
public class UriHelper
{
/* IsLocal; when running in web context:
* [1] give LinkProvider http[s] scheme; see CreateBase(string baseUri)
* [2] give ImageProvider relative path starting with '/' - see:
* Join(string relativeUri)
*/
public bool IsLocal { get; set; }
public HttpContext HttpContext { get; private set; }
public Uri BaseUri { get; private set; }
public UriHelper(string baseUri) : this(baseUri, true) {}
public UriHelper(string baseUri, bool isLocal)
{
IsLocal = isLocal;
HttpContext = HttpContext.Current;
BaseUri = CreateBase(baseUri);
}
/* get URI for IImageProvider to instantiate iTextSharp.text.Image for
* each <img> element in the HTML.
*/
public string Combine(string relativeUri)
{
/* when running in a web context, the HTML is coming from a MVC view
* or web form, so convert the incoming URI to a **local** path
*/
if (HttpContext != null && !BaseUri.IsAbsoluteUri && IsLocal)
{
return HttpContext.Server.MapPath(
// Combine() checks directory traversal exploits
VirtualPathUtility.Combine(BaseUri.ToString(), relativeUri)
);
}
return BaseUri.Scheme == Uri.UriSchemeFile
? Path.Combine(BaseUri.LocalPath, relativeUri)
// for this example we're assuming URI.Scheme is http[s]
: new Uri(BaseUri, relativeUri).AbsoluteUri;
}
private Uri CreateBase(string baseUri)
{
if (HttpContext != null)
{ // running on a web server; need to update original value
var req = HttpContext.Request;
baseUri = IsLocal
// IImageProvider; absolute virtual path (starts with '/')
// used to convert to local file system path. see:
// Combine(string relativeUri)
? req.ApplicationPath
// ILinkProvider; absolute http[s] URI scheme
: req.Url.GetLeftPart(UriPartial.Authority)
+ HttpContext.Request.ApplicationPath;
}
Uri uri;
if (Uri.TryCreate(baseUri, UriKind.RelativeOrAbsolute, out uri)) return uri;
throw new InvalidOperationException("cannot create a valid BaseUri");
}
}
实现 ILinkProvider
非常简单,因为 UriHelper
给出了基本 URI。我们只需要正确的 URI 方案(file
或 http[s]
):
// make hyperlinks with relative URLs absolute
public class LinkProvider : ILinkProvider
{
// rfc1738 - file URI scheme section 3.10
public const char SEPARATOR = '/';
public string BaseUrl { get; private set; }
public LinkProvider(UriHelper uriHelper)
{
var uri = uriHelper.BaseUri;
/* simplified implementation that only takes into account:
* Uri.UriSchemeFile || Uri.UriSchemeHttp || Uri.UriSchemeHttps
*/
BaseUrl = uri.Scheme == Uri.UriSchemeFile
// need trailing separator or file paths break
? uri.AbsoluteUri.TrimEnd(SEPARATOR) + SEPARATOR
// assumes Uri.UriSchemeHttp || Uri.UriSchemeHttps
: BaseUrl = uri.AbsoluteUri;
}
public string GetLinkRoot()
{
return BaseUrl;
}
}
IImageProvider
仅 需要 实现单个方法,Retrieve(string src)
,但 Store(string src, Image img)
很容易 - 请注意那里的内联注释和 GetImageRootPath()
:
// handle <img> elements in HTML
public class ImageProvider : IImageProvider
{
private UriHelper _uriHelper;
// see Store(string src, Image img)
private Dictionary<string, Image> _imageCache =
new Dictionary<string, Image>();
public virtual float ScalePercent { get; set; }
public virtual Regex Base64 { get; set; }
public ImageProvider(UriHelper uriHelper) : this(uriHelper, 67f) { }
// hard-coded based on general past experience ^^^
// but call the overload to supply your own
public ImageProvider(UriHelper uriHelper, float scalePercent)
{
_uriHelper = uriHelper;
ScalePercent = scalePercent;
Base64 = new Regex( // rfc2045, section 6.8 (alphabet/padding)
@"^data:image/[^;]+;base64,(?<data>[a-z0-9+/]+={0,2})$",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
}
public virtual Image ScaleImage(Image img)
{
img.ScalePercent(ScalePercent);
return img;
}
public virtual Image Retrieve(string src)
{
if (_imageCache.ContainsKey(src)) return _imageCache[src];
try
{
if (Regex.IsMatch(src, "^https?://", RegexOptions.IgnoreCase))
{
return ScaleImage(Image.GetInstance(src));
}
Match match;
if ((match = Base64.Match(src)).Length > 0)
{
return ScaleImage(Image.GetInstance(
Convert.FromBase64String(match.Groups["data"].Value)
));
}
var imgPath = _uriHelper.Combine(src);
return ScaleImage(Image.GetInstance(imgPath));
}
// not implemented to keep the SO answer (relatively) short
catch (BadElementException ex) { return null; }
catch (IOException ex) { return null; }
catch (Exception ex) { return null; }
}
/*
* always called after Retrieve(string src):
* [1] cache any duplicate <img> in the HTML source so the image bytes
* are only written to the PDF **once**, which reduces the
* resulting file size.
* [2] the cache can also **potentially** save network IO if you're
* running the parser in a loop, since Image.GetInstance() creates
* a WebRequest when an image resides on a remote server. couldn't
* find a CachePolicy in the source code
*/
public virtual void Store(string src, Image img)
{
if (!_imageCache.ContainsKey(src)) _imageCache.Add(src, img);
}
/* XMLWorker documentation for ImageProvider recommends implementing
* GetImageRootPath():
*
* http://demo.itextsupport.com/xmlworker/itextdoc/flatsite.html#itextdoc-menu-10
*
* but a quick run through the debugger never hits the breakpoint, so
* not sure if I'm missing something, or something has changed internally
* with XMLWorker....
*/
public virtual string GetImageRootPath() { return null; }
public virtual void Reset() { }
}
基于XML Worker documentation,将上面的ILinkProvider
和IImageProvider
的实现挂钩到一个简单的解析器中非常简单class:
/* a simple parser that uses XMLWorker and XMLParser to handle converting
* (most) images and hyperlinks internally
*/
public class SimpleParser
{
public virtual ILinkProvider LinkProvider { get; set; }
public virtual IImageProvider ImageProvider { get; set; }
public virtual HtmlPipelineContext HtmlPipelineContext { get; set; }
public virtual ITagProcessorFactory TagProcessorFactory { get; set; }
public virtual ICSSResolver CssResolver { get; set; }
/* overloads simplfied to keep SO answer (relatively) short. if needed
* set LinkProvider/ImageProvider after instantiating SimpleParser()
* to override the defaults (e.g. ImageProvider.ScalePercent)
*/
public SimpleParser() : this(null) { }
public SimpleParser(string baseUri)
{
LinkProvider = new LinkProvider(new UriHelper(baseUri, false));
ImageProvider = new ImageProvider(new UriHelper(baseUri, true));
HtmlPipelineContext = new HtmlPipelineContext(null);
// another story altogether, and not implemented for simplicity
TagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
CssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
}
/*
* when sending XHR via any of the popular JavaScript frameworks,
* <img> tags are **NOT** always closed, which results in the
* infamous iTextSharp.tool.xml.exceptions.RuntimeWorkerException:
* 'Invalid nested tag a found, expected closing tag img.' a simple
* workaround.
*/
public virtual string SimpleAjaxImgFix(string xHtml)
{
return Regex.Replace(
xHtml,
"(?<image><img[^>]+)(?<=[^/])>",
new MatchEvaluator(match => match.Groups["image"].Value + " />"),
RegexOptions.IgnoreCase | RegexOptions.Multiline
);
}
public virtual void Parse(Stream stream, string xHtml)
{
xHtml = SimpleAjaxImgFix(xHtml);
using (var stringReader = new StringReader(xHtml))
{
using (Document document = new Document())
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
HtmlPipelineContext
.SetTagFactory(Tags.GetHtmlTagProcessorFactory())
.SetLinkProvider(LinkProvider)
.SetImageProvider(ImageProvider)
;
var pdfWriterPipeline = new PdfWriterPipeline(document, writer);
var htmlPipeline = new HtmlPipeline(HtmlPipelineContext, pdfWriterPipeline);
var cssResolverPipeline = new CssResolverPipeline(CssResolver, htmlPipeline);
XMLWorker worker = new XMLWorker(cssResolverPipeline, true);
XMLParser parser = new XMLParser(worker);
parser.Parse(stringReader);
}
}
}
}
如内联评论,SimpleAjaxImgFix(string xHtml)
专门处理 XHR that may send unclosed <img>
tags, which is valid HTML
, but invalid XML
that will break XMLWorker
. A simple explanation & implementation of how to receive a PDF or other binary data with XHR and iTextSharp can be found here。
A Regex
用于 SimpleAjaxImgFix(string xHtml)
以便任何人使用 (copy/paste?) 代码不需要添加另一个nuget
包,但是像 HtmlAgilityPack 这样的 HTML
解析器应该 被使用,因为它变成了这个:
<div><img src='a.gif'><br><hr></div>
进入这个:
<div><img src='a.gif' /><br /><hr /></div>
只有几行代码:
var hDocument = new HtmlDocument()
{
OptionWriteEmptyNodes = true,
OptionAutoCloseOnEnd = true
};
hDocument.LoadHtml("<div><img src='a.gif'><br><hr></div>");
var closedTags = hDocument.DocumentNode.WriteTo();
另请注意 - 使用上面的 SimpleParser.Parse()
作为 通用 蓝图来另外实现自定义 ICSSResolver or ITagProcessorFactory, which is explained in the documentation.
现在应该解决问题中描述的问题。从 MVC Action Method
:
调用
[HttpPost] // some browsers have URL length limits
[ValidateInput(false)] // or throws HttpRequestValidationException
public ActionResult Index(string xHtml)
{
Response.ContentType = "application/pdf";
Response.AppendHeader(
"Content-Disposition", "attachment; filename=test.pdf"
);
var simpleParser = new SimpleParser();
simpleParser.Parse(Response.OutputStream, xHtml);
return new EmptyResult();
}
或从 Web Form
从 server control 得到 HTML
:
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=test.pdf");
using (var stringWriter = new StringWriter())
{
using (var htmlWriter = new HtmlTextWriter(stringWriter))
{
ConvertControlToPdf.RenderControl(htmlWriter);
}
var simpleParser = new SimpleParser();
simpleParser.Parse(Response.OutputStream, stringWriter.ToString());
}
Response.End();
或文件系统上带有超链接和图像的简单 HTML 文件:
<h1>HTML Page 00 on Local File System</h1>
<div>
<div>
Relative <img>: <img src='Images/alt-gravatar.png' />
</div>
<div>
Hyperlink to file system HTML page:
<a href='file-system-html-01.html'>Page 01</a>
</div>
</div>
或 HTML 来自远程网站:
<div>
<div>
<img width="200" alt="Wikipedia Logo"
src="portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png">
</div>
<div lang="en">
<a href="https://en.wikipedia.org/">English</a>
</div>
<div lang="en">
<a href="wiki/IText">iText</a>
</div>
</div>
以上两个 HTML
片段 运行 来自控制台应用程序:
var filePaths = Path.Combine(basePath, "file-system-html-00.html");
var htmlFile = File.ReadAllText(filePaths);
var remoteUrl = Path.Combine(basePath, "wikipedia.html");
var htmlRemote = File.ReadAllText(remoteUrl);
var outputFile = Path.Combine(basePath, "filePaths.pdf");
var outputRemote = Path.Combine(basePath, "remoteUrl.pdf");
using (var stream = new FileStream(outputFile, FileMode.Create))
{
var simpleParser = new SimpleParser(basePath);
simpleParser.Parse(stream, htmlFile);
}
using (var stream = new FileStream(outputRemote, FileMode.Create))
{
var simpleParser = new SimpleParser("https://wikipedia.org");
simpleParser.Parse(stream, htmlRemote);
}
相当长的答案,但看看这里标记为 html
, pdf
, and itextsharp
, as of this writing (2016-02-23) there are 776 results against 4,063 total tagged itextsharp
的问题 - 那是 19%。
很有帮助post,
我无法将报告 html 中的图像呈现为 pdf。有你的 post 我可以做到。
我正在与 asp.mvc 5.
一起工作
我只需要改变ImageProviderClass的这个方法
public virtual string GetImageRootPath() { return null; }
至
public virtual string GetImageRootPath() { HostingEnvironment.MapPath("~/Content/Images/") }
谢谢!
我正在尝试在使用 MVC, and web forms. The <img>
and <a>
elements have absolute and relative URLs, and some of the <img>
elements are base64 的 ASP.NET
Web 应用程序中使用 iTextSharp 将 HTML
转换为 PDF
。 SO 和 Google 搜索结果中的典型答案使用通用 HTML
到 PDF
代码 XMLWorkerHelper
看起来像这样:
using (var stringReader = new StringReader(xHtml))
{
using (Document document = new Document())
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
XMLWorkerHelper.GetInstance().ParseXHtml(
writer, document, stringReader
);
}
}
所以样本 HTML
是这样的:
<div>
<h3>HTML Works, but Broken in Converted PDF</h3>
<div>Relative local <img>: <img src='./../content/images/kuujinbo_320-30.gif' /></div>
<div>
Base64 <img>:
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' />
</div>
<div><a href='/somePage.html'>Relative local hyperlink, broken in PDF</a></div>
<div>
生成的 PDF:(1) 缺少所有图像,并且 (2) 所有具有相对 URL 的超链接都是损坏并使用 file URI scheme (file///XXX...
) 而不是指向正确的网站。
这里的一些答案和 Google 搜索中的其他答案建议将相对 URL 替换为绝对 URL,这对于一次性案例来说完全可以接受。但是,对于这个问题,用硬编码字符串全局替换所有 <img src>
和 <a href>
属性是 不可接受的 ,所以请不要 post 回答像那样,因为它会相应地被否决。
我正在寻找适用于 许多 驻留在测试、开发和生产环境中的不同 Web 应用程序的解决方案。
开箱即用 XMLWorker
only understands absolute URIs, so the described issues are expected behavior. The parser can't automagically deduce URI schemes 或没有一些附加信息的路径。
实施 ILinkProvider fixes the broken hyperlink problem, and implementing an IImageProvider fixes the broken image problem. Since both implementations must perform URI resolution,这是第一步。下面的助手 class 做到了这一点,并且还尝试使 Web (ASP.NET
) 上下文调用(示例如下)尽可能简单:
// resolve URIs for LinkProvider & ImageProvider
public class UriHelper
{
/* IsLocal; when running in web context:
* [1] give LinkProvider http[s] scheme; see CreateBase(string baseUri)
* [2] give ImageProvider relative path starting with '/' - see:
* Join(string relativeUri)
*/
public bool IsLocal { get; set; }
public HttpContext HttpContext { get; private set; }
public Uri BaseUri { get; private set; }
public UriHelper(string baseUri) : this(baseUri, true) {}
public UriHelper(string baseUri, bool isLocal)
{
IsLocal = isLocal;
HttpContext = HttpContext.Current;
BaseUri = CreateBase(baseUri);
}
/* get URI for IImageProvider to instantiate iTextSharp.text.Image for
* each <img> element in the HTML.
*/
public string Combine(string relativeUri)
{
/* when running in a web context, the HTML is coming from a MVC view
* or web form, so convert the incoming URI to a **local** path
*/
if (HttpContext != null && !BaseUri.IsAbsoluteUri && IsLocal)
{
return HttpContext.Server.MapPath(
// Combine() checks directory traversal exploits
VirtualPathUtility.Combine(BaseUri.ToString(), relativeUri)
);
}
return BaseUri.Scheme == Uri.UriSchemeFile
? Path.Combine(BaseUri.LocalPath, relativeUri)
// for this example we're assuming URI.Scheme is http[s]
: new Uri(BaseUri, relativeUri).AbsoluteUri;
}
private Uri CreateBase(string baseUri)
{
if (HttpContext != null)
{ // running on a web server; need to update original value
var req = HttpContext.Request;
baseUri = IsLocal
// IImageProvider; absolute virtual path (starts with '/')
// used to convert to local file system path. see:
// Combine(string relativeUri)
? req.ApplicationPath
// ILinkProvider; absolute http[s] URI scheme
: req.Url.GetLeftPart(UriPartial.Authority)
+ HttpContext.Request.ApplicationPath;
}
Uri uri;
if (Uri.TryCreate(baseUri, UriKind.RelativeOrAbsolute, out uri)) return uri;
throw new InvalidOperationException("cannot create a valid BaseUri");
}
}
实现 ILinkProvider
非常简单,因为 UriHelper
给出了基本 URI。我们只需要正确的 URI 方案(file
或 http[s]
):
// make hyperlinks with relative URLs absolute
public class LinkProvider : ILinkProvider
{
// rfc1738 - file URI scheme section 3.10
public const char SEPARATOR = '/';
public string BaseUrl { get; private set; }
public LinkProvider(UriHelper uriHelper)
{
var uri = uriHelper.BaseUri;
/* simplified implementation that only takes into account:
* Uri.UriSchemeFile || Uri.UriSchemeHttp || Uri.UriSchemeHttps
*/
BaseUrl = uri.Scheme == Uri.UriSchemeFile
// need trailing separator or file paths break
? uri.AbsoluteUri.TrimEnd(SEPARATOR) + SEPARATOR
// assumes Uri.UriSchemeHttp || Uri.UriSchemeHttps
: BaseUrl = uri.AbsoluteUri;
}
public string GetLinkRoot()
{
return BaseUrl;
}
}
IImageProvider
仅 需要 实现单个方法,Retrieve(string src)
,但 Store(string src, Image img)
很容易 - 请注意那里的内联注释和 GetImageRootPath()
:
// handle <img> elements in HTML
public class ImageProvider : IImageProvider
{
private UriHelper _uriHelper;
// see Store(string src, Image img)
private Dictionary<string, Image> _imageCache =
new Dictionary<string, Image>();
public virtual float ScalePercent { get; set; }
public virtual Regex Base64 { get; set; }
public ImageProvider(UriHelper uriHelper) : this(uriHelper, 67f) { }
// hard-coded based on general past experience ^^^
// but call the overload to supply your own
public ImageProvider(UriHelper uriHelper, float scalePercent)
{
_uriHelper = uriHelper;
ScalePercent = scalePercent;
Base64 = new Regex( // rfc2045, section 6.8 (alphabet/padding)
@"^data:image/[^;]+;base64,(?<data>[a-z0-9+/]+={0,2})$",
RegexOptions.Compiled | RegexOptions.IgnoreCase
);
}
public virtual Image ScaleImage(Image img)
{
img.ScalePercent(ScalePercent);
return img;
}
public virtual Image Retrieve(string src)
{
if (_imageCache.ContainsKey(src)) return _imageCache[src];
try
{
if (Regex.IsMatch(src, "^https?://", RegexOptions.IgnoreCase))
{
return ScaleImage(Image.GetInstance(src));
}
Match match;
if ((match = Base64.Match(src)).Length > 0)
{
return ScaleImage(Image.GetInstance(
Convert.FromBase64String(match.Groups["data"].Value)
));
}
var imgPath = _uriHelper.Combine(src);
return ScaleImage(Image.GetInstance(imgPath));
}
// not implemented to keep the SO answer (relatively) short
catch (BadElementException ex) { return null; }
catch (IOException ex) { return null; }
catch (Exception ex) { return null; }
}
/*
* always called after Retrieve(string src):
* [1] cache any duplicate <img> in the HTML source so the image bytes
* are only written to the PDF **once**, which reduces the
* resulting file size.
* [2] the cache can also **potentially** save network IO if you're
* running the parser in a loop, since Image.GetInstance() creates
* a WebRequest when an image resides on a remote server. couldn't
* find a CachePolicy in the source code
*/
public virtual void Store(string src, Image img)
{
if (!_imageCache.ContainsKey(src)) _imageCache.Add(src, img);
}
/* XMLWorker documentation for ImageProvider recommends implementing
* GetImageRootPath():
*
* http://demo.itextsupport.com/xmlworker/itextdoc/flatsite.html#itextdoc-menu-10
*
* but a quick run through the debugger never hits the breakpoint, so
* not sure if I'm missing something, or something has changed internally
* with XMLWorker....
*/
public virtual string GetImageRootPath() { return null; }
public virtual void Reset() { }
}
基于XML Worker documentation,将上面的ILinkProvider
和IImageProvider
的实现挂钩到一个简单的解析器中非常简单class:
/* a simple parser that uses XMLWorker and XMLParser to handle converting
* (most) images and hyperlinks internally
*/
public class SimpleParser
{
public virtual ILinkProvider LinkProvider { get; set; }
public virtual IImageProvider ImageProvider { get; set; }
public virtual HtmlPipelineContext HtmlPipelineContext { get; set; }
public virtual ITagProcessorFactory TagProcessorFactory { get; set; }
public virtual ICSSResolver CssResolver { get; set; }
/* overloads simplfied to keep SO answer (relatively) short. if needed
* set LinkProvider/ImageProvider after instantiating SimpleParser()
* to override the defaults (e.g. ImageProvider.ScalePercent)
*/
public SimpleParser() : this(null) { }
public SimpleParser(string baseUri)
{
LinkProvider = new LinkProvider(new UriHelper(baseUri, false));
ImageProvider = new ImageProvider(new UriHelper(baseUri, true));
HtmlPipelineContext = new HtmlPipelineContext(null);
// another story altogether, and not implemented for simplicity
TagProcessorFactory = Tags.GetHtmlTagProcessorFactory();
CssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(true);
}
/*
* when sending XHR via any of the popular JavaScript frameworks,
* <img> tags are **NOT** always closed, which results in the
* infamous iTextSharp.tool.xml.exceptions.RuntimeWorkerException:
* 'Invalid nested tag a found, expected closing tag img.' a simple
* workaround.
*/
public virtual string SimpleAjaxImgFix(string xHtml)
{
return Regex.Replace(
xHtml,
"(?<image><img[^>]+)(?<=[^/])>",
new MatchEvaluator(match => match.Groups["image"].Value + " />"),
RegexOptions.IgnoreCase | RegexOptions.Multiline
);
}
public virtual void Parse(Stream stream, string xHtml)
{
xHtml = SimpleAjaxImgFix(xHtml);
using (var stringReader = new StringReader(xHtml))
{
using (Document document = new Document())
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
HtmlPipelineContext
.SetTagFactory(Tags.GetHtmlTagProcessorFactory())
.SetLinkProvider(LinkProvider)
.SetImageProvider(ImageProvider)
;
var pdfWriterPipeline = new PdfWriterPipeline(document, writer);
var htmlPipeline = new HtmlPipeline(HtmlPipelineContext, pdfWriterPipeline);
var cssResolverPipeline = new CssResolverPipeline(CssResolver, htmlPipeline);
XMLWorker worker = new XMLWorker(cssResolverPipeline, true);
XMLParser parser = new XMLParser(worker);
parser.Parse(stringReader);
}
}
}
}
如内联评论,SimpleAjaxImgFix(string xHtml)
专门处理 XHR that may send unclosed <img>
tags, which is valid HTML
, but invalid XML
that will break XMLWorker
. A simple explanation & implementation of how to receive a PDF or other binary data with XHR and iTextSharp can be found here。
A Regex
用于 SimpleAjaxImgFix(string xHtml)
以便任何人使用 (copy/paste?) 代码不需要添加另一个nuget
包,但是像 HtmlAgilityPack 这样的 HTML
解析器应该 被使用,因为它变成了这个:
<div><img src='a.gif'><br><hr></div>
进入这个:
<div><img src='a.gif' /><br /><hr /></div>
只有几行代码:
var hDocument = new HtmlDocument()
{
OptionWriteEmptyNodes = true,
OptionAutoCloseOnEnd = true
};
hDocument.LoadHtml("<div><img src='a.gif'><br><hr></div>");
var closedTags = hDocument.DocumentNode.WriteTo();
另请注意 - 使用上面的 SimpleParser.Parse()
作为 通用 蓝图来另外实现自定义 ICSSResolver or ITagProcessorFactory, which is explained in the documentation.
现在应该解决问题中描述的问题。从 MVC Action Method
:
[HttpPost] // some browsers have URL length limits
[ValidateInput(false)] // or throws HttpRequestValidationException
public ActionResult Index(string xHtml)
{
Response.ContentType = "application/pdf";
Response.AppendHeader(
"Content-Disposition", "attachment; filename=test.pdf"
);
var simpleParser = new SimpleParser();
simpleParser.Parse(Response.OutputStream, xHtml);
return new EmptyResult();
}
或从 Web Form
从 server control 得到 HTML
:
Response.ContentType = "application/pdf";
Response.AppendHeader("Content-Disposition", "attachment; filename=test.pdf");
using (var stringWriter = new StringWriter())
{
using (var htmlWriter = new HtmlTextWriter(stringWriter))
{
ConvertControlToPdf.RenderControl(htmlWriter);
}
var simpleParser = new SimpleParser();
simpleParser.Parse(Response.OutputStream, stringWriter.ToString());
}
Response.End();
或文件系统上带有超链接和图像的简单 HTML 文件:
<h1>HTML Page 00 on Local File System</h1>
<div>
<div>
Relative <img>: <img src='Images/alt-gravatar.png' />
</div>
<div>
Hyperlink to file system HTML page:
<a href='file-system-html-01.html'>Page 01</a>
</div>
</div>
或 HTML 来自远程网站:
<div>
<div>
<img width="200" alt="Wikipedia Logo"
src="portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png">
</div>
<div lang="en">
<a href="https://en.wikipedia.org/">English</a>
</div>
<div lang="en">
<a href="wiki/IText">iText</a>
</div>
</div>
以上两个 HTML
片段 运行 来自控制台应用程序:
var filePaths = Path.Combine(basePath, "file-system-html-00.html");
var htmlFile = File.ReadAllText(filePaths);
var remoteUrl = Path.Combine(basePath, "wikipedia.html");
var htmlRemote = File.ReadAllText(remoteUrl);
var outputFile = Path.Combine(basePath, "filePaths.pdf");
var outputRemote = Path.Combine(basePath, "remoteUrl.pdf");
using (var stream = new FileStream(outputFile, FileMode.Create))
{
var simpleParser = new SimpleParser(basePath);
simpleParser.Parse(stream, htmlFile);
}
using (var stream = new FileStream(outputRemote, FileMode.Create))
{
var simpleParser = new SimpleParser("https://wikipedia.org");
simpleParser.Parse(stream, htmlRemote);
}
相当长的答案,但看看这里标记为 html
, pdf
, and itextsharp
, as of this writing (2016-02-23) there are 776 results against 4,063 total tagged itextsharp
的问题 - 那是 19%。
很有帮助post,
我无法将报告 html 中的图像呈现为 pdf。有你的 post 我可以做到。
我正在与 asp.mvc 5.
一起工作我只需要改变ImageProviderClass的这个方法
public virtual string GetImageRootPath() { return null; }
至
public virtual string GetImageRootPath() { HostingEnvironment.MapPath("~/Content/Images/") }
谢谢!