remark-strip-html-but-iframe.js
// iframe以外のHTML文字列を禁止する。 | |
// remark-strip-htmlを参考にした。 | |
const IS_ATTR_ALLOWED = { | |
src: true, | |
frameborder: true, | |
height: true, | |
width: true, | |
}; | |
/** | |
* HTML文字列から許可されていない属性を取り除く。 | |
* @param {string} html - HTML文字列 | |
* @returns {string} | |
*/ | |
function filterAttributes(html) { | |
const attrs = {}; | |
html.replace(/(\S+?)=["']?(\S*)["']?/gi, (_, key, value) => { | |
if (IS_ATTR_ALLOWED[key]) { | |
attrs[key] = value; | |
} | |
}); | |
const iframe = document.createElement('iframe'); | |
for (const k in attrs) { | |
iframe.setAttribute(k, attrs[k]); | |
} | |
return iframe.outerHTML; | |
} | |
/** | |
* トークンからiframe以外のHTML文字列を削除する。 | |
* iframeについては属性を制限する。 | |
* @param {{ type: string, value: string }} token | |
* @returns {{ type: string, value: string }} | |
*/ | |
function transformHtmlToken(token) { | |
let value = token.value.replace(/(\n|\r|\r\n)/g, ''); | |
if (value.match(/^\s*(<iframe\s+[^>]*>).*(<\/\s*iframe>)\s*$/i)) { | |
// <iframe></iframe> の中身を削除 | |
value = RegExp.$1 + RegExp.$2; | |
value = filterAttributes(value); | |
} else if (value.match(/^\s*(<iframe\s+[^>]*\/>)\s*$/i)) { | |
// <iframe/> は許す | |
value = RegExp.$1; | |
value = filterAttributes(value); | |
} else { | |
// iframe以外の場合は空文字列にする | |
value = ''; | |
} | |
return { type: 'html', value: value }; | |
} | |
/** | |
* 1つの要素を処理する | |
* @param {IToken} token | |
* @returns {IToken} | |
*/ | |
function processToken(token) { | |
const type = token && token.type; | |
let newToken = token; | |
if (type === 'html') { | |
newToken = transformHtmlToken(token); | |
} | |
if ('length' in token) { | |
newToken = processTokens(token); | |
} | |
if (token.children) { | |
newToken.children = processTokens(newToken.children); | |
} | |
return newToken; | |
} | |
/** | |
* 複数の要素を処理する。 | |
* HTML文字列を削除した時、前後の文字列や段落を結合するために必要。 | |
* @param {IToken[]} tokens | |
* @returns {IToken[]} | |
*/ | |
function processTokens(tokens) { | |
let result = []; | |
tokens.forEach(token => { | |
const newToken = processToken(token); | |
if (newToken && typeof newToken.length === 'number') { | |
result = result.concat(newToken.map(t => token)); | |
} else { | |
result.push(newToken); | |
} | |
}); | |
return clean(result); | |
} | |
/** | |
* 連続する要素の文字列を連結する | |
* @param {IToken[]} tokens | |
* @returns {IToken[]} | |
*/ | |
function clean(tokens) { | |
const result = []; | |
let prev = null; | |
tokens.forEach(token => { | |
if ( | |
prev && | |
typeof token.value !== 'undefined' && | |
token.type === prev.type | |
) { | |
prev.value += token.value; | |
} else { | |
result.push(token); | |
prev = token; | |
} | |
}); | |
return result; | |
} | |
module.exports = function() { | |
return processToken; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment