如何在抽象語法樹中修改節點
我最近偶然發現的最強大的概念之一是抽象語法樹或AST的想法。如果您曾經學習過煉金術,您可能會記得,煉金術士的整個動機是通過科學或神秘的方法發現某種方法將非金牌轉化為黃金。
AST是這樣的。使用ASTS,我們可以將Markdown轉換為HTML,JSX變成JavaScript,等等。
為什麼AST有用?
在職業生涯的早期,我嘗試使用發現和重複方法來更改文件。這最終變得相當複雜,因此我嘗試使用正則表達式。我最終放棄了這個想法,因為它是如此脆弱。該應用程序一直都破裂了,因為有人會以我沒想到的方式輸入文本,並且會打破我的正則表達式,從而導致整個應用程序掉落。
之所以如此困難,是因為HTML靈活。這使得使用正則表達式很難解析。基於字符串的替代品很容易打破,因為它可能錯過了一場比賽,匹配過多或做一些怪異的事情,從而導致無效的標記使頁面看起來很笨拙。
另一方面,ASTS將HTML變成更加結構化的東西,這使得潛入文本節點並僅在該文本上進行替換變得更加簡單,或者與元素混亂而無需完全處理文本。
這使AST轉換更安全,比純粹基於字符串的解決方案更安全,並且容易出錯。
AST是用什麼?
首先,讓我們使用幾行降價來查看最小文檔。將其保存為稱為home.md的文件,我們將保存在我們網站的內容文件夾中。
# 你好世界! ! [cardigan corgi](<https:>)一個可愛的corgi! 還有一些文字在這裡。</https:>
假設我們知道Markdown,我們可以推斷出,當這種降價解析時,最終將是一個H1標籤,上面寫著“ Hello World!”然後,文本的兩個段落:第一個包含corgi的圖像和一些旨在描述它的文本,第二個文本說:“還有一些文字在這裡。”
但是,如何從降價轉換為HTML呢?
那就是AST進來的地方!
由於它支持多種語言,因此我們將使用Unist語法樹規範,更具體地說是項目統一。
安裝依賴項
首先,我們需要安裝所需的依賴項將降價分析為AST並將其轉換為HTML。為此,我們需要確保將文件夾初始化為包裝。在您的終端中運行以下命令:
#確保您在根文件夾中(``content''中的位置) #將此文件夾初始化為NPM軟件包 npm init #安裝依賴項 NPM安裝統一的備註備註html
如果我們假設我們的降價存儲在home.md中,則可以通過以下代碼獲得AST:
const fs = require('fs'); const Unified = require('unified'); const markdown = require('eRext-parse'); const html = require('dempress-html'); const contents = unified() 。 .use(html) 。 .tostring(); console.log(內容);
該代碼利用了Node的內置FS模塊,這使我們能夠訪問和操縱文件系統。有關其工作原理的更多信息,請查看官方文檔。
如果我們將其保存為src/index.js並使用節點從命令行執行此腳本,我們將在終端中看到以下內容:
$ node src/index.js <h1 id="你好世界">你好世界! </h1> <p> <img src="%E2%80%9C" alt="如何在抽象語法樹中修改節點" >” alt =“ cardigan corgi”> </p><p>還有更多文字。 </p>
我們告訴Unified使用Relecor-Parse將Markdown文件轉換為AST,然後使用Relever-HTML將Markdown AST變成HTML,或者更具體地說,它將其變成稱為VFILE的東西。使用ToString()方法將AST變成可以在瀏覽器中顯示的實際HTML字符串!
多虧了開源社區的辛勤工作,言論為將降級變成HTML所做的所有辛勤工作。 (請參閱差異)
接下來,讓我們看一下這是如何工作的。
AST長什麼樣?
要查看實際的AST,讓我們編寫一個小插件來記錄它:
const fs = require('fs'); const Unified = require('unified'); const markdown = require('eRext-parse'); const html = require('dempress-html'); const contents = unified() 。 。 .use(html) 。 .tostring();
運行腳本的輸出現在將為:
{ “ type”:“ root”, “孩子們”: [ { “類型”:“標題”, “深度”:1, “孩子們”: [ { “ type”:“ text”, “價值”:“你好世界!”, “位置”: {} } ],, “位置”: {} },, { “ type”:“段落”, “孩子們”: [ { “類型”:“圖像”, “標題”:null, “ url”:“ <https:>”, “ Alt”:“ Cardigan Corgi”, “位置”: {} },, { “ type”:“ text”, “價值”:“可愛的corgi!”, “位置”: {} } ],, “位置”: {} },, { “ type”:“段落”, “孩子們”: [ { “ type”:“ text”, “ value”:“還有更多文本。”, “位置”: {} } ],, “位置”: {} } ],, “位置”: {} }</https:>
請注意,位置值已被截斷以節省空間。它們包含有關文檔中節點位置的信息。出於本教程的目的,我們不會使用此信息。 (請參閱差異)
這是有點不知所措,但是如果我們放大,我們可以看到降價的每個部分都變成了一種節點,其中包含文本節點。
例如,標題變為:
{ “類型”:“標題”, “深度”:1, “孩子們”: [ { “ type”:“ text”, “價值”:“你好世界!”, “位置”: {} } ],, “位置”: {} }
這就是這意味著:
- 這些類型告訴我們我們正在處理哪種節點。
- 每個節點類型都有描述節點的其他屬性。標題上的深度屬性告訴我們它的標題是多少 - 深度為1是一個
標籤,2表示
等等。
- 孩子們的數組告訴我們這個節點裡面有什麼。在標題和段落中,只有文字,但是我們也可以在這裡看到內聯元素,例如。
這是ASTS的力量:我們現在將Markdown文檔描述為計算機可以理解的對象。如果我們想將其打印回Markdown,Markdown編譯器將知道一個深度為1的“標題”節點以#開始,並且帶有“ Hello”的子文本節點意味著最終行應該是#Hello。
AST轉換如何工作
通常使用訪問者模式進行轉換AST。知道這是如何生產力的來源的來源和出現並不重要的,但是如果您很好奇,Soham Kamani對人類的JavaScript設計模式有一個很好的例子,可以幫助解釋其工作原理。重要的是要知道的是,AST工作中的大多數資源都會談論“訪問節點”,該節點大致轉化為“找到AST的一部分,以便我們可以用它來做些事情”。這種工作實踐的方式是,我們編寫了一個將應用於與我們的標準匹配的AST節點的函數。
關於其工作原理的一些重要說明:
- AST可能是巨大的,因此出於績效原因,我們將直接突變節點。這與我通常如何處理事物的方式背道而馳 - 作為一般規則,我不喜歡變異全球狀態 - 但在這種情況下這是有意義的。
- 訪客遞歸工作。這意味著,如果我們處理一個節點並創建相同類型的新節點,則訪問者也將在新創建的節點上運行,除非我們明確告訴訪客不這樣做。
- 在本教程中,我們不會太深入,但是這兩個想法將幫助我們了解我們開始弄亂代碼時發生的事情。
如何修改AST的HTML輸出?
但是,如果我們想更改降價的輸出怎麼辦?假設我們的目標是用圖形元素包裝圖像標籤並提供標題,這樣:
<figud> <img src="%E2%80%9C" alt="如何在抽象語法樹中修改節點" >” alt =“ Cardigan Corgi” /> <figcaption>可愛的corgi! </figcaption> </figud>
為此,我們需要轉換HTML AST,而不是降價AST,因為Markdown沒有一種創建數字或Figcaption元素的方法。幸運的是,由於Unified與多個解析器可互操作,因此我們可以在不編寫一堆自定義代碼的情況下做到這一點。
將降價AST轉換為HTML AST
要將Markdown AST轉換為HTML AST,請添加Reform-Hearize,然後切換到將AST轉回HTML的重新構造。
NPM安裝插圖讀物重新構造
在SRC/index.js中進行以下更改以切換到rehype:
const fs = require('fs'); const Unified = require('unified'); const markdown = require('eRext-parse'); const備註2 heaute = require('eREAND-HERAIMET'); const html = require('rehype-stringify'); const contents = unified() 。 。 。 .use(html) 。 .tostring(); console.log(內容);
請注意,HTML變量從RelectH-HTML更改為重新構造 - 兩者都將AST轉換為可以將其串起為HTML的格式
如果我們運行腳本,我們可以在AST中看到圖像元素如下:
{ “類型”:“元素”, “ tagname”:“ img”, “特性”: { “ src”:“ https://images.dog.ceo/breeds/corgi-cardigan/n02113186_1030.jpg”, “ Alt”:“ Cardigan Corgi” },, “孩子們”: [], “位置”: {} }
這是圖像的HTML表示形式的AST,因此我們可以開始更改它以使用圖形元素。 (請參閱差異)
為統一寫一個插件
為了用圖形元素包裝我們的IMG元素,我們需要編寫一個插件。在Unified中,使用use()方法添加插件,該方法接受插件作為第一個參數,並將任何選項作為第二個參數:
.use(插件,選項)
插件代碼是接收選項的函數(在統一行話中稱為“附件”)。這些選項用於創建一個新功能(稱為“變壓器”),該功能接收AST並確實可以對其進行轉換。有關插件的更多詳細信息,請查看統一文檔中的插件概述。
它返回的功能將接收整個AST作為參數,並且不會返回任何內容。 (請記住,AST在全球範圍內被突變。)創建一個與index.js同一文件夾中的稱為img-to-gigure.js的新文件,然後將以下內容放置在內部:
Module.exports = options => tree => { console.log(tree); };
要使用此功能,我們需要將其添加到src/index.js:
const fs = require('fs'); const Unified = require('unified'); const markdown = require('eRext-parse'); const備註2 heaute = require('eREAND-HERAIMET'); const html = require('rehype-stringify'); const imgtofigure = require('./ img-to-to-figure'); const contents = unified() 。 。 .use(imgtofigure) 。 .tostring(); console.log(內容);
如果我們運行腳本,我們將看到整棵樹在控制台中登錄:
{ 類型:“根”, 孩子們: [ { 類型:“元素”, tagname:'p', 特性: {}, 孩子:[陣列], 位置:[對象] },, {type:'text',value:'\\ n'}, { 類型:“元素”, tagname:'p', 特性: {}, 孩子:[陣列], 位置:[對象] } ],, 位置: { 開始:{行:1,列:1,偏移:0}, 結束:{行:4,列:1,偏移:129} } }
(請參閱差異)
將訪問者添加到插件
接下來,我們需要添加一個訪客。這將使我們真正了解該代碼。統一利用了許多帶有Unist-util-*前綴的實用程序軟件包,使我們可以在不編寫自定義代碼的情況下使用AST進行常見的事情。
我們可以使用Unist-Util-訪問來修改節點。這給了我們一個參觀三個論點的訪問助手:
- 我們正在與之合作的整個AST
- 一個謂詞功能,以識別我們要訪問的節點
- 對我們想要進行的任何更改的函數
要安裝,請在命令行中運行以下內容:
NPM安裝Unist-Util-訪問
讓我們通過添加以下代碼在插件中實現訪問者:
cont訪問= require('Unist-util-visit'); Module.exports = options => tree => { 訪問( 樹, //僅訪問包含IMG元素的P標籤 節點=> node.tagname ==='p'&& node.children.some(n => n.tagname ===='img'), 節點=> { console.log(node); } ); };
當我們運行此操作時,我們可以看到只有一個段落節點記錄了:
{ 類型:“元素”, tagname:'p', 特性: {}, 孩子們: [ { 類型:“元素”, tagname:'img', 屬性:[對象], 孩子們: [], 位置:[對象] },, {type:'text',值:'可愛的corgi! ',位置:[object]} ],, 位置: { 開始:{行:3,列:1,偏移:16}, 結束:{行:3,列:102,偏移:117} } }
完美的!我們只會獲得具有要修改的圖像的段落節點。現在我們可以開始改變AST!
(請參閱差異)
將圖像包裹在圖元素中
現在我們已經擁有圖像屬性,我們可以開始更改AST。請記住,因為AST確實可以很大,所以我們將它們變為適當的地方,以避免創建大量副本並可能減慢腳本的速度。
我們首先將節點的標籤名更改為數字而不是段落。其餘的細節現在可以保持不變。
在src/img-to-gigure.js中進行以下更改:
cont訪問= require('Unist-util-visit'); Module.exports = options => tree => { 訪問( 樹, //僅訪問包含IMG元素的P標籤 節點=> node.tagname ==='p'&& node.children.some(n => n.tagname ===='img'), 節點=> { node.tagname ='figie'; } ); };
如果我們再次運行腳本並查看輸出,我們可以看到我們越來越近了!
<h1 id="你好世界">你好世界! </h1> <figug> <img src="%E2%80%9C" alt="如何在抽象語法樹中修改節點" >” alt =“ cardigan corgi”> <p>還有更多文字。 </p></figug>
(請參閱差異)
將圖像旁邊的文字用作標題
為了避免需要編寫自定義語法,我們將使用帶有圖像的任何傳遞的文本作為圖像標題。
我們可以假設通常在Markdown中圖像沒有內聯文字,但是值得注意的是,這可能100%會導致意外字幕出現給人們寫Markdown。我們將在本教程中承擔這種風險。如果您打算將其投入生產中,請確保權衡權衡取捨,並選擇最適合您的情況。
要使用文本,我們將在父節點內尋找文本節點。如果我們找到一個,我們想抓住它的價值作為我們的標題。如果找不到字幕,我們根本不想轉換此節點,因此我們可以儘早返回。
對src/img-to-gigure.js進行以下更改以獲取標題:
cont訪問= require('Unist-util-visit'); Module.exports = options => tree => { 訪問( 樹, //僅訪問包含IMG元素的P標籤 節點=> node.tagname ==='p'&& node.children.some(n => n.tagname ===='img'), 節點=> { //找到文本節點 const textNode = node.children.find(n => n.type ===='text'); //如果沒有字幕,我們不需要轉換節點 如果(!textNode)返回; const標題= textnode.value.trim(); console.log({catchion}); node.tagname ='figie'; } ); };
運行腳本,我們可以看到記錄的標題:
{標題:'可愛的科吉! ' }
(請參閱差異)
在圖中添加小提琴元素
現在我們有字幕文本,我們可以添加一個圖形以顯示它。我們可以通過創建一個新的節點並刪除舊文本節點來做到這一點,但是由於我們正在將文本節點更改為元素並不那麼複雜。
不過,元素沒有文本,因此我們需要在Figcaption元素的孩子中添加一個新的文本節點來顯示字幕文本。
對src/img-to-gigure.js進行以下更改,以將標題添加到標記中:
cont訪問= require('Unist-util-visit'); Module.exports = options => tree => { 訪問( 樹, //僅訪問包含IMG元素的P標籤 節點=> node.tagname ==='p'&& node.children.some(n => n.tagname ===='img'), 節點=> { //找到文本節點 const textNode = node.children.find(n => n.type ===='text'); //如果沒有字幕,我們不需要轉換節點 如果(!textNode)返回; const標題= textnode.value.trim(); //將文本節點更改為包含文本節點的figcaption元素 textnode.type ='element'; textnode.tagname ='figcaption'; textnode.Children = [ { 類型:“文本”, 價值:標題 } ]; node.tagname ='figie'; } ); };
如果我們使用節點src/index.js再次運行腳本,我們會看到包裹在圖形元素中的轉換圖像,並用figcaption進行了描述!
<h1 id="你好世界">你好世界! </h1> <figug> <img src="%E2%80%9C" alt="如何在抽象語法樹中修改節點" >” <p>還有更多文字。 </p></figug>
(請參閱差異)
將轉換的內容保存到新文件
既然我們已經進行了大量轉換,我們想將這些調整保存到實際文件中,以便我們可以共享它們。
由於Markdown不包含完整的HTML文檔,因此我們將添加一個名為Rehype-Document的Rehype插件,以添加完整的文檔結構和標題標籤。
通過運行安裝:
NPM安裝rehype-Document
接下來,對src/index.js進行以下更改:
const fs = require('fs'); const Unified = require('unified'); const markdown = require('eRext-parse'); const備註2 heaute = require('eREAND-HERAIMET'); const doc = require('rehype-document'); const html = require('rehype-stringify'); const imgtofigure = require('./ img-to-to-figure'); const contents = unified() 。 。 .use(imgtofigure) .use(doc,{title:'轉換文檔!'}) .use(html) 。 .tostring(); const outputdir =`$ {process.cwd()}/public`; 如果(!fs.existsync(outputdir)){ fs.mkdirsync(outputdir); } fs.WriteFileSync(`$ {outputDir}/home.html`,contents);
再次運行腳本,我們將能夠在Root中看到一個名為public的新文件夾,在內部我們會看到home.html。在內部,我們的轉換文檔保存了!
<meta charset="“" utf-8> <title>轉換文檔! </title> <meta name="“" viewport content="“" width="設備寬度,初始尺度="> <h1 id="你好世界">你好世界! </h1> <figug> <img src="%E2%80%9C" alt="如何在抽象語法樹中修改節點" >” <p>還有更多文字。 </p> </figug>
以上是如何在抽象語法樹中修改節點的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

您是否曾經在項目上需要一個倒計時計時器?對於這樣的東西,可以自然訪問插件,但實際上更多

在元素個數不固定的情況下如何通過CSS選擇第一個指定類名的子元素在處理HTML結構時,常常會遇到元素個數不�...

關於Flex佈局中紫色斜線區域的疑問在使用Flex佈局時,你可能會遇到一些令人困惑的現象,比如在開發者工具(d...

格子呢是一塊圖案布,通常與蘇格蘭有關,尤其是他們時尚的蘇格蘭語。在Tartanify.com上,我們收集了5,000多個格子呢
