├── .gitignore ├── text-diagram ├── welcome.htm ├── examples │ ├── object_declaration.txt │ ├── tcp_1.txt │ ├── todd.txt │ └── tcp_2.txt ├── t.gif ├── favicon.ico ├── contact.htm ├── seq.htm ├── style.css ├── index.htm └── text-diagram.js └── README /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /text-diagram/welcome.htm: -------------------------------------------------------------------------------- 1 | Welcome to Text Diagram! 2 | -------------------------------------------------------------------------------- /text-diagram/examples/object_declaration.txt: -------------------------------------------------------------------------------- 1 | object x 2 | object y 3 | object z 4 | z->x 5 | -------------------------------------------------------------------------------- /text-diagram/t.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weidagang/text-diagram/HEAD/text-diagram/t.gif -------------------------------------------------------------------------------- /text-diagram/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weidagang/text-diagram/HEAD/text-diagram/favicon.ico -------------------------------------------------------------------------------- /text-diagram/examples/tcp_1.txt: -------------------------------------------------------------------------------- 1 | note right of Server: Wait for client 2 | Client->Server: SYNC 3 | note right of Server: Received SYNC, send SYNC + ACK 4 | 5 | -------------------------------------------------------------------------------- /text-diagram/examples/todd.txt: -------------------------------------------------------------------------------- 1 | Todd->April: April, what're you doing? 2 | note right of April: Shopping on taobao. 3 | April->Todd: Well, I'm taobaoing. 4 | Todd->Monad: And you? 5 | Monad->Todd: I'm reading book. 6 | Todd->Monad: Good boy! -------------------------------------------------------------------------------- /text-diagram/examples/tcp_2.txt: -------------------------------------------------------------------------------- 1 | note left of Server: LISTEN 2 | Client->Server: #1 SYNC 3 | note right of Client: SYNC_SENT 4 | note left of Server: SYNC-RECEIVED 5 | Server->Client: #2 SYNC + ACK 6 | note right of Client: ESTABLISHED 7 | Client->Server: #3 ACK 8 | note left of Server: ESTABLISHED 9 | -------------------------------------------------------------------------------- /text-diagram/contact.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Text Diagram - About me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Author:Todd Wei
Email:weidagang[at]gmail.com
Blog:http://www.cnblogs.com/weidagang2046
Weibo:@weidagang
29 | 30 | 31 | -------------------------------------------------------------------------------- /text-diagram/seq.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 18 | 19 | 20 |
21 | 22 | 23 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /text-diagram/style.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | color : black; 3 | display: inline-block; 4 | zoom: 1; /* zoom and *display = ie7 hack for display:inline-block */ 5 | *display: inline; 6 | vertical-align: baseline; 7 | margin: 0 2px; 8 | outline: none; 9 | cursor: pointer; 10 | text-align: center; 11 | text-decoration: none; 12 | font: 14px/100% Arial, Helvetica, sans-serif; 13 | padding: .5em 2em .55em; 14 | text-shadow: 0 1px 1px rgba(0,0,0,.3); 15 | -webkit-border-radius: .5em; 16 | -moz-border-radius: .5em; 17 | border-radius: .5em; 18 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2); 19 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2); 20 | box-shadow: 0 1px 2px rgba(0,0,0,.2); 21 | } 22 | 23 | .btn:hover { 24 | text-decoration: none; 25 | } 26 | 27 | .btn:active { 28 | position: relative; 29 | top: 1px; 30 | } 31 | 32 | .white { 33 | color: #606060; 34 | border: solid 1px #b7b7b7; 35 | background: #fff; 36 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#d1d1d1)); 37 | background: -moz-linear-gradient(top, #fff, #d1d1d1); 38 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#d1d1d1'); 39 | } 40 | 41 | .white:hover { 42 | background: #d1d1d1; 43 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#c1c1c1)); 44 | background: -moz-linear-gradient(top, #fff, #c1c1c1); 45 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#c1c1c1'); 46 | } 47 | 48 | .white:active { 49 | color: #d1d1d1; 50 | background: -webkit-gradient(linear, left top, left bottom, from(#d1d1d1), to(#fff)); 51 | background: -moz-linear-gradient(top, #d1d1d1, #fff); 52 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#d1d1d1', endColorstr='#ffffff'); 53 | } 54 | 55 | .code_box { 56 | font-family:'Bitstream Vera Sans Mono', 'Consolas', 'Courier New'; 57 | line-height:16px; 58 | font-size: 0.8em; 59 | 60 | position:relative; 61 | padding:5px; 62 | border:2px solid #999999; 63 | color:#333; 64 | background:#fff; 65 | /* css3 */ 66 | -webkit-border-radius:10px; 67 | -moz-border-radius:10px; 68 | border-radius:10px; 69 | } 70 | 71 | #btn_div { 72 | padding-top: 10px; 73 | padding-bottom: 5px; 74 | } 75 | 76 | #canvas pre { 77 | font-family:'Bitstream Vera Sans Mono', 'Lucida Console', 'Courier New'; 78 | font-size: 0.8em; 79 | } 80 | 81 | #title { 82 | font-size: 1.2em; 83 | font-weight: bold; 84 | padding-right: 10px; 85 | } 86 | 87 | #header { 88 | padding-left: 5px; 89 | } 90 | 91 | #header a { 92 | font-size: 0.8em; 93 | } 94 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | TextDiagam is a web tool for drawing UML sequence diagram in pure text. 2 | 3 | http://weidagang.github.com/text-diagram 4 | 5 | Input: 6 | 7 | object April Todd Monad 8 | April->April: Prepare food for lunch. 9 | space 5 10 | note left of April: Lunch is ready. 11 | April->Todd: Todd, what are you doing? 12 | note right of Todd: @_@ 13 | Todd->April: Well...\nI'm programming. 14 | space 5 15 | April->Monad: How about you? 16 | Monad->April: I'm reading book. 17 | April->Monad: Good boy! 18 | note right of Monad: ^_^ 19 | 20 | 21 | Output: 22 | 23 | +-------+ +-------+ +-------+ 24 | | April | | Todd | | Monad | 25 | +-------+ +-------+ +-------+ 26 | | | | 27 | | Prepare food for lunch. | | 28 | |------------------------ | | 29 | | | | | 30 | |<----------------------- | | 31 | | | | 32 | | | | 33 | | | | 34 | | | | 35 | | | | 36 | ------------------\ | | | 37 | | Lunch is ready. |-| | | 38 | |-----------------| | | | 39 | | | | 40 | | Todd, what are you doing? | | 41 | |------------------------------>| | 42 | | | ------\ | 43 | | |-| @_@ | | 44 | | | |-----| | 45 | | | | 46 | | Well... | | 47 | | I'm programming. | | 48 | |<------------------------------| | 49 | | | | 50 | | | | 51 | | | | 52 | | | | 53 | | | | 54 | | | | 55 | | How about you? | | 56 | |------------------------------------------>| 57 | | | | 58 | | I'm reading book. | 59 | |<------------------------------------------| 60 | | | | 61 | | Good boy! | | 62 | |------------------------------------------>| 63 | | | | ------\ 64 | | | |-| ^_^ | 65 | | | | |-----| 66 | | | | 67 | -------------------------------------------------------------------------------- /text-diagram/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Text Diagram 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 36 | 37 |
38 | 39 | 40 |
41 | 42 |
43 |
44 | 45 | 46 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /text-diagram/text-diagram.js: -------------------------------------------------------------------------------- 1 | /* 2 | * text-diagram.js 3 | * 4 | * Copyright 2011, Todd Wei 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * 7 | * Author: weidagang@gmail.com 8 | */ 9 | 10 | /* 11 | * Sequence diagram grammar: 12 | * 13 | * ::= | 14 | * ::= 15 | * ::= | | | 16 | * ::= "object" 17 | * ::= "->" 18 | * ::= "of" 19 | * ::= 20 | * ::= ([a-z]|[A-Z]|[0-9]|_)+ 21 | * ::= | ":" 22 | * ::= ":" 23 | * ::= 24 | * ::= ';' | '\n' | EOF 25 | */ 26 | 27 | var nl = ie ? '\r' : '\n'; 28 | 29 | /** 30 | * The main function to draw UML sequence diagram. 3 major steps: 31 | * 1) parse the source code into AST; 32 | * 2) convert the AST to ASCII image objects; 33 | * 3) convert image objects to HTML; 34 | */ 35 | function sequence_diagram(in_src) { 36 | var ast = parser.sequence_diagram(in_src); 37 | //console.log('ast:', ast); 38 | 39 | if (null == ast) { 40 | return null; 41 | } 42 | 43 | var cimage = html_render.to_cimage(ast); 44 | var dom_ele = html_render.to_html(cimage); 45 | return dom_ele; 46 | }; 47 | 48 | //render diagram as html 49 | var html_render = (function() { 50 | //cpoint 51 | function _cpoint(in_c, in_x, in_y, in_z) { 52 | return { c : in_c, x : in_x, y : in_y, z : in_z }; 53 | } 54 | 55 | //create a ccanvas 56 | function _ccanvas(in_x, in_y) { 57 | var m = new Array(in_y); 58 | for (i = 0; i < in_y; ++i) { 59 | m[i] = new Array(in_x); 60 | } 61 | return m; 62 | } 63 | 64 | //convert ccanvas to DOM element 65 | function _to_html(in_m) { 66 | var pre = document.createElement('pre'); 67 | pre.id = 'diagram'; 68 | 69 | for (y = 0; y < in_m.length; ++y) { 70 | for (x = 0; x < in_m[y].length; ++x) { 71 | var c = in_m[y][x] ? in_m[y][x].c : ' '; 72 | pre.appendChild(document.createTextNode(c)); 73 | } 74 | pre.appendChild(document.createTextNode(nl)); 75 | } 76 | 77 | return pre; 78 | } 79 | 80 | function _add_meta(in_ast) { 81 | in_ast.meta = {}; 82 | 83 | var meta = in_ast.meta; 84 | meta.objs = []; 85 | meta.obj_idxes = {}; 86 | meta.boxes = {}; 87 | meta.lines = {}; 88 | meta.x_spans = {}; 89 | meta.notes = {}; 90 | meta.messages = {}; 91 | meta.statements = []; 92 | 93 | function _traverse(ast) { 94 | ast.meta = ast.meta || {}; 95 | 96 | // set index for each object (participant) 97 | function _add_obj(obj) { 98 | if (null == meta.obj_idxes[obj]) { 99 | var idx = meta.objs.length; 100 | meta.obj_idxes[obj] = idx; 101 | meta.objs.push(obj); 102 | meta.boxes[obj] = {}; 103 | meta.lines[obj] = {}; 104 | meta.x_spans[obj] = {}; 105 | meta.notes[obj] = []; 106 | meta.messages[obj] = []; 107 | } 108 | } 109 | 110 | if ('object_declaration' == ast.type) { 111 | for (var i in ast.attr.names) { 112 | _add_obj(ast.attr.names[i]); 113 | } 114 | } 115 | else if ('message_statement' == ast.type) { 116 | meta.statements.push(ast); 117 | 118 | var s = ast.attr.sender; 119 | var r = ast.attr.receiver; 120 | 121 | //object index 122 | _add_obj(s); 123 | _add_obj(r); 124 | ast.meta.sender_index = meta.obj_idxes[s]; 125 | ast.meta.receiver_index = meta.obj_idxes[r]; 126 | 127 | var left_obj = meta.objs[Math.min(ast.meta.sender_index, ast.meta.receiver_index)]; 128 | var right_obj = meta.objs[Math.max(ast.meta.sender_index, ast.meta.receiver_index)]; 129 | ast.meta.left_obj = left_obj; 130 | ast.meta.right_obj = right_obj; 131 | 132 | meta.messages[left_obj].push(ast); 133 | } 134 | else if ('note_statement' == ast.type) { 135 | meta.statements.push(ast); 136 | 137 | var obj = ast.attr.object; 138 | var side = ast.attr.side; 139 | var content = ast.attr.content; 140 | 141 | _add_obj(obj); 142 | 143 | meta.notes[obj].push(ast); 144 | } 145 | 146 | for (var i in ast.children) { 147 | _traverse(ast.children[i]); 148 | } 149 | } 150 | 151 | _traverse(in_ast); 152 | 153 | //calculate position for each object (participant) 154 | for (var i = 0; i < meta.objs.length; ++i) { 155 | var obj = meta.objs[i]; 156 | var box_width = _box_width(obj); 157 | var half_box_width = (box_width - 1) / 2; 158 | 159 | meta.boxes[obj].x1 = (0 == i ? 0 : meta.boxes[meta.objs[i-1]].x2 + 1); 160 | meta.x_spans[obj].x2 = box_width; 161 | 162 | var pre_line_offset = (0 == i ? -1 : meta.lines[meta.objs[i-1]].x_offset); 163 | 164 | // 1) box.x1 165 | //// left note 166 | for (var j = 0; j < meta.notes[obj].length; ++j) { 167 | var note_ast = meta.notes[obj][j]; 168 | var note_width = _note_width(note_ast.attr.content); 169 | if ('left' == note_ast.attr.side) { 170 | meta.boxes[obj].x1 = Math.max(meta.boxes[obj].x1, pre_line_offset + 1 + note_width + 1 - half_box_width); 171 | } 172 | } 173 | 174 | //// previous right note 175 | if (i > 0) { 176 | var pre_obj = meta.objs[i-1]; 177 | for (var k = 0; k < meta.notes[pre_obj].length; ++k) { 178 | var note_ast = meta.notes[pre_obj][k]; 179 | if ('right' == note_ast.attr.side) { 180 | meta.boxes[obj].x1 = Math.max(meta.boxes[obj].x1, meta.lines[pre_obj].x_offset + 1 + _note_width(note_ast.attr.content)); 181 | } 182 | } 183 | } 184 | 185 | //// message 186 | for (var j = 0; j < i; j++) { 187 | var pre_obj = meta.objs[j]; 188 | for (var k = 0; k < meta.messages[pre_obj].length; ++k) { 189 | var msg_ast = meta.messages[pre_obj][k]; 190 | if (msg_ast.meta.right_obj == obj) { 191 | meta.boxes[obj].x1 = Math.max(meta.boxes[obj].x1, meta.lines[pre_obj].x_offset + 1 + _msg_width(msg_ast.attr.message)); 192 | } 193 | } 194 | } 195 | //// previous self message 196 | if (i > 0) { 197 | var pre_obj = meta.objs[i-1]; 198 | for (var j = 0; j < meta.messages[pre_obj].length; ++j) { 199 | var tmp_ast = meta.messages[pre_obj][j]; 200 | if (tmp_ast.meta.sender_index == tmp_ast.meta.receiver_index) { 201 | var message_width = _msg_width(tmp_ast.attr.message); 202 | meta.boxes[obj].x1 = Math.max(meta.boxes[obj].x1, meta.lines[pre_obj].x_offset + 1 + message_width); 203 | } 204 | } 205 | } 206 | 207 | meta.boxes[obj].x2 = meta.boxes[obj].x1 + box_width; 208 | 209 | // 2) line.x_offset 210 | meta.lines[obj].x_offset = meta.boxes[obj].x1 + half_box_width; 211 | 212 | // 3) x_span 213 | meta.x_spans[obj].x1 = meta.boxes[obj].x1; 214 | for (var j = 0; j < meta.notes[obj].length; ++j) { 215 | var note = meta.notes[obj][j]; 216 | var note_width = _note_width(note); 217 | if ('right' == note.side) { 218 | meta.x_spans[obj].x2 = Math.max(meta.x_spans[obj].x2, meta.lines[obj].x_offset + 1 + note_width); 219 | } 220 | } 221 | for (var j = 0; j < meta.messages[obj].length; ++j) { 222 | var tmp_ast = meta.messages[obj][j]; 223 | if (tmp_ast.meta.sender_index == tmp_ast.meta.receiver_index) { 224 | var message_width = _msg_width(tmp_ast.attr.message); 225 | meta.x_spans[obj].x2 = Math.max(meta.x_spans[obj].x2, meta.lines[obj].x_offset + 1 + message_width); 226 | } 227 | } 228 | } 229 | 230 | //get canvas width 231 | var min_x = 0; 232 | var max_x = 0; 233 | for (var i in meta.x_spans) { 234 | min_x = Math.min(meta.x_spans[i].x1, min_x); 235 | max_x = Math.max(meta.x_spans[i].x2, max_x); 236 | } 237 | meta.min_x = min_x; 238 | meta.max_x = max_x; 239 | meta.width = max_x - min_x; 240 | 241 | //get canvas height 242 | function _get_height(ast, in_y_offset) { 243 | ast.meta.y1 = in_y_offset; 244 | 245 | if ('object_declaration' == ast.type) { 246 | ast.meta.y2 = in_y_offset; 247 | } 248 | else if ('message_statement' == ast.type) { 249 | if (ast.meta.sender_index == ast.meta.receiver_index) 250 | { 251 | ast.meta.y2 = in_y_offset + 4 + ast.attr.message.split('\\n').length; 252 | } 253 | else 254 | { 255 | ast.meta.y2 = in_y_offset + 2 + ast.attr.message.split('\\n').length;; 256 | } 257 | } 258 | else if ('note_statement' == ast.type) { 259 | ast.meta.y2 = in_y_offset + 2 + ast.attr.content.split('\\n').length; 260 | } 261 | else if ('space_statement' == ast.type) { 262 | ast.meta.y2 = in_y_offset + ast.attr.gap_size; 263 | } 264 | else { 265 | var y_offset; 266 | if ('sequence_diagram' == ast.type) { 267 | y_offset = 3; 268 | } 269 | else { 270 | y_offset = in_y_offset; 271 | } 272 | 273 | if (ast.children.length > 0) { 274 | for (var i in ast.children) { 275 | _get_height(ast.children[i], y_offset); 276 | y_offset = ast.children[i].meta.y2; 277 | } 278 | 279 | ast.meta.y2 = ast.children[ast.children.length - 1].meta.y2; 280 | } 281 | else { 282 | ast.meta.y2 = ast.meta.y1; 283 | } 284 | 285 | if ('sequence_diagram' == ast.type) { 286 | ast.meta.y2 += 1; 287 | } 288 | } 289 | 290 | return ast.meta.y2 - ast.meta.y1; 291 | } 292 | 293 | _get_height(in_ast, 0); 294 | meta.height = in_ast.meta.y2; 295 | } 296 | 297 | //convert ast to cimage 298 | function _to_cimage(in_ast) { 299 | //add meta info to tree 300 | _add_meta(in_ast); 301 | 302 | var meta = in_ast.meta; 303 | 304 | //init canvas 305 | var ccanvas = _ccanvas(meta.width, meta.height); 306 | //console.log(meta.width + ", " + meta.height); 307 | 308 | //name box 309 | for (var i in meta.objs) { 310 | var obj = meta.objs[i]; 311 | var cbox = _cbox(obj); 312 | _draw_cpoints(ccanvas, meta.boxes[obj].x1 - meta.min_x, 0, cbox); 313 | } 314 | 315 | //life line 316 | for (var i in meta.objs) { 317 | var obj = meta.objs[i]; 318 | var cline = _lifeline(meta.height - 3); 319 | _draw_cpoints(ccanvas, meta.lines[obj].x_offset - meta.min_x, 3, cline); 320 | } 321 | 322 | //messages and notes 323 | for (var i in meta.statements) { 324 | var ast = meta.statements[i]; 325 | //console.log(ast); 326 | if ('message_statement' == ast.type) { 327 | var s = ast.attr.sender; 328 | var r = ast.attr.receiver; 329 | var leftObj = meta.obj_idxes[s] < meta.obj_idxes[r] ? s : r; 330 | var rightObj = meta.obj_idxes[s] < meta.obj_idxes[r] ? r : s; 331 | var line_len = meta.lines[rightObj].x_offset - meta.lines[leftObj].x_offset - 1; 332 | 333 | var cmessage = _cmessage(ast.attr.message, line_len, s == leftObj, s == r); 334 | 335 | _draw_cpoints(ccanvas, meta.lines[leftObj].x_offset + 1 - meta.min_x, ast.meta.y1, cmessage); 336 | } 337 | else if ('note_statement' == ast.type) { 338 | var obj = ast.attr.object; 339 | var side = ast.attr.side; 340 | var content = ast.attr.content; 341 | var cnote = _cnote(content, 'left' == side); 342 | if ('right' == side) { 343 | _draw_cpoints(ccanvas, meta.lines[obj].x_offset + 1 - meta.min_x, ast.meta.y1, cnote); 344 | } 345 | else if ('left' == side) { 346 | _draw_cpoints(ccanvas, meta.lines[obj].x_offset - 1 - _note_width(content) - meta.min_x, ast.meta.y1, cnote); 347 | } 348 | } 349 | } 350 | 351 | return ccanvas; 352 | } 353 | 354 | function _draw_cpoints(in_canvas, in_x_offset, in_y_offset, in_cpoints) { 355 | for (var i in in_cpoints) { 356 | var p = in_cpoints[i]; 357 | //console.log("x: " + (in_x_offset + p.x) + ", y: " + (in_y_offset + p.y)); 358 | in_canvas[in_y_offset+p.y][in_x_offset+p.x] = { c : p.c, z : p.z }; 359 | } 360 | } 361 | 362 | function _note_width(msg) { 363 | var content = ('string' == typeof(msg) ? msg : msg.attr.content); 364 | var lines = content.split('\\n'); 365 | var max = 0; 366 | for (var i = 0; i < lines.length; ++i) { 367 | if (lines[i].length > max) { 368 | max = lines[i].trim().length; 369 | } 370 | } 371 | return max + 4; 372 | } 373 | 374 | function _note_height(msg) { 375 | var content = ('string' == typeof(msg) ? msg : msg.attr.content); 376 | var lines = content.split('\\n'); 377 | return lines.length + 2; 378 | } 379 | 380 | function _box_width(msg) { 381 | return msg.length % 2 ? msg.length + 4 : msg.length + 5; 382 | } 383 | 384 | function _msg_width(msg) { 385 | var lines = msg.split('\\n'); 386 | var max = 0; 387 | for (var i = 0; i < lines.length; ++i) { 388 | if (lines[i].length > max) { 389 | max = lines[i].trim().length; 390 | } 391 | } 392 | return max + 2; 393 | } 394 | 395 | // create image for note 396 | function _cnote(msg, is_left) { 397 | var i; 398 | var x = _note_width(msg); 399 | var y = _note_height(msg); 400 | 401 | var out_cimage = []; 402 | 403 | //association line 404 | if (is_left) { 405 | out_cimage.push(_cpoint('-', x, 1, 0)); 406 | xoffset = 0; 407 | } 408 | else { 409 | out_cimage.push(_cpoint('-', 0, 1, 0)); 410 | xoffset = 1; 411 | } 412 | 413 | //up and bottom line 414 | for (i = 0; i <= x - 1; ++i) { 415 | out_cimage.push(_cpoint('-', xoffset + i, 0, 0)); 416 | out_cimage.push(_cpoint('-', xoffset + i , y - 1, 0)); 417 | } 418 | 419 | out_cimage.push(_cpoint('\\', xoffset + x - 1, 0, 0)); 420 | 421 | //left and right line 422 | for (i = 0; i < y - 1; ++i) { 423 | out_cimage.push(_cpoint('|', xoffset + 0, i + 1, 0)); 424 | out_cimage.push(_cpoint('|', xoffset + x - 1, i + 1, 0)); 425 | } 426 | 427 | //content 428 | var lines = msg.split('\\n'); 429 | for (var idx = 0; idx < lines.length; ++idx) { 430 | var line = lines[idx].trim(); 431 | 432 | for (i = 1; i < x-1; ++i) { 433 | out_cimage.push(_cpoint(' ', xoffset + i, idx + 1, 0)); 434 | } 435 | 436 | for (i = 2; i < 2 + line.length; ++i) { 437 | out_cimage.push(_cpoint(line.charAt(i-2), xoffset + i, idx + 1, 0)); 438 | } 439 | } 440 | 441 | return out_cimage; 442 | } 443 | 444 | function _cmessage(message, line_len, leftToRight, isSelfMessage) { 445 | var cpoints = []; 446 | 447 | var lines = message.split('\\n'); 448 | var t_length = 0; 449 | for(var idx = 0; idx < lines.length; idx++) { 450 | if(lines[idx].length > t_length) { 451 | t_length = lines[idx].trim().length; 452 | } 453 | } 454 | 455 | if (isSelfMessage) { 456 | line_len = t_length; 457 | 458 | //message 459 | for(var idx = 0; idx < lines.length; idx++) { 460 | for (var i = 0; i < lines[idx].length; ++i) { 461 | cpoints.push(_cpoint(lines[idx].charAt(i), 1 + i, 1 + idx, 0)); 462 | } 463 | } 464 | 465 | //upper line 466 | for (var i = 0; i < line_len + 1; ++i) { 467 | cpoints.push(_cpoint('-', i, 1 + lines.length, 0)); 468 | } 469 | 470 | //bar 471 | cpoints.push(_cpoint('|', line_len, 2 + lines.length, 0)); 472 | 473 | //lower line 474 | cpoints.push(_cpoint('<', 0, 3 + lines.length, 0)); 475 | for (var i = 1; i < line_len + 1; ++i) { 476 | cpoints.push(_cpoint('-', i, 3 + lines.length, 0)); 477 | } 478 | } 479 | else if (leftToRight) { 480 | //message 481 | for(var idx = 0; idx < lines.length; idx++) { 482 | for (var i = 0; i < lines[idx].length; ++i) { 483 | cpoints.push(_cpoint(lines[idx].charAt(i), 1 + i, 1 + idx, 0)); 484 | } 485 | } 486 | 487 | //arrow 488 | for (var i = 0; i < line_len - 1; ++i) { 489 | cpoints.push(_cpoint('-', i, 1 + lines.length, 0)); 490 | } 491 | cpoints.push(_cpoint('>', line_len - 1, 1 + lines.length, 0)); 492 | } 493 | else { 494 | //message 495 | for(var idx = 0; idx < lines.length; idx++) { 496 | for (var i = 0; i < lines[idx].length; ++i) { 497 | cpoints.push(_cpoint(lines[idx].charAt(lines[idx].length - 1 - i), line_len - 1 - i - 1, 1 + idx, 0)); 498 | } 499 | } 500 | 501 | //arrow 502 | cpoints.push(_cpoint('<', 0, 1 + lines.length, 0)); 503 | for (var i = 1; i < line_len; ++i) { 504 | cpoints.push(_cpoint('-', i, 1 + lines.length, 0)); 505 | } 506 | } 507 | 508 | return cpoints; 509 | } 510 | 511 | /* 512 | +------+ 513 | | Todd | 514 | +------+ 515 | */ 516 | function _cbox(obj) { 517 | var i; 518 | var x = obj.length % 2 ? obj.length + 4 : obj.length + 5; 519 | var y = 3; 520 | 521 | var out_cimage = []; 522 | 523 | //up and bottom line 524 | out_cimage.push(_cpoint('+', 0, 0, 0)); 525 | out_cimage.push(_cpoint('+', x - 1, 0, 0)); 526 | out_cimage.push(_cpoint('+', 0, 2, 0)); 527 | out_cimage.push(_cpoint('+', x - 1, 2, 0)); 528 | for (i = 1; i < x - 1; ++i) { 529 | out_cimage.push(_cpoint('-', i, 0, 0)); //m[0][i] = _cpoint('-', 0); 530 | out_cimage.push(_cpoint('-', i , 2, 0)); //m[2][i] = _cpoint('-', 0); 531 | } 532 | 533 | //left and right line 534 | out_cimage.push(_cpoint('|', 0, 1, 0));//m[1][0] = _cpoint('|', 0); 535 | out_cimage.push(_cpoint('|', x - 1, 1, 0));//m[1][x-1] = _cpoint('|', 0); 536 | 537 | //name 538 | for (i = 1; i < x-1; ++i) { 539 | out_cimage.push(_cpoint(' ', i, 1, 0)); //m[1][i] = null; 540 | } 541 | for (i = 2; i < 2 + obj.length; ++i) { 542 | out_cimage.push(_cpoint(obj.charAt(i-2), i, 1, 0)); //m[1][i] = _cpoint(obj[i-2], 0); 543 | } 544 | 545 | return out_cimage; 546 | } 547 | 548 | function _lifeline(in_height) { 549 | var cline = []; 550 | for (var j = 0; j < in_height; ++j) { 551 | cline.push(_cpoint('|', 0, j, 0)); 552 | } 553 | return cline; 554 | } 555 | 556 | function _object_width(name) { 557 | return name.length % 2 ? name.length + 4 : name.length + 5; 558 | } 559 | 560 | 561 | return { 562 | to_html : _to_html, 563 | to_cimage : _to_cimage 564 | }; 565 | })(); 566 | 567 | 568 | //string utility 569 | var util = (function() { 570 | var digits = '0123456789'; 571 | var lowers = 'abcdefghijklmnopqrstuvwxyz'; 572 | var uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 573 | var underscore = '_'; 574 | 575 | var _is_in = function(in_list, in_c) { 576 | return in_list.indexOf(in_c) >= 0; 577 | }; 578 | 579 | return { 580 | is_alpha: function(in_c) { 581 | return _is_in(lowers + uppers, in_c); 582 | }, 583 | 584 | is_digit: function(in_c) { 585 | return _is_in(digits, in_c); 586 | }, 587 | 588 | is_alpha_digit: function(in_c) { 589 | return _is_in(digits + lowers + uppers, in_c); 590 | }, 591 | 592 | is_underscore: function(in_c) { 593 | return underscore == in_c; 594 | }, 595 | 596 | is_whitespace: function(in_c) { 597 | return ' ' == in_c || '\t' == in_c; 598 | }, 599 | 600 | trim: function(in_str) { 601 | return in_str.replace(/^\s+|\s+$/g, '') 602 | } 603 | }; 604 | })(); 605 | 606 | //program parser 607 | var parser = (function() { 608 | //token constructor 609 | function _token(in_type, in_value) { 610 | return { type: in_type, value: in_value }; 611 | } 612 | 613 | //lexical analysis 614 | var _lexical_analyze = function(in_buffer) { 615 | in_buffer += ';'; //append ; 616 | 617 | var r_tokens = []; 618 | var idx = 0; 619 | var buffer_length = in_buffer.length; 620 | var state = 0; 621 | var tmp_buffer = ''; 622 | 623 | var _back = function() { 624 | tmp_buffer = ''; 625 | idx--; 626 | state = 0; 627 | }; 628 | 629 | //state machine 630 | while (idx < buffer_length) { 631 | var c = in_buffer.charAt(idx++); 632 | switch (state) { 633 | case 0: //initial state 634 | if (util.is_alpha_digit(c)) { 635 | tmp_buffer = c; 636 | state = 1; 637 | } 638 | else if ('/' == c) { 639 | state = 3; 640 | } 641 | else if ('-' == c) { 642 | tmp_buffer = c; 643 | state = 2; 644 | } 645 | else if (':' == c) { 646 | r_tokens.push(_token(':', c)); 647 | } 648 | else if (';' == c) { 649 | r_tokens.push(_token(';', c)); 650 | } 651 | else if ('\n' == c || '\r' == c) { 652 | r_tokens.push(_token('newline', c)); 653 | } 654 | else if (util.is_whitespace(c)) { 655 | r_tokens.push(_token('space', c)); 656 | } 657 | else { 658 | r_tokens.push(_token('word', c)); 659 | } 660 | break; 661 | 662 | case 1: //word 663 | if (util.is_alpha_digit(c) || util.is_underscore(c)) { 664 | tmp_buffer = tmp_buffer + c; 665 | } 666 | else { 667 | r_tokens.push(_token('word', tmp_buffer)); 668 | _back(); 669 | } 670 | 671 | break; 672 | 673 | case 2: //arrow 674 | if ('>' == c) { 675 | tmp_buffer += c; 676 | r_tokens.push(_token('arrow', tmp_buffer)); 677 | tmp_buffer = ''; 678 | state = 0; 679 | } 680 | else { 681 | r_tokens.push(_token('word', tmp_buffer)); 682 | _back(); 683 | } 684 | break; 685 | 686 | case 3: //second slash in comment 687 | if ('/' == c) { 688 | state = 4; 689 | } 690 | break; 691 | 692 | case 4: //comment line 693 | if ('\n' == c || '\r' == c) { 694 | // r_tokens.push(_token('newline', c)); 695 | state = 0; 696 | } 697 | break; 698 | 699 | default: 700 | return null; 701 | } 702 | } 703 | 704 | r_tokens.push(_token('eof')); 705 | 706 | return r_tokens; 707 | }; 708 | 709 | //parse program to abstract syntax tree 710 | function sequence_diagram(src) { 711 | var tokens = _lexical_analyze(src); 712 | //console.log('tokens:', tokens); 713 | var ast = _sequence_diagram(tokens); 714 | return ast; 715 | } 716 | 717 | function _sequence_diagram(tokens) { 718 | var r = _statements(tokens, 0); 719 | 720 | if (null != r && r.length == tokens.length) { 721 | return { type: 'sequence_diagram', attr: {}, children : [ r ], offset : 0, length : tokens.length } 722 | } 723 | 724 | return null; 725 | } 726 | 727 | function _statements(in_tokens, in_offset) { 728 | var match_result = { type : 'statements', attr: {}, children : [], offset : in_offset, length : 0 }; 729 | 730 | for (var idx = in_offset; idx < in_tokens.length; ) { 731 | var type = in_tokens[idx].type; 732 | var value = in_tokens[idx].value; 733 | 734 | if ('word' == type) { 735 | var r = null; 736 | if ('object' == value) { 737 | r = _object_declaration(in_tokens, idx); 738 | } 739 | else if ('alt' == value) { 740 | alert('alt statement'); 741 | } 742 | else if ('opt' == value) { 743 | alert('opt statement'); 744 | } 745 | else if ('loop' == value) { 746 | alert('loop statement'); 747 | } 748 | else if ('note' == value) { 749 | r = _note_statement(in_tokens, idx); 750 | } 751 | else if ('space' == value) { 752 | r = _space_statement(in_tokens, idx); 753 | } 754 | else { 755 | r = _message_statement(in_tokens, idx); 756 | } 757 | 758 | if (null == r) { 759 | return null; 760 | } 761 | match_result.children.push(r); 762 | idx += r.length; 763 | } 764 | else { 765 | ++idx; 766 | } 767 | } 768 | 769 | match_result.length = idx - in_offset; 770 | return match_result; 771 | } 772 | 773 | function _is_object(in_str) { 774 | if (null == in_str || 0 == in_str.length) { 775 | return false; 776 | } 777 | 778 | for (var i in in_str) { 779 | var c = in_str.charAt(i); 780 | if (!util.is_alpha_digit(c) && !util.is_underscore(c)) { 781 | return false; 782 | } 783 | } 784 | 785 | return true; 786 | } 787 | 788 | function _is_keyword(in_word) { 789 | var keywords = { 'alt' : true, 'opt' : true, 'loop' : true, 'note' : true, 'space' : true }; 790 | return true == keywords[in_word]; 791 | } 792 | 793 | function _object_declaration(in_tokens, in_offset) { 794 | var match_result = { 795 | type : 'object_declaration', 796 | attr : { name : null, names : [] }, 797 | offset : in_offset, 798 | length : 0 799 | }; 800 | 801 | var state = 0; 802 | for (var i = in_offset; i < in_tokens.length && 2 != state; ++i) { 803 | var type = in_tokens[i].type; 804 | var value = in_tokens[i].value; 805 | 806 | switch(state) { 807 | case 0: //'object' 808 | if ('space' == type) { 809 | continue; 810 | } 811 | if ('object' != value) { 812 | return null; 813 | } 814 | state = 1; 815 | break; 816 | case 1: //names 817 | if ('space' == type) { 818 | continue; 819 | } 820 | else if (';' == type || 'newline' == type || 'eof' == type) { 821 | if (0 == match_result.attr.names.length) { 822 | return null; 823 | } 824 | state = 2; 825 | break; 826 | } 827 | else if ('word' != type || _is_keyword(value) || !_is_object(value)) { 828 | return null; 829 | } 830 | 831 | match_result.attr.names.push(value); 832 | break; 833 | } 834 | } 835 | 836 | if (2 != state) { 837 | return null; 838 | } 839 | 840 | match_result.length = i - in_offset; 841 | return match_result; 842 | } 843 | 844 | function _space_statement(in_tokens, in_offset) { 845 | var match_result = { 846 | type : 'space_statement', 847 | attr : { object : null, side : null, content: ''}, 848 | offset : in_offset, 849 | length : 0 850 | }; 851 | 852 | var state = 0; 853 | for (var i = in_offset; i < in_tokens.length && 2 != state; ++i) { 854 | var type = in_tokens[i].type; 855 | var value = in_tokens[i].value; 856 | 857 | switch(state) { 858 | case 0: //'space' 859 | if ('space' == type) { 860 | continue; 861 | } 862 | if ('space' != value) { 863 | return null; 864 | } 865 | state = 1; 866 | break; 867 | case 1: //number 868 | if ('space' == type) { 869 | continue; 870 | } 871 | var gap_size = parseInt(value); 872 | if (isNaN(gap_size)) { 873 | return null; 874 | } 875 | match_result.attr.gap_size = gap_size; 876 | state = 2; 877 | break; 878 | } 879 | } 880 | 881 | match_result.length = i - in_offset; 882 | return match_result; 883 | } 884 | 885 | function _note_statement(in_tokens, in_offset) { 886 | var match_result = { 887 | type : 'note_statement', 888 | attr : { object : null, side : null, content: ''}, 889 | offset : in_offset, 890 | length : 0 891 | }; 892 | 893 | var state = 0; 894 | for (var i = in_offset; i < in_tokens.length && 6 != state; ++i) { 895 | var type = in_tokens[i].type; 896 | var value = in_tokens[i].value; 897 | 898 | switch(state) { 899 | case 0: //'note' 900 | if ('space' == type) { 901 | continue; 902 | } 903 | if ('note' != value) { 904 | return null; 905 | } 906 | state = 1; 907 | break; 908 | case 1: //side 909 | if ('space' == type) { 910 | continue; 911 | } 912 | if ('left' != value && 'right' != value) { 913 | return null; 914 | } 915 | match_result.attr.side = value; 916 | state = 2; 917 | break; 918 | case 2: //'of' 919 | if ('space' == type) { 920 | continue; 921 | } 922 | if ('of' != value) { 923 | return null; 924 | } 925 | state = 3; 926 | break; 927 | case 3: //object 928 | if ('space' == type) { 929 | continue; 930 | } 931 | if ('word' != type || _is_keyword(value) || !_is_object(value)) { 932 | return null; 933 | } 934 | match_result.attr.object = value; 935 | state = 4; 936 | break; 937 | case 4: //':' 938 | if ('space' == in_tokens[i].type) { 939 | continue; 940 | } 941 | if (type != ':') { 942 | return null; 943 | } 944 | state = 5; 945 | break; 946 | case 5: //content 947 | if (type == ';' || type == 'newline' || type == 'eof') { 948 | state = 6; 949 | break; 950 | } 951 | if ('space' == type) { 952 | '' != match_result.attr.content && (match_result.attr.content += value); 953 | } 954 | else { 955 | match_result.attr.content += value; 956 | } 957 | break; 958 | } 959 | } 960 | 961 | if (6 != state) { 962 | return null; 963 | } 964 | 965 | match_result.length = i - in_offset; 966 | return match_result; 967 | } 968 | 969 | function _message_statement(in_tokens, in_offset) { 970 | var match_result = { 971 | type : 'message_statement', 972 | attr: { sender : null, receiver : null, message : '' }, 973 | children : [], 974 | offset : in_offset, 975 | length : 0 976 | }; 977 | 978 | var state = 0; 979 | for (var i = in_offset; i < in_tokens.length && 5 != state; ++i) { 980 | var type = in_tokens[i].type; 981 | var value = in_tokens[i].value; 982 | 983 | switch (state) { 984 | case 0: //sender 985 | if ('space' == type) { 986 | continue; 987 | } 988 | if (type != 'word' || _is_keyword(value) || !_is_object(value)) { 989 | return null; 990 | } 991 | match_result.attr.sender = value; 992 | state = 1; 993 | break; 994 | case 1: //arrow 995 | if ('space' == in_tokens[i].type) { 996 | continue; 997 | } 998 | if (type != 'arrow') { 999 | return null; 1000 | } 1001 | state = 2; 1002 | break; 1003 | case 2: //receiver 1004 | if ('space' == in_tokens[i].type) { 1005 | continue; 1006 | } 1007 | if (type != 'word' || _is_keyword(value) || !_is_object(value)) { 1008 | return null; 1009 | } 1010 | match_result.attr.receiver = value; 1011 | state = 3; 1012 | break; 1013 | case 3: //: 1014 | if ('space' == in_tokens[i].type) { 1015 | continue; 1016 | } 1017 | if (type == ';' || type == 'newline') { 1018 | state = 5; 1019 | break; 1020 | } 1021 | if (type != ':') { 1022 | return null; 1023 | } 1024 | state = 4; 1025 | break; 1026 | case 4: 1027 | if (type == ';' || type == 'newline' || type == 'eof') { 1028 | state = 5; 1029 | break; 1030 | } 1031 | 1032 | //ignore leading spaces 1033 | if ('space' == type) { 1034 | '' != match_result.attr.message && (match_result.attr.message += value); 1035 | } 1036 | else { 1037 | match_result.attr.message += value; 1038 | } 1039 | break; 1040 | default: 1041 | return null; 1042 | } 1043 | } 1044 | 1045 | if (state < 3) { 1046 | return null; 1047 | } 1048 | 1049 | match_result.length = i - in_offset; 1050 | return match_result; 1051 | } 1052 | 1053 | return { 1054 | sequence_diagram : sequence_diagram 1055 | }; 1056 | })(); 1057 | --------------------------------------------------------------------------------