整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          把HTML轉(zhuǎn)成PDF的4個方案及實現(xiàn)方法

          本文中,我將展示如何使用 Node.js、Puppeteer、headless Chrome 和 Docker 從樣式復(fù)雜的 React 頁面生成 PDF 文檔。

          背景:幾個月前,一個客戶要求我們開發(fā)一個功能,用戶可以得到 PDF 格式的 React 頁面內(nèi)容。該頁面基本上是患者病例的報告和數(shù)據(jù)可視化結(jié)果,其中包含許多 SVG。另外還有一些特殊的請求來操縱布局,并對 HTML 元素進(jìn)行一些重新排列。因此與原始的 React 頁面相比,PDF 中應(yīng)該有不同的樣式和額外的內(nèi)容。

          由于這個任務(wù)比用簡單的 CSS 規(guī)則解決要復(fù)雜得多,所以我們先探討了可能的實現(xiàn)方法。我們找到了 3 個主要解決方案。這篇博文將指導(dǎo)你了解它們的可能性并最終實施。

          目錄:

          • 在客戶端還是服務(wù)器端生成?
          • 方案1:從 DOM 制作屏幕截圖
          • 方案2:僅使用 PDF 庫
          • 最終方案3:Node.js、Puppeteer 和 Headless Chrome
          • 樣式控制
          • 將文件發(fā)送到客戶端并保存
          • 在 Docker 中使用 Puppeteer
          • 方案3 +1:CSS打印規(guī)則
          • 總結(jié)

          在客戶端還是服務(wù)器端生成?

          在客戶端和服務(wù)器端都可以生成PDF文件。但是讓后端處理它可能更有意義,因為你并不想耗盡用戶瀏覽器可以提供的所有資源。

          即便如此,我仍然會展示這兩種方法的解決方案。

          方案1:從 DOM 制作屏幕截圖

          乍一看,這個解決方案似乎是最簡單的,事實證明的確是這樣,但它有其自身的局限性。如果你沒有特殊需求,例如在 PDF 中選擇文本或?qū)ξ谋具M(jìn)行搜索,那么這就是一種簡單易用的方法。

          此方法簡單明了:從頁面創(chuàng)建屏幕截圖,并把它放到 PDF 文件中。非常直截了當(dāng)。我們可以使用兩個包來實現(xiàn):

          • Html2canvas,根據(jù) DOM 生成截圖
          • jsPdf,一個生成PDF的庫

          開始編碼:

          npm install html2canvas jspdf

          就這樣!

          請注意 html2canvas 的 onclone方法。當(dāng)你在截圖之前需要操縱 DOM(例如隱藏打印按鈕)時,它是非常方便的。我看到過很多使用這個包的項目。但不幸的是,這不是我們想要的,因為我們需要在后端完成對 PDF 的創(chuàng)建工作。

          方案2:只使用 PDF 庫

          NPM上有幾個庫,如 jsPDF(如上所述)或PDFKit。他們的問題是,如果我想使用這些庫,我將不得不重新調(diào)整頁面結(jié)構(gòu)。這肯定會損害可維護(hù)性,因為我需要將所有后續(xù)更改應(yīng)用到 PDF 模板和 React 頁面中。

          請看下面的代碼。你需要親自手動創(chuàng)建 PDF 文檔。你需要遍歷 DOM 并找出每個元素并將其轉(zhuǎn)換為 PDF 格式,這是一項繁瑣的工作。必須找到一個更簡單的方法。

          這段代碼段來自 PDFKit 文檔。但是如果你的目標(biāo)是直接生成一個 PDF 文件,而不是對一個已經(jīng)存在的(并且不斷變化的)HTML 頁面進(jìn)行轉(zhuǎn)換,它還是很有用的。

          最終方案3:基于 Node.js 的 Puppeteer 和 Headless Chrome

          什么是 Puppeteer?其文檔中寫道:

          Puppeteer 是一個 Node 庫,它提供了一個高級 API 來控制 DevTools 協(xié)議上的 Chrome 或 Chromium。 Puppeteer 默認(rèn)以 headless 模式運(yùn)行 Chrome 或 Chromium,但其也可以被配置為完整的(non-headless)模式運(yùn)行。

          它本質(zhì)上是一個可以從 Node.js 運(yùn)行的瀏覽器。如果你讀過它的文檔,其中首先提到的就是你可以用 Puppeteer 來生成頁面的截圖和PDF。優(yōu)秀!這正是我們想要的。

          先用 npmi i puppeteer 安裝 Puppeteer,并實現(xiàn)我們的功能。

          這是一個簡單的功能,可導(dǎo)航到 URL 并生成站點的 PD F文件。

          首先,我們啟動瀏覽器(僅在 headless 模式下支持 PDF 生成),然后打開新頁面,設(shè)置視口,并導(dǎo)航到提供的URL。

          設(shè)置 waitUntil:'networkidle0' 選項意味著當(dāng)至少500毫秒沒有網(wǎng)絡(luò)連接時,Puppeteer 會認(rèn)為導(dǎo)航已完成。 (可以從 API docs 獲取更多信息。)

          之后,我們將 PDF 保存為變量,關(guān)閉瀏覽器并返回 PDF。

          注意:page.pdf 方法接收 options 對象,你可以使用 'path' 選項將文件保存到磁盤。如果未提供路徑,則 PDF 將不會被保存到磁盤,而是會得到緩沖區(qū)。(稍后我將討論如何處理它。)

          如果需要先登錄才能從受保護(hù)的頁面生成 PDF,首先你要導(dǎo)航到登錄頁面,檢查表單元素的 ID 或名稱,填寫它們,然后提交表單:

          要始終將登錄憑據(jù)保存在環(huán)境變量中,不要硬編碼!

          樣式控制

          Puppeteer 也有這種樣式操作的解決方案。你可以在生成 PDF 之前插入樣式標(biāo)記,Puppeteer 將生成具有已修改樣式的文件。

          await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })
          

          將文件發(fā)送到客戶端并保存

          好的,現(xiàn)在你已經(jīng)在后端生成了一個 PDF 文件。接下來做什么?

          如上所述,如果你不把文件保存到磁盤,將會得到一個緩沖區(qū)。你只需要把含有適當(dāng)內(nèi)容類型的緩沖區(qū)發(fā)送到前端即可。

          現(xiàn)在,你只需在瀏覽器向服務(wù)器發(fā)送請求即可得到生成的 PDF。

          一旦發(fā)送了請求,緩沖區(qū)的內(nèi)容就應(yīng)該開始下載了。最后一步是將緩沖區(qū)數(shù)據(jù)轉(zhuǎn)換為 PDF 文件。

          就這樣!如果單擊“保存”按鈕,那么瀏覽器將會保存 PDF。

          在 Docker 中使用 Puppeteer

          我認(rèn)為這是實施中最棘手的部分 —— 所以讓我?guī)湍愎?jié)省幾個小時的百度時間。

          官方文檔指出*“在 Docker 中使用 headless Chrome 并使其運(yùn)行起來可能會非常棘手”*。官方文檔有疑難解答部分,你可以找到有關(guān)用 Docker 安裝 puppeteer 的所有必要信息。

          如果你在 Alpine 鏡像上安裝 Puppeteer,請確保在看到頁面的這一部分時再向下滾動一點。否則你可能會忽略一個事實:你無法運(yùn)行最新的 Puppeteer 版本,并且你還需要用一個標(biāo)記禁用 shm :

          const browser = await puppeteer.launch({
           headless: true,
           args: ['--disable-dev-shm-usage']
          });
          

          否則,Puppeteer 子進(jìn)程可能會在正常啟動之前耗盡內(nèi)存。

          方案 3 + 1:CSS 打印規(guī)則

          可能有人認(rèn)為從開發(fā)人員的角度來看,簡單地使用 CSS 打印規(guī)則很容易。沒有 NPM 模塊,只有純 CSS。但是在跨瀏覽器兼容性方面,它的表現(xiàn)如何呢?

          在選擇 CSS 打印規(guī)則時,你必須在每個瀏覽器中測試結(jié)果,以確保它提供的布局是相同的,并且它不是100%能做到這一點。

          例如,在給定元素后面插入一個 break-after 并不是一個多么高深的技術(shù),但是你可能會驚訝的發(fā)現(xiàn)要在 Firefox 中使用它需要使用變通方法。

          除非你是一位經(jīng)驗豐富的 CSS 大師,在創(chuàng)建可打印頁面方面有很多的經(jīng)驗,否則這可能會非常耗時。

          如果你可以使打印樣式表保持簡單,打印規(guī)則是很好用的。

          讓我們來看一個例子吧。

          @media print {
           .print-button {
           display: none;
           }
           
           .content div {
           break-after: always;
           }
          }
          

          上面的 CSS 隱藏了打印按鈕,并在每個 div 之后插入一個分頁符,其中包含content 類。有一篇很棒的文章總結(jié)了你可以用打印規(guī)則做什么,以及它們有什么問題,包括瀏覽器兼容性。

          考慮到所有因素,如果你想從不那么復(fù)雜的頁面生成 PDF,CSS打印規(guī)則非常有效。

          總結(jié)

          讓我們快速回顧前面介紹的方案,以便從 HTML 頁面生成 PDF 文件:

          • 從 DOM 產(chǎn)生截圖:當(dāng)你需要從頁面創(chuàng)建快照時(例如創(chuàng)建縮略圖)可能很有用,但是當(dāng)你需要處理大量數(shù)據(jù)時就會有些捉襟見肘。
          • 只用 PDF 庫:如果你打算從頭開始以編程方式創(chuàng)建 PDF 文件,這是一個完美的解決方案。否則,你需要同時維護(hù) HTML 和 PDF 模板,這絕對是一個禁忌。
          • Puppeteer:盡管在 Docker 上工作相對困難,但它為我們的實現(xiàn)提供了最好的結(jié)果,而且編寫代碼也是最簡單的。
          • CSS打印規(guī)則:如果你的用戶受過足夠的教育,知道如何把頁面內(nèi)容打印到文件,并且你的頁面相對簡單,那么它可能是最輕松的解決方案。正如你在我們的案例中所看到的,事實并非如此。

          雖然今天是愚人節(jié),但是以上所有內(nèi)容都是在真的!

          作者:瘋狂的技術(shù)宅

          鏈接:https://juejin.im/post/5ca1dc0251882543d569e075

          HTML標(biāo)簽相關(guān)的字符串格式化

          string nl2br ( string $string )

          nl2br() 就是將\n 替換成 <br> //javascript對\n才能夠執(zhí)行換行,對</br>是不能執(zhí)行換行

          htmlspecialchars() 把一些預(yù)定義的字符轉(zhuǎn)換為 HTML 實體。

          string htmlspecialchars(string,quotestyle,[character-set])

          轉(zhuǎn)換以下字符及對應(yīng)的實體

          & (和號) 成為 &
          " (雙引號) 成為 "
          ' (單引號) 成為 '
          < (小于) 成為 <
          > (大于) 成為 >

          第二個參數(shù): ENT_COMPAT 只轉(zhuǎn)換雙引號, 保留單引號, 為默認(rèn)值 compat: 兼容性

          ENT_QUOTES 同時轉(zhuǎn)換兩種引號 quotes: 引號

          ENT_NOQUOTES 不對引號進(jìn)行轉(zhuǎn)換

          <html>
          <body>
          <?php
          $str = "John & \" 'Adams'";
          echo htmlspecialchars($str, ENT_COMPAT);
          echo "<br />";
          echo htmlspecialchars($str, ENT_QUOTES);
          echo "<br />";
          echo htmlspecialchars($str, ENT_NOQUOTES);
          ?>
          </body>
          </html>

          輸出結(jié)果:John & " 'Adams'

          John & " 'Adams'

          John & " 'Adams'

          htmlentities() 可以將所有的非ASCII碼字符轉(zhuǎn)換為對應(yīng)的實體代碼;除字母、數(shù)字、\外, 漢字和鍵盤上其他字符都轉(zhuǎn)換

          <?php
          $str = "A 'quote' \" is <b>bold</b>" ;
          echo htmlentities ( $str ); // 輸出后源代碼: A 'quote' is <b>bold</b>
          echo htmlentities ( $str , ENT_QUOTES ); // 輸出后源代碼: A 'quote' is <b>bold</b>
          ?>

          返回的結(jié)果:A 'quote' "is <b>bold</b>

          A 'quote' "is <b>bold</b>

          注意: htmlspecialchars()和htmlentities作用直接輸出HTML腳本

          htmlspecialchars()和htmlentities()函數(shù)對于轉(zhuǎn)義字符"\"處理,不會轉(zhuǎn)義實體代碼,要么當(dāng)轉(zhuǎn)義字符對待,要么原樣輸出;

          PHP中htmlentities和htmlspecialchars的區(qū)別

          這兩個函數(shù)的功能都是轉(zhuǎn)換字符為HTML字符編碼, 特別是url和代碼字符串。防止字符標(biāo)記被瀏覽器執(zhí)行。

          使用中文時沒什么區(qū)別, 但htmlentities會格式化中文字符使得中文輸入是亂碼。

          htmlentities轉(zhuǎn)換所有的html標(biāo)記, htmlspecialchars只格式化& ' " < 和 > 這幾個特殊符號

          addslashes() 在指定的預(yù)定義字符前添加反斜杠。

          這些預(yù)定義字符是:單引號 (') 雙引號 (") 反斜杠 (\) NULL字符(\x00)

          提示:該函數(shù)可用于為存儲在數(shù)據(jù)庫中的字符串以及數(shù)據(jù)庫查詢語句準(zhǔn)備合適的字符串。

          注釋:默認(rèn)情況下,PHP 指令 magic_quotes_gpc 為 on,對所有的 GET、POST 和 COOKIE數(shù)據(jù)自動運(yùn)行 addslashes()。

          不要對已經(jīng)被magic_quotes_gpc轉(zhuǎn)義過的字符串使用 addslashes(),因為這樣會導(dǎo)致雙層轉(zhuǎn)義。

          遇到這種情況時可以使用函數(shù) get_magic_quotes_gpc() 進(jìn)行檢測。(如:$c=(!get_magic_quotes_gpc())?addslashes($c):$c;)

          在本例中,我們要向字符串中的預(yù)定義字符添加反斜杠:

          <?php
          $str = "Who's John Adams?";
          echo $str . " This is not safe in a database query.<br />";
          echo addslashes($str) . " This is safe in a database query.";
          ?>

          輸出:

          Who's John Adams? This is not safe in a database query.

          Who\'s John Adams? This is safe in a database query.

          <?php
          header("Content-type:text/html; charset=utf-8");
          $str = "wo are \x0a studying \x00 php";
          echo $str;
          echo "<br>";
          echo addslashes($str);
          ?>

          輸出:

          wo are studying php

          wo are studying >wo are studying \0 php< php



          stripslashes() 刪除反斜線("\")

          在提交的表單數(shù)據(jù)中 ' " \ 等字符前被自動加上一個\ ,這是配置文件php.ini中選項magic_quotes_gpc在起作用,

          默認(rèn)是打開的,如果不處理則將數(shù)據(jù)保存到數(shù)據(jù)庫時,有可能會被數(shù)據(jù)庫誤當(dāng)成控制符號而引起錯誤。

          通常htmlspecialchars()和stripslashes()函數(shù)復(fù)合的方式,聯(lián)合處理表單中的提交的數(shù)據(jù)htmlspecialchars(stripslashes())

          strip_tags()

          string strip_tags ( string $str [, string $allowable_tags ] )

          剝?nèi)?HTML、XML 以及 PHP 的標(biāo)簽。

          <?php
          echo strip_tags("Hello <b><i>world!</i></b>","<b>");
          ?>

          輸出結(jié)果:Hello world!

          實例:

          <?php
          $str = "<b>webserver;</b> & \ 'Linux' & Apache";
          echo "$str"; //直接輸出
          echo "<br/>";
          echo htmlspecialchars($str,ENT_COMPAT); //只轉(zhuǎn)換雙引號,為默認(rèn)參數(shù)
          echo "<br />";
          echo htmlspecialchars($str,ENT_NOQUOTES); //不對引號進(jìn)行轉(zhuǎn)換
          echo "<br />";
          echo htmlspecialchars($str,ENT_QUOTES); //同時轉(zhuǎn)換單引號和雙引號
          echo "<br />";
          echo htmlentities($str); //將所有的非ASCII碼字符轉(zhuǎn)換為對應(yīng)的實體代碼
          echo "<br />";
          echo addslashes($str); //將" ' \ 字符前添加反斜線
          echo "<br />";
          echo stripslashes($str); //刪除反斜線
          echo "<br />";
          echo strip_tags($str); //刪除<html>標(biāo)記
          ?>

          輸出結(jié)果:

          webserver; & \ 'Linux' & Apache

          markdown中寫下你的文章,并使用Python將它們轉(zhuǎn)換成HTML-作者Florian Dahlitz,于2020年5月18日(15分鐘)

          介紹

          幾個月前,我想開通自己的博客,而不是使用像Medium這樣的網(wǎng)站。這是一個非常基礎(chǔ)的博客,所有的文章都是HTML形式的。然而,有一天,我突然產(chǎn)生了自己編寫Markdown到HTML生成器的想法,最終這將允許我用markdown來編寫文章。此外,為它添加諸如估計閱讀時間之類的擴(kuò)展特性會更容易。長話短說,我實現(xiàn)了自己的markdown到HTML生成器,我真的很喜歡它!

          在本系列文章中,我想向您展示如何構(gòu)建自己的markdown到HTML生成器。該系列由三部分組成:

          • 第一部分(本文)介紹了整個管線的實現(xiàn)。

          • 第二部分通過一個模塊擴(kuò)展了實現(xiàn)的管線,該模塊用于計算給定文章的預(yù)計閱讀時間。

          • 第三部分演示如何使用管線生成自己的RSS摘要。

          這三部分中使用的代碼都可以在GitHub上找到。

          備注:我的文章中markdown到HTML生成器的想法基于Anthony Shaw文章中的實現(xiàn)。

          項目構(gòu)建

          為了遵循本文的內(nèi)容,您需要安裝幾個軟件包。我們把它們放進(jìn)requirements.txt文件。

          Markdown是一個包,它允許您將markdown代碼轉(zhuǎn)換為HTML。之后我們用Flask產(chǎn)生靜態(tài)文件。

          但在安裝之前,請創(chuàng)建一個虛擬環(huán)境,以避免Python安裝出現(xiàn)問題:

          激活后,您可以使用pip安裝requirements.txt中的依賴。

          很好!讓我們創(chuàng)建幾個目錄來更好地組織代碼。首先,我們創(chuàng)建一個app目錄。此目錄包含我們提供博客服務(wù)的Flask應(yīng)用程序。所有后續(xù)目錄都將在app目錄內(nèi)創(chuàng)建。其次,我們創(chuàng)建一個名為posts的目錄。此目錄包含要轉(zhuǎn)換為HTML文件的markdown文件。接下來,我們創(chuàng)建一個templates目錄,其中包含稍后使用Flask展示的模板。在templates目錄中,我們再創(chuàng)建兩個目錄:

          posts包含生成的HTML文件,這些文件與應(yīng)用程序根目錄中posts目錄中的文件相對應(yīng)。

          shared包含在多個文件中使用的HTML文件。

          此外,我們還創(chuàng)建了一個名為services的目錄。該目錄將包含我們在Flask應(yīng)用程序中使用的模塊,或者為它生成某些東西。最后,創(chuàng)建一個名為static的目錄帶有兩個子目錄images和css。自定義CSS文件和文章的縮略圖將存儲在此處。

          您的最終項目結(jié)構(gòu)應(yīng)如下所示:

          令人驚嘆!我們完成了一般的項目設(shè)置。我們來看看Flask的設(shè)置。

          Flask設(shè)置

          路由

          我們在上一節(jié)安裝了Flask。但是,我們?nèi)匀恍枰粋€Python文件來定義用戶可以訪問的端點。在app目錄中創(chuàng)建main.py并將以下內(nèi)容復(fù)制到其中。

          該文件定義了一個具有兩個端點的基礎(chǔ)版Flask應(yīng)用程序。用戶可以使用/route訪問第一個端點返回索引頁,其中列出了所有文章。

          第二個端點是更通用的端點。它接受post的名稱并返回相應(yīng)的HTML文件。

          接下來,我們通過向app目錄中添加一個__init__.py,將其轉(zhuǎn)換為一個Python包。此文件為空。如果您使用UNIX計算機(jī),則可以從項目的根目錄運(yùn)行以下命令:

          模板

          現(xiàn)在,我們創(chuàng)建兩個模板文件index.html以及l(fā)ayout.html,都存儲在templates/shared目錄中。這個layout.html模板將用于單個博客條目,而index.html模板用于生成索引頁,從中我們可以訪問每個帖子。讓我們從index.html模板開始。

          它是一個基本的HTML文件,其中有兩個元標(biāo)記、一個標(biāo)題和兩個樣式表。注意,我們使用一個遠(yuǎn)程樣式表和一個本地樣式表。遠(yuǎn)程樣式表用于啟用Bootstrap[1]類。第二個是自定義樣式。我們晚點再定義它們。

          HTML文件的主體包含一個容器,其中包含Jinja2[2]邏輯,用于為每個post生成Bootstrap卡片[3]。您是否注意到我們不直接基于變量名訪問這些值,而是需要將[0]添加到其中?這是因為文章中解析的元數(shù)據(jù)是列表。實際上,每個元數(shù)據(jù)元素都是由單一元素組成的列表。我們稍后再看。到目前為止,還不錯。讓我們看看layout.html模板。

          如你所見,它比前一個短一點,簡單一點。文件頭與index.html文件很相似,除了我們有不同的標(biāo)題。當(dāng)然,我們可以共用一個模板,但是我不想讓事情變得更復(fù)雜。

          body中的容器僅定義一個h1標(biāo)記。然后,我們提供給模板的內(nèi)容被插入并呈現(xiàn)。

          樣式

          正如上一節(jié)所承諾的,我們將查看自定義CSS文件style.css. 我們在static/css中找到該文件,并根據(jù)需要自定義頁面。下面是我們將用于基礎(chǔ)示例的內(nèi)容:

          我不喜歡Bootstrap中blockquotes的默認(rèn)外觀,所以我們在左側(cè)添加了一點間距和邊框。此外,blockquote段落底部的頁邊空白將被刪除。不刪除的話看起來很不自然。

          最后但并非最不重要的是,左右兩邊的填充被刪除。由于兩邊都有額外的填充,縮略圖沒有正確對齊,所以在這里刪除它們。

          到現(xiàn)在為止,一直都還不錯。我們完成了關(guān)于Flask的所有工作。讓我們開始寫一些帖子吧!

          寫文章

          正如標(biāo)題所承諾的,你可以用markdown寫文章-是的!在寫文章的時候,除了保證正確的markdown格式外,沒有其他需要注意的事情。

          在完成本文之后,我們需要在文章中添加一些元數(shù)據(jù)。此元數(shù)據(jù)添加在文章之前,并由三個破折號分隔開來---。下面是一個示例文章(post1.md)的摘錄:

          注意:您可以在GitHub庫的app/posts/post1.md中找到完整的示例文章。

          在我們的例子中,元數(shù)據(jù)由標(biāo)題、副標(biāo)題、類別、發(fā)布日期和index.html中卡片對應(yīng)縮略圖的路徑組成.

          我們在HTML文件中使用了元數(shù)據(jù),你還記得嗎?元數(shù)據(jù)規(guī)范必須是有效的YAML。示例形式是鍵后面跟著一個冒號和值。最后,冒號后面的值是列表中的第一個也是唯一的元素。這就是我們通過模板中的索引運(yùn)算符訪問這些值的原因。

          假設(shè)我們寫完了文章。在我們可以開始轉(zhuǎn)換之前,還有一件事要做:我們需要為我們的帖子生成縮略圖!為了讓事情更簡單,只需從你的電腦或網(wǎng)絡(luò)上隨機(jī)選取一張圖片,命名它為placeholder.jpg并把它放到static/images目錄中。GitHub存儲庫中兩篇文章的元數(shù)據(jù)包含一個代表圖像的鍵值對,值是placeholder.jpg。

          注意:在GitHub存儲庫中,您可以找到我提到的兩篇示例文章。

          markdown到HTML轉(zhuǎn)換器

          最后,我們可以開始實現(xiàn)markdown to HTML轉(zhuǎn)換器。因此,我們使用我們在開始時安裝的第三方包Markdown。我們先創(chuàng)建一個新模塊,轉(zhuǎn)換服務(wù)將在其中運(yùn)行。因此,我們在service目錄中創(chuàng)建了converter.py。我們一步一步看完整個腳本。您可以在GitHub存儲庫中一次查看整個腳本。

          首先,我們導(dǎo)入所需的所有內(nèi)容并創(chuàng)建幾個常量:

          ROOT指向我們項目的根。因此,它是包含app的目錄。

          POSTS_DIR是以markdown編寫的文章的路徑。

          TEMPLATE_DIR分別指向?qū)?yīng)的templates目錄。

          BLOG_TEMPLATE_文件存儲layout.html的路徑。

          INDEX_TEMPLATE_FILE是index.html

          BASE_URL是我們項目的默認(rèn)地址,例如。https://florian-dahlitz.de.默認(rèn)值(如果不是通過環(huán)境變量DOMAIN提供的話)是http://0.0.0.0:5000。

          接下來,我們創(chuàng)建一個名為generate_entries的新函數(shù)。這是我們定義的唯一一個轉(zhuǎn)換文章的函數(shù)。

          在函數(shù)中,我們首先獲取POSTS_DIR目錄中所有markdown文件的路徑。pathlib的awesome glob函數(shù)幫助我們實現(xiàn)它。

          此外,我們定義了Markdown包需要使用的擴(kuò)展。默認(rèn)情況下,本文中使用的所有擴(kuò)展都隨它的安裝一起提供。

          注意:您可以在文檔[4]中找到有關(guān)擴(kuò)展的更多信息。

          此外,我們實例化了一個新的文件加載程序,并創(chuàng)建了一個在轉(zhuǎn)換項目時使用的環(huán)境。隨后,將創(chuàng)建一個名為all_posts的空列表。此列表將包含我們處理后的所有帖子。現(xiàn)在,我們進(jìn)入for循環(huán)并遍歷POSTS_DIR中找到的所有文章。

          我們啟動for循環(huán),并打印當(dāng)前正在處理的post的路徑。如果有什么東西出問題了,這尤其有用。然后我們就知道,哪個文章的轉(zhuǎn)換失敗了。

          接下來,我們在默認(rèn)url之后增加一部分。假設(shè)我們有一篇標(biāo)題為“面向初學(xué)者的Python”的文章。我們將文章存儲在一個名為python-for-beginners.md,的文件中,因此生成的url將是http://0.0.0.0:5000/posts/python-for-beginners。

          變量url_html存儲的字符串與url相同,只是我們在末尾添加了.html。我們使用此變量定義另一個稱為target_file.的變量。變量指向存儲相應(yīng)HTML文件的位置。

          最后,我們定義了一個變量md,它表示markdown.Markdown的實例,用于將markdown代碼轉(zhuǎn)換為HTML。您可能會問自己,為什么我們沒有在for循環(huán)之前實例化這個實例,而是在內(nèi)部實例化。當(dāng)然,對于我們這里的小例子來說,這沒有什么區(qū)別(只是執(zhí)行時間稍微短一點)。但是,如果使用諸如腳注之類的擴(kuò)展來使用腳注,則需要為每個帖子實例化一個新實例,因為腳注添加后就不會從此實例中刪除。因此,如果您的第一篇文章使用了一些腳注,那么即使您沒有明確定義它們,所有其他文章也將具有相同的腳注。

          讓我們轉(zhuǎn)到for循環(huán)中的第一個with代碼塊。

          實際上,with代碼塊打開當(dāng)前post并將其內(nèi)容讀入變量content。之后調(diào)用_md.convert將以markdown方式寫入的內(nèi)容轉(zhuǎn)換為HTML。隨后,env環(huán)境根據(jù)提供的模板BLOG_TEMPLATE_FILE(即layout.html如果你還記得的話)渲染生成的HTML。

          第二個with 代碼塊用于將第一個with 代碼塊中創(chuàng)建的文檔寫入目標(biāo)文件。

          以下三行代碼從元數(shù)據(jù)中獲取發(fā)布日期(被發(fā)布的日期),將其轉(zhuǎn)換為正確的格式(RFC 2822),并將其分配回文章的元數(shù)據(jù)。此外,生成的post_dict被添加到all_posts列表中。

          我們現(xiàn)在出了for循環(huán),因此,我們遍歷了posts目錄中找到的所有posts并對其進(jìn)行了處理。讓我們看看generate_entries函數(shù)中剩下的三行代碼。

          我們按日期倒序?qū)ξ恼逻M(jìn)行排序,所以首先顯示最新的文章。隨后,我們將文章寫到模板目錄一個新創(chuàng)建的index.html文件中。別把index.html錯認(rèn)為templates/shared目錄中的那個。templates/shared目錄中的是模板,這個是我們要使用Flask服務(wù)的生成的。

          最后我們在函數(shù)generate_entries之后添加以下if語句。

          這意味著如果我們通過命令行執(zhí)行文件,它將調(diào)用generate_entries函數(shù)。

          太棒了,我們完成了converter.py腳本!讓我們從項目的根目錄運(yùn)行以下命令來嘗試:

          您應(yīng)該看到一些正在轉(zhuǎn)換的文件的路徑。假設(shè)您編寫了兩篇文章或使用了GitHub存儲庫中的兩篇文章,那么您應(yīng)該在templates目錄中找到三個新創(chuàng)建的文件。首先是index.html,它直接位于templates目錄中,其次是templates/posts目錄中的兩個HTML文件,它們對應(yīng)于markdown文件。

          最后啟動Flask應(yīng)用程序并轉(zhuǎn)到http://0.0.0.0:5000。

          總結(jié)

          太棒了,你完成了這個系列的第一部分!在本文中,您已經(jīng)學(xué)習(xí)了如何利用Markdown包創(chuàng)建自己的Markdown to HTML生成器。您實現(xiàn)了整個管線,它是高度可擴(kuò)展的,您將在接下來的文章中看到這一點。

          希望你喜歡這篇文章。一定要和你的朋友和同事分享。如果你還沒有,考慮在Twitter上關(guān)注我@DahlitzF或者訂閱我的通知,這樣你就不會錯過任何即將發(fā)表的文章。保持好奇心,不斷編碼!

          參考文獻(xiàn)

          Bootstrap (http://getbootstrap.com/)

          Primer on Jinja Templating (https://realpython.com/primer-on-jinja-templating/)

          Bootstrap Card (https://getbootstrap.com/docs/4.4/components/card/)

          Python-Markdown Extensions (https://python-markdown.github.io/extensions/)

          Tweet

          英文原文:https://florian-dahlitz.de/blog/build-a-markdown-to-html-conversion-pipeline-using-python
          譯者:阿布銩

          主站蜘蛛池模板: 亚洲日韩中文字幕无码一区| 国产一区二区三区四| 久久综合亚洲色一区二区三区 | 无码国产亚洲日韩国精品视频一区二区三区 | 日韩在线不卡免费视频一区| 国产成人一区二区三区精品久久| 清纯唯美经典一区二区| 日韩成人无码一区二区三区| 国产美女视频一区| 国产精品香蕉在线一区| 精品国产香蕉伊思人在线在线亚洲一区二区 | 国产伦理一区二区| 国产在线观看一区二区三区| 亚洲区精品久久一区二区三区| 中文字幕精品一区二区三区视频| 风间由美在线亚洲一区| 亚洲一区二区三区免费观看 | 亚洲AV无码一区二区乱孑伦AS| 国产精品成人一区二区三区| 精品国产伦一区二区三区在线观看| 三上悠亚一区二区观看| 无码人妻一区二区三区精品视频 | 欧美av色香蕉一区二区蜜桃小说| 成人精品一区二区三区不卡免费看| 精品一区二区AV天堂| 国模极品一区二区三区| 国产激情一区二区三区小说| 国产日本一区二区三区| 亚洲色偷偷偷网站色偷一区| 无码人妻精品一区二区三区在线| a级午夜毛片免费一区二区| 国产天堂一区二区综合| 一区二区三区四区国产| 一区二区国产精品| 午夜无码视频一区二区三区| 精品视频一区在线观看| 精品视频一区二区观看| 亚洲性日韩精品国产一区二区| 国产人妖视频一区在线观看| 韩国福利视频一区二区| 国产精品美女一区二区|