Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
776 lars 1
// flowchart, v1.4.1
2
// Copyright (c)2015 Adriano Raiano (adrai).
3
// Distributed under MIT license
4
// http://adrai.github.io/flowchart.js
5
(function() {
6
 
7
  // add indexOf to non ECMA-262 standard compliant browsers
8
  if (!Array.prototype.indexOf) {
9
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
10
      "use strict";
11
      if (this === null) {
12
        throw new TypeError();
13
      }
14
      var t = Object(this);
15
      var len = t.length >>> 0;
16
      if (len === 0) {
17
        return -1;
18
      }
19
      var n = 0;
20
      if (arguments.length > 0) {
21
        n = Number(arguments[1]);
22
        if (n != n) { // shortcut for verifying if it's NaN
23
          n = 0;
24
        } else if (n !== 0 && n != Infinity && n != -Infinity) {
25
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
26
        }
27
      }
28
      if (n >= len) {
29
        return -1;
30
      }
31
      var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
32
      for (; k < len; k++) {
33
        if (k in t && t[k] === searchElement) {
34
          return k;
35
        }
36
      }
37
      return -1;
38
    };
39
  }
40
 
41
  // add lastIndexOf to non ECMA-262 standard compliant browsers
42
  if (!Array.prototype.lastIndexOf) {
43
    Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) {
44
      "use strict";
45
      if (this === null) {
46
        throw new TypeError();
47
      }
48
      var t = Object(this);
49
      var len = t.length >>> 0;
50
      if (len === 0) {
51
        return -1;
52
      }
53
      var n = len;
54
      if (arguments.length > 1) {
55
        n = Number(arguments[1]);
56
        if (n != n) {
57
          n = 0;
58
        } else if (n !== 0 && n != (1 / 0) && n != -(1 / 0)) {
59
          n = (n > 0 || -1) * Math.floor(Math.abs(n));
60
        }
61
      }
62
      var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
63
      for (; k >= 0; k--) {
64
        if (k in t && t[k] === searchElement) {
65
          return k;
66
        }
67
      }
68
      return -1;
69
    };
70
  }
71
 
72
  if (!String.prototype.trim) {
73
    String.prototype.trim = function() {
74
      return this.replace(/^\s+|\s+$/g, '');
75
    };
76
  }
77
 
78
  var root = this,
79
      flowchart = {};
80
 
81
  // Export the flowchart object for **CommonJS**.
82
  // If we're not in CommonJS, add `flowchart` to the
83
  // global object or to jquery.
84
  if (typeof module !== 'undefined' && module.exports) {
85
     module.exports = flowchart;
86
  } else {
87
    root.flowchart = root.flowchart || flowchart;
88
  }
89
  // defaults
90
  var o = {
91
    'x': 0,
92
    'y': 0,
93
    'line-width': 3,
94
    'line-length': 50,
95
    'text-margin': 10,
96
    'font-size': 14,
97
    'font-color': 'black',
98
    // 'font': 'normal',
99
    // 'font-family': 'calibri',
100
    // 'font-weight': 'normal',
101
    'line-color': 'black',
102
    'element-color': 'black',
103
    'fill': 'white',
104
    'yes-text': 'yes',
105
    'no-text': 'no',
106
    'arrow-end': 'block',
107
    'class': 'flowchart',
108
    'scale': 1,
109
    'symbols': {
110
      'start': {},
111
      'end': {},
112
      'condition': {},
113
      'inputoutput': {},
114
      'operation': {},
115
      'subroutine': {}
116
    }//,
117
    // 'flowstate' : {
118
    //   'past' : { 'fill': '#CCCCCC', 'font-size': 12},
119
    //   'current' : {'fill': 'yellow', 'font-color': 'red', 'font-weight': 'bold'},
120
    //   'future' : { 'fill': '#FFFF99'},
121
    //   'invalid': {'fill': '#444444'}
122
    // }
123
  };
124
  function _defaults(options, defaultOptions) {
125
    if (!options || typeof options === 'function') {
126
      return defaultOptions;
127
    }
128
 
129
    var merged = {};
130
    for (var attrname in defaultOptions) {
131
      merged[attrname] = defaultOptions[attrname];
132
    }
133
 
134
    for (attrname in options) {
135
      if (options[attrname]) {
136
        if (typeof merged[attrname] === 'object') {
137
          merged[attrname] = _defaults(merged[attrname], options[attrname]);
138
        } else {
139
          merged[attrname] = options[attrname];
140
        }
141
      }
142
    }
143
    return merged;
144
  }
145
 
146
  function _inherits(ctor, superCtor) {
147
    if (typeof(Object.create) === 'function') {
148
      // implementation from standard node.js 'util' module
149
      ctor.super_ = superCtor;
150
      ctor.prototype = Object.create(superCtor.prototype, {
151
        constructor: {
152
          value: ctor,
153
          enumerable: false,
154
          writable: true,
155
          configurable: true
156
        }
157
      });
158
    } else {
159
      // old school shim for old browsers
160
      ctor.super_ = superCtor;
161
      var TempCtor = function () {};
162
      TempCtor.prototype = superCtor.prototype;
163
      ctor.prototype = new TempCtor();
164
      ctor.prototype.constructor = ctor;
165
    }
166
  }
167
 
168
  // move dependent functions to a container so that
169
  // they can be overriden easier in no jquery environment (node.js)
170
  var f = {
171
    defaults: _defaults,
172
    inherits: _inherits
173
  };
174
  function drawPath(chart, location, points) {
175
    var i, len;
176
    var path = 'M{0},{1}';
177
    for (i = 2, len = 2 * points.length + 2; i < len; i+=2) {
178
      path += ' L{' + i + '},{' + (i + 1) + '}';
179
    }
180
    var pathValues = [location.x, location.y];
181
    for (i = 0, len = points.length; i < len; i++) {
182
      pathValues.push(points[i].x);
183
      pathValues.push(points[i].y);
184
    }
185
    var symbol = chart.paper.path(path, pathValues);
186
    symbol.attr('stroke', chart.options['element-color']);
187
    symbol.attr('stroke-width', chart.options['line-width']);
188
 
189
    var font = chart.options['font'];
190
    var fontF = chart.options['font-family'];
191
    var fontW = chart.options['font-weight'];
192
 
193
    if (font) symbol.attr({ 'font': font });
194
    if (fontF) symbol.attr({ 'font-family': fontF });
195
    if (fontW) symbol.attr({ 'font-weight': fontW });
196
 
197
    return symbol;
198
  }
199
 
200
  function drawLine(chart, from, to, text) {
201
    var i, len;
202
 
203
    if (Object.prototype.toString.call(to) !== '[object Array]') {
204
      to = [to];
205
    }
206
 
207
    var path = 'M{0},{1}';
208
    for (i = 2, len = 2 * to.length + 2; i < len; i+=2) {
209
      path += ' L{' + i + '},{' + (i + 1) + '}';
210
    }
211
    var pathValues = [from.x, from.y];
212
    for (i = 0, len = to.length; i < len; i++) {
213
      pathValues.push(to[i].x);
214
      pathValues.push(to[i].y);
215
    }
216
 
217
    var line = chart.paper.path(path, pathValues);
218
    line.attr({
219
      stroke: chart.options['line-color'],
220
      'stroke-width': chart.options['line-width'],
221
      'arrow-end': chart.options['arrow-end']
222
    });
223
 
224
    var font = chart.options['font'];
225
    var fontF = chart.options['font-family'];
226
    var fontW = chart.options['font-weight'];
227
 
228
    if (font) line.attr({ 'font': font });
229
    if (fontF) line.attr({ 'font-family': fontF });
230
    if (fontW) line.attr({ 'font-weight': fontW });
231
 
232
    if (text) {
233
 
234
      var centerText = false;
235
 
236
      var textPath = chart.paper.text(0, 0, text);
237
 
238
      var isHorizontal = false;
239
      var firstTo = to[0];
240
 
241
      if (from.y === firstTo.y) {
242
        isHorizontal = true;
243
      }
244
 
245
      var x = 0,
246
          y = 0;
247
 
248
      if (centerText) {
249
        if (from.x > firstTo.x) {
250
          x = from.x - (from.x - firstTo.x)/2;
251
        } else {
252
          x = firstTo.x - (firstTo.x - from.x)/2;
253
        }
254
 
255
        if (from.y > firstTo.y) {
256
          y = from.y - (from.y - firstTo.y)/2;
257
        } else {
258
          y = firstTo.y - (firstTo.y - from.y)/2;
259
        }
260
 
261
        if (isHorizontal) {
262
          x -= textPath.getBBox().width/2;
263
          y -= chart.options['text-margin'];
264
        } else {
265
          x += chart.options['text-margin'];
266
          y -= textPath.getBBox().height/2;
267
        }
268
      } else {
269
        x = from.x;
270
        y = from.y;
271
 
272
        if (isHorizontal) {
273
          x += chart.options['text-margin']/2;
274
          y -= chart.options['text-margin'];
275
        } else {
276
          x += chart.options['text-margin']/2;
277
          y += chart.options['text-margin'];
278
        }
279
      }
280
 
281
      textPath.attr({
282
        'text-anchor': 'start',
283
        'font-size': chart.options['font-size'],
284
        'fill': chart.options['font-color'],
285
        x: x,
286
        y: y
287
      });
288
 
289
      if (font) textPath.attr({ 'font': font });
290
      if (fontF) textPath.attr({ 'font-family': fontF });
291
      if (fontW) textPath.attr({ 'font-weight': fontW });
292
    }
293
 
294
    return line;
295
  }
296
 
297
  function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
298
    // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
299
    var denominator, a, b, numerator1, numerator2, result = {
300
      x: null,
301
      y: null,
302
      onLine1: false,
303
      onLine2: false
304
    };
305
    denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
306
    if (denominator === 0) {
307
      return result;
308
    }
309
    a = line1StartY - line2StartY;
310
    b = line1StartX - line2StartX;
311
    numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
312
    numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
313
    a = numerator1 / denominator;
314
    b = numerator2 / denominator;
315
 
316
    // if we cast these lines infinitely in both directions, they intersect here:
317
    result.x = line1StartX + (a * (line1EndX - line1StartX));
318
    result.y = line1StartY + (a * (line1EndY - line1StartY));
319
    /*
320
    // it is worth noting that this should be the same as:
321
    x = line2StartX + (b * (line2EndX - line2StartX));
322
    y = line2StartX + (b * (line2EndY - line2StartY));
323
    */
324
    // if line1 is a segment and line2 is infinite, they intersect if:
325
    if (a > 0 && a < 1) {
326
      result.onLine1 = true;
327
    }
328
    // if line2 is a segment and line1 is infinite, they intersect if:
329
    if (b > 0 && b < 1) {
330
      result.onLine2 = true;
331
    }
332
    // if line1 and line2 are segments, they intersect if both of the above are true
333
    return result;
334
  }
335
  function FlowChart(container, options) {
336
    options = options || {};
337
 
338
    this.paper = new Raphael(container);
339
 
340
    this.options = f.defaults(options, o);
341
 
342
    this.symbols = [];
343
    this.lines = [];
344
    this.start = null;
345
  }
346
 
347
  FlowChart.prototype.handle = function(symbol) {
348
    if (this.symbols.indexOf(symbol) <= -1) {
349
      this.symbols.push(symbol);
350
    }
351
 
352
    var flowChart = this;
353
 
354
    if (symbol instanceof(Condition)) {
355
      symbol.yes = function(nextSymbol) {
356
        symbol.yes_symbol = nextSymbol;
357
        if(symbol.no_symbol) {
358
          symbol.pathOk = true;
359
        }
360
        return flowChart.handle(nextSymbol);
361
      };
362
      symbol.no = function(nextSymbol) {
363
        symbol.no_symbol = nextSymbol;
364
        if(symbol.yes_symbol) {
365
          symbol.pathOk = true;
366
        }
367
        return flowChart.handle(nextSymbol);
368
      };
369
    } else {
370
      symbol.then = function(nextSymbol) {
371
        symbol.next = nextSymbol;
372
        symbol.pathOk = true;
373
        return flowChart.handle(nextSymbol);
374
      };
375
    }
376
 
377
    return symbol;
378
  };
379
 
380
  FlowChart.prototype.startWith = function(symbol) {
381
    this.start = symbol;
382
    return this.handle(symbol);
383
  };
384
 
385
  FlowChart.prototype.render = function() {
386
    var maxWidth = 0,
387
        maxHeight = 0,
388
        i = 0,
389
        len = 0,
390
        maxX = 0,
391
        maxY = 0,
392
        symbol;
393
 
394
    for (i = 0, len = this.symbols.length; i < len; i++) {
395
      symbol = this.symbols[i];
396
      if (symbol.width > maxWidth) {
397
        maxWidth = symbol.width;
398
      }
399
      if (symbol.height > maxHeight) {
400
        maxHeight = symbol.height;
401
      }
402
    }
403
 
404
    for (i = 0, len = this.symbols.length; i < len; i++) {
405
      symbol = this.symbols[i];
406
      symbol.shiftX(this.options.x + (maxWidth - symbol.width)/2 + this.options['line-width']);
407
      symbol.shiftY(this.options.y + (maxHeight - symbol.height)/2 + this.options['line-width']);
408
    }
409
 
410
    this.start.render();
411
    // for (i = 0, len = this.symbols.length; i < len; i++) {
412
    //   symbol = this.symbols[i];
413
    //   symbol.render();
414
    // }
415
 
416
    for (i = 0, len = this.symbols.length; i < len; i++) {
417
      symbol = this.symbols[i];
418
      symbol.renderLines();
419
    }
420
 
421
    maxX = this.maxXFromLine;
422
 
423
    for (i = 0, len = this.symbols.length; i < len; i++) {
424
      symbol = this.symbols[i];
425
      var x = symbol.getX() + symbol.width;
426
      var y = symbol.getY() + symbol.height;
427
      if (x > maxX) {
428
        maxX = x;
429
      }
430
      if (y > maxY) {
431
        maxY = y;
432
      }
433
    }
434
 
435
    var scale = this.options['scale'];
436
    var lineWidth = this.options['line-width'];
437
    this.paper.setSize((maxX * scale) + (lineWidth * scale), (maxY * scale) + (lineWidth * scale));
438
    this.paper.setViewBox(0, 0, maxX + lineWidth, maxY + lineWidth, true);
439
  };
440
 
441
  FlowChart.prototype.clean = function() {
442
    if (this.paper) {
443
      var paperDom = this.paper.canvas;
444
      paperDom.parentNode.removeChild(paperDom);
445
    }
446
  };
447
  function Symbol(chart, options, symbol) {
448
    this.chart = chart;
449
    this.group = this.chart.paper.set();
450
    this.symbol = symbol;
451
    this.connectedTo = [];
452
    this.symbolType = options.symbolType;
453
    this.flowstate = (options.flowstate || 'future');
454
 
455
    this.next_direction = options.next && options['direction_next'] ? options['direction_next'] : undefined;
456
 
457
    this.text = this.chart.paper.text(0, 0, options.text);
458
    //Raphael does not support the svg group tag so setting the text node id to the symbol node id plus t
459
    if (options.key) { this.text.node.id = options.key + 't'; }
460
    this.text.node.setAttribute('class', this.getAttr('class') + 't');
461
 
462
    this.text.attr({
463
      'text-anchor': 'start',
464
      'x'          : this.getAttr('text-margin'),
465
      'fill'       : this.getAttr('font-color'),
466
      'font-size'  : this.getAttr('font-size')
467
    });
468
 
469
    var font  = this.getAttr('font');
470
    var fontF = this.getAttr('font-family');
471
    var fontW = this.getAttr('font-weight');
472
 
473
    if (font) this.text.attr({ 'font': font });
474
    if (fontF) this.text.attr({ 'font-family': fontF });
475
    if (fontW) this.text.attr({ 'font-weight': fontW });
476
 
477
    if (options.link) { this.text.attr('href', options.link); }
478
    if (options.target) { this.text.attr('target', options.target); }
479
 
480
    var maxWidth = this.getAttr('maxWidth');
481
    if (maxWidth) {
482
      // using this approach: http://stackoverflow.com/a/3153457/22466
483
      var words = options.text.split(' ');
484
      var tempText = "";
485
      for (var i=0, ii=words.length; i<ii; i++) {
486
        var word = words[i];
487
        this.text.attr("text", tempText + " " + word);
488
        if (this.text.getBBox().width > maxWidth) {
489
          tempText += "\n" + word;
490
        } else {
491
          tempText += " " + word;
492
        }
493
      }
494
      this.text.attr("text", tempText.substring(1));
495
    }
496
 
497
    this.group.push(this.text);
498
 
499
    if (symbol) {
500
      var tmpMargin = this.getAttr('text-margin');
501
 
502
      symbol.attr({
503
        'fill' : this.getAttr('fill'),
504
        'stroke' : this.getAttr('element-color'),
505
        'stroke-width' : this.getAttr('line-width'),
506
        'width' : this.text.getBBox().width + 2 * tmpMargin,
507
        'height' : this.text.getBBox().height + 2 * tmpMargin
508
      });
509
 
510
      symbol.node.setAttribute('class', this.getAttr('class'));
511
 
512
      if (options.link) { symbol.attr('href', options.link); }
513
      if (options.target) { symbol.attr('target', options.target); }
514
      if (options.key) { symbol.node.id = options.key; }
515
 
516
      this.group.push(symbol);
517
      symbol.insertBefore(this.text);
518
 
519
      this.text.attr({
520
        'y': symbol.getBBox().height/2
521
      });
522
 
523
      this.initialize();
524
    }
525
 
526
  }
527
 
528
  /* Gets the attribute based on Flowstate, Symbol-Name and default, first found wins */
529
  Symbol.prototype.getAttr = function(attName) {
530
    if (!this.chart) {
531
      return undefined;
532
    }
533
    var opt3 = (this.chart.options) ? this.chart.options[attName] : undefined;
534
    var opt2 = (this.chart.options.symbols) ? this.chart.options.symbols[this.symbolType][attName] : undefined;
535
    var opt1;
536
    if (this.chart.options.flowstate && this.chart.options.flowstate[this.flowstate]) {
537
      opt1 = this.chart.options.flowstate[this.flowstate][attName];
538
    }
539
    return (opt1 || opt2 || opt3);
540
  };
541
 
542
  Symbol.prototype.initialize = function() {
543
    this.group.transform('t' + this.getAttr('line-width') + ',' + this.getAttr('line-width'));
544
 
545
    this.width = this.group.getBBox().width;
546
    this.height = this.group.getBBox().height;
547
  };
548
 
549
  Symbol.prototype.getCenter = function() {
550
    return {x: this.getX() + this.width/2,
551
            y: this.getY() + this.height/2};
552
  };
553
 
554
  Symbol.prototype.getX = function() {
555
    return this.group.getBBox().x;
556
  };
557
 
558
  Symbol.prototype.getY = function() {
559
    return this.group.getBBox().y;
560
  };
561
 
562
  Symbol.prototype.shiftX = function(x) {
563
    this.group.transform('t' + (this.getX() + x) + ',' + this.getY());
564
  };
565
 
566
  Symbol.prototype.setX = function(x) {
567
    this.group.transform('t' + x + ',' + this.getY());
568
  };
569
 
570
  Symbol.prototype.shiftY = function(y) {
571
    this.group.transform('t' + this.getX() + ',' + (this.getY() + y));
572
  };
573
 
574
  Symbol.prototype.setY = function(y) {
575
    this.group.transform('t' + this.getX() + ',' + y);
576
  };
577
 
578
  Symbol.prototype.getTop = function() {
579
    var y = this.getY();
580
    var x = this.getX() + this.width/2;
581
    return {x: x, y: y};
582
  };
583
 
584
  Symbol.prototype.getBottom = function() {
585
    var y = this.getY() + this.height;
586
    var x = this.getX() + this.width/2;
587
    return {x: x, y: y};
588
  };
589
 
590
  Symbol.prototype.getLeft = function() {
591
    var y = this.getY() + this.group.getBBox().height/2;
592
    var x = this.getX();
593
    return {x: x, y: y};
594
  };
595
 
596
  Symbol.prototype.getRight = function() {
597
    var y = this.getY() + this.group.getBBox().height/2;
598
    var x = this.getX() + this.group.getBBox().width;
599
    return {x: x, y: y};
600
  };
601
 
602
  Symbol.prototype.render = function() {
603
    if (this.next) {
604
 
605
      var lineLength = this.getAttr('line-length');
606
 
607
      if (this.next_direction === 'right') {
608
 
609
        var rightPoint = this.getRight();
610
        var leftPoint = this.next.getLeft();
611
 
612
        if (!this.next.isPositioned) {
613
 
614
          this.next.setY(rightPoint.y - this.next.height/2);
615
          this.next.shiftX(this.group.getBBox().x + this.width + lineLength);
616
 
617
          var self = this;
618
          (function shift() {
619
            var hasSymbolUnder = false;
620
            var symb;
621
            for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
622
              symb = self.chart.symbols[i];
623
 
624
              var diff = Math.abs(symb.getCenter().x - self.next.getCenter().x);
625
              if (symb.getCenter().y > self.next.getCenter().y && diff <= self.next.width/2) {
626
                hasSymbolUnder = true;
627
                break;
628
              }
629
            }
630
 
631
            if (hasSymbolUnder) {
632
              self.next.setX(symb.getX() + symb.width + lineLength);
633
              shift();
634
            }
635
          })();
636
 
637
          this.next.isPositioned = true;
638
 
639
          this.next.render();
640
        }
641
      } else {
642
        var bottomPoint = this.getBottom();
643
        var topPoint = this.next.getTop();
644
 
645
        if (!this.next.isPositioned) {
646
          this.next.shiftY(this.getY() + this.height + lineLength);
647
          this.next.setX(bottomPoint.x - this.next.width/2);
648
          this.next.isPositioned = true;
649
 
650
          this.next.render();
651
        }
652
      }
653
    }
654
  };
655
 
656
  Symbol.prototype.renderLines = function() {
657
    if (this.next) {
658
      if (this.next_direction) {
659
        this.drawLineTo(this.next, '', this.next_direction);
660
      } else {
661
        this.drawLineTo(this.next);
662
      }
663
    }
664
  };
665
 
666
  Symbol.prototype.drawLineTo = function(symbol, text, origin) {
667
    if (this.connectedTo.indexOf(symbol) < 0) {
668
      this.connectedTo.push(symbol);
669
    }
670
 
671
    var x = this.getCenter().x,
672
        y = this.getCenter().y,
673
        top = this.getTop(),
674
        right = this.getRight(),
675
        bottom = this.getBottom(),
676
        left = this.getLeft();
677
 
678
    var symbolX = symbol.getCenter().x,
679
        symbolY = symbol.getCenter().y,
680
        symbolTop = symbol.getTop(),
681
        symbolRight = symbol.getRight(),
682
        symbolBottom = symbol.getBottom(),
683
        symbolLeft = symbol.getLeft();
684
 
685
    var isOnSameColumn = x === symbolX,
686
        isOnSameLine = y === symbolY,
687
        isUnder = y < symbolY,
688
        isUpper = y > symbolY,
689
        isLeft = x > symbolX,
690
        isRight = x < symbolX;
691
 
692
    var maxX = 0,
693
        line,
694
        lineLength = this.getAttr('line-length'),
695
        lineWith = this.getAttr('line-width');
696
 
697
    if ((!origin || origin === 'bottom') && isOnSameColumn && isUnder) {
698
      line = drawLine(this.chart, bottom, symbolTop, text);
699
      this.bottomStart = true;
700
      symbol.topEnd = true;
701
      maxX = bottom.x;
702
    } else if ((!origin || origin === 'right') && isOnSameLine && isRight) {
703
      line = drawLine(this.chart, right, symbolLeft, text);
704
      this.rightStart = true;
705
      symbol.leftEnd = true;
706
      maxX = symbolLeft.x;
707
    } else if ((!origin || origin === 'left') && isOnSameLine && isLeft) {
708
      line = drawLine(this.chart, left, symbolRight, text);
709
      this.leftStart = true;
710
      symbol.rightEnd = true;
711
      maxX = symbolRight.x;
712
    } else if ((!origin || origin === 'right') && isOnSameColumn && isUpper) {
713
      line = drawLine(this.chart, right, [
714
        {x: right.x + lineLength/2, y: right.y},
715
        {x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
716
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
717
        {x: symbolTop.x, y: symbolTop.y}
718
      ], text);
719
      this.rightStart = true;
720
      symbol.topEnd = true;
721
      maxX = right.x + lineLength/2;
722
    } else if ((!origin || origin === 'right') && isOnSameColumn && isUnder) {
723
      line = drawLine(this.chart, right, [
724
        {x: right.x + lineLength/2, y: right.y},
725
        {x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
726
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
727
        {x: symbolTop.x, y: symbolTop.y}
728
      ], text);
729
      this.rightStart = true;
730
      symbol.topEnd = true;
731
      maxX = right.x + lineLength/2;
732
    } else if ((!origin || origin === 'bottom') && isLeft) {
733
      if (this.leftEnd && isUpper) {
734
        line = drawLine(this.chart, bottom, [
735
          {x: bottom.x, y: bottom.y + lineLength/2},
736
          {x: bottom.x + (bottom.x - symbolTop.x)/2, y: bottom.y + lineLength/2},
737
          {x: bottom.x + (bottom.x - symbolTop.x)/2, y: symbolTop.y - lineLength/2},
738
          {x: symbolTop.x, y: symbolTop.y - lineLength/2},
739
          {x: symbolTop.x, y: symbolTop.y}
740
        ], text);
741
      } else {
742
        line = drawLine(this.chart, bottom, [
743
          {x: bottom.x, y: symbolTop.y - lineLength/2},
744
          {x: symbolTop.x, y: symbolTop.y - lineLength/2},
745
          {x: symbolTop.x, y: symbolTop.y}
746
        ], text);
747
      }
748
      this.bottomStart = true;
749
      symbol.topEnd = true;
750
      maxX = bottom.x + (bottom.x - symbolTop.x)/2;
751
    } else if ((!origin || origin === 'bottom') && isRight) {
752
      line = drawLine(this.chart, bottom, [
753
        {x: bottom.x, y: bottom.y + lineLength/2},
754
        {x: bottom.x + (bottom.x - symbolTop.x)/2, y: bottom.y + lineLength/2},
755
        {x: bottom.x + (bottom.x - symbolTop.x)/2, y: symbolTop.y - lineLength/2},
756
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
757
        {x: symbolTop.x, y: symbolTop.y}
758
      ], text);
759
      this.bottomStart = true;
760
      symbol.topEnd = true;
761
      maxX = bottom.x + (bottom.x - symbolTop.x)/2;
762
    } else if ((origin && origin === 'right') && isLeft) {
763
      line = drawLine(this.chart, right, [
764
        {x: right.x + lineLength/2, y: right.y},
765
        {x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
766
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
767
        {x: symbolTop.x, y: symbolTop.y}
768
      ], text);
769
      this.rightStart = true;
770
      symbol.topEnd = true;
771
      maxX = right.x + lineLength/2;
772
    } else if ((origin && origin === 'right') && isRight) {
773
      line = drawLine(this.chart, right, [
774
        {x: symbolTop.x, y: right.y},
775
        {x: symbolTop.x, y: symbolTop.y}
776
      ], text);
777
      this.rightStart = true;
778
      symbol.topEnd = true;
779
      maxX = right.x + lineLength/2;
780
    } else if ((origin && origin === 'bottom') && isOnSameColumn && isUpper) {
781
      line = drawLine(this.chart, bottom, [
782
        {x: bottom.x, y: bottom.y + lineLength/2},
783
        {x: right.x + lineLength/2, y: bottom.y + lineLength/2},
784
        {x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
785
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
786
        {x: symbolTop.x, y: symbolTop.y}
787
      ], text);
788
      this.bottomStart = true;
789
      symbol.topEnd = true;
790
      maxX = bottom.x + lineLength/2;
791
    } else if ((origin === 'left') && isOnSameColumn && isUpper) {
792
      var diffX = left.x - lineLength/2;
793
      if (symbolLeft.x < left.x) {
794
        diffX = symbolLeft.x - lineLength/2;
795
      }
796
      line = drawLine(this.chart, left, [
797
        {x: diffX, y: left.y},
798
        {x: diffX, y: symbolTop.y - lineLength/2},
799
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
800
        {x: symbolTop.x, y: symbolTop.y}
801
      ], text);
802
      this.leftStart = true;
803
      symbol.topEnd = true;
804
      maxX = left.x;
805
    } else if ((origin === 'left')) {
806
      line = drawLine(this.chart, left, [
807
        {x: symbolTop.x + (left.x - symbolTop.x)/ 2, y: left.y},
808
        {x: symbolTop.x + (left.x - symbolTop.x)/ 2, y: symbolTop.y - lineLength/2},
809
        {x: symbolTop.x, y: symbolTop.y - lineLength/2},
810
        {x: symbolTop.x, y: symbolTop.y}
811
      ], text);
812
      this.leftStart = true;
813
      symbol.topEnd = true;
814
      maxX = left.x;
815
    }
816
 
817
    if (line) {
818
      var self = this;
819
      for (var l = 0, llen = this.chart.lines.length; l < llen; l++) {
820
        var otherLine = this.chart.lines[l];
821
        var i,
822
            len,
823
            intersections,
824
            inter;
825
 
826
        var ePath = otherLine.attr('path'),
827
            lPath = line.attr('path');
828
 
829
        for (var iP = 0, lenP = ePath.length - 1; iP < lenP; iP++) {
830
          var newPath = [];
831
          newPath.push(['M', ePath[iP][1], ePath[iP][2]]);
832
          newPath.push(['L', ePath[iP + 1][1], ePath[iP + 1][2]]);
833
 
834
          var line1_from_x = newPath[0][1];
835
          var line1_from_y = newPath[0][2];
836
          var line1_to_x = newPath[1][1];
837
          var line1_to_y = newPath[1][2];
838
 
839
          for (var lP = 0, lenlP = lPath.length - 1; lP < lenlP; lP++) {
840
            var newLinePath = [];
841
            newLinePath.push(['M', lPath[lP][1], lPath[lP][2]]);
842
            newLinePath.push(['L', lPath[lP + 1][1], lPath[lP + 1][2]]);
843
 
844
            var line2_from_x = newLinePath[0][1];
845
            var line2_from_y = newLinePath[0][2];
846
            var line2_to_x = newLinePath[1][1];
847
            var line2_to_y = newLinePath[1][2];
848
 
849
            var res = checkLineIntersection(line1_from_x, line1_from_y, line1_to_x, line1_to_y, line2_from_x, line2_from_y, line2_to_x, line2_to_y);
850
            if (res.onLine1 && res.onLine2) {
851
 
852
              var newSegment;
853
              if (line2_from_y === line2_to_y) {
854
                if (line2_from_x > line2_to_x) {
855
                  newSegment = ['L', res.x + lineWith * 2,  line2_from_y];
856
                  lPath.splice(lP + 1, 0, newSegment);
857
                  newSegment = ['C', res.x + lineWith * 2,  line2_from_y, res.x, line2_from_y - lineWith * 4, res.x - lineWith * 2, line2_from_y];
858
                  lPath.splice(lP + 2, 0, newSegment);
859
                  line.attr('path', lPath);
860
                } else {
861
                  newSegment = ['L', res.x - lineWith * 2,  line2_from_y];
862
                  lPath.splice(lP + 1, 0, newSegment);
863
                  newSegment = ['C', res.x - lineWith * 2,  line2_from_y, res.x, line2_from_y - lineWith * 4, res.x + lineWith * 2, line2_from_y];
864
                  lPath.splice(lP + 2, 0, newSegment);
865
                  line.attr('path', lPath);
866
                }
867
              } else {
868
                if (line2_from_y > line2_to_y) {
869
                  newSegment = ['L', line2_from_x, res.y + lineWith * 2];
870
                  lPath.splice(lP + 1, 0, newSegment);
871
                  newSegment = ['C', line2_from_x, res.y + lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y - lineWith * 2];
872
                  lPath.splice(lP + 2, 0, newSegment);
873
                  line.attr('path', lPath);
874
                } else {
875
                  newSegment = ['L', line2_from_x, res.y - lineWith * 2];
876
                  lPath.splice(lP + 1, 0, newSegment);
877
                  newSegment = ['C', line2_from_x, res.y - lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y + lineWith * 2];
878
                  lPath.splice(lP + 2, 0, newSegment);
879
                  line.attr('path', lPath);
880
                }
881
              }
882
 
883
              lP += 2;
884
              len += 2;
885
            }
886
          }
887
        }
888
      }
889
 
890
      this.chart.lines.push(line);
891
    }
892
 
893
    if (!this.chart.maxXFromLine || (this.chart.maxXFromLine && maxX > this.chart.maxXFromLine)) {
894
      this.chart.maxXFromLine = maxX;
895
    }
896
  };
897
  function Start(chart, options) {
898
    var symbol = chart.paper.rect(0, 0, 0, 0, 20);
899
    options = options || {};
900
    options.text = options.text || 'Start';
901
    Symbol.call(this, chart, options, symbol);
902
  }
903
  f.inherits(Start, Symbol);
904
 
905
 
906
  // Start.prototype.render = function() {
907
  //   if (this.next) {
908
  //     var lineLength = this.chart.options.symbols[this.symbolType]['line-length'] || this.chart.options['line-length'];
909
 
910
  //     var bottomPoint = this.getBottom();
911
  //     var topPoint = this.next.getTop();
912
 
913
  //     if (!this.next.isPositioned) {
914
  //       this.next.shiftY(this.getY() + this.height + lineLength);
915
  //       this.next.setX(bottomPoint.x - this.next.width/2);
916
  //       this.next.isPositioned = true;
917
 
918
  //       this.next.render();
919
  //     }
920
  //   }
921
  // };
922
 
923
  // Start.prototype.renderLines = function() {
924
  //   if (this.next) {
925
  //     this.drawLineTo(this.next);
926
  //   }
927
  // };
928
  function End(chart, options) {
929
    var symbol = chart.paper.rect(0, 0, 0, 0, 20);
930
    options = options || {};
931
    options.text = options.text || 'End';
932
    Symbol.call(this, chart, options, symbol);
933
  }
934
  f.inherits(End, Symbol);
935
  function Operation(chart, options) {
936
    var symbol = chart.paper.rect(0, 0, 0, 0);
937
    options = options || {};
938
    Symbol.call(this, chart, options, symbol);
939
  }
940
  f.inherits(Operation, Symbol);
941
  function Subroutine(chart, options) {
942
    var symbol = chart.paper.rect(0, 0, 0, 0);
943
    options = options || {};
944
    Symbol.call(this, chart, options, symbol);
945
 
946
    symbol.attr({
947
      width: this.text.getBBox().width + 4 * this.getAttr('text-margin')
948
    });
949
 
950
    this.text.attr({
951
      'x': 2 * this.getAttr('text-margin')
952
    });
953
 
954
    var innerWrap = chart.paper.rect(0, 0, 0, 0);
955
    innerWrap.attr({
956
      x: this.getAttr('text-margin'),
957
      stroke: this.getAttr('element-color'),
958
      'stroke-width': this.getAttr('line-width'),
959
      width: this.text.getBBox().width + 2 * this.getAttr('text-margin'),
960
      height: this.text.getBBox().height + 2 * this.getAttr('text-margin'),
961
      fill: this.getAttr('fill')
962
    });
963
    if (options.key) { innerWrap.node.id = options.key + 'i'; }
964
 
965
    var font = this.getAttr('font');
966
    var fontF = this.getAttr('font-family');
967
    var fontW = this.getAttr('font-weight');
968
 
969
    if (font) innerWrap.attr({ 'font': font });
970
    if (fontF) innerWrap.attr({ 'font-family': fontF });
971
    if (fontW) innerWrap.attr({ 'font-weight': fontW });
972
 
973
    if (options.link) { innerWrap.attr('href', options.link); }
974
    if (options.target) { innerWrap.attr('target', options.target); }
975
    this.group.push(innerWrap);
976
    innerWrap.insertBefore(this.text);
977
 
978
    this.initialize();
979
  }
980
  f.inherits(Subroutine, Symbol);
981
  function InputOutput(chart, options) {
982
    options = options || {};
983
    Symbol.call(this, chart, options);
984
    this.textMargin = this.getAttr('text-margin');
985
 
986
    this.text.attr({
987
      x: this.textMargin * 3
988
    });
989
 
990
    var width = this.text.getBBox().width + 4 * this.textMargin;
991
    var height = this.text.getBBox().height + 2 * this.textMargin;
992
    var startX = this.textMargin;
993
    var startY = height/2;
994
 
995
    var start = {x: startX, y: startY};
996
    var points = [
997
      {x: startX - this.textMargin, y: height},
998
      {x: startX - this.textMargin + width, y: height},
999
      {x: startX - this.textMargin + width + 2 * this.textMargin, y: 0},
1000
      {x: startX - this.textMargin + 2 * this.textMargin, y: 0},
1001
      {x: startX, y: startY}
1002
    ];
1003
 
1004
    var symbol = drawPath(chart, start, points);
1005
 
1006
    symbol.attr({
1007
      stroke: this.getAttr('element-color'),
1008
      'stroke-width': this.getAttr('line-width'),
1009
      fill: this.getAttr('fill')
1010
    });
1011
    if (options.link) { symbol.attr('href', options.link); }
1012
    if (options.target) { symbol.attr('target', options.target); }
1013
    if (options.key) { symbol.node.id = options.key; }
1014
    symbol.node.setAttribute('class', this.getAttr('class'));
1015
 
1016
    this.text.attr({
1017
      y: symbol.getBBox().height/2
1018
    });
1019
 
1020
    this.group.push(symbol);
1021
    symbol.insertBefore(this.text);
1022
 
1023
    this.initialize();
1024
  }
1025
  f.inherits(InputOutput, Symbol);
1026
 
1027
  InputOutput.prototype.getLeft = function() {
1028
    var y = this.getY() + this.group.getBBox().height/2;
1029
    var x = this.getX() + this.textMargin;
1030
    return {x: x, y: y};
1031
  };
1032
 
1033
  InputOutput.prototype.getRight = function() {
1034
    var y = this.getY() + this.group.getBBox().height/2;
1035
    var x = this.getX() + this.group.getBBox().width - this.textMargin;
1036
    return {x: x, y: y};
1037
  };
1038
  function Condition(chart, options) {
1039
    options = options || {};
1040
    Symbol.call(this, chart, options);
1041
    this.textMargin = this.getAttr('text-margin');
1042
    this.yes_direction = 'bottom';
1043
    this.no_direction = 'right';
1044
    if (options.yes && options['direction_yes'] && options.no && !options['direction_no']) {
1045
      if (options['direction_yes'] === 'right') {
1046
        this.no_direction = 'bottom';
1047
        this.yes_direction = 'right';
1048
      } else {
1049
        this.no_direction = 'right';
1050
        this.yes_direction = 'bottom';
1051
      }
1052
    } else if (options.yes && !options['direction_yes'] && options.no && options['direction_no']) {
1053
      if (options['direction_no'] === 'right') {
1054
        this.yes_direction = 'bottom';
1055
        this.no_direction = 'right';
1056
      } else {
1057
        this.yes_direction = 'right';
1058
        this.no_direction = 'bottom';
1059
      }
1060
    } else {
1061
      this.yes_direction = 'bottom';
1062
      this.no_direction = 'right';
1063
    }
1064
 
1065
    this.yes_direction = this.yes_direction || 'bottom';
1066
    this.no_direction = this.no_direction || 'right';
1067
 
1068
    this.text.attr({
1069
      x: this.textMargin * 2
1070
    });
1071
 
1072
    var width = this.text.getBBox().width + 3 * this.textMargin;
1073
    width += width/2;
1074
    var height = this.text.getBBox().height + 2 * this.textMargin;
1075
    height += height/2;
1076
    height = Math.max(width * 0.5, height);
1077
    var startX = width/4;
1078
    var startY = height/4;
1079
 
1080
    this.text.attr({
1081
      x: startX + this.textMargin/2
1082
    });
1083
 
1084
    var start = {x: startX, y: startY};
1085
    var points = [
1086
      {x: startX - width/4, y: startY + height/4},
1087
      {x: startX - width/4 + width/2, y: startY + height/4 + height/2},
1088
      {x: startX - width/4 + width, y: startY + height/4},
1089
      {x: startX - width/4 + width/2, y: startY + height/4 - height/2},
1090
      {x: startX - width/4, y: startY + height/4}
1091
    ];
1092
 
1093
    var symbol = drawPath(chart, start, points);
1094
 
1095
    symbol.attr({
1096
      stroke: this.getAttr('element-color'),
1097
      'stroke-width': this.getAttr('line-width'),
1098
      fill: this.getAttr('fill')
1099
    });
1100
    if (options.link) { symbol.attr('href', options.link); }
1101
    if (options.target) { symbol.attr('target', options.target); }
1102
    if (options.key) { symbol.node.id = options.key; }
1103
    symbol.node.setAttribute('class', this.getAttr('class'));
1104
 
1105
    this.text.attr({
1106
      y: symbol.getBBox().height/2
1107
    });
1108
 
1109
    this.group.push(symbol);
1110
    symbol.insertBefore(this.text);
1111
 
1112
    this.initialize();
1113
  }
1114
  f.inherits(Condition, Symbol);
1115
 
1116
  Condition.prototype.render = function() {
1117
 
1118
    if (this.yes_direction) {
1119
      this[this.yes_direction + '_symbol'] = this.yes_symbol;
1120
    }
1121
 
1122
    if (this.no_direction) {
1123
      this[this.no_direction + '_symbol'] = this.no_symbol;
1124
    }
1125
 
1126
    var lineLength = this.getAttr('line-length');
1127
 
1128
    if (this.bottom_symbol) {
1129
      var bottomPoint = this.getBottom();
1130
      var topPoint = this.bottom_symbol.getTop();
1131
 
1132
      if (!this.bottom_symbol.isPositioned) {
1133
        this.bottom_symbol.shiftY(this.getY() + this.height + lineLength);
1134
        this.bottom_symbol.setX(bottomPoint.x - this.bottom_symbol.width/2);
1135
        this.bottom_symbol.isPositioned = true;
1136
 
1137
        this.bottom_symbol.render();
1138
      }
1139
    }
1140
 
1141
    if (this.right_symbol) {
1142
      var rightPoint = this.getRight();
1143
      var leftPoint = this.right_symbol.getLeft();
1144
 
1145
      if (!this.right_symbol.isPositioned) {
1146
 
1147
        this.right_symbol.setY(rightPoint.y - this.right_symbol.height/2);
1148
        this.right_symbol.shiftX(this.group.getBBox().x + this.width + lineLength);
1149
 
1150
        var self = this;
1151
        (function shift() {
1152
          var hasSymbolUnder = false;
1153
          var symb;
1154
          for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
1155
            symb = self.chart.symbols[i];
1156
 
1157
            var diff = Math.abs(symb.getCenter().x - self.right_symbol.getCenter().x);
1158
            if (symb.getCenter().y > self.right_symbol.getCenter().y && diff <= self.right_symbol.width/2) {
1159
              hasSymbolUnder = true;
1160
              break;
1161
            }
1162
          }
1163
 
1164
          if (hasSymbolUnder) {
1165
            self.right_symbol.setX(symb.getX() + symb.width + lineLength);
1166
            shift();
1167
          }
1168
        })();
1169
 
1170
        this.right_symbol.isPositioned = true;
1171
 
1172
        this.right_symbol.render();
1173
      }
1174
    }
1175
  };
1176
 
1177
  Condition.prototype.renderLines = function() {
1178
    if (this.yes_symbol) {
1179
      this.drawLineTo(this.yes_symbol, this.getAttr('yes-text'), this.yes_direction);
1180
    }
1181
 
1182
    if (this.no_symbol) {
1183
      this.drawLineTo(this.no_symbol, this.getAttr('no-text'), this.no_direction);
1184
    }
1185
  };
1186
  function parse(input) {
1187
    input = input || '';
1188
    input = input.trim();
1189
 
1190
    var chart = {
1191
      symbols: {},
1192
      start: null,
1193
      drawSVG: function(container, options) {
1194
        var self = this;
1195
 
1196
        if (this.diagram) {
1197
          this.diagram.clean();
1198
        }
1199
 
1200
        var diagram = new FlowChart(container, options);
1201
        this.diagram = diagram;
1202
        var dispSymbols = {};
1203
 
1204
        function getDisplaySymbol(s) {
1205
          if (dispSymbols[s.key]) {
1206
            return dispSymbols[s.key];
1207
          }
1208
 
1209
          switch (s.symbolType) {
1210
            case 'start':
1211
              dispSymbols[s.key] = new Start(diagram, s);
1212
              break;
1213
            case 'end':
1214
              dispSymbols[s.key] = new End(diagram, s);
1215
              break;
1216
            case 'operation':
1217
              dispSymbols[s.key] = new Operation(diagram, s);
1218
              break;
1219
            case 'inputoutput':
1220
              dispSymbols[s.key] = new InputOutput(diagram, s);
1221
              break;
1222
            case 'subroutine':
1223
              dispSymbols[s.key] = new Subroutine(diagram, s);
1224
              break;
1225
            case 'condition':
1226
              dispSymbols[s.key] = new Condition(diagram, s);
1227
              break;
1228
            default:
1229
              return new Error('Wrong symbol type!');
1230
          }
1231
 
1232
          return dispSymbols[s.key];
1233
        }
1234
 
1235
        (function constructChart(s, prevDisp, prev) {
1236
          var dispSymb = getDisplaySymbol(s);
1237
 
1238
          if (self.start === s) {
1239
            diagram.startWith(dispSymb);
1240
          } else if (prevDisp && prev && !prevDisp.pathOk) {
1241
            if (prevDisp instanceof(Condition)) {
1242
              if (prev.yes === s) {
1243
                prevDisp.yes(dispSymb);
1244
              }
1245
              if (prev.no === s) {
1246
                prevDisp.no(dispSymb);
1247
              }
1248
            } else {
1249
              prevDisp.then(dispSymb);
1250
            }
1251
          }
1252
 
1253
          if (dispSymb.pathOk) {
1254
            return dispSymb;
1255
          }
1256
 
1257
          if (dispSymb instanceof(Condition)) {
1258
            if (s.yes) {
1259
              constructChart(s.yes, dispSymb, s);
1260
            }
1261
            if (s.no) {
1262
              constructChart(s.no, dispSymb, s);
1263
            }
1264
          } else if (s.next) {
1265
            constructChart(s.next, dispSymb, s);
1266
          }
1267
 
1268
          return dispSymb;
1269
        })(this.start);
1270
 
1271
        diagram.render();
1272
      },
1273
      clean: function() {
1274
        this.diagram.clean();
1275
      }
1276
    };
1277
 
1278
    var lines = [];
1279
    var prevBreak = 0;
1280
    for (var i0 = 1, i0len = input.length; i0 < i0len; i0++) {
1281
      if(input[i0] === '\n' && input[i0 - 1] !== '\\') {
1282
        var line0 = input.substring(prevBreak, i0);
1283
        prevBreak = i0 + 1;
1284
        lines.push(line0.replace(/\\\n/g, '\n'));
1285
      }
1286
    }
1287
 
1288
    if(prevBreak < input.length) {
1289
      lines.push(input.substr(prevBreak));
1290
    }
1291
 
1292
    for (var l = 1, len = lines.length; l < len;) {
1293
      var currentLine = lines[l];
1294
 
1295
      if (currentLine.indexOf(': ') < 0 && currentLine.indexOf('(') < 0 && currentLine.indexOf(')') < 0 && currentLine.indexOf('->') < 0 && currentLine.indexOf('=>') < 0) {
1296
        lines[l - 1] += '\n' + currentLine;
1297
        lines.splice(l, 1);
1298
        len--;
1299
      } else {
1300
        l++;
1301
      }
1302
    }
1303
 
1304
    function getSymbol(s) {
1305
      var startIndex = s.indexOf('(') + 1;
1306
      var endIndex = s.indexOf(')');
1307
      if (startIndex >= 0 && endIndex >= 0) {
1308
        return chart.symbols[s.substring(0, startIndex - 1)];
1309
      }
1310
      return chart.symbols[s];
1311
    }
1312
 
1313
    function getNextPath(s) {
1314
      var next = 'next';
1315
      var startIndex = s.indexOf('(') + 1;
1316
      var endIndex = s.indexOf(')');
1317
      if (startIndex >= 0 && endIndex >= 0) {
1318
        next = flowSymb.substring(startIndex, endIndex);
1319
        if (next.indexOf(',') < 0) {
1320
          if (next !== 'yes' && next !== 'no') {
1321
            next = 'next, ' + next;
1322
          }
1323
        }
1324
      }
1325
      return next;
1326
    }
1327
 
1328
    while (lines.length > 0) {
1329
      var line = lines.splice(0, 1)[0];
1330
 
1331
      if (line.indexOf('=>') >= 0) {
1332
        // definition
1333
        var parts = line.split('=>');
1334
        var symbol = {
1335
          key: parts[0],
1336
          symbolType: parts[1],
1337
          text: null,
1338
          link: null,
1339
          target: null,
1340
          flowstate: null
1341
        };
1342
 
1343
        var sub;
1344
 
1345
        if (symbol.symbolType.indexOf(': ') >= 0) {
1346
          sub = symbol.symbolType.split(': ');
1347
          symbol.symbolType = sub[0];
1348
          symbol.text = sub[1];
1349
        }
1350
 
1351
        if (symbol.text && symbol.text.indexOf(':>') >= 0) {
1352
          sub = symbol.text.split(':>');
1353
          symbol.text = sub[0];
1354
          symbol.link = sub[1];
1355
        } else if (symbol.symbolType.indexOf(':>') >= 0) {
1356
          sub = symbol.symbolType.split(':>');
1357
          symbol.symbolType = sub[0];
1358
          symbol.link = sub[1];
1359
        }
1360
 
1361
        if (symbol.symbolType.indexOf('\n') >= 0) {
1362
          symbol.symbolType = symbol.symbolType.split('\n')[0];
1363
        }
1364
 
1365
        /* adding support for links */
1366
        if (symbol.link) {
1367
          var startIndex = symbol.link.indexOf('[') + 1;
1368
          var endIndex = symbol.link.indexOf(']');
1369
          if (startIndex >= 0 && endIndex >= 0) {
1370
            symbol.target = symbol.link.substring(startIndex, endIndex);
1371
            symbol.link = symbol.link.substring(0, startIndex - 1);
1372
          }
1373
        }
1374
        /* end of link support */
1375
 
1376
        /* adding support for flowstates */
1377
        if (symbol.text) {
1378
          if (symbol.text.indexOf('|') >= 0) {
1379
            var txtAndState = symbol.text.split('|');
1380
            symbol.text = txtAndState[0];
1381
            symbol.flowstate = txtAndState[1].trim();
1382
          }
1383
        }
1384
        /* end of flowstate support */
1385
 
1386
        chart.symbols[symbol.key] = symbol;
1387
 
1388
      } else if (line.indexOf('->') >= 0) {
1389
        // flow
1390
        var flowSymbols = line.split('->');
1391
        for (var i = 0, lenS = flowSymbols.length; i < lenS; i++) {
1392
          var flowSymb = flowSymbols[i];
1393
 
1394
          var realSymb = getSymbol(flowSymb);
1395
          var next = getNextPath(flowSymb);
1396
 
1397
          var direction = null;
1398
          if (next.indexOf(',') >= 0) {
1399
            var condOpt = next.split(',');
1400
            next = condOpt[0];
1401
            direction = condOpt[1].trim();
1402
          }
1403
 
1404
          if (!chart.start) {
1405
            chart.start = realSymb;
1406
          }
1407
 
1408
          if (i + 1 < lenS) {
1409
            var nextSymb = flowSymbols[i + 1];
1410
            realSymb[next] = getSymbol(nextSymb);
1411
            realSymb['direction_' + next] = direction;
1412
            direction = null;
1413
          }
1414
        }
1415
      }
1416
 
1417
    }
1418
    return chart;
1419
  }
1420
  // public api interface
1421
  flowchart.parse = parse;
1422
 
1423
})();