マークダウン形式文字列のコードブロック外に存在する特定の記号をサニタイズする。

下記のようなコードブロックとコードブロック外の混在するマークダウン形式の文章があるとします。

![33178870c736b737203c8b0678552038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/e52c61a3-18e4-010a-2fed-66b4005de7c9.png)

以下の要件で文字列をサニタイズします。

・以下の記号をエスケープシーケンスに置き換えます。
（ただしコードブロック内の記号については置き換えない。）
(ただし、画像のURL内に含まれる&は置き換えない）

```
& は &amp; に置き換え
< は　　&lt;　　に置き換え
>　　は &gt;　　に置き換え
" は &quot;　　に置き換え
' は &#x27;　　に置き換え
```
# 実装方針
1.文字列を「```」で分割する。
2.分割されたブロックがそれぞれコードブロック部分か、そうでないかを判別する。
3.コードブロック外の文字列の場合、上記の記号を置き換える。
4.画像URLないの&については置き換えない。
5.分割した文字列を再結合する。

```
サニタイズ後の文字列

ここはコード外です。# 大文字&lt; &gt; &amp; &#x27; &quot;&lt;&gt;&amp;&#x27;&quot;```javascriptconst test = () => {	console.log('テスト')const one = 1;const two = 2;if(one < tow && one >=0){	console.log("over")	}}```コード外です２。*斜体*&lt;a onmouseover=alert(document.cookie)&gt;click me!&lt;/a&gt;```javascriptonmouseover=alert(document.cookie)>click me!</a>```![image](https://firebasestorage.googleapis.com/v0/b/searchistory.appspot.com/o/images%2F45d84e1d-5c54-48df-ae98-8ec8c7a6cdd1?alt=media&token=4c436dd6-92f7-41a2-8211-0cd41f769278)	
```

### 1についての関数(文字列を「```」で分割する。)

```javascript
const originWord = "```javascript <inCodeWord> ``` <outCodeWord>  ```javascrpt <inCodeWord> ```"

const sanitize = (originWord) => {
  const splitReg = /(?=```)/
  const splitedArray = originWord.split(splitReg);

  console.log(splitedArray);
}

//結果
//["```javascript <inCodeWord> ","``` <outCodeWord>  ","```javascrpt <inCodeWord> ","```"]

// 正規表現の/(?=```)/でsplitすることにより肯定先読みで、
// ```の直前の位置で文字列を分割し配列化する。
```
### 2についての実装追加(分割されたブロックがそれぞれコードブロック部分か、そうでないかを判別する。)

配列の各文字列の先頭の```の直後に文字（言語名）が続く場合はコードブロック内、そうでなければコードブロック外と判定する。

```javascript
const originWord = "```javascript <inCodeWord> ``` <outCodeWord>  ```javascrpt <inCodeWord> ```"

const sanitize = (originWord) => {
  const splitReg = /(?=```)/
  const splitedArray = originWord.split(splitReg);

  for (let i = 0; i < splitedArray.length; i++) {
    const splitReg = /^```\w+\s/
    if (!splitReg.test(splitedArray[i])) {
      console.log("out code");
    } else {
      console.log("in code");
    }
  }
}

sanitize(originWord)

//結果
//"in code", "out code", "in code"
```

### 3についての実装追加(コードブロック外の文字列の場合、上記の記号を置き換える。)

```javascript
const originWord = "```javascript <inCodeWord>' ``` <outCodeWord>  ```javascrpt <inCodeWord> ```"

//記号置き換え関数を追加
const escapeHTML = (word) => {
  return word.replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, "&#x27;");
}

const sanitize = (originWord) => {
  const splitReg = /(?=```)/
  const splitedArray = originWord.split(splitReg);

  for (let i = 0; i < splitedArray.length; i++) {
    const splitReg = /^```\w+\s/
    if (!splitReg.test(splitedArray[i])) {
      const sanitizedBlockWord = escapeHTML(splitedArray[i])
      console.log(sanitizedBlockWord)
    } else {
      console.log(splitedArray[i]);
    }
  }
}

sanitize(originWord)

//結果
//"```javascript <inCodeWord>' "
//"``` &lt;outCodeWord&gt;  "
//"```javascrpt <inCodeWord> "
```

### 4についての実装追加(画像URL内の&については置き換えない。)

```javascript

//略　

const originWord = "```javascript &  ``` media&token &```"

const escapeHTML = (word) => {
  return word.replace(/(?<!(media))&(?!(token))?/g, '&amp;')//修正
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, "&#x27;");
}

//略　

//結果
//"```javascript &  "
//"``` media&token &amp;"
//"```"

//escapeHTMLの&の置き換え関数を修正
//画像URLに含まれる&は必ず media&tokenという形式なので、正規表現で置き換え対象から除外する。
``` 

### 5の実装追加(分割した文字列を再結合する。)

```javascript
const originWord = "```javascript <inCodeWord> ``` <outCodeWord>&  media&token ```javascrpt <inCodeWord> ```"

const escapeHTML = (word) => {
  return word.replace(/(?<!(media))&(?!(token))?/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, "&#x27;");
}

const sanitize = (originWord) => {
  const splitReg = /(?=```)/
  const splitedArray = originWord.split(splitReg);

  let sanitizedAllWord = ""

  for (let i = 0; i < splitedArray.length; i++) {
    const splitReg = /^```\w+\s/
    if (!splitReg.test(splitedArray[i])) {
      sanitizedAllWord = sanitizedAllWord + escapeHTML(splitedArray[i]);
    } else {
      sanitizedAllWord = sanitizedAllWord + splitedArray[i];
    }
  }
  return sanitizedAllWord;
}

console.log(sanitize(originWord));

//結果
//"```javascript <inCodeWord> ``` &lt;outCodeWord&gt;&lt;  media&token ```javascrpt <inCodeWord> ```"
```

以上です。