Index.html
<html>
<head>
<title>正则表达式图形化工具</title>
<link rel="stylesheet" type="text/css" href="css/common.css">
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<style type="text/css">
.animation-box{
width: 600px;
height: 400px;
}
.info-box{
width: 600px;
height: 400px;
overflow: scroll;
}
.flow-chart{
text-align: center;
}
.flow-chart ul{
}
.flow-chart ul li{
margin-top: 5px;
border: 1px solid red;
padding: 5px;
margin: 5px;
}
.flow-chart ul li .reg{
background-color: #DB3434;
color: #fff;
padding: 5px 80px;
font-size: 18px;
}
.flow-chart ul li .res{
background-color: #146E1F;
color: #fff;
padding: 2px 80px;
font-size: 18px;
height: 18px;
line-height: 18px;
}
.content{
display: inline-block;
vertical-align: middle;
}
.branch{
display: inline-block;
vertical-align: middle;
}
#source{
width:800px;
margin:20px auto 0;
text-align:center;
margin-bottom: 5px;
}
#source ul li{
margin-top: 5px;
}
#regexpInput,#textInput{
width: 500px;
height: 30px;
border: 1px solid gray;
padding: 0 5px;
}
#beginBtn{
padding: 5px 15px;
}
text{
font-size: 20px;
}
</style>
<div id="source">
<ul >
<li>正则表达式:<input type="text" id="regexpInput"/></li>
<li><p style="color:red;">请填入正则表达式,支持格式:<br />1,没有斜线,如:<span style="font-size:20px;">\d|\s</span> ; <br />
2,有斜线,如:<span style="font-size:20px;">/\d|\s/</span></p></li>
<li><p style="color:red;">点击展示按钮,将会在下方生成正则流程图;同时在控制台中将会输出该正则的数据结构。</p></li>
<li><p style="color:red;">请在高级浏览器中运行。</p></li>
<li style="display:none;">需要匹配的字符串:<input type="text" id="textInput"/></li>
<li><input type="button" id="beginBtn" value="展示流程图" /></li>
</ul>
</div>
<style type="text/css">
path{
stroke:#000000;
fill:none;
stroke-width: 2px
}
rect{
fill:#bada55;
}
text{
fill:#000;
}
tspan{
}
</style>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100%" width="100%" id="flowChart">
<g></g>
</svg>
<div style="height:20px;position:fixed;top:0;left:0;text-algin:center;font-size:20px;font-weight:bold;background-color:white;" id="regFixed"></div>
</body>
<script type="text/javascript" src="js/require.js"></script>
<script src="js/d3.v3.min.js" charset="utf-8"></script>
<script src="js/dagre-d3.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<script type="text/javascript">
require.config({
baseUrl: "js/"
});
var regexpInputDom=document.getElementById('regexpInput');
var textInputDom=document.getElementById('textInput');
var beginBtnDom=document.getElementById('beginBtn');
beginBtnDom.onclick=function(){
require(['main'],function(main){
main.init(textInputDom.value,regexpInputDom.value);
});
}
$(window).scroll(function(){
if($(this).scrollTop()>40&&$('#regexpInput').val()!==''){
$('#regFixed').html($('#regexpInput').val()).show();
}else{
$('#regFixed').hide();
}
});
</script>
</html>
common.css
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{padding:0;margin:0}
address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:normal}
pre,code,kbd,samp,tt{font-family:monospace;line-height:100%}
q:before,q:after{content:''}
abbr,acronym{border:0;font-variant:normal}
sup{vertical-align:text-top}
sub{vertical-align:text-bottom}
ul,li{list-style:none}
em,.em{font-style:italic}
strong,.strong{font-weight:bold}
img{border:0;vertical-align:top;-ms-interpolation-mode:bicubic}
table{border-collapse:collapse;border-spacing:0}
caption,th{text-align:left}
fieldset{border:none}
legend{display:none}
textarea{resize:none}
button{overflow:visible}
label,button{cursor:pointer;_cursor:hand}
input,select,textarea{font:normal normal 14px "Verdana";color:#333;outline:none;}
textarea,input[type|="text"],input[type|="password"]{-webkit-appearance:none}
abbr[title],
acronym[title]{border-bottom:1px dotted;cursor:help}
input[type="hidden"]{display:none!important}
h1,.h1{font-size:18px;font-weight:bold}
h2,.h2{font-size:16px;font-weight:bold}
h3,.h3{font-size:14px;font-weight:bold}
h4,.h4{font-size:14px;font-weight:normal}
h5,.h5{font-size:12px;font-weight:normal}
h6,.h6{font-size:12px;font-weight:normal}
.f12px{font-size:12px}
.f14px{font-size:14px}
.fl{float:left}
.fr{float:right}
a{color:#333;text-decoration:none;outline:none}
a:hover{text-decoration:underline;outline:none}
.blod a,.blod{font-weight:bold}
.center{text-align:center}
.mid{vertical-align:middle}
.hide{display:none}
.submit{cursor:pointer;border:0}
.collr,.collr .colr{overflow:hidden;zoom:1}
.collr .coll{float:left;_margin-right:-3px}
.collr .coll img{vertical-align:top}
.p-collr{position:relative;zoom:1}
.p-collr .coll{position:absolute;left:0;top:0}
.p-collr .colr{zoom:1}
.clear{clear:both;font-size:0px;width:0px;height:0;visibility:hidden;line-height:0}
.clearfix{*zoom:1}
.clearfix:after{content:"";display:block;height:0;clear:both;visibility:hidden}
body{background-color:#FFF;font:12px/22px Arial,Tahoma,Verdana,"\5B8B\4F53";color:#333}
main.js
define('main', ['draw', 'meta', 'NFA'], function(draw, meta, NFA) {
function init(inputStr, regexpStr) {
if (regexpStr === '') {
return;
} else if (/^\//.test(regexpStr) && /(?:\/|\/i|\/g|\/m)$/.test(regexpStr)) {
regexpStr = regexpStr.replace(/^\//, '').replace(/(\/|\/i|\/g|\/m)$/, '');
}
try {
var reg = new RegExp(regexpStr);
} catch (e) {
alert(e);
}
var metas = [];
meta(regexpStr, metas, 1);
console.log('该正则表达式的生成数据结构如下:');
console.log(metas);
var obj = document.getElementById('flowChart');
draw.drawModel(metas, obj);
}
return {
init: init
}
});
draw.js
define('draw', ['description'], function(des) {
var obj = {
drawModel: function(arr) {
var g = new dagreD3.graphlib.Graph().setGraph({});
g.setGraph({
nodesep: 70,
ranksep: 50,
rankdir: "LR",
marginx: 20,
marginy: 20
});
var preNodes = [];
var depth = [];
var topIndex = 0;
function drawBranch(branchArr, curPreNodes, type, operator) {
depth.push(1);
var curNodeName = '';
var bPreNodes = [];
var len = depth.length;
if (type) {
preNodes = curPreNodes;
curNodeName = 'node' + topIndex + depth.length + 't' + 's' + Math.random();
g.setNode(curNodeName, {
label: '进入' + type
});
if (type === '捕获分组') {
g.node(curNodeName).style = "fill:#3B639F";
} else if (type === '非捕获分组') {
g.node(curNodeName).style = "fill:#f5f8fc";
} else if (type === '肯定环视') {
g.node(curNodeName).style = "fill:#10c2ce";
} else if (type === '否定环视') {
g.node(curNodeName).style = "fill:#BBFFFF";
}
preNodes.forEach(function(nodeName) {
g.setEdge(nodeName, curNodeName, {
label: ''
});
});
curPreNodes = [curNodeName];
preNodes = curPreNodes;
}
for (var i = 0, len = branchArr.length; i < len; i++) {
if (len !== 1) {
preNodes = curPreNodes;
curNodeName = 'node' + topIndex + depth.length + i + 's' + Math.random();
g.setNode(curNodeName, {
label: '进入' + '分支'
});
g.node(curNodeName).style = "fill:#aa73d1";
preNodes.forEach(function(nodeName) {
g.setEdge(nodeName, curNodeName, {
label: ''
});
});
preNodes = [curNodeName];
}
for (var j = 0, c = branchArr[i].length; j < c; j++) {
if (depth.length === 1) {
topIndex = '' + i + j;
}
if (branchArr[i][j].branch.length === 0) {
curNodeName = 'node' + topIndex + depth.length + i + j + Math.random();
g.setNode(curNodeName, {
label: des(branchArr[i][j].atom, j) + ' 匹配' + des(branchArr[i][j].operator, j, true)
});
preNodes.forEach(function(nodeName) {
g.setEdge(nodeName, curNodeName, {
label: ''
});
});
preNodes = [curNodeName];
} else {
preNodes = drawBranch(branchArr[i][j].branch, preNodes, branchArr[i][j].type, branchArr[i][j].operator);
}
}
if (len !== 1) {
curNodeName = 'node' + topIndex + depth.length + i + 'e' + Math.random();
g.setNode(curNodeName, {
label: ('分支' + '结束') + (operator ? ' 匹配' + des(operator, undefined, true) : '')
});
g.node(curNodeName).style = "fill:#aa73d1";
preNodes.forEach(function(nodeName) {
g.setEdge(nodeName, curNodeName, {
label: ''
});
});
preNodes = [curNodeName];
}
bPreNodes.push(preNodes);
}
if (type) {
curNodeName = 'node' + topIndex + depth.length + 't' + 'e' + Math.random();
g.setNode(curNodeName, {
label: (type + '结束') + (operator ? ' 匹配' + des(operator, undefined, true) : '')
});
if (type === '捕获分组') {
g.node(curNodeName).style = "fill:#3B639F";
} else if (type === '非捕获分组') {
g.node(curNodeName).style = "fill:#f5f8fc";
} else if (type === '肯定环视') {
g.node(curNodeName).style = "fill:#10c2ce";
} else if (type === '否定环视') {
g.node(curNodeName).style = "fill:#BBFFFF";
}
bPreNodes.forEach(function(nodeName) {
g.setEdge(nodeName, curNodeName, {
label: ''
});
});
preNodes = [curNodeName];
bPreNodes = preNodes;
}
depth.pop();
return bPreNodes;
}
g.setNode('start', {
label: 'start',
title:'ssss'
});
preNodes.push('start');
g.node('start').style = "fill: #999";
preNodes = drawBranch(arr, preNodes);
g.setNode('end', {
label: 'end'
});
g.node('end').style = "fill: #999";
preNodes.forEach(function(nodeName) {
g.setEdge(nodeName, 'end', {
label: ''
});
});
g.nodes().forEach(function(v) {
var node = g.node(v);
node.rx = node.ry = 5;
});
var svg = d3.select("svg"),
inner = svg.select("g");
var render = new dagreD3.render();
render(inner, g);
var zoom = d3.behavior.zoom().on("zoom", function() {
inner.attr("transform", "scale(" + d3.event.scale + ")");
});
svg.call(zoom);
var initialScale = Math.max(0.6, $(window).width() / g.graph().width);
zoom
.scale(initialScale)
.event(svg);
svg.attr('width', g.graph().width * initialScale);
d3.selectAll('g').data([1,2,3,4,5,6,7]).enter().append('text').text(function(d){return d;});
}
};
return obj;
});
define('meta', function() {
var operatorsReg = /^(?:\+\?|\*\?|\?\?|\+|\*|\?|\{\d+,\d*\}\??|\{\d+,?\}\??)/;
function findOperator(str) {
var oResult = operatorsReg.exec(str);
if (oResult === null) {
return {
operator: '',
length: 0
}
} else {
return {
operator: oResult[0],
length: oResult[0].length
}
}
}
function readGroup(str) {
var r = /[(|)]|\(\?\:/g,
res,
leftGroup = [],
rightGroup = [],
orGroup = [];
var tempStr = str.replace(/\\./g, '&&');
while (res = r.exec(str)) {
if (res[0] === '(' || res[0] === '(?:') {
leftGroup.push(r.lastIndex - 1);
} else if (res[0] === '|') {
orGroup.push(r.lastIndex - 1);
} else if (res[0] === ')') {
rightGroup.push(r.lastIndex - 1);
}
}
var paraArr = [],
tempParaCL = 0,
tempParaCR = 0,
paraL = 0,
paraR = 0;
for (var k = 0, b = 0, c = tempStr.length; k < c; k++) {
tempParaCL = 0,
tempParaCR = 0,
paraL = 0,
paraR = 0;
if (tempStr.charAt(k) === '(') {
paraL = k;
for (b = k + 1; b < c; b++) {
if (tempStr.charAt(b) === ')') {
tempParaCR++;
if (tempParaCR > tempParaCL) {
paraR = b;
paraArr.push({
l: paraL,
r: paraR
});
break;
}
} else if (tempStr.charAt(b) === '(') {
tempParaCL++;
}
}
}
}
var resArr = [];
if (leftGroup.length === 0) {
if (orGroup.length) {
for (var j = 0, l = orGroup.length; j < l; j++) {
if (j === 0) {
resArr.push({
l: 0,
r: orGroup[j] - 1,
or: orGroup[j]
});
} else {
resArr.push({
l: orGroup[j - 1] + 1,
r: orGroup[j] - 1,
or: orGroup[j]
});
}
}
} else {
resArr = [];
}
} else {
if (orGroup.length) {
var tempCountL = 0,
tempCountR = 0,
realL = 0,
realR = 0;
for (var j = 0, l = orGroup.length; j < l; j++) {
tempCountL = 0,
tempCountR = 0,
realL = 0,
realR = 0;
for (var n = orGroup[j]; n > 0; n--) {
if (tempStr[n] === ')') {
tempCountR++;
} else if (tempStr[n] === '(') {
tempCountL++;
if (tempCountL > tempCountR) {
realL = n;
break;
}
}
}
tempCountL = 0,
tempCountR = 0;
for (n = orGroup[j]; n < tempStr.length; n++) {
if (tempStr[n] === '(') {
tempCountL++;
} else if (tempStr[n] === ')') {
tempCountR++;
if (tempCountR > tempCountL) {
realR = n;
break;
}
}
}
resArr.push({
l: realL,
r: realR,
or: orGroup[j]
});
}
} else {
resArr = [];
}
}
return {
leftGroup: leftGroup,
rightGroup: rightGroup,
orGroup: orGroup,
resArr: resArr,
paraArr: paraArr
}
}
function getRightPara(leftPara, paraArr) {
for (var i = 0, len = paraArr.length; i < len; i++) {
if (paraArr[i].l === leftPara) {
return paraArr[i].r;
}
}
}
var groupIndex = 1;
function parseMeta(str, arr, isInit) {
if (isInit) {
groupIndex = 1;
}
var groupObj = readGroup(str);
var atIndex = 0,
atChar = '',
tempAtom = '',
isUnicode = false,
isHex = false,
paraRight = 0,
finaIndex = 0;
for (var len = str.length; atIndex < len; atIndex++) {
isUnicode = false;
isHex = false;
atChar = str.charAt(atIndex);
if (arr[finaIndex] === undefined) {
arr[finaIndex] = [];
}
arr[finaIndex][arr[finaIndex].length] = {};
if (atChar === '\\') {
subStr = str.substring(atIndex + 1);
if (/^u[0-9a-fA-F]{4}/.test(subStr)) {
tempAtom = '\\\\' + /^u[0-9a-fA-F]{4}/.exec(subStr)[0];
isUnicode = true;
atIndex += 5;
} else {
isUnicode = false;
}
if (/^x[0-9a-fA-F]{2}/.test(subStr)) {
tempAtom = '\\\\' + /^x[0-9a-fA-F]{2}/.exec(subStr);
atIndex += 3;
isHex = true;
} else {
isHex = false;
}
if (!isUnicode && !isHex) {
tempFun = makeMeta(atChar);
atIndex++;
tempAtom = tempFun(str.charAt(atIndex));
}
arr[finaIndex][arr[finaIndex].length - 1].index = atIndex;
arr[finaIndex][arr[finaIndex].length - 1].atom = tempAtom;
operatorObj = findOperator(str.substring(atIndex + 1));
arr[finaIndex][arr[finaIndex].length - 1].operator = operatorObj.operator;
atIndex += operatorObj.length;
arr[finaIndex][arr[finaIndex].length - 1].branch = [];
arr[finaIndex][arr[finaIndex].length - 1].length = 0;
} else if (atChar === '[') {
arr[finaIndex][arr[finaIndex].length - 1].index = atIndex;
subStr = str.substring(atIndex);
tempFun = makeMeta('');
tempAtom = tempFun((/\[.*?(?=([^\\])(\]))/.exec(subStr)[0]) + (/\[.*?(?=([^\\])(\]))/.exec(subStr)[1]) + (/\[.*?(?=([^\\])(\]))/.exec(subStr)[2]));
atIndex += tempAtom.length;
arr[finaIndex][arr[finaIndex].length - 1].atom = tempAtom;
operatorObj = findOperator(str.substring(atIndex));
arr[finaIndex][arr[finaIndex].length - 1].operator = operatorObj.operator;
atIndex += operatorObj.length - 1;
arr[finaIndex][arr[finaIndex].length - 1].branch = [];
arr[finaIndex][arr[finaIndex].length - 1].length = 0;
} else if (atChar === '|') {
arr[finaIndex].pop();
finaIndex++;
if (atIndex === 0) {
arr[finaIndex - 1] = [];
arr[finaIndex - 1][0] = {};
arr[finaIndex - 1][0].index = atIndex;
tempAtom = '';
arr[finaIndex - 1][0].atom = tempAtom;
arr[finaIndex - 1][0].operator = '';
arr[finaIndex - 1][0].branch = [];
arr[finaIndex - 1][0].length = 0;
}
if (atIndex === str.length - 1 || str[atIndex + 1] === '|') {
arr[finaIndex] = [];
arr[finaIndex][0] = {};
arr[finaIndex][0].index = atIndex;
tempAtom = '';
arr[finaIndex][0].atom = tempAtom;
arr[finaIndex][0].operator = '';
arr[finaIndex][0].branch = [];
arr[finaIndex][0].length = 0;
}
} else if (atChar === '(') {
paraRight = getRightPara(atIndex, groupObj.paraArr);
arr[finaIndex][arr[finaIndex].length - 1].index = atIndex;
subStr = str.substring(atIndex + 1, paraRight);
if (/^\?:/.test(subStr)) {
atIndex += 2;
subStr = /^\?:(.*)$/.exec(subStr)[1];
arr[finaIndex][arr[finaIndex].length - 1].type = '非捕获分组';
} else if (/^\?=/.test(subStr)) {
atIndex += 2;
subStr = /^\?=(.*)$/.exec(subStr)[1];
arr[finaIndex][arr[finaIndex].length - 1].type = '肯定环视';
} else if (/^\?!/.test(subStr)) {
atIndex += 2;
subStr = /^\?!(.*)$/.exec(subStr)[1];
arr[finaIndex][arr[finaIndex].length - 1].type = '否定环视';
} else {
arr[finaIndex][arr[finaIndex].length - 1].type = '捕获分组' + groupIndex++;
}
atIndex += subStr.length + 1;
arr[finaIndex][arr[finaIndex].length - 1].atom = subStr;
operatorObj = findOperator(str.substring(atIndex + 1));
arr[finaIndex][arr[finaIndex].length - 1].operator = operatorObj.operator;
atIndex += operatorObj.length;
arr[finaIndex][arr[finaIndex].length - 1].branch = [];
parseMeta(subStr, arr[finaIndex][arr[finaIndex].length - 1].branch);
arr[finaIndex][arr[finaIndex].length - 1].length = 0;
} else {
arr[finaIndex][arr[finaIndex].length - 1].index = atIndex;
tempAtom = str.charAt(atIndex);
arr[finaIndex][arr[finaIndex].length - 1].atom = tempAtom;
operatorObj = findOperator(str.substring(atIndex + 1));
arr[finaIndex][arr[finaIndex].length - 1].operator = operatorObj.operator;
atIndex += operatorObj.length;
arr[finaIndex][arr[finaIndex].length - 1].branch = [];
arr[finaIndex][arr[finaIndex].length - 1].length = 0;
}
}
}
function makeMeta(firstChar) {
var meta = firstChar;
return function(atChar) {
meta += atChar;
return meta;
}
}
return parseMeta;
});
运行结果如图: