Skip to content

Instantly share code, notes, and snippets.

@sounisi5011
Created March 16, 2018 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sounisi5011/90a6bd2fbc27a344dd1b4d644d78833d to your computer and use it in GitHub Desktop.
Save sounisi5011/90a6bd2fbc27a344dd1b4d644d78833d to your computer and use it in GitHub Desktop.
DOMノードをMarkdownに変換する関数(未完成)
/**
* @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