Created
March 16, 2018 14:09
-
-
Save sounisi5011/90a6bd2fbc27a344dd1b4d644d78833d to your computer and use it in GitHub Desktop.
DOMノードをMarkdownに変換する関数(未完成)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @param {!Node} targetNode Markdownに変換するDOMノード | |
* @param {boolean} childOnly trueの場合、DOMノードの子要素のみを変換する | |
* @return {string} | |
*/ | |
function dom2markdown(targetNode, childOnly=false) { | |
/** | |
* 文字列をMarkdown用にエスケープする | |
* @param {string} text エスケープする文字列 | |
* @param {string} escapeChar エスケープする文字 | |
*/ | |
function inlineTextEscape(text, escapeChar) { | |
if (quoteText) { | |
const escapedEscapeChar = escapeChar.replace(/[\\^\]]/g, '\\$&'); | |
const escapeRegex = new RegExp(`[${escapedEscapeChar}]`, 'g'); | |
return text.replace(escapeRegex, '\\$&'); | |
} else { | |
return text; | |
} | |
} | |
/** | |
* 文字列に接頭辞を追加し、二行目以降を接頭辞に合わせてインデントする | |
* @param {string} text インデントする文字列 | |
* @param {string} prefix 接頭辞の文字列 | |
* @param {boolean} blankLineIndent trueの場合、空行もインデントする。 | |
* @return {string} インデントした文字列 | |
*/ | |
function prefixIndent(text, prefix, blankLineIndent=false) { | |
const regex = blankLineIndent ? /\r\n?|\n/g : /(?:\r\n?|\n)(?![\r\n])/g; | |
const indentStr = ' '.repeat(prefix.length); | |
return prefix + text.replace(regex, '$&' + indentStr); | |
} | |
/* | |
* Markdownではないテキストを整形する | |
* @param {string} text | |
*/ | |
function formatRawText(text) { | |
return String(text) | |
.replace(/[ \t\f\r\n]+/g, ' ') | |
.replace(/[\\]/g, '\\$&'); | |
} | |
function trim(text) { | |
return text | |
.replace(/^[\r\n]+|[\r\n]+$/g, '') // 前後の改行文字を削除 | |
.replace(/[ \t\f]+$/gm, ''); // 行末にあるスペースを削除 | |
} | |
/** | |
* 対象要素をASTに変換する | |
* @return {!Node} targetNode 変換対象のDOMノード | |
* @return {!Object} 変換されたASTオブジェクト | |
*/ | |
function generateAst(targetNode) { | |
/** | |
* ASTの子孫要素から、条件に合致する要素を削除する。 | |
* 要素の子要素は、その要素と置き換えるようにして展開される。 | |
* | |
* @param {!Array.<!Object>} targetNodeList | |
* @param {function(!Object): boolean} callback | |
* @return {!Array.<!Object>} | |
*/ | |
function removeAstChildren(targetNodeList, callback) { | |
return targetNodeList | |
.reduce(function removeCallback(array, node) { | |
if (callback(node)) { | |
array.push( | |
...node.children | |
.reduce(removeCallback, []) | |
); | |
} else { | |
array.push(node); | |
} | |
return array; | |
}, []); | |
} | |
switch (targetNode.nodeType) { | |
case Node.ELEMENT_NODE: | |
/* | |
* 要素ノードの場合 | |
*/ | |
const tagName = targetNode.tagName.toLowerCase(); | |
if ('a' === tagName) { | |
return { | |
type: 'link', | |
url: targetNode.href, | |
title: targetNode.title, | |
children: generateAstChildren(targetNode), | |
}; | |
} else if ('em' === tagName || 'strong' === tagName || 'code' === tagName) { | |
return { | |
type: tagName, | |
children: removeAstChildren( | |
generateAstChildren(targetNode), | |
node => node.type === tagName | |
), | |
}; | |
// const contentText = generate(node, true); | |
// const quoteChar = '`'; | |
// | |
// let quoteText = quoteChar; | |
// while (contentText.includes(quoteText)) { | |
// quoteText += quoteChar; | |
// } | |
// | |
// return ( | |
// quoteText + | |
// (/^[\s`]/.test(contentText) ? ' ' : '') + | |
// contentText + | |
// (/[\s`]$/.test(contentText) ? ' ' : '') + | |
// quoteText | |
// ); | |
} else if ('ul' === tagName || 'ol' === tagName) { | |
const itemList = []; | |
const isOl = 'ol' === tagName; | |
let count = 1; | |
let childElem = targetNode.firstElementChild; | |
while (childElem) { | |
if (childElem.tagName.toLowerCase() === 'li') { | |
itemList.push({ | |
prefix: isOl ? `${count++}. ` : '* ', | |
children: generateAstChildren(childElem), | |
}); | |
} | |
childElem = childElem.nextElementSibling; | |
}; | |
return {type: 'list', items: itemList}; | |
// let output = "\n"; | |
// let childElem = node.firstElementChild; | |
// let count = 1; | |
// while (childElem) { | |
// if (childElem.tagName.toLowerCase() === 'li') { | |
// const prefix = ('ol' === tagName ? `${count++}.` : '*') + ' '; | |
// const contentText = trim(generate(childElem, true)); | |
// output += prefixIndent(contentText, prefix) + "\n"; | |
// } | |
// childElem = childElem.nextElementSibling; | |
// }; | |
// return output; | |
} else if ('p' === tagName) { | |
const children = generateAstChildren(targetNode); | |
if (targetNode.getAttribute('role') === 'note') { | |
return {type: 'note', children}; | |
} else { | |
return {type: 'p', children}; | |
} | |
// const contentText = trim(generate(targetNode, true)); | |
// return ( | |
// "\n" + | |
// ( | |
// targetNode.getAttribute('role') === 'note' ? | |
// prefixIndent(contentText, 'Note: ') : | |
// contentText | |
// ) + | |
// "\n" | |
// ); | |
} else if ('br' === tagName) { | |
return {type: 'br'}; | |
} else { | |
return {type: 'html', value: targetNode.outerHTML}; | |
} | |
case Node.TEXT_NODE: | |
/* | |
* テキストノードの場合 | |
*/ | |
const value = targetNode.nodeValue | |
.replace(/[ \t\f]+/g, ' '); | |
if (value !== '') { | |
return {type: 'text', value}; | |
} else { | |
return {}; | |
} | |
default: | |
/* | |
* その他のノードの場合 | |
*/ | |
console.warn('Unknown DOM Node', targetNode); | |
return {}; | |
} | |
} | |
/** | |
* 対象要素の子要素のみをASTに変換する | |
* @param {!Node} targetNode | |
* @return {!Array.<!Object>} | |
*/ | |
function generateAstChildren(targetNode) { | |
const astList = []; | |
let childNode = targetNode.firstChild; | |
while (childNode) { | |
const astObj = generateAst(childNode); | |
if (astObj && astObj.type) astList.push(astObj); | |
childNode = childNode.nextSibling; | |
}; | |
return astList; | |
} | |
const astObj = ( | |
childOnly ? | |
{ | |
type: 'fragment', | |
children: generateAstChildren(targetNode), | |
} : | |
generateAst(targetNode) | |
); | |
(function xxx(ast) { | |
const type = ast.type; | |
if (type === 'br') { | |
ast.value = " \n"; | |
} else if (type === 'p') { | |
const children = ast.children; | |
/* | |
* 子要素を処理 | |
*/ | |
ast.value = children | |
.map((xxx)) | |
.join(''); | |
/* | |
* 子要素の先頭と末尾の空白文字を消す | |
*/ | |
const firstChild = children[0]; | |
const lastChild = children[children.length - 1]; | |
firstChild.value = firstChild.value.replace(/^[\n ]+/, ''); | |
lastChild.value = lastChild.value.replace(/[\n ]+$/, ''); | |
ast.value = | |
} else if (type === 'list') { | |
} | |
})(astObj); | |
// /** | |
// * @param {!Object} ast | |
// * @param {?Object} parentAst | |
// * @param {?Object} prevAst | |
// * @param {?Object} nextAst | |
// * @return {string} | |
// */ | |
// function ast2text(ast, parentAst=null, prevAst=null, nextAst=null) { | |
// const type = ast.type; | |
// let outputText = ''; | |
// | |
// if (type === 'text') { | |
// // let value = ast.value; | |
// // if (!prevAst || /^(?:list|p|note)$/.test(prevAst.type)) { | |
// // value = value.replace('^ +', ''); | |
// // } | |
// // if (!nextAst || /^(?:list|p|note)$/.test(nextAst.type)) { | |
// // value = value.replace(' +$', ''); | |
// // } | |
// return ast.value; | |
// | |
// } else if (type === 'br') { | |
// return " \n"; | |
// | |
// } else if (type === 'p') { | |
// const parentAst = ast; | |
// return ( | |
// ast.children | |
// .map((ast, index, children) => { | |
// return ast2text(ast, parentAst, children[index - 1], children[index + 1]); | |
// }) | |
// ); | |
// } | |
// | |
// switch (ast.type) { | |
// // block | |
// case 'list': | |
// case 'p': | |
// case 'note': | |
// | |
// case 'link': | |
// case 'em': | |
// case 'strong': | |
// case 'code': | |
// case 'br': | |
// case 'html': | |
// case 'text': | |
// case 'fragment': | |
// } | |
// | |
// return outputText; | |
// } | |
// | |
// /** | |
// * @param {!Array.<!Object>} astList | |
// * @param {!Object} parentAst | |
// * @return {string} | |
// */ | |
// function astList2text(astList, parentAst={}) { | |
// | |
// } | |
return astObj; | |
// return trim(generate(node, childOnly)) | |
// .replace(/[ \t\f]*<br>[ \t\f]*/g, " \n"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment