Created
April 10, 2020 11:47
-
-
Save endam/6cb383863fe934871435ab29acb9e36e to your computer and use it in GitHub Desktop.
Backlog wiki記法→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
class Convert { | |
execute(val) { | |
const | |
codes = []; | |
const | |
quotes = []; | |
const | |
paragraphs = []; | |
const | |
textLevelSemanticsCheck = (content) => { | |
const patterns = [ | |
// 要素を<tagName>表記しているパターンをエスケープ | |
{ | |
pattern: /<(.*?)>/g, | |
replacement: (m, p1) => { | |
return `<${p1}>`; | |
} | |
}, | |
// 抜け漏れしている<をエスケープ。>は他の記法で利用されているためエスケープしない | |
{ | |
pattern: /</g, | |
replacement: '<' | |
}, | |
// strong | |
{ | |
pattern: /''(.*?)''/g, | |
replacement: (m, p1) => { | |
return ` **${p1.trim()}** `; | |
} | |
}, | |
// em | |
{ | |
pattern: /'(.*?)'/g, | |
replacement: (m, p1) => { | |
return ` *${p1.trim()}* `; | |
} | |
}, | |
// strike | |
{ | |
pattern: /%%(.*?)%%/g, | |
replacement: (m, p1) => { | |
return ` ~~${p1.trim()}~~ `; | |
} | |
}, | |
// a | |
{ | |
pattern: /\[\[(.*?)[:>](.*?)\]\]/g, | |
replacement: (m, p1, p2) => { | |
return `[${p1.trim()}](${p2})`; | |
} | |
}, | |
// 色指定(Markdown in Backlogでは無視される) | |
{ | |
pattern: /&color\((.*?)\)(\s+)?{(.*?)}/ig, | |
replacement: (m, p1, p2, p3) => { | |
return `<span style="color: ${p1};">${p3}</span>`; | |
} | |
}, | |
// その他埋め込み | |
{ | |
pattern: /#image\((.*?)\)/, | |
replacement: (m, p1) => { | |
return `![${p1}]`; | |
} | |
}, | |
{ | |
pattern: /#thumbnail\((.*?)\)/, | |
replacement: (m, p1) => { | |
return `![${p1}]`; | |
} | |
}, | |
{ | |
pattern: /#attach\((.*?):(.*?)\)/, | |
replacement: (m, p1, p2) => { | |
return `[${p1}][${p2}]`; | |
} | |
}, | |
// 余計な半角スペースの連続を削除 | |
{ | |
pattern: / {2}/g, | |
replacement: ' ' | |
} | |
]; | |
patterns.forEach(({pattern, replacement}) => { | |
content = content.replace(pattern, replacement); | |
}); | |
return content; | |
}; | |
const | |
patterns = [ | |
// 改行コード | |
{ | |
pattern: /\n\r/g, | |
replacement: '\n' | |
}, | |
{ | |
pattern: /\r/g, | |
replacement: '\n' | |
}, | |
// 見出し | |
{ | |
pattern: /^(\*+)(.*)$/gm, | |
replacement: (m, p1, p2) => { | |
return `\n${p1.replace(/\*/g, '#')} ${textLevelSemanticsCheck(p2.trim())}\n`; | |
} | |
}, | |
// theadなしテーブル | |
{ | |
pattern: /\n\|([\s|\S]*?)\|\n(?!\|)/g, | |
replacement: (m, p1) => { | |
return `\n|${p1}|\n\n`; | |
} | |
}, | |
{ | |
pattern: /\n\|([\s|\S]*?)\|\n\n/g, | |
replacement: (m, p1) => { | |
if (p1.indexOf('|h\n') === -1) { | |
let i = 0; | |
let max = p1.split('\n')[0].split('|').length; | |
let row = ''; | |
for (i; i < max; i++) { | |
row += '|:--'; | |
} | |
row += '|'; | |
return `\n${row}\n|${p1}|\n`; | |
} | |
return `\n|${p1}|\n`; | |
} | |
}, | |
// 行見出しテーブル | |
{ | |
pattern: /^\|(.*)\|(\s?)$/gm, | |
replacement: (m, p1, p2) => { | |
p1 = p1.replace(/\|~/g, '|'); | |
p1 = p1.replace(/^~/g, ''); | |
p1 = p1.replace(/\|\|/g, '| |'); | |
p1 = p1.replace(/^\|/g, ' |'); | |
p1 = p1.replace(/^\|/g, ' |'); // 先頭が空 | |
p1 = p1.replace(/\|$/g, '| '); // 最後が空 | |
return `|${p1}|${p2}`; | |
} | |
}, | |
// 列見出しテーブル | |
{ | |
pattern: /^\|(.*)\|h\s?$/gm, | |
replacement: (m, p1) => { | |
let row = ''; | |
let cell = p1.split('|'); | |
let i = 0; | |
let max = cell.length; | |
for (i; i < max; i++) { | |
row += '|:--'; | |
} | |
row += '|'; | |
p1 = p1.replace(/\|~/g, '|'); | |
p1 = p1.replace(/^~/g, ''); | |
p1 = p1.replace(/\|\|/g, '| |'); | |
p1 = p1.replace(/^\|/g, ' |'); // 先頭が空 | |
p1 = p1.replace(/\|$/g, '| '); // 最後が空 | |
return `\n|${p1}|\n${row}`; | |
} | |
}, | |
// テーブルに改行 | |
{ | |
pattern: /\n\|([\s|\S]*?)\|\n([^|])/g, | |
replacement: (m, p1, p2) => { | |
return `\n|${textLevelSemanticsCheck(p1)}|\n\n${p2}`; | |
} | |
}, | |
{ | |
pattern: /\n\|([\s|\S]*?)\|\n(?!\|)/g, | |
replacement: (m, p1) => { | |
return `\n\n|${p1}|\n\n`; | |
} | |
}, | |
// 順序リスト | |
{ | |
pattern: /\n\+([\s|\S]*?)\n\n/g, | |
replacement: (m, p1) => { | |
return `\n+${p1}\n\n\n`; | |
} | |
}, | |
{ | |
pattern: /\n\+([\s|\S]*?)\n\n/g, | |
replacement: (m, p1) => { | |
let result = ''; | |
p1 = '\n+' + p1.trim(); | |
// スペースの整形 | |
p1 = p1.replace(/^(\++)(.*)$/gm, (m2, p2, p3) => { | |
return `${p2} ${p3.trim()}`; | |
}); | |
p1 = p1.trim(); | |
{ | |
const symbolCount = []; // index番号はインデントレベル。インデントが上がったら | |
let currentLevel = 0; | |
p1.split('\n').forEach((line) => { | |
const level = line.split(' ')[0].length; | |
if (level < currentLevel) { | |
symbolCount[currentLevel] = 0; | |
} | |
currentLevel = level; | |
symbolCount[currentLevel] = symbolCount[currentLevel] ? symbolCount[currentLevel] + 1 : 1; | |
result += textLevelSemanticsCheck(line.replace('+ ', symbolCount[currentLevel] + '. ')) + '\n'; | |
}); | |
} | |
// ネストがあるときに残っている + をスペースに変換 | |
p1 = result.replace(/^(\++)(.*)/gm, (m2, p2, p3) => { | |
const max = p2.length; | |
let i = 0; | |
let indent = ''; | |
for (i; i < max; i++) { | |
indent += ' '; | |
} | |
return indent + p3; | |
}); | |
return `\n${p1}\n`; | |
} | |
}, | |
// 非順序リスト | |
{ | |
pattern: /^(-+)(.*)$/gm, | |
replacement: (m, p1, p2) => { | |
let i = 0; | |
let max = p1.length - 1; | |
let indent = ''; | |
if (!p2) { | |
return p1; // hr要素 | |
} | |
for (i; i < max; i++) { | |
indent += ' '; | |
} | |
indent += '-'; | |
p2 = textLevelSemanticsCheck(p2).trim(); | |
return `${indent} ${p2}`; | |
} | |
}, | |
// 改行をbr要素に変換(Markdown in Backlogでは無視されます) | |
{ | |
pattern: /&br;/g, | |
replacement: ' <br>' | |
}, | |
{ | |
pattern: /&/g, | |
replacement: '&' | |
} | |
]; | |
// 正規表現を助けるための改行を追加 | |
val = '\n' + val + '\n\n'; | |
// 目次 | |
val = val.replace(/^#contents$/gm, '[toc]\n'); | |
// コード範囲指定を隠す | |
val = val.replace(/\n{code}([\s|\S]*?){\/code}\n/g, (m, p1) => { | |
codes.push(p1); | |
return `\n{{CODE_REPACE_BACKLOG_TO_MARKDOWN-${codes.length - 1}}}\n`; | |
}); | |
// 引用範囲指定を隠す | |
val = val.replace(/\n{quote}([\s|\S]*?){\/quote}\n/g, (m, p1) => { | |
quotes.push(p1); | |
return `\n{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-${quotes.length - 1}}}\n`; | |
}); | |
// 通常テキスト(パラグラフ)を隠す | |
val = val.replace(/^.*$/gm, (() => { | |
const isP = /^(?![*\|\-\+\s>)`])(.*)$/; | |
return (p1) => { | |
if ( | |
p1 && | |
isP.test(p1) && | |
!p1.startsWith('{{CODE_REPACE_BACKLOG_TO_MARKDOWN') && | |
!p1.startsWith('{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN') | |
) { | |
paragraphs.push(p1); | |
return `{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-${paragraphs.length - 1}}}`; | |
} | |
return p1; | |
}; | |
})()); | |
// パラグラフの塊は最後に空行を開けさせる | |
val = val.replace(/\n{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-.*?}}\n(?!{{)/g, (p1) => { | |
return `${p1}\n`; | |
}); | |
// 範囲指定型以外のBacklog記法を置き換える | |
patterns | |
.forEach( | |
({ | |
pattern | |
, | |
replacement | |
} | |
) => { | |
val = val.replace(pattern, replacement); | |
} | |
) | |
; | |
// 範囲指定系を埋めもどす前に無駄な改行を削除する | |
while (/\n\n\n/g.test(val)) { | |
val = val.replace('\n\n\n', '\n\n'); | |
} | |
// コード範囲指定を戻す | |
val = val.replace(/{{CODE_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (m, p1) => { | |
let content = codes[Number(p1)].trim(); | |
if (content) { | |
return '\n```\n' + codes[Number(p1)].trim() + '\n```\n'; | |
} | |
return '\n```\n```\n'; | |
}); | |
// 引用範囲指定を戻す | |
val = val.replace(/{{QUOTE_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (m, p1) => { | |
let content = quotes[Number(p1)].trim(); | |
content = content.split('\n').join('\n> '); | |
return '\n> ' + content + '\n'; | |
}); | |
// パラグラフを戻す | |
val = val.replace(/{{PARAGRAPHS_REPACE_BACKLOG_TO_MARKDOWN-(.*?)}}/g, (m, p1) => { | |
let content = paragraphs[Number(p1)].trim(); | |
content = textLevelSemanticsCheck(content); | |
return content; | |
}); | |
val = val.replace(/''(.*?)''/g, (m, p1) => { | |
return ` **${p1.trim()}** `; | |
}) | |
return val.trim(); | |
} | |
} | |
module.exports = Convert; |
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
const Convert = require('./Convert.js'); | |
const convert = new Convert(); | |
const fl = require('node-filelist'); | |
const files = [ "****" ]; //読み込みたいファイルディレクトリまたはパス(配列なので複数指定可) | |
const option = { "ext" : "md" }; //読み込みたいファイルの拡張子(指定がない場合は全てのファイルを読み込みます) | |
const fs = require("fs-extra"); | |
fl.read(files, option , (results) => { | |
for(let i = 0; i < results.length; i++){ | |
const filePath = results[i].path; | |
console.log(filePath); | |
if (/.+\/\.(.+?)([?#;].*)?$/.test(filePath)) { | |
continue; | |
} | |
const newFilePath = filePath.replace('KCT_KC', 'KCT_KC_1'); | |
dir = newFilePath.substring(0, newFilePath.lastIndexOf('/')) + '/'; | |
console.log(dir); | |
if (!fs.existsSync(dir)) { | |
fs.mkdirsSync(dir); | |
} | |
let text = fs.readFileSync(filePath, 'utf8'); | |
text = convert.execute(text); | |
fs.writeFileSync(newFilePath, text); | |
} | |
}); |
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
{ | |
"name": "converter", | |
"version": "1.0.0", | |
"description": "", | |
"main": "main.js", | |
"dependencies": { | |
"fs-extra": "^9.0.0", | |
"node-filelist": "^1.0.0" | |
}, | |
"devDependencies": {}, | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"author": "", | |
"license": "ISC" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment