2017-05-25 34 views
1

所以我想寫一個正則表達式的JavaScript,這將允許我用標籤替換**作爲一種自卷降價到HTML轉換器。正則表達式沒有倒標記Markdown加密

例如

**bold** - ><strong>bold</strong>

\**not** - >**not**因爲*被逃脫。

我有以下的正則表達式,似乎運作良好:

/(?<!\\)(?:\\\\)*(\*\*)([^\\\*]+)(\*\*)/g 

然而,JS不支持lookbehinds!我重寫它使用向前看符號:

/(\*\*)([^\\\*]+)*(\*\*)(?!\\)(?:\\\\)*/g 

但是這需要我去扭轉,因爲我需要支持多字節字符(see here),這是不可取的字符串。我並不完全反對使用該答案中提到的庫,但我更喜歡一種解決方案,如果可能的話,不需要我添加一個。

有沒有辦法重寫我的正則表達式而不使用後視?

編輯:

思考這個多一點之後,我甚至開始懷疑正則表達式是偶數解決這個問題的最好方法,但我會離開的問題了出於興趣。

+0

應該採取什麼結果給予輸入'** * foo的**吧'或'** FOO \ ** **吧' ? –

+0

我預計分別爲'foo * bar'和'foo \ ** bar'。我的正則表達式很可能不包括所有的情況,因爲我還沒有能夠測試它。我不太在意缺少邊緣案例,更關心用正面背景編寫正則表達式,但指出遺漏案例仍然有幫助,所以謝謝!我也不是超級,超級關心邊緣案例,因爲這是在一個管理工具中使用,在這種管理工具中,這樣的奇怪情況並不是真正的問題。 – thatidiotguy

+0

你真的期望格式不正確的字符串嗎?你知道,即使一個正確的解析器會產生不正確的結果,如果你的字符串格式不正確。試試https://regex101.com/r/J8imcO/1 –

回答

3

解決缺失lookbehinds的一種方法是首先匹配不需要的模式,然後使用替代匹配所需的模式。然後應用條件替換,用你自己想要的和你想要的替換不想要的模式。

你的具體情況,這意味着比賽後,才\*第一和**<something>**。然後用

input.replace(/\\\*|\*\*(.*?)\*\*/, function(m, p1) { 
    return m == '\\*' ? m : '<strong>' + p1 + '</strong>'; 
}) 

做條件替換。

雖然真正的正則表達式更復雜。首先,您需要確保避開反斜槓本身(即\\**bold**應該變爲\\<strong>bold</strong>)。因此,您需要分別以與\*相同的方式匹配\\

其次,****之間的表達式還可能包含一些轉義的星號和斜線。爲了應對這種需要匹配\\\**明確且只有別的非貪婪地後(使用交替)。這可以表示爲(?:\\\\|\\\*\*|\*(?!\*)|[\S\s])*?

因此最終的正則表達式變成

\\\\|\\\*|\*\*((?:\\\\|\\\*\*|\*(?!\*)|[\S\s])*?)\*\* 

演示:https://regex101.com/r/Da35r5/1

的JavaScript替換演示:

function convert() { 
 
    var md = document.getElementById("md").value; 
 
    var re = /\\\\|\\\*|\*\*((?:\\\\|\\\*\*|\*(?!\*)|[\S\s])*?)\*\*/g; 
 
    var html = md.replace(re, function(match, p1) { 
 
    return match.startsWith('\\') ? match : '<strong>' + p1 + '</strong>'; 
 
    }); 
 
    document.getElementById("html").value = html; 
 
}
<span style="display:inline-block"> 
 
MD 
 
<textarea id="md" cols="20" rows="10" style="display:block"> 
 
**bold** 
 
**foo * bar ** 
 
**foo \** bar** 
 
**fo\\\\** bar** ** 
 
\**bold** ** 
 
\\**bold** 
 
** multi 
 
line** 
 
</textarea> 
 
</span> 
 

 
<span style="display:inline-block"> 
 
HTML 
 
<textarea id="html" cols="50" rows="10" style="display:block"> 
 
</textarea> 
 
</span> 
 

 
<button onclick="convert()" style="display:block">Convert</button>

+0

感謝您對消除後視的一般策略的詳細解釋。我在執行split(「\ n」)後逐行解析字符串,所以甚至不需要多行! – thatidiotguy

0

試試這個公式,不看(提前|後面)在所有:

(?:(?:[\\])\*\*(?:.+?)\*\*|(?:[^\\\n]|^)\*\*(.+)\*\*)

Demo

0

考慮下面的正則表達式:

/(.*?)(\\\\|\\\*|\*\*)/g 

你可以把它當作一個標記。它對一些(或沒有)文本進行非貪婪匹配,後面跟着一個特殊字符序列\\,\*,最後是**。按照此順序進行匹配可確保正確處理**foo \** bar\\**等奇怪邊緣案例(<strong>foo \** bar\</strong>)。這使得在其替代功能中具有switch的非常簡單的String.prototype.replace。布爾型bold標誌可幫助我們確定**是否應替換爲<strong></strong>。

const TOKENIZER = /(.*?)(\\\\|\\\*|\*\*)/g; 

function render(str) { 
    let bold = false; 
    return str.replace(TOKENIZER, (_, text, special) => { 
    switch (special) { 
     case '\\\\': 
     return text + '\\'; 
     case '\\*': 
     return text + '*'; 
     case '**': 
     bold = !bold; 
     return text + (bold ? '<strong>' : '</strong>'); 
     default: 
     return text + special; 
    } 
    }); 
} 

在這裏,我假設\\應該成爲\\*應該成爲*,在正常的降價解析器。這與德米特里的解決方案不同,但更簡單。看到它在行動中下面的代碼片段:

const TOKENIZER = /(.*?)(\\\\|\\\*|\*\*)/g; 
 

 
function render(str) { 
 
    let bold = false; 
 
    return str.replace(TOKENIZER, (_, text, special) => { 
 
    switch (special) { 
 
     case '\\\\': 
 
     return text + '\\'; 
 
     case '\\*': 
 
     return text + '*'; 
 
     case '**': 
 
     bold = !bold; 
 
     return text + (bold ? '<strong>' : '</strong>'); 
 
     default: 
 
     return text + special; 
 
    } 
 
    }); 
 
} 
 

 
// Test 
 
const input = document.getElementById('input'); 
 
const outputText = document.getElementById('output-text'); 
 
const outputHtml = document.getElementById('output-html'); 
 

 
function makeOutput(str) { 
 
    const result = render(str); 
 
    outputText.value = render(str); 
 
    outputHtml.innerHTML = render(str); 
 
} 
 

 
input.addEventListener('input', evt => makeOutput(evt.target.value)); 
 
makeOutput(input.value);
body{font-family:'Helvetica Neue',Helvetica,sans-serif} 
 
textarea{display:block;font-family:monospace;width:100%;margin-bottom:1em} 
 
div{padding:2px;background-color:lightgoldenrodyellow}
<label for="input">Input</label> 
 
<textarea id="input" rows="3">aaa **BBB** ccc \**ddd** EEE \\**fff \**ggg** HHH**</textarea> 
 

 
Output HTML: 
 
<textarea id="output-text" rows="3" disabled></textarea> 
 

 
Rendered HTML: 
 
<div id="output-html"></div>