富文本--格式化數據並保留指定樣式實現[原創]

富文本實現格式化,格式化的時候保留圖文、文字換行、樣式。html

開始

有一個需求,粘貼的時候格式化富文本內容,可是保留圖文、文字換行、樣式。node

一開始我作的時候是所有格式化,也就是粘貼的時候拿到html(怎麼拿到百度下就有了,通常你們也都是對別人的富文本進行改造。),而後轉DOM,而後getInnerText.大功告成。這時候產品說,保留圖文和換行哦。nginx

因而修改下。web

保留圖文和換行

這個比較簡單,看下就好了。跨域

  • 流程:獲取img並轉存 -> 替換img標籤 -> 獲取到純文本 -> 添加換行 -> 恢復圖片
  • 保存圖片的話先把圖片下載下來,而後拿到blob再上傳到服務器。

不作解析。 (核心代碼就這些) bash

雖然比較簡單,可是也有幾個地方須要注意的是服務器

  • 圖片複製粘貼進來的直接下載會跨域,也就別想繞過去了,弄個代理吧 (1)
  • world複製竟來的別想了拿到圖片,純web作不到,由於拿到的是純本地路徑,能夠提醒單圖複製粘貼
  • 複製進來圖片最好先檢驗是否圖片存在,檢驗能夠新建一個img,而後看是否走入onload
  • 微信公衆號的比較特殊,若是用nginx(大神忽略), 返回圖片是一個微信警告,能夠用nodejs作代理

Koa代理代碼

main.js微信

const error_img_url = 'https://hongqiaojiaoyu.oss-cn-shenzhen.aliyuncs.com/huazhang/imgs/error.png'
const Koa = require('koa');
const app = new Koa();
const get_url_buffer = require('./js/get_url_buffer')
const port = 3024

// 響應
app.use(async (ctx, next) => {
    const path = ctx.request.path
    console.log(path)
    if(path.match(/^\/_proxy\/.*/)){
        var body = await new Promise(function(resolve, reject) {
            get_url_buffer(
                (path.match(/(?:(?<=\/_proxy\/)).*/) || [error_img_url])[0],
                (body) => {
                    resolve(body)
                },
                (uri) => {
                    console.log(uri)
                }
            )
        })
        ctx.set("Access-Control-Allow-Origin", "*")
        ctx.status = 200
        ctx.type = 'jpg'
        ctx.length = Buffer.byteLength(body)
        ctx.body = body
    }
});

app.listen(port);
console.log(`啓動完成 端口${port}`)
複製代碼

get_url_buffer.jsapp

var http = require('http')
var https = require('https')
const url = require('url')

/**
 * 獲取圖片buffer
 * @param {*} _url 要轉換的地址
 * @param {*} success 成功 success(buffer)
 * @param {*} fail 失敗 fail(uri)
 */
var get_url_buffer = (_url, success, fail) => {
    var uri = url.parse(_url)
    // console.log(uri)
    console.log(_url)
    var option = {
        ...uri,
        method: 'GET',
    }
    var handle_cb = (res) => {
        if(res.statusCode === 301){
            const url = res.headers['location']
            console.log('重定向', url)
            get_url_buffer(url, success, fail)
            return
        }
        var img = []
        var size = 0
        res.on("data", (chunk) => {
            img.push(chunk)
            size += chunk.length
        })
        res.on("end", () => {
            // console.log(size)
            const buffer = Buffer.concat(img, size)
            success(buffer)
        })
    }
    if(uri.protocol === 'https:'){
        return https.request(option, handle_cb).end()
    }
    if(uri.protocol === 'http:'){
        return http.request(option, handle_cb).end()
    }
    console.log('協議異常', uri)
    fail(uri)
}

module.exports = get_url_buffer
複製代碼
  • 下載的時候判斷是否是https的
  • 判斷是否是重定向了,重定向就拿到定向後的代碼繼續

到這裏,咱們實現了第一個功能dom

可是這時候需求添加了一條,以下

圖文、換行,指定樣式

這時候咱們很差下手,咱們須要作一個抉擇

  • 樣式怎麼來,換行怎麼計算。
  • 或者用哪一個插件
  • 或者要不換個富文本算了。

若是已經有必定修改這時候換富文本不太合適,只能去抄別人富文本的或者去找插件。

可是我遇到了以下兩個問題,因此仍是決定本身來

  • 看起來抄代碼很容易,其實代碼綁定性質很強的
  • 插件看了slate-paste-html-plugin,可是用起來一堆報錯,就放棄了

思路

1: 要去掉垃圾代碼,咱們就須要重組html格式
2: 去掉html可是樣式不會影響
3: 要計算出換行

這個不太好說,我畫了一張圖

左側那個是dom樹,右側是修改規則 (可是這個是理想狀態,實際獲取到的代碼不會這麼理想,稍後再說怎麼解決)

  • 經過左圖能夠看楚,div裏面會有span或者p
  • 固然還有其餘,這個p表明的是塊元素,span表明的是行內塊元素
  • 咱們把dom拍扁,這樣格式化出來就沒有垃圾
  • 排扁後咱們須要對元素進行換行操做,這時候須要塊元素和行內元素了,咱們須要計算這個,計算規則在下一行
  • 右側圖,紅色表明尋找路徑,綠色表明起點,紅色通過的區域若是沒有塊元素,那麼當前起點的dom就算是行內塊,反之亦然
  • 爲了性能,咱們須要先判斷他是否是塊元素,若是是塊元素,就不須要再找路徑了

有了思路,就能夠實現了

實現

咱們須要先遞歸獲取每個節點,若是是最後一個,那麼就計算。 (嗯,很完美
but, 剛剛不是說了,有些不是很符合理想,好比長這樣

<p>
    我是文字
    <span>我也是</span>
</p>
複製代碼

按照思路,咱們不該該拿這樣的dom

因此須要先轉下, 代碼以下,相信大家能夠看懂。

補全span

/** * 補全span * @param {*} html html片斷 */
function tag_full(html){
    const tag = 'span'
    const reg1 = /(?<=\<(?:\/\w|\w).*>)([^<|>]*)(?=<\w.*>)/g
    const reg2 = /(?<=\<(?:\/\w).*>)([^<|>]*)(?=<\/\w.*>)/g
    const replace_value = (_, p1) => (p1.replace(/\s|\S|t|r/, '') == '' ? '' : `<${tag}>${p1}</${tag}>`)
    return html.replace(reg1, replace_value).replace(reg2, replace_value)
}
複製代碼

轉完以後,咱們拿最後的節點,可是咱們還缺乏紅色路徑。

可是,紅色路徑怎麼拿,看起來有點複雜,咱們先拿到當前節點路徑再說,就讓其餘的事情隨風吧。

遞歸獲取節點

function traverse_tree(node, path){
    if (!node) return
    if (node.children && node.children.length > 0) {
        for (let i = 0; i < node.children.length; i++) {
            let next_node = node.children[i]
            traverse_tree(next_node, [...path, {node: node, index: i}])
        }
    }else{
        // 其餘代碼...
    }
}
複製代碼

經過遞歸,咱們遍歷的時候順手拿下節點路徑和每一個路徑下面的節點。

拿到以後,咱們和上面一個node節點(這裏指的是最後節點)計算分叉位置, 代碼以下

獲取分叉點

/** * 獲取分叉點 * @param {*} old_path [number] * @param {*} path [number] */
function get_path_diff_index(old_path, path){
    let index = 0
    let k = 0
    for(let item of path){
        old_path[k] === item && index++
        k++
    }
    return index
}
複製代碼

而後計算什麼類型就能夠了。

這時候,咱們就能夠再對img、等標籤處理了,還有樣式。

樣式代碼送上

設置樣式

/** * 根據style設置style * @param {*} node node節點 * @param {<filter_styles>} styles ['bold'] */
function set_style(node, filter_styles){
    let style = {}
    for(let item of filter_styles){
        switch(item){
            case 'bold': {
                let key = 'font-weight'
                let value = get_style(node, key)
                value >= 600 && (style[key] = item)
                break
            }
            case 'color': {
                let value = get_style(node, item)
                console.log(node, value)
                style[item] = value
                break
            }
        }
        ... 其餘
    }
    return style
}
複製代碼

而後

合併HTML

/**
 * 合併html
 * @param {*} html_arr 
 */
function merge_html(html_arr){
    function get_style_string(style){
        let style_string = ''
        for(let key in style){
            style_string += `${key}:${style[key]};`
        }
        return style_string ? ` style="${style_string}"` : ''
    }
    const res = html_arr
    .map(e =>
        e.tag === 'img' ? `<p><img src="${e.src}" class="userChoosImg" style="max-width:100%;display: inline-block;" /></p>` :
        block_element_tags.includes(e.tag) ? 
            // 忽略空白文本
            (e.text === '' ? '' : 
                `<${e.tag}${get_style_string(e.style)}>${e.text}</${e.tag}>`) : 
        ``
    )
    .join('')
    console.log('html_format_res: ', res)
    return res
}
複製代碼

而後再循環判斷img,把img合併處理,既然走以前的處理圖片方法便可。

尾聲

點個贊再走唄

--完--

相關文章
相關標籤/搜索