Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
PDF 轉(zhuǎn)Word 是一個(gè)非常非常普遍的需求,可謂人人忌危,為什么如此普遍的需求,卻如此難行呢,還得看為什么會(huì)有這樣的一個(gè)需求。
PDF文檔遵循iOS32000的規(guī)范是由Adobe 公司推出的文檔格式,之所以應(yīng)用如此廣泛,是因?yàn)镻DF精確定位了每個(gè)字符的坐標(biāo)、根據(jù)坐標(biāo)繪制的各種形狀,使用PDF格式傳輸和打印文檔可以保證格式的一致性,然后很多PDF文件是可用于閱讀,展示,打印,但編輯起來是非常困難,如格式調(diào)整,文字修改,樣式調(diào)整等,那么就衍生了PDF 轉(zhuǎn)Word這一歷史性的需求,但因?yàn)閮烧咧g采用的編碼規(guī)范以及布局機(jī)制的完全不一致,導(dǎo)致轉(zhuǎn)換起來會(huì)非常復(fù)雜,一般的工具不是格式錯(cuò)亂,就是內(nèi)容錯(cuò)亂,很難達(dá)到客戶的原生期望。
其難點(diǎn)在于建立從PDF基于元素位置的格式到Word基于內(nèi)容的格式的映射。PDF文檔實(shí)際并不存在段落、表格的概念,PDF轉(zhuǎn)Word要做的就是將PDF文檔中“橫、豎線條圍繞著文本”解析為Word的“表格”,將“文本及下方的一條橫線”解析為“文本下劃線”,等等。
兩個(gè)工具兩套規(guī)則,自古以來兩個(gè)工具之間的兼容轉(zhuǎn)換,除非是為一家所有,會(huì)有通用的標(biāo)準(zhǔn)和接口預(yù)留,達(dá)到很好的兼容性,但 Adobe和微軟都是巨大的科技企業(yè),且兩款軟件功能都是非常強(qiáng)大且覆蓋面全,要做到完美的匹配所有規(guī)則更是非常苦難。
對于報(bào)表用戶來說,很多用戶會(huì)將報(bào)表理解為報(bào)告,報(bào)告自然會(huì)聯(lián)想到Word,那么就很希望在頁面中展示的內(nèi)容能夠成 Word 文件來進(jìn)行存檔,編輯等作用。
ActiveReportsJS 是一款前端的報(bào)表開發(fā)工具,不與后端關(guān)聯(lián),因此想要將展示的HTML 生成Word,研發(fā)團(tuán)隊(duì)經(jīng)過一些調(diào)研發(fā)現(xiàn)整個(gè)過程會(huì)非常復(fù)雜非常困難,正如他們反饋:“不是一個(gè)sprint能解決的問題”,就PDF.js 背后都有強(qiáng)大的Mozilla支撐,更何況Word文檔是依托微軟的Office開發(fā)組件去生成的。
但在實(shí)際接觸客戶的時(shí)候,許多用戶都會(huì)來詢問相關(guān)內(nèi)容包括如何用報(bào)表設(shè)計(jì)類似審批表、人事履歷表、檢測報(bào)告等很常見的Word報(bào)告。用戶對結(jié)果都比較滿意,但唯一用戶不滿的是報(bào)表結(jié)果只能生成pdf。這是傳統(tǒng),這也是核心需求,也是痛點(diǎn)。
本葡萄就有些很著急,于是不信這個(gè)邪,在前端工具如此豐富的情況下,竟沒有一個(gè)這樣可用的工具?
開始搜索,打開google,榨干全部腦汁的詞匯量輸入了我需要的關(guān)鍵詞,搜索到了以下結(jié)果。
乍一看,第一條完全吻合,Node.js 雖說是服務(wù)端也不是不可以接受,只要有方案即可。
看著非常有戲。
代碼簡單:
但仔細(xì)看看代碼,果然老天在為我們送東西的時(shí)候都在背后的標(biāo)好了價(jià)格:
心想如果可以,付費(fèi)就付費(fèi)吧,畢竟我們也是做付費(fèi)商業(yè)軟件的專業(yè)er,版權(quán)意識還是需要有的。
點(diǎn)擊登錄,用谷歌賬號登陸成功后,即可在項(xiàng)目中引用cloudmersive-convert-api-client 安裝包。
該JS 庫提供了將近幾十種的API及Class用于處理轉(zhuǎn)換不同的格式文件:除了將PDF轉(zhuǎn)Word外,還有其他發(fā)的文件格式轉(zhuǎn)換,使用起來也是非常簡單,
可以識別本地的PDF 文件,轉(zhuǎn)換結(jié)果:
因?yàn)檎麄€(gè)轉(zhuǎn)換API 只是CloudMersive 的一個(gè)API功能,整個(gè)產(chǎn)品還附加其他的安全檢驗(yàn)等功能,因此產(chǎn)品是按月及并發(fā)數(shù)收費(fèi)的。大家可自行搜索了解,不過他們網(wǎng)站倒是提供好了幾個(gè)文件轉(zhuǎn)換的工具非常好用,無需登錄直接獲取轉(zhuǎn)換結(jié)果
通過搜索發(fā)現(xiàn)PDF對象流直接用JS 轉(zhuǎn)換為Word 文件是非常困難的, 而且經(jīng)過驗(yàn)證ARJS 導(dǎo)出PDF 文件可以用Word軟件打開,那么突然想到是否可以找一個(gè)中間件,將PDF流直接轉(zhuǎn)換為doc或docx格式,但搜索一番,嘗試之后,只是在.pdf前面加了document.docx.pdf
該方法嘗試失敗。
跟技術(shù)大咖聊了之后,才發(fā)現(xiàn)pdf和word雖然本質(zhì)都是二進(jìn)制流,但內(nèi)部的聲明等都是各自文件特有的屬性,因此不能直接轉(zhuǎn)換,簡而言之就是是什么文件流就只能保存什么文件流。且PDF 和 Word是兩大技術(shù)公司背書,直接轉(zhuǎn)換得用專業(yè)的工具,因此此路不通。
于是乎,退而求其次,HTML 是萬能的,HTML 可以轉(zhuǎn)萬物, HTML 轉(zhuǎn)PDF, HTML 轉(zhuǎn)圖片,HTML 轉(zhuǎn)Excel等等等,那么 ActiveReportsJS 提供了可將報(bào)表導(dǎo)出為HTML 文件且格式完全一致,那么方法來了,我直接使用HTML 轉(zhuǎn) Word不是更方便些?Google搜索果然此類資料比PDF 轉(zhuǎn)Word多了百倍,而且看代碼也是操作非常簡單:
只需3步驟:
1、將報(bào)表導(dǎo)出HTML
var pageReport = new ARJS.PageReport();
pageReport.load('./BandedReport.rdlx-json')
.then(function() { return pageReport.run() })
.then(function(pageDocument) { return HTMLExport.exportDocument(pageDocument) })
2、加工HTML 代碼增加office 標(biāo)記
var header = "<html xmlns:o='urn:schemas-microsoft-com:office:office' "+
"xmlns:w='urn:schemas-microsoft-com:office:word' "+
"xmlns='http://www.w3.org/TR/REC-html40'>"
let reg=/<html>/;
console.log(reg.test(htmlcode));
var test= htmlcode.replace(reg,header);
var sourceHTML='data:application/vnd.ms-word;charset=utf-8,'+encodeURIComponent(test);
3、 創(chuàng)建 a 標(biāo)簽,直接下載 doc格式
var fileDownload = document.createElement("a");
document.body.appendChild(fileDownload);
fileDownload.href = sourceHTML;
fileDownload.download = 'document.doc';
fileDownload.click();
document.body.removeChild(fileDownload);
看看結(jié)果:效果很Nice
兩種轉(zhuǎn)化結(jié)果總結(jié)如下:
通過一番嘗試也算是有一個(gè)Workaround,考慮到報(bào)告類的報(bào)表一般以文本內(nèi)容為主,樣式也比較樸素,所以使用html到Word轉(zhuǎn)換不失為一個(gè)快速簡潔的方法,大部分需要保存為Word 還是為了進(jìn)行二次編輯。本葡萄也在努力尋找HTML 轉(zhuǎn)Word 樣式保留的方法,有新的進(jìn)展會(huì)給大家更新第二篇。
轉(zhuǎn)載請注明出處:葡萄城官網(wǎng),葡萄城為開發(fā)者提供專業(yè)的開發(fā)工具、解決方案和服務(wù),賦能開發(fā)者。微信公眾號:“葡萄城社區(qū)”。
.NET的SelectPdf Html到Pdf轉(zhuǎn)換器-社區(qū)版是.NET的SelectPdf庫中提供的功能強(qiáng)大的html到pdf轉(zhuǎn)換器的免費(fèi)版本。
轉(zhuǎn)換器提供了許多強(qiáng)大的選項(xiàng)(將任何網(wǎng)頁轉(zhuǎn)換為pdf,將任何html字符串轉(zhuǎn)換為pdf,html5 / css3 / javascript支持,頁眉和頁腳支持等),唯一的限制是它最多可以生成pdf文檔。5頁長。
.NET的免費(fèi)HTML至Pdf轉(zhuǎn)換器–社區(qū)版功能:最多生成5頁pdf文檔,將任何網(wǎng)頁轉(zhuǎn)換為pdf,將任何原始html字符串轉(zhuǎn)換為pdf,設(shè)置pdf頁面設(shè)置(頁面大小,頁面方向,頁面邊距) ,在轉(zhuǎn)換過程中調(diào)整內(nèi)容大小以適合pdf頁面,設(shè)置pdf文檔屬性,設(shè)置pdf查看器首選項(xiàng),設(shè)置pdf安全性(密碼,權(quán)限),設(shè)置轉(zhuǎn)換延遲和網(wǎng)頁導(dǎo)航超時(shí),自定義頁眉和頁腳,在頁眉中支持html和頁腳,自動(dòng)和手動(dòng)分頁符,在每個(gè)頁面上重復(fù)html表頭,支持@media類型屏幕和打印,支持內(nèi)部和外部鏈接,基于html元素自動(dòng)生成書簽,支持HTTP標(biāo)頭,支持HTTP cookie,支持需要身份驗(yàn)證的網(wǎng)頁,支持代理服務(wù)器,啟用/禁用javascript,修改顏色空間,多線程支持,HTML5 / CSS3支持,Web字體支持等等。
1、nuget 引用
Install-Package Select.HtmlToPdf
2、方法
using SelectPdf;
using System.Collections.Specialized;
using System.IO;
using System.Web;
namespace BQoolCommon.Helpers.File
{
public class WebToPdf
{
public WebToPdf()
{
//SelectPdf.GlobalProperties.LicenseKey = "your-license-key";
}
/// <summary>
/// 將 Html 轉(zhuǎn)成 PDF,並儲(chǔ)存成檔案
/// </summary>
/// <param name="html">html</param>
/// <param name="fileName">絕對路徑</param>
public void SaveToFileByHtml(string html, string fileName)
{
var doc = SetPdfDocument(html);
doc.Save(fileName);
}
/// <summary>
/// 傳入 Url 轉(zhuǎn)成 PDF,並儲(chǔ)存成檔案
/// </summary>
/// <param name="url">url</param>
/// <param name="fileName">絕對路徑</param>
/// <param name="httpCookies">Cookies</param>
public void SaveToFileByUrl(string url, string fileName, NameValueCollection httpCookies)
{
var doc = SetPdfDocument(url, httpCookies);
doc.Save(fileName);
}
/// <summary>
/// 將 Html 轉(zhuǎn)成 PDF,並輸出成 byte[] 格式
/// </summary>
/// <param name="html">html</param>
/// <returns></returns>
public byte[] GetFileByteByHtml(string html)
{
var doc = SetPdfDocument(html);
return doc.Save();
}
/// <summary>
/// 傳入 Url 轉(zhuǎn)成 PDF,並輸出成 byte[] 格式
/// </summary>
/// <param name="url">url</param>
/// <param name="httpCookies">Cookies</param>
/// <returns></returns>
public byte[] GetFileByteByUrl(string url, NameValueCollection httpCookies)
{
var doc = SetPdfDocument(url, httpCookies);
return doc.Save();
}
/// <summary>
/// 將 Html 轉(zhuǎn)成 PDF,並輸出成 Stream 格式
/// </summary>
/// <param name="html">html</param>
/// <returns></returns>
public Stream GetFileStreamByHtml(string html)
{
var doc = SetPdfDocument(html);
var pdfStream = new MemoryStream();
doc.Save(pdfStream);
pdfStream.Position = 0;
return pdfStream;
}
/// <summary>
/// 傳入 Url 轉(zhuǎn)成 PDF,並輸出成 Stream 格式
/// </summary>
/// <param name="html">html</param>
/// <returns></returns>
public Stream GetFileStreamByUrl(string url, NameValueCollection httpCookies)
{
var doc = SetPdfDocument(url, httpCookies);
var pdfStream = new MemoryStream();
doc.Save(pdfStream);
pdfStream.Position = 0;
return pdfStream;
}
private PdfDocument SetPdfDocument(string html)
{
var converter = new HtmlToPdf();
converter.Options.WebPageWidth = 1200;
html = HttpUtility.HtmlDecode(html);
return converter.ConvertHtmlString(html);
}
private PdfDocument SetPdfDocument(string url, NameValueCollection httpCookies)
{
var converter = new HtmlToPdf();
converter.Options.WebPageWidth = 1200;
if (httpCookies != && httpCookies.Count != 0)
{
converter.Options.HttpCookies.Add(httpCookies);
}
return converter.ConvertUrl(url);
}
}
}
3、調(diào)用
/// <summary>
/// 下載pdf
/// </summary>
public void Downpdf(string data)
{
var stream = new BQoolCommon.Helpers.File.WebToPdf().GetFileStreamByHtml(Gethtml(data));
Response.Clear();
//二進(jìn)制流數(shù)據(jù)(如常見的文件下載)
Response.ContentType = "application/octet-stream";
//通知瀏覽器下載文件而不是打開
Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode("Profit and Loss Statement.pdf", System.Text.Encoding.UTF8));
var bytes = StreamToBytes(stream);
Response.BinaryWrite(bytes);
Response.Flush();
stream.Close();
stream.Dispose();
Response.End();
}
那么如何獲取指定頁面的html 呢 傳入對應(yīng)的model 獲得指定動(dòng)態(tài)的html
private string Gethtml(string data)
{
string str = "";
str = this.ControllerContext.RenderViewToString("ProfitDetails", data);
return str;
}
using BQoolCommon.Helpers.Format;
using Newtonsoft.Json;
using OrdersManager.Models.ViewModel.Report;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace OrdersManager.Web.Infrastructure
{
public static class HelperExtensions
{
public static string RenderViewToString(this ControllerContext context, string viewName, string data)
{
if (string.IsOrEmpty(viewName))
viewName = context.RouteData.GetRequiredString("action");
context.Controller.ViewData.Model = JsonConvert.DeserializeObject<ProfitDetailsmodel>(StringTools.Base64Decode(StringTools.Base64Decode(data)));
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(context, viewName);
var viewContext = new ViewContext(context,
viewResult.View,
context.Controller.ViewData,
context.Controller.TempData,
sw);
try
{
viewResult.View.Render(viewContext, sw);
}
catch (Exception ex)
{
throw;
}
return sw.GetStringBuilder().ToString();
}
}
}
}
https://www.nuget.org/packages/Select.HtmlToPdf/
一篇文章我們介紹了一個(gè)html/xml解析器——htmlparser,這篇文章我們介紹另外一個(gè)解析模塊htmlparser2,后者是對前者的重構(gòu),同時(shí)對前者的API做了部分兼容。
安裝
const { Parser } = require('htmlparser2');
const parser = new Parser(handler, options);
parser.parseComplete('html/xml內(nèi)容');
寫法
const { Parser } = require('htmlparser2');
const parser = new Parser(handler, options);
parser.parseComplete('html/xml內(nèi)容');
htmlparser2提供了一個(gè)解析器——Parser,初始化它至少需要一個(gè)handler,options是可選的。
handler是一個(gè)對象,在這個(gè)對象上可以設(shè)置很多的鉤子函數(shù),Parser解析時(shí)會(huì)在每個(gè)階段運(yùn)行對應(yīng)的鉤子函數(shù)。
以下是可以設(shè)置的所有的鉤子函數(shù),
htmlparser模塊是通過正則表達(dá)式來解析html內(nèi)容的,而htmlparser2則不同,它會(huì)按順序讀取html的每個(gè)字符,并且推測后面字符是標(biāo)簽名、屬性還是其他的類型,所以htmlparser2在解析完每一個(gè)標(biāo)簽后都會(huì)運(yùn)行相應(yīng)的鉤子函數(shù)。
先來看一下例子,
圖1
圖1中設(shè)置了所有的鉤子函數(shù)以便來說明每個(gè)鉤子函數(shù)的作用,運(yùn)行一下,
圖2
對照圖1和圖2就能看出來每個(gè)鉤子函數(shù)的運(yùn)行時(shí)機(jī),這其中有以下幾個(gè)鉤子函數(shù)需要注意一下。
除了自定義handler以外,htmlparser2還提供了幾個(gè)handler,比如DomHandler,用法如下:
圖3
運(yùn)行一下,我們看看結(jié)果,
圖4
如果4所示,DomHandler處理的結(jié)果是以數(shù)組的形式輸出的,在每個(gè)單元數(shù)據(jù)中還可以拿到上一個(gè)、下一個(gè)以及父節(jié)點(diǎn)的數(shù)據(jù)。
htmlparser2還可以通過操作流Stream解析內(nèi)容,寫法如下:
圖5
這篇文章和上一篇是姊妹篇,都是介紹解析html/xml內(nèi)容的模塊,通過對比,我們發(fā)現(xiàn)htmlparser2模塊功能更強(qiáng)大一些,也更靈活一些,同時(shí)也兼容htmlparser模塊的一些接口。雖然兩者功能類似,但是這給了我們更多的選擇性。
喜歡我的文章就關(guān)注我吧,有問題可以發(fā)表評論,我們一起學(xué)習(xí),共同成長!
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。