pandao / editor.md

The open source embeddable online markdown editor (component).
http://editor.md.ipandao.com/
MIT License
13.85k stars 2.42k forks source link

是否考虑加入导入word文档和支持从office文档粘贴转换为markdown的功能? #790

Open cnspray opened 4 years ago

cnspray commented 4 years ago

提供一个思路: 一、从office 文档粘贴为markdown的功能主要使用turndown.js和turndown-plugin-gfm.js,粘贴时判断是否从office 粘贴,如果是,去掉head和注释,去掉隐藏元素,然后转换。 存在问题:图片显示的是本地地址。另外不能拦截到editor的默认粘贴事件,导致粘贴时,总是重复。

二、导入word文档直接转换为markdown的功能主要使用mammoth.browser.js,因为该插件转换为markdown 的支持不好,先转换为html,再使用使用turndown.js和turndown-plugin-gfm.js,转换为markdown。使用该插件,对于 包含<w:vanish />的元素应跳过不解析。

使用mammoth.browser.js转换的word,里面包含图片的,自动转换为base64编码,如果后端有服务器的,将base64上传至服务器,返回路径。

cnspray commented 4 years ago

使用turndown.js和turndown-plugin-gfm.js转换为markdown的好处是,非标准表格为解析为html格式,包含合并之类的。下面是自己尝试做的一个demo.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>

<script src="https://unpkg.com/turndown/dist/turndown.js"></script>
<script src="https://unpkg.com/turndown-plugin-gfm/dist/turndown-plugin-gfm.js"></script>

</head>
<body>
<div id="tmpdiv">dddd</div>
<textarea   style="width:100%;height:100px"   id="dd" ></textarea>
<textarea   style="width:100%;height:500px"   id="dd1" ></textarea>

</body>
<script>
dd.addEventListener("paste",
function(e) {
    if (! (e.clipboardData && e.clipboardData.items)) {
        return;
    }
    for (var i = 0,
    len = e.clipboardData.items.length; i < len; i++) {
        var item = e.clipboardData.items[i];

        if (item.kind === "string" && item.type === "text/html") {
            item.getAsString(function(str) {
                if(isWordDocument(str)){
                //处理一下html,忽略特殊情况下尾部乱码
                str=str.match(/(<html[\s\S]*<\/html>)/gi)[0];
                /* document.getElementById("dd1").innerText=str=str.replace(/<head>[\s\S]*<\/head>/gi,"")
                //去掉注释
                .replace(/<!--[\s\S]*?-->/ig, ""); */
                //return;
                //去掉头部描述
                str=str.replace(/<head>[\s\S]*<\/head>/gi,"")
                //去掉注释
                .replace(/<!--[\s\S]*?-->/ig, "")
                //去掉隐藏元素
                .replace(/<([a-z0-9]*)[^>]*\s*display:none[\s\S]*?><\/\1>/gi,'');
                //处理其他冗余代码
                str=filterPasteWord(str);
                //上传文件
                str=str.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, function (match, capture) {
    var path=capture.replace("file:///",'');
    //upload
    return '<img  src="' + path + '" />';
})
                var gfm = turndownPluginGfm.gfm;
                var turndownService = new TurndownService({
    headingStyle: 'atx',
    hr: '- - -',
    bulletListMarker: '-',
    codeBlockStyle: 'indented',
    fence: '```',
    emDelimiter: '_',
    strongDelimiter: '**'});
                turndownService.use(gfm);
                turndownService.keep(['sub', 'sup']);
                var markdown = turndownService.turndown(str);
                document.getElementById("dd1").innerText=markdown;
                return markdown;
                }else{
                var gfm = turndownPluginGfm.gfm;
                var turndownService = new TurndownService({
    headingStyle: 'atx',
    hr: '- - -',
    bulletListMarker: '-',
    codeBlockStyle: 'indented',
    fence: '```',
    emDelimiter: '_',
    strongDelimiter: '**'});
                turndownService.use(gfm);
                turndownService.keep(['sub', 'sup']);
                var markdown = turndownService.turndown(str);
                document.getElementById("dd1").innerText=markdown;
                return markdown;
                }
             })
        } 
    }
});

//判断粘贴的内容是否来自office
function isWordDocument(str) {
    return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/ig.test(str) || /\"urn:schemas-microsoft-com:office:office/ig.test(str);
}

//去除冗余属性和标签
function filterPasteWord(str) {
    return str.replace(/[\t\r\n]+/g, ' ').replace(/<!--[\s\S]*?-->/ig, "")
    //转换图片
    .replace(/<v:shape [^>]*>[\s\S]*?.<\/v:shape>/gi,
    function(str) {
        //opera能自己解析出image所这里直接返回空
        if ( !! window.opera && window.opera.version) {
            return '';
        }
        try {
            //有可能是bitmap占为图,无用,直接过滤掉,主要体现在粘贴excel表格中
            if (/Bitmap/i.test(str)) {
                return '';
            }
            var src = str.match(/src=\s*"([^"]*)"/i)[1];
            return '<img  src="' + src + '" />';
        } catch(e) {
            return '';
        }
    })

    //针对wps添加的多余标签处理
    .replace(/<\/?div[^>]*>/g, '')
    .replace(/<\/?span[^>]*>/g, '')
    .replace(/<\/?font[^>]*>/g, '')
    .replace(/<\/?col[^>]*>/g, '')
    .replace(/<\/?(span|div|o:p|v:.*?|input|label)[\s\S]*?>/g, '')
    //去掉所有属性,需要保留单元格合并
    //.replace(/<([a-zA-Z]+)\s*[^><]*>/g, "<$1>")
    //去掉多余的属性
    .replace(/v:\w+=(["']?)[^'"]+\1/g, '')
    .replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi, "").replace(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>")
    //去掉多余的属性
    .replace(/\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/gi, '')
    //清除多余的font/span不能匹配&nbsp;有可能是空格
    .replace(/<(font|span)[^>]*>(\s*)<\/\1>/gi,
    function(a, b, c) {
        return c.replace(/[\t\r\n ]+/g, " ");
    })
    //去掉style属性
    .replace(/(<[a-z][^>]*)\sstyle=(["'])([^\2]*?)\2/gi, "$1")
    // 去除不带引号的属性
    .replace(/(class|border|cellspacing|MsoNormalTable|valign|width|center|&nbsp;|x:str|height|x:num|cellpadding)(=[^ \f\n\r\t\v>]*)?/g, "")
    // 去除多余空格
    .replace(/(\S+)(\s+)/g, function(match, p1, p2) {
        return p1 + ' ';
    })
    .replace(/(\s)(>|<)/g, function(match, p1, p2) {
        return p2;
    })
    //处理表格中的p标签
    .replace(/(<table[^>]*[\s\S]*?><\/table>)/gi,function(a){
                if (a.match(/(<table>)/gi).length>1){ return a} else {
    return a.replace(/<\/p><p>/g, "<br/>")
    .replace(/<\/?p[^>]*>/g, '')
    .replace(/<td>&nbsp;<\/td>/g, "<td></td>")
    }});

}

</script>