├── LICENSE ├── README ├── CREDITS ├── src ├── main │ └── js │ │ ├── uritemplates.ebnf │ │ └── uritemplates.js └── test │ └── webenv │ ├── index.html │ ├── syntax.html │ ├── qunit │ ├── qunit.css │ └── qunit.js │ ├── test.html │ └── js │ ├── ebnfdiagram.js │ └── ebnf-jq.js └── Makefile /LICENSE: -------------------------------------------------------------------------------- 1 | URI-templates.js is distributed under the APLv2. (http://opensource.org/licenses/Apache-2.0) 2 | 3 | Check the CREDITS files for the licenses of other open projects that were used to create this. 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Javascript implementation for the URI Templates draft spec maintained at http://code.google.com/p/uri-templates/ 2 | 3 | steps for testing: 4 | ----------------- 5 | * checkout from github 6 | * build with 'make' 7 | * check test-report at src/test/webenv/test.html 8 | 9 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | uri-templates.js uses a number of different other projects. 2 | You might want to check those (and their licenses) out 3 | 4 | [jquery/qunit] 5 | * version 6 | Version: 1.5.2 7 | 8 | * live used or downloaded from 9 | http://code.jquery.com/* 10 | 11 | * by 12 | The jQuery Project. http://jquery.org/ 13 | 14 | * license (see http://jquery.org/license) 15 | MIT (http://www.opensource.org/licenses/mit-license.php) and 16 | GPLv2 (http://www.opensource.org/licenses/gpl-2.0.php) 17 | 18 | * see docs at 19 | http://docs.jquery.com/Main_Page 20 | 21 | -------------------------------------------------------------------------------- /src/main/js/uritemplates.ebnf: -------------------------------------------------------------------------------- 1 | 2 | "uri-templates syntax (draft 0.8)" { 3 | 4 | uri-template = { literals | expression } . 5 | 6 | expression = "{" [ operator ] variable_list "}" . 7 | 8 | operator = op_level2 | op_level3 | op_reserve . 9 | 10 | op_level2 = "+" | "#" . 11 | 12 | op_level3 = "." | "/" | ";" | "?" | "&" . 13 | 14 | op_reserve = "=" | "," | "!" | "@" | "|" . 15 | 16 | variable_list = varspec { "," varspec } . 17 | 18 | varspec = varname [ modifier_level4 ] . 19 | 20 | varname = varchar { ["."] varchar } . 21 | 22 | varchar = ALPHA | DIGIT | "_" | pct_encoded . 23 | 24 | modifier_level4 = prefix | explode . 25 | 26 | prefix = ":" max_length . 27 | 28 | max_length = DIGIT [ DIGIT [ DIGIT [ DIGIT ] ] ] . 29 | 30 | explode = "*" . 31 | 32 | } "See http://code.google.com/p/uri-templates/ for updates and work on the spec." 33 | -------------------------------------------------------------------------------- /src/test/webenv/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | 22 |

URI Templates tester

23 |

24 | Provide some variables in javascript: 25 |

26 | 32 |

33 | Describe your template 34 |

35 | 36 | 37 |
38 |

39 | 
40 | 
41 |   

Automated tests

42 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/webenv/syntax.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 |

uri-templates syntax

15 |
16 | 
17 |     "uri-templates syntax (draft 0.8)" {
18 |      
19 |       uri-template    = { literals | expression } .
20 |       
21 |       expression      =  "{" [ operator ] variable_list "}" .
22 |       
23 |       operator        =  op_level2 | op_level3 | op_reserve .
24 |       
25 |       op_level2       =  "+" | "#" .
26 |       
27 |       op_level3       =  "." | "/" | ";" | "?" | "&" .
28 |       
29 |       op_reserve      =  "=" | "," | "!" | "@" | "|" .
30 |       
31 |       variable_list   =  varspec { "," varspec } .
32 | 
33 |       varspec         =  varname [ modifier_level4 ] .
34 | 
35 |       varname         =  varchar { ["."] varchar } .
36 | 
37 |       varchar         =  ALPHA | DIGIT | "_" | pct_encoded .
38 | 
39 |       modifier_level4 =  prefix | explode .
40 | 
41 |       prefix          = ":" max_length .
42 | 
43 |       max_length      = DIGIT [ DIGIT  [ DIGIT  [ DIGIT ] ] ] .
44 | 
45 |       explode         = "*"  .
46 | 
47 |     } "See http://code.google.com/p/uri-templates/ for updates and work on the spec."
48 | 
49 | 
50 | 51 | 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=0.9 2 | DATE=$(shell date +%Y-%m-%d) 3 | SRC=./src/main 4 | BUILD=./build 5 | DIST=./dist/${VERSION} 6 | NAME = uritemplates 7 | UGLIFY_JS ?= `which uglifyjs` 8 | WATCHR ?= `which watchr` 9 | 10 | build: js 11 | 12 | js: 13 | @@if test ! -z ${UGLIFY_JS}; then \ 14 | mkdir -p ${BUILD}; \ 15 | sed -e 's/@VERSION/'"v${VERSION}"'/' -e 's/@DATE/'"${DATE}"'/' <${SRC}/js/${NAME}.js >${BUILD}/${NAME}.js; \ 16 | uglifyjs -o ${BUILD}/${NAME}.min.js ${BUILD}/${NAME}.js;\ 17 | echo "js compress and uglify sucessful! - `date`"; \ 18 | else \ 19 | echo "You must have the UGLIFYJS minifier installed in order to minify uritemplates' js."; \ 20 | echo "You can install it by running: npm install uglify-js -g"; \ 21 | fi 22 | 23 | makedist: 24 | @@if test -d ${BUILD}; then \ 25 | mkdir -p ${DIST}; \ 26 | cp ${BUILD}/${NAME}.js ${DIST}/${NAME}-${VERSION}.js; \ 27 | cp ${BUILD}/${NAME}.min.js ${DIST}/${NAME}-${VERSION}.min.js; \ 28 | echo "success building distro for version ${VERSION} - `date`; \ 29 | echo "now test then add, commit, tag and push through git to publish"; \ 30 | else \ 31 | echo "No build is available. Run 'make' or explicitely 'make build' first."; \ 32 | fi 33 | 34 | dist: build makedist 35 | 36 | watch: 37 | @@if test ! -z ${WATCHR}; then \ 38 | echo "Watching files in src/main"; \ 39 | watchr -e "watch('src/main/.*/.*js') { system 'make' }"; \ 40 | else \ 41 | echo "You must have the watchr installed in order to watch uritemplate js files."; \ 42 | echo "You can install it by running: gem install watchr"; \ 43 | fi 44 | 45 | .PHONY: build watch 46 | -------------------------------------------------------------------------------- /src/test/webenv/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-banner { 58 | height: 5px; 59 | } 60 | 61 | #qunit-testrunner-toolbar { 62 | padding: 0.5em 0 0.5em 2em; 63 | color: #5E740B; 64 | background-color: #eee; 65 | } 66 | 67 | #qunit-userAgent { 68 | padding: 0.5em 0 0.5em 2.5em; 69 | background-color: #2b81af; 70 | color: #fff; 71 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 72 | } 73 | 74 | 75 | /** Tests: Pass/Fail */ 76 | 77 | #qunit-tests { 78 | list-style-position: inside; 79 | } 80 | 81 | #qunit-tests li { 82 | padding: 0.4em 0.5em 0.4em 2.5em; 83 | border-bottom: 1px solid #fff; 84 | list-style-position: inside; 85 | } 86 | 87 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 88 | display: none; 89 | } 90 | 91 | #qunit-tests li strong { 92 | cursor: pointer; 93 | } 94 | 95 | #qunit-tests li a { 96 | padding: 0.5em; 97 | color: #c2ccd1; 98 | text-decoration: none; 99 | } 100 | #qunit-tests li a:hover, 101 | #qunit-tests li a:focus { 102 | color: #000; 103 | } 104 | 105 | #qunit-tests ol { 106 | margin-top: 0.5em; 107 | padding: 0.5em; 108 | 109 | background-color: #fff; 110 | 111 | border-radius: 15px; 112 | -moz-border-radius: 15px; 113 | -webkit-border-radius: 15px; 114 | 115 | box-shadow: inset 0px 2px 13px #999; 116 | -moz-box-shadow: inset 0px 2px 13px #999; 117 | -webkit-box-shadow: inset 0px 2px 13px #999; 118 | } 119 | 120 | #qunit-tests table { 121 | border-collapse: collapse; 122 | margin-top: .2em; 123 | } 124 | 125 | #qunit-tests th { 126 | text-align: right; 127 | vertical-align: top; 128 | padding: 0 .5em 0 0; 129 | } 130 | 131 | #qunit-tests td { 132 | vertical-align: top; 133 | } 134 | 135 | #qunit-tests pre { 136 | margin: 0; 137 | white-space: pre-wrap; 138 | word-wrap: break-word; 139 | } 140 | 141 | #qunit-tests del { 142 | background-color: #e0f2be; 143 | color: #374e0c; 144 | text-decoration: none; 145 | } 146 | 147 | #qunit-tests ins { 148 | background-color: #ffcaca; 149 | color: #500; 150 | text-decoration: none; 151 | } 152 | 153 | /*** Test Counts */ 154 | 155 | #qunit-tests b.counts { color: black; } 156 | #qunit-tests b.passed { color: #5E740B; } 157 | #qunit-tests b.failed { color: #710909; } 158 | 159 | #qunit-tests li li { 160 | margin: 0.5em; 161 | padding: 0.4em 0.5em 0.4em 0.5em; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #5E740B; 171 | background-color: #fff; 172 | border-left: 26px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 26px solid #EE5757; 189 | } 190 | 191 | #qunit-tests > li:last-child { 192 | border-radius: 0 0 15px 15px; 193 | -moz-border-radius: 0 0 15px 15px; 194 | -webkit-border-bottom-right-radius: 15px; 195 | -webkit-border-bottom-left-radius: 15px; 196 | } 197 | 198 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 199 | #qunit-tests .fail .test-name, 200 | #qunit-tests .fail .module-name { color: #000000; } 201 | 202 | #qunit-tests .fail .test-actual { color: #EE5757; } 203 | #qunit-tests .fail .test-expected { color: green; } 204 | 205 | #qunit-banner.qunit-fail { background-color: #EE5757; } 206 | 207 | 208 | /** Result */ 209 | 210 | #qunit-testresult { 211 | padding: 0.5em 0.5em 0.5em 2.5em; 212 | 213 | color: #2b81af; 214 | background-color: #D2E0E6; 215 | 216 | border-bottom: 1px solid white; 217 | } 218 | 219 | /** Fixture */ 220 | 221 | #qunit-fixture { 222 | position: absolute; 223 | top: -10000px; 224 | left: -10000px; 225 | } 226 | -------------------------------------------------------------------------------- /src/main/js/uritemplates.js: -------------------------------------------------------------------------------- 1 | /* 2 | UriTemplates Template Processor - Version: @VERSION - Dated: @DATE 3 | (c) marc.portier@gmail.com - 2011-2012 4 | Licensed under APLv2 (http://opensource.org/licenses/Apache-2.0) 5 | */ 6 | 7 | ; 8 | var uritemplate = (function() { 9 | 10 | // Below are the functions we originally used from jQuery. 11 | // The implementations below are often more naive then what is inside jquery, but they suffice for our needs. 12 | 13 | function isFunction(fn) { 14 | return typeof fn == 'function'; 15 | } 16 | 17 | function isEmptyObject (obj) { 18 | for(var name in obj){ 19 | return false; 20 | } 21 | return true; 22 | } 23 | 24 | function extend(base, newprops) { 25 | for (var name in newprops) { 26 | base[name] = newprops[name]; 27 | } 28 | return base; 29 | } 30 | 31 | /** 32 | * Create a runtime cache around retrieved values from the context. 33 | * This allows for dynamic (function) results to be kept the same for multiple 34 | * occuring expansions within one template. 35 | * Note: Uses key-value tupples to be able to cache null values as well. 36 | */ 37 | //TODO move this into prep-processing 38 | function CachingContext(context) { 39 | this.raw = context; 40 | this.cache = {}; 41 | } 42 | CachingContext.prototype.get = function(key) { 43 | var val = this.lookupRaw(key); 44 | var result = val; 45 | 46 | if (isFunction(val)) { // check function-result-cache 47 | var tupple = this.cache[key]; 48 | if (tupple !== null && tupple !== undefined) { 49 | result = tupple.val; 50 | } else { 51 | result = val(this.raw); 52 | this.cache[key] = {key: key, val: result}; 53 | // NOTE: by storing tupples we make sure a null return is validly consistent too in expansions 54 | } 55 | } 56 | return result; 57 | }; 58 | 59 | CachingContext.prototype.lookupRaw = function(key) { 60 | return CachingContext.lookup(this, this.raw, key); 61 | }; 62 | 63 | CachingContext.lookup = function(me, context, key) { 64 | var result = context[key]; 65 | if (result !== undefined) { 66 | return result; 67 | } else { 68 | var keyparts = key.split('.'); 69 | var i = 0, keysplits = keyparts.length - 1; 70 | for (i = 0; i 3 | 4 | 5 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 705 | 706 | 707 |

QUnit tests for uri-templates

708 |

709 |
710 |

711 |
    712 |
    test markup, will be hidden
    713 | 714 | 715 | -------------------------------------------------------------------------------- /src/test/webenv/js/ebnfdiagram.js: -------------------------------------------------------------------------------- 1 | ;var G_vmlCanvasManager; 2 | (function($){ 3 | 4 | $.extend({ 5 | "mergedClone": function(base, spec) { 6 | return $.extend(true, $.extend({}, base), spec); 7 | } 8 | }); 9 | 10 | function makeCanvas(w, h, fn, hidden) { 11 | fn = fn || function() {}; 12 | h = h ? Math.floor(h) : 100; 13 | w = w ? Math.floor(w) : 300; 14 | 15 | var $cnv; 16 | 17 | // check up on ie support via explorercanvas to initialize properly 18 | if (G_vmlCanvasManager !== undefined && G_vmlCanvasManager !== null) { 19 | // explorercanvas kinda expects elms to be built this way, not the jquery way. 20 | var cnv = document.createElement('canvas'); 21 | cnv.setAttribute('height', h); 22 | cnv.setAttribute('width', w); 23 | cnv = G_vmlCanvasManager.initElement(cnv); 24 | $cnv = $(cnv); 25 | } else { 26 | $cnv = $(''); 27 | } 28 | 29 | fn($cnv); 30 | 31 | if (hidden) { $cnv.hide(); } 32 | 33 | return $cnv; 34 | } 35 | 36 | function makeDiagram($elm, styles) { 37 | var ebnf = $elm.text(); 38 | 39 | //TODO perform hight/width calculations 40 | var h = $elm.height(); 41 | var w = $elm.width(); 42 | 43 | var $cnv = makeCanvas( $elm.width(), $elm.height(), function($cnv) {$cnv.insertAfter($elm);}, true); 44 | 45 | var dia = $cnv.ebnfcanvas(styles)[0]; 46 | 47 | var showDiagram = function(dia) { 48 | $elm.hide("normal"); 49 | dia.$cnv.show("normal"); 50 | }; 51 | 52 | $.ebnfParse( ebnf, 53 | function(syn){ 54 | dia.setSyntax(syn); 55 | showDiagram(dia); 56 | }, 57 | function(err) { 58 | dia.showErrors(err); 59 | showDiagram(dia); 60 | } 61 | ); 62 | } 63 | 64 | 65 | //default style 66 | var STYLES = { 67 | "SYNTAX": { 68 | "margin" : 10, 69 | "round" : 10, 70 | "background-fill" : "rgba(200,200,100, 0.3)", 71 | "background-stroke" : "rgba(255,255,255, 0)", 72 | "background-weight" : 0, 73 | "separator-stroke" : "rgba( 0, 0, 0, 0.7)", 74 | "separator-weight" : 2, 75 | "title-font" : "30px Arial", 76 | "title-color" : "rgba( 0, 0, 0, 0.7)", 77 | "title-align" : "left", 78 | "comment-font" : "20px Optimer", 79 | "comment-color" : "rgba(0, 0, 0, 0.5)", 80 | "comment-align" : "left" 81 | }, 82 | "PRODUCTION_SET": { 83 | }, 84 | "PRODUCTION": { 85 | "margin" : 10, 86 | "round" : 10, 87 | "background-fill" : "rgba(255,255,255, 0.75)", 88 | "background-stroke" : "rgba(255,255,255, 0)", 89 | "background-weight" : 0, 90 | "font" : "10px Arial", 91 | "color" : "rgba( 0, 0, 0, 0.7)", 92 | "align" : "left", 93 | "baseline" : "bottom", 94 | "trail" : 75 95 | }, 96 | "TERM_SET": { 97 | }, 98 | "FACTOR_SET": { 99 | }, 100 | "LITERAL": { 101 | "round" : 10, 102 | "background-fill" : "rgba( 20, 20,200, 0.5)", 103 | "background-stroke" : "rgba( 10, 10, 10, 0.8)", 104 | "background-weight" : 3 105 | }, 106 | "IDENTIFIER": { 107 | "round" : 0, 108 | "background-fill" : "rgba(200, 20, 20, 0.5)", 109 | "background-stroke" : "rgba( 10, 10, 10, 0.8)", 110 | "background-weight" : 3 111 | }, 112 | "GROUP": { 113 | }, 114 | "REPEATING": { 115 | }, 116 | "OPTIONAL": { 117 | }, 118 | "DIAGRAM": { 119 | "line-stroke" : "rgba( 10, 10, 10, 1)", 120 | "line-weight" : 2, 121 | "grid" : 12, 122 | "font-width" : 10, 123 | "font-height" : 24, 124 | "font" : "12px Arial", 125 | "color" : "rgba( 0, 0, 0, 0.7)" 126 | } 127 | }; 128 | 129 | 130 | function EbnfDiagram($elm, styles) { 131 | this.$cnv = $elm; 132 | 133 | this.styles = $.mergedClone(STYLES, styles); 134 | this.style = this.styles.DIAGRAM; 135 | this.init(); 136 | } 137 | 138 | EbnfDiagram.prototype.getGC = function() { 139 | if (! this.gc) { 140 | this.gc = this.$cnv.get(0).getContext('2d'); 141 | } 142 | return this.gc; 143 | }; 144 | 145 | 146 | 147 | // Internal GCLIB with standard operations/drawing assistance.. 148 | var GCLIB = {}; 149 | 150 | /** Draws a centered (ie at 0,0) rounded rectangle with the specified width, height and percentage of round-ness on the corners */ 151 | GCLIB.roundedRect = function(gc, width, height, r, atx, aty) { 152 | if (! (width && height)) { 153 | return; 154 | } 155 | 156 | r = r || 0; 157 | atx = atx || 0; 158 | aty = aty || 0; 159 | 160 | var w = width/2; 161 | var h = height/2; 162 | 163 | gc.beginPath(); 164 | if (r) { 165 | gc.arc( atx -w + r, aty -h + r, r, -Math.PI , -Math.PI/2, false); 166 | gc.arc( atx +w - r, aty -h + r, r, -Math.PI/2, 0 , false); 167 | gc.arc( atx +w - r, aty +h - r, r, 0 , Math.PI/2 , false); 168 | gc.arc( atx -w + r, aty +h - r, r, Math.PI/2 , Math.PI , false); 169 | } else { 170 | gc.moveTo( atx -w, aty -h); 171 | gc.lineTo( atx +w, aty -h); 172 | gc.lineTo( atx +w, aty +h); 173 | gc.lineTo( atx -w, aty +h); 174 | } 175 | gc.closePath(); 176 | gc.fill(); 177 | gc.stroke(); 178 | }; 179 | 180 | /** Draws text centered at 0,0 */ 181 | GCLIB.centeredText = function(gc, text, x, y) { 182 | x = x || 0; 183 | y = y || 0; 184 | gc.fillText(text, x, y); 185 | }; 186 | 187 | /** Draws line from a to b */ 188 | GCLIB.line = function(gc, ax, ay, bx, by) { 189 | gc.beginPath(); 190 | gc.moveTo( ax, ay); 191 | gc.lineTo( bx, by); 192 | gc.closePath(); 193 | gc.stroke(); 194 | }; 195 | 196 | 197 | /** Draws slide-in from a to b */ 198 | GCLIB.slideIn = function(gc, g, atx , aty, w, h){ 199 | w = Math.max(w , 2*g); // minimal width 200 | h = h || 0; 201 | if (h === 0) { 202 | GCLIB.line(gc, atx, aty, atx+w, aty); 203 | return; 204 | } 205 | gc.beginPath(); 206 | gc.arc( atx , aty + g, g, -Math.PI/2, 0,false); 207 | gc.lineTo( atx + g, aty + h - g); 208 | gc.arc( atx +2*g, aty + h - g, g, Math.PI, Math.PI/2,true); 209 | gc.lineTo(atx + w, aty + h); 210 | gc.stroke(); 211 | }; 212 | 213 | GCLIB.slideOut = function(gc, g, tox, toy, w, h){ 214 | w = Math.max(w , 2*g); // minimal width 215 | h = h || 0; 216 | if (h === 0) { 217 | gc.beginPath(); 218 | gc.moveTo(tox -w, toy); 219 | gc.lineTo( tox , toy); 220 | gc.stroke(); 221 | return; 222 | } 223 | gc.beginPath(); 224 | gc.moveTo(tox -w, toy + h); 225 | gc.lineTo(tox - 2*g, toy + h); 226 | gc.arc( tox - 2 * g , toy + h - g, g, Math.PI/2, 0, true); 227 | gc.lineTo( tox -g , toy + g); 228 | gc.arc( tox, toy + g, g, -Math.PI, -Math.PI/2, false); 229 | gc.stroke(); 230 | }; 231 | 232 | GCLIB.loopDown = function(gc, g, atx, aty, h){ 233 | h = h || 0; 234 | if (h === 0) { 235 | return; 236 | } 237 | gc.beginPath(); 238 | gc.arc( atx , aty + g, g, -Math.PI/2, 0,false); 239 | gc.lineTo( atx + g, aty + h - g); 240 | gc.arc( atx , aty + h - g, g, 0, Math.PI/2,false); 241 | gc.stroke(); 242 | }; 243 | 244 | GCLIB.loopUp = function(gc, g, tox, toy, h){ 245 | h = h || 0; 246 | if (h === 0) { 247 | return; 248 | } 249 | gc.beginPath(); 250 | gc.arc( tox , toy + h - g, g, Math.PI/2, Math.PI, false); 251 | gc.lineTo( tox - g, toy + g); 252 | gc.arc( tox , toy + g, g, -Math.PI, -Math.PI/2,false); 253 | gc.stroke(); 254 | }; 255 | 256 | var DEFAULT_FILL = "rgb(255,255,255)"; 257 | var DEFAULT_STROKE = "rgb( 0, 0, 0)"; 258 | var DEFAULT_WEIGHT = 0; 259 | var DEFAULT_FONT = "8px Arial"; 260 | var DEFAULT_COLOR = "rgb( 0, 0, 0)"; 261 | var DEFAULT_ALIGN = "center"; 262 | var DEFAULT_BASELINE = "middle"; 263 | 264 | /** Sets drawstyles */ 265 | GCLIB.setDrawStyle = function(gc, style, pfx) { 266 | pfx = pfx ? pfx + "-" : ""; 267 | gc.fillStyle = style[pfx + "fill"] || DEFAULT_FILL; 268 | gc.strokeStyle = style[pfx + "stroke"] || DEFAULT_STROKE; 269 | gc.lineWidth = style[pfx + "weight"] || DEFAULT_WEIGHT; 270 | }; 271 | 272 | /** Sets font-styles */ 273 | GCLIB.setTextStyle = function(gc, style, pfx) { 274 | pfx = pfx ? pfx + "-" : ""; 275 | gc.font = style[pfx + "font"] || DEFAULT_FONT; 276 | gc.fillStyle = style[pfx + "color"] || DEFAULT_COLOR; 277 | gc.textAlign = style[pfx + "align"] || DEFAULT_ALIGN; 278 | gc.textBaseline = style[pfx + "baseline"] || DEFAULT_BASELINE; 279 | }; 280 | 281 | 282 | var NODEHANDLERS = { 283 | "SYNTAX": { 284 | "prepare": function(node, dia, gc) { 285 | var s = node.style; 286 | var m = s.margin; 287 | 288 | var c = node.set; 289 | dia.prepare(c); 290 | 291 | node.height = c.height + 2*m + 2*30 + 2*20; // TODO calculated room for title and comment 292 | node.width = c.width + 2*m; 293 | }, 294 | "draw" : function(node, dia, gc) { 295 | var s = node.style; 296 | var m = s.margin; 297 | 298 | var ox = node.width / 2; 299 | var oy = node.height / 2; 300 | var ty = 30; //TODO calc 301 | var cy = 20; //TODO calc 302 | 303 | GCLIB.setDrawStyle(gc, s, "background"); 304 | GCLIB.roundedRect(gc, (node.width - m), (node.height - m), s.round, ox, oy); 305 | 306 | GCLIB.setTextStyle(gc, s, "title"); 307 | GCLIB.centeredText(gc, node.title, 2*m, ty); 308 | 309 | GCLIB.setDrawStyle(gc, node.style, "separator"); 310 | GCLIB.line(gc, 2*m, 2*ty, node.width - 2*m, 2*ty); 311 | 312 | dia.draw(node.set, m, 2*ty + m); 313 | 314 | GCLIB.line(gc, 2*m, node.height - 2*cy, node.width - 2*m, node.height - 2*cy); 315 | 316 | GCLIB.setTextStyle(gc, s, "comment"); 317 | GCLIB.centeredText(gc, node.comment, 2*m, node.height - cy); 318 | } 319 | }, 320 | "PRODUCTION_SET": { 321 | "prepare": function(node, dia, gc) { 322 | var sumH = 0; 323 | var maxW = 0; 324 | 325 | var i = 0, c = node.children; 326 | for (i = 0; i< c.length; i++){ 327 | var ci = c[i]; 328 | dia.prepare(ci); 329 | sumH += ci.height; 330 | maxW = Math.max(maxW, ci.width); 331 | } 332 | 333 | // align all widths 334 | for ( i = 0; i < c.length; i++){ 335 | c[i].width = maxW; 336 | } 337 | 338 | node.width = maxW; 339 | node.height = sumH; 340 | }, 341 | "draw" : function(node, dia, gc) { 342 | var s = node.style; 343 | 344 | var offH = 0; 345 | 346 | var i = 0, c = node.children; 347 | 348 | for (i = 0; i < c.length; i++){ 349 | var ci = c[i]; 350 | dia.draw(ci, 0, offH); 351 | offH += ci.height; 352 | } 353 | } 354 | }, 355 | "PRODUCTION": { 356 | "prepare": function(node, dia, gc) { 357 | var s = node.style; 358 | var ds = dia.style; 359 | var g = ds.grid; 360 | var m = s.margin; 361 | 362 | var c = node.expr; 363 | dia.prepare(c); 364 | 365 | node.width = c.width + 2*m + s.trail; 366 | node.height = c.height+ 2*m; 367 | }, 368 | "draw" : function(node, dia, gc) { 369 | var s = node.style; 370 | var ds = dia.style; 371 | var g = ds.grid; 372 | var m = s.margin; 373 | var tx = 10; 374 | var ty = 10; 375 | 376 | var ox = node.width / 2; 377 | var oy = node.height / 2; 378 | 379 | GCLIB.setDrawStyle(gc, s, "background"); 380 | GCLIB.roundedRect(gc, (node.width - m), (node.height - m), s.round, ox, oy); 381 | 382 | GCLIB.setTextStyle(gc, s); 383 | GCLIB.centeredText(gc, node.id, m + tx, m + ty); 384 | 385 | GCLIB.setDrawStyle(gc, ds, "line"); 386 | GCLIB.line(gc, m , m+ 2*g, m + s.trail, m+ 2*g); 387 | 388 | // delegate to expression 389 | var c = node.expr; 390 | dia.draw(c, m + s.trail, m); 391 | 392 | GCLIB.line(gc, m + s.trail + node.expr.width , m+ 2*g, node.width -m, m + 2*g); 393 | GCLIB.line(gc, node.width - m, m+ 1.5 * g, node.width - m, m+ 2.5*g); 394 | } 395 | }, 396 | "TERM_SET": { 397 | "prepare": function(node, dia, gc) { 398 | var ds = dia.style; 399 | var g = ds.grid; 400 | 401 | var sumH = 0; 402 | var maxW = 0; 403 | 404 | var i = 0, c = node.children; 405 | for (i = 0; i< c.length; i++){ 406 | var ci = c[i]; 407 | dia.prepare(ci); 408 | sumH += ci.height; 409 | maxW = Math.max(maxW, ci.width); 410 | } 411 | 412 | node.width = maxW + 6 * g; // make room for parallelization lines 413 | node.height = sumH + (c.length - 1) * g; // make room for space between terms 414 | }, 415 | "draw" : function(node, dia, gc) { 416 | var s = node.style; 417 | var ds = dia.style; 418 | var g = ds.grid; 419 | 420 | var offH = 0; 421 | 422 | var i = 0, c = node.children; 423 | 424 | GCLIB.setDrawStyle(gc, ds, "line"); 425 | for (i = 0; i < c.length; i++){ 426 | var ci = c[i]; 427 | GCLIB.slideIn (gc, g, 0 , 2*g, 3*g, offH); 428 | 429 | dia.draw(ci, 3*g, offH); 430 | 431 | GCLIB.line(gc, ci.width + 3*g , offH + 2*g, node.width - 3*g, offH + 2*g); 432 | GCLIB.slideOut(gc, g, node.width, 2*g, 3*g, offH); 433 | 434 | offH += ci.height + g; //add space between terms 435 | } 436 | } 437 | }, 438 | "FACTOR_SET": { 439 | "prepare": function(node, dia, gc) { 440 | var ds = dia.style; 441 | var g = ds.grid; 442 | 443 | var sumW = 0; 444 | var maxH = 0; 445 | 446 | var i = 0, c = node.children; 447 | for (i = 0; i< c.length; i++){ 448 | var ci = c[i]; 449 | dia.prepare(ci); 450 | sumW += ci.width; 451 | maxH = Math.max(maxH, ci.height); 452 | } 453 | 454 | // no need to align the heights 455 | 456 | node.width = sumW; 457 | node.height = maxH; 458 | }, 459 | "draw" : function(node, dia, gc) { 460 | var s = node.style; 461 | var ds = dia.style; 462 | var g = ds.grid; 463 | 464 | var offW = 0; 465 | 466 | var i = 0, c = node.children; 467 | for (i = 0; i < c.length; i++){ 468 | var ci = c[i]; 469 | dia.draw(ci, offW, 0); 470 | offW += ci.width; 471 | } 472 | } 473 | }, 474 | "LITERAL": { 475 | "prepare": function(node, dia, gc) { 476 | 477 | var l = Math.max(3, node.txt.length); 478 | var ds = dia.style; 479 | var g = ds.grid; 480 | 481 | node.width = l * ds["font-width"] + 2 * g; 482 | node.height = 4 * g ; 483 | }, 484 | "draw" : function(node, dia, gc) { 485 | var s = node.style; 486 | var ds = dia.style; 487 | var g = ds.grid; 488 | var ox = node.width / 2; 489 | var oy = node.height /2; 490 | 491 | GCLIB.setDrawStyle(gc, ds, "line"); 492 | GCLIB.line(gc, 0, 2*g, g, 2*g); 493 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g); 494 | 495 | 496 | GCLIB.setDrawStyle(gc,s, "background"); 497 | GCLIB.roundedRect(gc, node.width - 2 * g, ds["font-height"], s.round, ox, oy); 498 | 499 | GCLIB.setTextStyle(gc, ds); 500 | GCLIB.centeredText(gc, node.txt, ox, oy); 501 | } 502 | }, 503 | "IDENTIFIER": { 504 | "prepare": function(node, dia, gc) { 505 | 506 | var l = Math.max(3, node.id.length); 507 | var ds = dia.style; 508 | var g = ds.grid; 509 | 510 | node.width = l * dia.style["font-width"] + 2*g; 511 | node.height = 4*g ; 512 | }, 513 | "draw" : function(node, dia, gc) { 514 | var s = node.style; 515 | var ds = dia.style; 516 | var g = ds.grid; 517 | var ox = node.width / 2; 518 | var oy = node.height /2; 519 | 520 | GCLIB.setDrawStyle(gc, ds, "line"); 521 | GCLIB.line(gc, 0, 2*g, g, 2*g); 522 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g); 523 | 524 | 525 | GCLIB.setDrawStyle(gc,s, "background"); 526 | GCLIB.roundedRect(gc, node.width - 2 * g, ds["font-height"], s.round, ox, oy); 527 | 528 | GCLIB.setTextStyle(gc, ds); 529 | GCLIB.centeredText(gc, node.id , ox, oy); 530 | } 531 | }, 532 | "GROUP": { 533 | "prepare": function(node, dia, gc) { 534 | 535 | var ds = dia.style; 536 | var g = ds.grid; 537 | 538 | var c = node.expr; 539 | dia.prepare(c); 540 | 541 | node.width = c.width + 2 * g; 542 | node.height = c.height; 543 | }, 544 | "draw" : function(node, dia, gc) { 545 | var s = node.style; 546 | var ds = dia.style; 547 | var g = ds.grid; 548 | 549 | GCLIB.setDrawStyle(gc, ds, "line"); 550 | GCLIB.line(gc, 0, 2*g, g, 2*g); 551 | 552 | dia.draw(node.expr, g, 0); 553 | 554 | GCLIB.line(gc, node.width - g, 2*g, node.width, 2*g); 555 | } 556 | }, 557 | "REPEATING": { 558 | "prepare": function(node, dia, gc) { 559 | var ds = dia.style; 560 | var g = ds.grid; 561 | 562 | var c = node.expr; 563 | dia.prepare(c); 564 | 565 | node.width = c.width + 6*g; 566 | node.height = c.height; 567 | }, 568 | "draw" : function(node, dia, gc) { 569 | var s = node.style; 570 | var ds = dia.style; 571 | var g = ds.grid; 572 | 573 | var c = node.expr; 574 | 575 | GCLIB.setDrawStyle(gc, ds, "line"); 576 | GCLIB.slideOut(gc, g, 2*g, 0, 2*g, 2*g); 577 | GCLIB.line(gc, 2*g , 0, node.width - 2*g, 0); 578 | GCLIB.slideIn (gc, g, node.width - 2*g, 0, 2*g, 2*g); 579 | 580 | GCLIB.line(gc, 0, 2*g, 3*g, 2*g); 581 | dia.draw(c, 3*g, 0); 582 | GCLIB.line(gc, node.width - 3*g, 2*g, node.width, 2*g); 583 | 584 | GCLIB.loopDown(gc, g, 3*g + c.width, 2*g, - 2*g + c.height); 585 | GCLIB.line(gc, 3*g , c.height, node.width - 3*g, c.height); 586 | GCLIB.loopUp(gc, g, 3*g, 2*g, - 2*g + c.height); 587 | } 588 | }, 589 | "OPTIONAL": { 590 | "prepare": function(node, dia, gc) { 591 | var ds = dia.style; 592 | var g = ds.grid; 593 | 594 | var c = node.expr; 595 | dia.prepare(c); 596 | 597 | node.width = c.width + 6*g; 598 | node.height = c.height + 2*g; 599 | }, 600 | "draw" : function(node, dia, gc) { 601 | var s = node.style; 602 | var ds = dia.style; 603 | var g = ds.grid; 604 | 605 | GCLIB.setDrawStyle(gc, ds, "line"); 606 | GCLIB.line(gc, 0 , 2*g, node.width, 2*g); 607 | 608 | GCLIB.slideIn (gc, g, 0 , 2*g, 3*g, 2*g); 609 | 610 | dia.draw(node.expr, 3*g, 2*g); 611 | 612 | GCLIB.slideOut(gc, g, node.width, 2*g, 3*g, 2*g); 613 | } 614 | } 615 | }; 616 | 617 | //actual diagram code 618 | EbnfDiagram.prototype.init = function() { 619 | 620 | this.height = this.$cnv.attr('height'); 621 | this.width = this.$cnv.attr('width'); 622 | 623 | // clear the canvas 624 | this.getGC().clearRect( 0, 0, this.width, this.height); 625 | }; 626 | 627 | EbnfDiagram.prototype.showErrors = function(err) { 628 | var i = 0, l = err.length; 629 | for (i = 0; i++; i 16 | 17 | This is in the public domain. 18 | */ 19 | 20 | 21 | ; 22 | (function($){ 23 | 24 | // node structs 25 | function Syntax(set, title, comment) { 26 | this.set = set; 27 | this.title = title || ""; 28 | this.comment = comment || ""; 29 | } 30 | Syntax.prototype.toString = function() { 31 | var l = "\n---------------------------------------------------------\n"; 32 | return l + "-- " + this.title + l + this.set + l +"comment:\n" + this.comment + l; 33 | } 34 | Syntax.prototype.nodetype="SYNTAX"; 35 | 36 | 37 | var PRODUCTION_SET = { 38 | "label" : "@@", 39 | "join" : function(i) { return "\n" + i + "| "; }, 40 | "nodetype": "PRODUCTION_SET" 41 | }; 42 | var TERM_SET = { 43 | "label" : "", 44 | "join" : function(i) { return (i==0) ? "" : "|";}, 45 | "nodetype": "TERM_SET" 46 | }; 47 | var FACTOR_SET = { 48 | "label" : "", 49 | "join" : function(i) { return ""; }, 50 | "nodetype": "FACTOR_SET" 51 | }; 52 | function Set(t) { 53 | this.label = t.label; 54 | this.join = t.join; 55 | this.nodetype = t.nodetype; 56 | this.children = new Array(); 57 | } 58 | Set.prototype.addChild = function(node) { 59 | this.children.push(node); 60 | };; 61 | Set.prototype.toString = function() { 62 | var cnt = this.children.length; 63 | var o = this.label; 64 | for (var i = 0; i < cnt; i++) 65 | o += this.join(i) + this.children[i].toString(); 66 | return o; 67 | } 68 | 69 | 70 | function Production(id, expr) { 71 | this.id = id; 72 | this.expr = expr; 73 | } 74 | Production.prototype.toString = function(ind) { 75 | ind = ind || ""; 76 | return ind + this.id + "=" + this.expr + " ." ; 77 | } 78 | Production.prototype.nodetype="PRODUCTION"; 79 | 80 | 81 | function Identifier(id) { 82 | this.id = id; 83 | } 84 | Identifier.prototype.toString = function() { 85 | return this.id; 86 | }; 87 | Identifier.prototype.nodetype="IDENTIFIER"; 88 | 89 | 90 | function Literal(txt) { 91 | this.txt = txt; 92 | } 93 | Literal.prototype.toString = function() { 94 | return "\"" + this.txt + "\""; 95 | }; 96 | Literal.prototype.nodetype="LITERAL"; 97 | 98 | 99 | function Optional(expr) { 100 | this.expr = expr; 101 | } 102 | Optional.prototype.toString = function() { 103 | return "[" + this.expr + "]"; 104 | }; 105 | Optional.prototype.nodetype="OPTIONAL"; 106 | 107 | 108 | function Repeating(expr) { 109 | this.expr = expr; 110 | } 111 | Repeating.prototype.toString = function() { 112 | return "{" + this.expr + "}"; 113 | }; 114 | Repeating.prototype.nodetype="REPEATING"; 115 | 116 | 117 | function Group(expr) { 118 | this.expr = expr; 119 | } 120 | Group.prototype.toString = function() { 121 | return "(" + this.expr + ")"; 122 | }; 123 | Group.prototype.nodetype="GROUP"; 124 | 125 | 126 | // node factories 127 | function createNode(type, childs) { 128 | var children = new Array(); 129 | 130 | for( var i = 2; i < arguments.length; i++ ) 131 | children.push( arguments[i] ); 132 | 133 | return new Node(type, children); 134 | }; 135 | 136 | function createSyntax(set, title, comment) { 137 | return new Syntax(set, title, comment); 138 | }; 139 | 140 | function createSet() { 141 | return new Set(PRODUCTION_SET); 142 | } 143 | 144 | function createProduction(id, expr) { 145 | return new Production(id, expr); 146 | } 147 | 148 | function createExpression() { 149 | return new Set(TERM_SET); 150 | } 151 | 152 | function createTerm() { 153 | return new Set(FACTOR_SET); 154 | } 155 | 156 | function createIdentifier(id) { 157 | return new Identifier(id); 158 | } 159 | 160 | function createLiteral(txt) { 161 | return new Literal(txt); 162 | } 163 | 164 | function createOptional(termset) { 165 | return new Optional(termset); 166 | } 167 | 168 | function createRepeating(termset) { 169 | return new Repeating(termset); 170 | } 171 | 172 | function createGroup(termset) { 173 | return new Group(termset); 174 | } 175 | 176 | var parseComplete = function(syn){}; 177 | 178 | 179 | 180 | var _dbg_withparsetree = false; 181 | var _dbg_withtrace = false; 182 | var _dbg_withstepbystep = false; 183 | 184 | function __dbg_print( text ) 185 | { 186 | print( text ); 187 | } 188 | 189 | function __dbg_wait() 190 | { 191 | var kbd = new java.io.BufferedReader( 192 | new java.io.InputStreamReader( java.lang.System[ "in" ] ) ); 193 | 194 | kbd.readLine(); 195 | } 196 | 197 | function __lex( info ) 198 | { 199 | var state = 0; 200 | var match = -1; 201 | var match_pos = 0; 202 | var start = 0; 203 | var pos = info.offset + 1; 204 | 205 | do 206 | { 207 | pos--; 208 | state = 0; 209 | match = -2; 210 | start = pos; 211 | 212 | if( info.src.length <= start ) 213 | return 21; 214 | 215 | do 216 | { 217 | 218 | switch( state ) 219 | { 220 | case 0: 221 | if( ( info.src.charCodeAt( pos ) >= 9 && info.src.charCodeAt( pos ) <= 10 ) || info.src.charCodeAt( pos ) == 13 || info.src.charCodeAt( pos ) == 32 ) state = 1; 222 | else if( info.src.charCodeAt( pos ) == 40 ) state = 2; 223 | else if( info.src.charCodeAt( pos ) == 41 ) state = 3; 224 | else if( info.src.charCodeAt( pos ) == 44 ) state = 4; 225 | else if( info.src.charCodeAt( pos ) == 46 || info.src.charCodeAt( pos ) == 59 ) state = 5; 226 | else if( info.src.charCodeAt( pos ) == 61 ) state = 6; 227 | else if( ( info.src.charCodeAt( pos ) >= 65 && info.src.charCodeAt( pos ) <= 90 ) || info.src.charCodeAt( pos ) == 95 || ( info.src.charCodeAt( pos ) >= 97 && info.src.charCodeAt( pos ) <= 122 ) ) state = 7; 228 | else if( info.src.charCodeAt( pos ) == 91 ) state = 8; 229 | else if( info.src.charCodeAt( pos ) == 93 ) state = 9; 230 | else if( info.src.charCodeAt( pos ) == 123 ) state = 10; 231 | else if( info.src.charCodeAt( pos ) == 124 ) state = 11; 232 | else if( info.src.charCodeAt( pos ) == 125 ) state = 12; 233 | else if( info.src.charCodeAt( pos ) == 34 ) state = 14; 234 | else if( info.src.charCodeAt( pos ) == 39 ) state = 16; 235 | else state = -1; 236 | break; 237 | 238 | case 1: 239 | state = -1; 240 | match = 1; 241 | match_pos = pos; 242 | break; 243 | 244 | case 2: 245 | state = -1; 246 | match = 9; 247 | match_pos = pos; 248 | break; 249 | 250 | case 3: 251 | state = -1; 252 | match = 10; 253 | match_pos = pos; 254 | break; 255 | 256 | case 4: 257 | state = -1; 258 | match = 3; 259 | match_pos = pos; 260 | break; 261 | 262 | case 5: 263 | state = -1; 264 | match = 11; 265 | match_pos = pos; 266 | break; 267 | 268 | case 6: 269 | state = -1; 270 | match = 2; 271 | match_pos = pos; 272 | break; 273 | 274 | case 7: 275 | if( ( info.src.charCodeAt( pos ) >= 48 && info.src.charCodeAt( pos ) <= 57 ) || ( info.src.charCodeAt( pos ) >= 65 && info.src.charCodeAt( pos ) <= 90 ) || info.src.charCodeAt( pos ) == 95 || ( info.src.charCodeAt( pos ) >= 97 && info.src.charCodeAt( pos ) <= 122 ) ) state = 7; 276 | else state = -1; 277 | match = 12; 278 | match_pos = pos; 279 | break; 280 | 281 | case 8: 282 | state = -1; 283 | match = 7; 284 | match_pos = pos; 285 | break; 286 | 287 | case 9: 288 | state = -1; 289 | match = 8; 290 | match_pos = pos; 291 | break; 292 | 293 | case 10: 294 | state = -1; 295 | match = 5; 296 | match_pos = pos; 297 | break; 298 | 299 | case 11: 300 | state = -1; 301 | match = 4; 302 | match_pos = pos; 303 | break; 304 | 305 | case 12: 306 | state = -1; 307 | match = 6; 308 | match_pos = pos; 309 | break; 310 | 311 | case 13: 312 | state = -1; 313 | match = 13; 314 | match_pos = pos; 315 | break; 316 | 317 | case 14: 318 | if( info.src.charCodeAt( pos ) == 34 ) state = 13; 319 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14; 320 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17; 321 | else state = -1; 322 | break; 323 | 324 | case 15: 325 | if( info.src.charCodeAt( pos ) == 34 ) state = 13; 326 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14; 327 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17; 328 | else state = -1; 329 | match = 13; 330 | match_pos = pos; 331 | break; 332 | 333 | case 16: 334 | if( info.src.charCodeAt( pos ) == 39 ) state = 13; 335 | else if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 38 ) || ( info.src.charCodeAt( pos ) >= 40 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 16; 336 | else if( info.src.charCodeAt( pos ) == 92 ) state = 18; 337 | else state = -1; 338 | break; 339 | 340 | case 17: 341 | if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 33 ) || ( info.src.charCodeAt( pos ) >= 35 && info.src.charCodeAt( pos ) <= 91 ) || ( info.src.charCodeAt( pos ) >= 93 && info.src.charCodeAt( pos ) <= 254 ) ) state = 14; 342 | else if( info.src.charCodeAt( pos ) == 34 ) state = 15; 343 | else if( info.src.charCodeAt( pos ) == 92 ) state = 17; 344 | else state = -1; 345 | break; 346 | 347 | case 18: 348 | if( ( info.src.charCodeAt( pos ) >= 0 && info.src.charCodeAt( pos ) <= 254 ) ) state = 16; 349 | else state = -1; 350 | break; 351 | 352 | } 353 | 354 | 355 | pos++; 356 | 357 | } 358 | while( state > -1 ); 359 | 360 | } 361 | while( 1 > -1 && match == 1 ); 362 | 363 | if( match > -1 ) 364 | { 365 | info.att = info.src.substr( start, match_pos - start ); 366 | info.offset = match_pos; 367 | 368 | switch( match ) 369 | { 370 | case 13: 371 | { 372 | ; 373 | info.att = info.att.substr( 1, info.att.length - 2 ); 374 | info.att = info.att.replace(/\\\'/g,"\'").replace(/\\\"/g,"\"").replace(/\\\\/g,"\\"); 375 | } 376 | break; 377 | 378 | } 379 | 380 | 381 | } 382 | else 383 | { 384 | info.att = new String(); 385 | match = -1; 386 | } 387 | 388 | return match; 389 | } 390 | 391 | 392 | function __parse( src, err_off, err_la ) 393 | { 394 | var sstack = new Array(); 395 | var vstack = new Array(); 396 | var err_cnt = 0; 397 | var act; 398 | var go; 399 | var la; 400 | var rval; 401 | var parseinfo = new Function( "", "var offset; var src; var att;" ); 402 | var info = new parseinfo(); 403 | 404 | //Visual parse tree generation 405 | var treenode = new Function( "", "var sym; var att; var child;" ); 406 | var treenodes = new Array(); 407 | var tree = new Array(); 408 | var tmptree = null; 409 | 410 | /* Pop-Table */ 411 | var pop_tab = new Array( 412 | new Array( 0/* root' */, 1 ), 413 | new Array( 15/* root */, 1 ), 414 | new Array( 15/* root */, 0 ), 415 | new Array( 14/* syntax */, 5 ), 416 | new Array( 14/* syntax */, 4 ), 417 | new Array( 14/* syntax */, 4 ), 418 | new Array( 14/* syntax */, 3 ), 419 | new Array( 16/* productionset */, 1 ), 420 | new Array( 16/* productionset */, 2 ), 421 | new Array( 17/* production */, 4 ), 422 | new Array( 18/* expression */, 1 ), 423 | new Array( 18/* expression */, 3 ), 424 | new Array( 19/* term */, 1 ), 425 | new Array( 19/* term */, 2 ), 426 | new Array( 20/* factor */, 1 ), 427 | new Array( 20/* factor */, 1 ), 428 | new Array( 20/* factor */, 3 ), 429 | new Array( 20/* factor */, 3 ), 430 | new Array( 20/* factor */, 3 ) 431 | ); 432 | 433 | /* Action-Table */ 434 | var act_tab = new Array( 435 | /* State 0 */ new Array( 13/* "Literal" */,3 , 5/* "{" */,4 , 21/* "$" */,-2 ), 436 | /* State 1 */ new Array( 21/* "$" */,0 ), 437 | /* State 2 */ new Array( 21/* "$" */,-1 ), 438 | /* State 3 */ new Array( 5/* "{" */,5 ), 439 | /* State 4 */ new Array( 12/* "Identifier" */,8 ), 440 | /* State 5 */ new Array( 12/* "Identifier" */,8 ), 441 | /* State 6 */ new Array( 6/* "}" */,11 , 12/* "Identifier" */,8 ), 442 | /* State 7 */ new Array( 6/* "}" */,-7 , 12/* "Identifier" */,-7 ), 443 | /* State 8 */ new Array( 2/* "=" */,12 ), 444 | /* State 9 */ new Array( 6/* "}" */,13 , 12/* "Identifier" */,8 ), 445 | /* State 10 */ new Array( 6/* "}" */,-8 , 12/* "Identifier" */,-8 ), 446 | /* State 11 */ new Array( 13/* "Literal" */,14 , 21/* "$" */,-6 ), 447 | /* State 12 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ), 448 | /* State 13 */ new Array( 13/* "Literal" */,23 , 21/* "$" */,-4 ), 449 | /* State 14 */ new Array( 21/* "$" */,-5 ), 450 | /* State 15 */ new Array( 4/* "|" */,24 , 11/* "Terminator" */,25 ), 451 | /* State 16 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 , 11/* "Terminator" */,-10 , 4/* "|" */,-10 , 8/* "]" */,-10 , 6/* "}" */,-10 , 10/* ")" */,-10 ), 452 | /* State 17 */ new Array( 11/* "Terminator" */,-12 , 12/* "Identifier" */,-12 , 13/* "Literal" */,-12 , 7/* "[" */,-12 , 5/* "{" */,-12 , 9/* "(" */,-12 , 4/* "|" */,-12 , 8/* "]" */,-12 , 6/* "}" */,-12 , 10/* ")" */,-12 ), 453 | /* State 18 */ new Array( 11/* "Terminator" */,-14 , 12/* "Identifier" */,-14 , 13/* "Literal" */,-14 , 7/* "[" */,-14 , 5/* "{" */,-14 , 9/* "(" */,-14 , 4/* "|" */,-14 , 8/* "]" */,-14 , 6/* "}" */,-14 , 10/* ")" */,-14 ), 454 | /* State 19 */ new Array( 11/* "Terminator" */,-15 , 12/* "Identifier" */,-15 , 13/* "Literal" */,-15 , 7/* "[" */,-15 , 5/* "{" */,-15 , 9/* "(" */,-15 , 4/* "|" */,-15 , 8/* "]" */,-15 , 6/* "}" */,-15 , 10/* ")" */,-15 ), 455 | /* State 20 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ), 456 | /* State 21 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ), 457 | /* State 22 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ), 458 | /* State 23 */ new Array( 21/* "$" */,-3 ), 459 | /* State 24 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 ), 460 | /* State 25 */ new Array( 6/* "}" */,-9 , 12/* "Identifier" */,-9 ), 461 | /* State 26 */ new Array( 11/* "Terminator" */,-13 , 12/* "Identifier" */,-13 , 13/* "Literal" */,-13 , 7/* "[" */,-13 , 5/* "{" */,-13 , 9/* "(" */,-13 , 4/* "|" */,-13 , 8/* "]" */,-13 , 6/* "}" */,-13 , 10/* ")" */,-13 ), 462 | /* State 27 */ new Array( 4/* "|" */,24 , 8/* "]" */,31 ), 463 | /* State 28 */ new Array( 4/* "|" */,24 , 6/* "}" */,32 ), 464 | /* State 29 */ new Array( 4/* "|" */,24 , 10/* ")" */,33 ), 465 | /* State 30 */ new Array( 12/* "Identifier" */,18 , 13/* "Literal" */,19 , 7/* "[" */,20 , 5/* "{" */,21 , 9/* "(" */,22 , 11/* "Terminator" */,-11 , 4/* "|" */,-11 , 8/* "]" */,-11 , 6/* "}" */,-11 , 10/* ")" */,-11 ), 466 | /* State 31 */ new Array( 11/* "Terminator" */,-16 , 12/* "Identifier" */,-16 , 13/* "Literal" */,-16 , 7/* "[" */,-16 , 5/* "{" */,-16 , 9/* "(" */,-16 , 4/* "|" */,-16 , 8/* "]" */,-16 , 6/* "}" */,-16 , 10/* ")" */,-16 ), 467 | /* State 32 */ new Array( 11/* "Terminator" */,-17 , 12/* "Identifier" */,-17 , 13/* "Literal" */,-17 , 7/* "[" */,-17 , 5/* "{" */,-17 , 9/* "(" */,-17 , 4/* "|" */,-17 , 8/* "]" */,-17 , 6/* "}" */,-17 , 10/* ")" */,-17 ), 468 | /* State 33 */ new Array( 11/* "Terminator" */,-18 , 12/* "Identifier" */,-18 , 13/* "Literal" */,-18 , 7/* "[" */,-18 , 5/* "{" */,-18 , 9/* "(" */,-18 , 4/* "|" */,-18 , 8/* "]" */,-18 , 6/* "}" */,-18 , 10/* ")" */,-18 ) 469 | ); 470 | 471 | /* Goto-Table */ 472 | var goto_tab = new Array( 473 | /* State 0 */ new Array( 15/* root */,1 , 14/* syntax */,2 ), 474 | /* State 1 */ new Array( ), 475 | /* State 2 */ new Array( ), 476 | /* State 3 */ new Array( ), 477 | /* State 4 */ new Array( 16/* productionset */,6 , 17/* production */,7 ), 478 | /* State 5 */ new Array( 16/* productionset */,9 , 17/* production */,7 ), 479 | /* State 6 */ new Array( 17/* production */,10 ), 480 | /* State 7 */ new Array( ), 481 | /* State 8 */ new Array( ), 482 | /* State 9 */ new Array( 17/* production */,10 ), 483 | /* State 10 */ new Array( ), 484 | /* State 11 */ new Array( ), 485 | /* State 12 */ new Array( 18/* expression */,15 , 19/* term */,16 , 20/* factor */,17 ), 486 | /* State 13 */ new Array( ), 487 | /* State 14 */ new Array( ), 488 | /* State 15 */ new Array( ), 489 | /* State 16 */ new Array( 20/* factor */,26 ), 490 | /* State 17 */ new Array( ), 491 | /* State 18 */ new Array( ), 492 | /* State 19 */ new Array( ), 493 | /* State 20 */ new Array( 18/* expression */,27 , 19/* term */,16 , 20/* factor */,17 ), 494 | /* State 21 */ new Array( 18/* expression */,28 , 19/* term */,16 , 20/* factor */,17 ), 495 | /* State 22 */ new Array( 18/* expression */,29 , 19/* term */,16 , 20/* factor */,17 ), 496 | /* State 23 */ new Array( ), 497 | /* State 24 */ new Array( 19/* term */,30 , 20/* factor */,17 ), 498 | /* State 25 */ new Array( ), 499 | /* State 26 */ new Array( ), 500 | /* State 27 */ new Array( ), 501 | /* State 28 */ new Array( ), 502 | /* State 29 */ new Array( ), 503 | /* State 30 */ new Array( 20/* factor */,26 ), 504 | /* State 31 */ new Array( ), 505 | /* State 32 */ new Array( ), 506 | /* State 33 */ new Array( ) 507 | ); 508 | 509 | 510 | 511 | /* Symbol labels */ 512 | var labels = new Array( 513 | "root'" /* Non-terminal symbol */, 514 | "WHITESPACE" /* Terminal symbol */, 515 | "=" /* Terminal symbol */, 516 | "," /* Terminal symbol */, 517 | "|" /* Terminal symbol */, 518 | "{" /* Terminal symbol */, 519 | "}" /* Terminal symbol */, 520 | "[" /* Terminal symbol */, 521 | "]" /* Terminal symbol */, 522 | "(" /* Terminal symbol */, 523 | ")" /* Terminal symbol */, 524 | "Terminator" /* Terminal symbol */, 525 | "Identifier" /* Terminal symbol */, 526 | "Literal" /* Terminal symbol */, 527 | "syntax" /* Non-terminal symbol */, 528 | "root" /* Non-terminal symbol */, 529 | "productionset" /* Non-terminal symbol */, 530 | "production" /* Non-terminal symbol */, 531 | "expression" /* Non-terminal symbol */, 532 | "term" /* Non-terminal symbol */, 533 | "factor" /* Non-terminal symbol */, 534 | "$" /* Terminal symbol */ 535 | ); 536 | 537 | 538 | 539 | info.offset = 0; 540 | info.src = src; 541 | info.att = new String(); 542 | 543 | if( !err_off ) 544 | err_off = new Array(); 545 | if( !err_la ) 546 | err_la = new Array(); 547 | 548 | sstack.push( 0 ); 549 | vstack.push( 0 ); 550 | 551 | la = __lex( info ); 552 | 553 | while( true ) 554 | { 555 | act = 35; 556 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) 557 | { 558 | if( act_tab[sstack[sstack.length-1]][i] == la ) 559 | { 560 | act = act_tab[sstack[sstack.length-1]][i+1]; 561 | break; 562 | } 563 | } 564 | 565 | /* 566 | _print( "state " + sstack[sstack.length-1] + " la = " + la + " info.att = >" + 567 | info.att + "< act = " + act + " src = >" + info.src.substr( info.offset, 30 ) + "..." + "<" + 568 | " sstack = " + sstack.join() ); 569 | */ 570 | 571 | if( _dbg_withtrace && sstack.length > 0 ) 572 | { 573 | __dbg_print( "\nState " + sstack[sstack.length-1] + "\n" + 574 | "\tLookahead: " + labels[la] + " (\"" + info.att + "\")\n" + 575 | "\tAction: " + act + "\n" + 576 | "\tSource: \"" + info.src.substr( info.offset, 30 ) + ( ( info.offset + 30 < info.src.length ) ? 577 | "..." : "" ) + "\"\n" + 578 | "\tStack: " + sstack.join() + "\n" + 579 | "\tValue stack: " + vstack.join() + "\n" ); 580 | 581 | if( _dbg_withstepbystep ) 582 | __dbg_wait(); 583 | } 584 | 585 | 586 | //Panic-mode: Try recovery when parse-error occurs! 587 | if( act == 35 ) 588 | { 589 | if( _dbg_withtrace ) 590 | __dbg_print( "Error detected: There is no reduce or shift on the symbol " + labels[la] ); 591 | 592 | err_cnt++; 593 | err_off.push( info.offset - info.att.length ); 594 | err_la.push( new Array() ); 595 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) 596 | err_la[err_la.length-1].push( labels[act_tab[sstack[sstack.length-1]][i]] ); 597 | 598 | //Remember the original stack! 599 | var rsstack = new Array(); 600 | var rvstack = new Array(); 601 | for( var i = 0; i < sstack.length; i++ ) 602 | { 603 | rsstack[i] = sstack[i]; 604 | rvstack[i] = vstack[i]; 605 | } 606 | 607 | while( act == 35 && la != 21 ) 608 | { 609 | if( _dbg_withtrace ) 610 | __dbg_print( "\tError recovery\n" + 611 | "Current lookahead: " + labels[la] + " (" + info.att + ")\n" + 612 | "Action: " + act + "\n\n" ); 613 | if( la == -1 ) 614 | info.offset++; 615 | 616 | while( act == 35 && sstack.length > 0 ) 617 | { 618 | sstack.pop(); 619 | vstack.pop(); 620 | 621 | if( sstack.length == 0 ) 622 | break; 623 | 624 | act = 35; 625 | for( var i = 0; i < act_tab[sstack[sstack.length-1]].length; i+=2 ) 626 | { 627 | if( act_tab[sstack[sstack.length-1]][i] == la ) 628 | { 629 | act = act_tab[sstack[sstack.length-1]][i+1]; 630 | break; 631 | } 632 | } 633 | } 634 | 635 | if( act != 35 ) 636 | break; 637 | 638 | for( var i = 0; i < rsstack.length; i++ ) 639 | { 640 | sstack.push( rsstack[i] ); 641 | vstack.push( rvstack[i] ); 642 | } 643 | 644 | la = __lex( info ); 645 | } 646 | 647 | if( act == 35 ) 648 | { 649 | if( _dbg_withtrace ) 650 | __dbg_print( "\tError recovery failed, terminating parse process..." ); 651 | break; 652 | } 653 | 654 | 655 | if( _dbg_withtrace ) 656 | __dbg_print( "\tError recovery succeeded, continuing" ); 657 | } 658 | 659 | /* 660 | if( act == 35 ) 661 | break; 662 | */ 663 | 664 | 665 | //Shift 666 | if( act > 0 ) 667 | { 668 | //Parse tree generation 669 | if( _dbg_withparsetree ) 670 | { 671 | var node = new treenode(); 672 | node.sym = labels[ la ]; 673 | node.att = info.att; 674 | node.child = new Array(); 675 | tree.push( treenodes.length ); 676 | treenodes.push( node ); 677 | } 678 | 679 | if( _dbg_withtrace ) 680 | __dbg_print( "Shifting symbol: " + labels[la] + " (" + info.att + ")" ); 681 | 682 | sstack.push( act ); 683 | vstack.push( info.att ); 684 | 685 | la = __lex( info ); 686 | 687 | if( _dbg_withtrace ) 688 | __dbg_print( "\tNew lookahead symbol: " + labels[la] + " (" + info.att + ")" ); 689 | } 690 | //Reduce 691 | else 692 | { 693 | act *= -1; 694 | 695 | if( _dbg_withtrace ) 696 | __dbg_print( "Reducing by producution: " + act ); 697 | 698 | rval = void(0); 699 | 700 | if( _dbg_withtrace ) 701 | __dbg_print( "\tPerforming semantic action..." ); 702 | 703 | switch( act ) 704 | { 705 | case 0: 706 | { 707 | rval = vstack[ vstack.length - 1 ]; 708 | } 709 | break; 710 | case 1: 711 | { 712 | parseComplete(vstack[ vstack.length - 1 ]); 713 | } 714 | break; 715 | case 2: 716 | { 717 | rval = vstack[ vstack.length - 0 ]; 718 | } 719 | break; 720 | case 3: 721 | { 722 | rval = createSyntax( vstack[ vstack.length - 3 ], vstack[ vstack.length - 5 ], vstack[ vstack.length - 1 ]); 723 | } 724 | break; 725 | case 4: 726 | { 727 | rval = createSyntax( vstack[ vstack.length - 2 ], vstack[ vstack.length - 4 ]); 728 | } 729 | break; 730 | case 5: 731 | { 732 | rval = createSyntax( vstack[ vstack.length - 3 ], undefined, vstack[ vstack.length - 1 ]); 733 | } 734 | break; 735 | case 6: 736 | { 737 | rval = createSyntax( vstack[ vstack.length - 2 ]); 738 | } 739 | break; 740 | case 7: 741 | { 742 | rval = createSet(); rval.addChild(vstack[ vstack.length - 1 ]); 743 | } 744 | break; 745 | case 8: 746 | { 747 | rval = vstack[ vstack.length - 2 ]; rval.addChild(vstack[ vstack.length - 1 ]); 748 | } 749 | break; 750 | case 9: 751 | { 752 | rval = createProduction(vstack[ vstack.length - 4 ], vstack[ vstack.length - 2 ]); 753 | } 754 | break; 755 | case 10: 756 | { 757 | rval = createExpression(); rval.addChild(vstack[ vstack.length - 1 ]); 758 | } 759 | break; 760 | case 11: 761 | { 762 | rval = vstack[ vstack.length - 3 ]; rval.addChild(vstack[ vstack.length - 1 ]); 763 | } 764 | break; 765 | case 12: 766 | { 767 | rval = createTerm(); rval.addChild(vstack[ vstack.length - 1 ]); 768 | } 769 | break; 770 | case 13: 771 | { 772 | rval = vstack[ vstack.length - 2 ]; vstack[ vstack.length - 2 ].addChild(vstack[ vstack.length - 1 ]); 773 | } 774 | break; 775 | case 14: 776 | { 777 | rval = createIdentifier(vstack[ vstack.length - 1 ]); 778 | } 779 | break; 780 | case 15: 781 | { 782 | rval = createLiteral(vstack[ vstack.length - 1 ]); 783 | } 784 | break; 785 | case 16: 786 | { 787 | rval = createOptional(vstack[ vstack.length - 2 ]); 788 | } 789 | break; 790 | case 17: 791 | { 792 | rval = createRepeating(vstack[ vstack.length - 2 ]); 793 | } 794 | break; 795 | case 18: 796 | { 797 | rval = createGroup(vstack[ vstack.length - 2 ]); 798 | } 799 | break; 800 | } 801 | 802 | 803 | 804 | if( _dbg_withparsetree ) 805 | tmptree = new Array(); 806 | 807 | if( _dbg_withtrace ) 808 | __dbg_print( "\tPopping " + pop_tab[act][1] + " off the stack..." ); 809 | 810 | for( var i = 0; i < pop_tab[act][1]; i++ ) 811 | { 812 | if( _dbg_withparsetree ) 813 | tmptree.push( tree.pop() ); 814 | 815 | sstack.pop(); 816 | vstack.pop(); 817 | } 818 | 819 | go = -1; 820 | for( var i = 0; i < goto_tab[sstack[sstack.length-1]].length; i+=2 ) 821 | { 822 | if( goto_tab[sstack[sstack.length-1]][i] == pop_tab[act][0] ) 823 | { 824 | go = goto_tab[sstack[sstack.length-1]][i+1]; 825 | break; 826 | } 827 | } 828 | 829 | if( _dbg_withparsetree ) 830 | { 831 | var node = new treenode(); 832 | node.sym = labels[ pop_tab[act][0] ]; 833 | node.att = new String(); 834 | node.child = tmptree.reverse(); 835 | tree.push( treenodes.length ); 836 | treenodes.push( node ); 837 | } 838 | 839 | if( act == 0 ) 840 | break; 841 | 842 | if( _dbg_withtrace ) 843 | __dbg_print( "\tPushing non-terminal " + labels[ pop_tab[act][0] ] ); 844 | 845 | sstack.push( go ); 846 | vstack.push( rval ); 847 | } 848 | } 849 | 850 | if( _dbg_withtrace ) 851 | __dbg_print( "\nParse complete." ); 852 | 853 | if( _dbg_withparsetree ) 854 | { 855 | if( err_cnt == 0 ) 856 | { 857 | __dbg_print( "\n\n--- Parse tree ---" ); 858 | __dbg_parsetree( 0, treenodes, tree ); 859 | } 860 | else 861 | { 862 | __dbg_print( "\n\nParse tree cannot be viewed. There where parse errors." ); 863 | } 864 | } 865 | 866 | return err_cnt; 867 | } 868 | 869 | 870 | function __dbg_parsetree( indent, nodes, tree ) 871 | { 872 | var str = new String(); 873 | for( var i = 0; i < tree.length; i++ ) 874 | { 875 | str = ""; 876 | for( var j = indent; j > 0; j-- ) 877 | str += "\t"; 878 | 879 | str += nodes[ tree[i] ].sym; 880 | if( nodes[ tree[i] ].att != "" ) 881 | str += " >" + nodes[ tree[i] ].att + "<" ; 882 | 883 | __dbg_print( str ); 884 | if( nodes[ tree[i] ].child.length > 0 ) 885 | __dbg_parsetree( indent + 1, nodes, nodes[ tree[i] ].child ); 886 | } 887 | } 888 | 889 | 890 | 891 | $.extend({ 892 | "ebnfParse": function(str, fn, errfn) { 893 | 894 | parseComplete = fn || function(syn) { 895 | alert(syn.toString()); 896 | }; 897 | 898 | var parseErrors = errfn || function(err) { 899 | alert("errors#" + err.length); 900 | }; 901 | 902 | 903 | var error_cnt = 0; 904 | var error_off = new Array(); 905 | var error_la = new Array(); 906 | 907 | var errors; 908 | if( ( error_cnt = __parse( str, error_off, error_la ) ) > 0 ) 909 | { 910 | errors=new Array(); 911 | for( var i = 0; i < error_cnt; i++ ) { 912 | var msg = "Parse error near >" + str.substr( error_off[i], 30 ) + 913 | "<, expecting \"" + error_la[i].join() + "\"" ; 914 | errors.push(msg); 915 | } 916 | parseErrors(errors); 917 | } 918 | } 919 | }); 920 | 921 | })(jQuery); 922 | 923 | -------------------------------------------------------------------------------- /src/test/webenv/qunit/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2011 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var defined = { 14 | setTimeout: typeof window.setTimeout !== "undefined", 15 | sessionStorage: (function() { 16 | try { 17 | return !!sessionStorage.getItem; 18 | } catch(e) { 19 | return false; 20 | } 21 | })() 22 | }; 23 | 24 | var testId = 0; 25 | 26 | var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { 27 | this.name = name; 28 | this.testName = testName; 29 | this.expected = expected; 30 | this.testEnvironmentArg = testEnvironmentArg; 31 | this.async = async; 32 | this.callback = callback; 33 | this.assertions = []; 34 | }; 35 | Test.prototype = { 36 | init: function() { 37 | var tests = id("qunit-tests"); 38 | if (tests) { 39 | var b = document.createElement("strong"); 40 | b.innerHTML = "Running " + this.name; 41 | var li = document.createElement("li"); 42 | li.appendChild( b ); 43 | li.className = "running"; 44 | li.id = this.id = "test-output" + testId++; 45 | tests.appendChild( li ); 46 | } 47 | }, 48 | setup: function() { 49 | if (this.module != config.previousModule) { 50 | if ( config.previousModule ) { 51 | QUnit.moduleDone( { 52 | name: config.previousModule, 53 | failed: config.moduleStats.bad, 54 | passed: config.moduleStats.all - config.moduleStats.bad, 55 | total: config.moduleStats.all 56 | } ); 57 | } 58 | config.previousModule = this.module; 59 | config.moduleStats = { all: 0, bad: 0 }; 60 | QUnit.moduleStart( { 61 | name: this.module 62 | } ); 63 | } 64 | 65 | config.current = this; 66 | this.testEnvironment = extend({ 67 | setup: function() {}, 68 | teardown: function() {} 69 | }, this.moduleTestEnvironment); 70 | if (this.testEnvironmentArg) { 71 | extend(this.testEnvironment, this.testEnvironmentArg); 72 | } 73 | 74 | QUnit.testStart( { 75 | name: this.testName 76 | } ); 77 | 78 | // allow utility functions to access the current test environment 79 | // TODO why?? 80 | QUnit.current_testEnvironment = this.testEnvironment; 81 | 82 | try { 83 | if ( !config.pollution ) { 84 | saveGlobal(); 85 | } 86 | 87 | this.testEnvironment.setup.call(this.testEnvironment); 88 | } catch(e) { 89 | QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); 90 | } 91 | }, 92 | run: function() { 93 | if ( this.async ) { 94 | QUnit.stop(); 95 | } 96 | 97 | if ( config.notrycatch ) { 98 | this.callback.call(this.testEnvironment); 99 | return; 100 | } 101 | try { 102 | this.callback.call(this.testEnvironment); 103 | } catch(e) { 104 | fail("Test " + this.testName + " died, exception and test follows", e, this.callback); 105 | QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); 106 | // else next test will carry the responsibility 107 | saveGlobal(); 108 | 109 | // Restart the tests if they're blocking 110 | if ( config.blocking ) { 111 | start(); 112 | } 113 | } 114 | }, 115 | teardown: function() { 116 | try { 117 | this.testEnvironment.teardown.call(this.testEnvironment); 118 | checkPollution(); 119 | } catch(e) { 120 | QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); 121 | } 122 | }, 123 | finish: function() { 124 | if ( this.expected && this.expected != this.assertions.length ) { 125 | QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); 126 | } 127 | 128 | var good = 0, bad = 0, 129 | tests = id("qunit-tests"); 130 | 131 | config.stats.all += this.assertions.length; 132 | config.moduleStats.all += this.assertions.length; 133 | 134 | if ( tests ) { 135 | var ol = document.createElement("ol"); 136 | 137 | for ( var i = 0; i < this.assertions.length; i++ ) { 138 | var assertion = this.assertions[i]; 139 | 140 | var li = document.createElement("li"); 141 | li.className = assertion.result ? "pass" : "fail"; 142 | li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); 143 | ol.appendChild( li ); 144 | 145 | if ( assertion.result ) { 146 | good++; 147 | } else { 148 | bad++; 149 | config.stats.bad++; 150 | config.moduleStats.bad++; 151 | } 152 | } 153 | 154 | // store result when possible 155 | if ( QUnit.config.reorder && defined.sessionStorage ) { 156 | if (bad) { 157 | sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); 158 | } else { 159 | sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); 160 | } 161 | } 162 | 163 | if (bad == 0) { 164 | ol.style.display = "none"; 165 | } 166 | 167 | var b = document.createElement("strong"); 168 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 169 | 170 | var a = document.createElement("a"); 171 | a.innerHTML = "Rerun"; 172 | a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 173 | 174 | addEvent(b, "click", function() { 175 | var next = b.nextSibling.nextSibling, 176 | display = next.style.display; 177 | next.style.display = display === "none" ? "block" : "none"; 178 | }); 179 | 180 | addEvent(b, "dblclick", function(e) { 181 | var target = e && e.target ? e.target : window.event.srcElement; 182 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 183 | target = target.parentNode; 184 | } 185 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 186 | window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); 187 | } 188 | }); 189 | 190 | var li = id(this.id); 191 | li.className = bad ? "fail" : "pass"; 192 | li.removeChild( li.firstChild ); 193 | li.appendChild( b ); 194 | li.appendChild( a ); 195 | li.appendChild( ol ); 196 | 197 | } else { 198 | for ( var i = 0; i < this.assertions.length; i++ ) { 199 | if ( !this.assertions[i].result ) { 200 | bad++; 201 | config.stats.bad++; 202 | config.moduleStats.bad++; 203 | } 204 | } 205 | } 206 | 207 | try { 208 | QUnit.reset(); 209 | } catch(e) { 210 | fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); 211 | } 212 | 213 | QUnit.testDone( { 214 | name: this.testName, 215 | failed: bad, 216 | passed: this.assertions.length - bad, 217 | total: this.assertions.length 218 | } ); 219 | }, 220 | 221 | queue: function() { 222 | var test = this; 223 | synchronize(function() { 224 | test.init(); 225 | }); 226 | function run() { 227 | // each of these can by async 228 | synchronize(function() { 229 | test.setup(); 230 | }); 231 | synchronize(function() { 232 | test.run(); 233 | }); 234 | synchronize(function() { 235 | test.teardown(); 236 | }); 237 | synchronize(function() { 238 | test.finish(); 239 | }); 240 | } 241 | // defer when previous test run passed, if storage is available 242 | var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); 243 | if (bad) { 244 | run(); 245 | } else { 246 | synchronize(run); 247 | }; 248 | } 249 | 250 | }; 251 | 252 | var QUnit = { 253 | 254 | // call on start of module test to prepend name to all tests 255 | module: function(name, testEnvironment) { 256 | config.currentModule = name; 257 | config.currentModuleTestEnviroment = testEnvironment; 258 | }, 259 | 260 | asyncTest: function(testName, expected, callback) { 261 | if ( arguments.length === 2 ) { 262 | callback = expected; 263 | expected = 0; 264 | } 265 | 266 | QUnit.test(testName, expected, callback, true); 267 | }, 268 | 269 | test: function(testName, expected, callback, async) { 270 | var name = '' + testName + '', testEnvironmentArg; 271 | 272 | if ( arguments.length === 2 ) { 273 | callback = expected; 274 | expected = null; 275 | } 276 | // is 2nd argument a testEnvironment? 277 | if ( expected && typeof expected === 'object') { 278 | testEnvironmentArg = expected; 279 | expected = null; 280 | } 281 | 282 | if ( config.currentModule ) { 283 | name = '' + config.currentModule + ": " + name; 284 | } 285 | 286 | if ( !validTest(config.currentModule + ": " + testName) ) { 287 | return; 288 | } 289 | 290 | var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); 291 | test.module = config.currentModule; 292 | test.moduleTestEnvironment = config.currentModuleTestEnviroment; 293 | test.queue(); 294 | }, 295 | 296 | /** 297 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 298 | */ 299 | expect: function(asserts) { 300 | config.current.expected = asserts; 301 | }, 302 | 303 | /** 304 | * Asserts true. 305 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 306 | */ 307 | ok: function(a, msg) { 308 | a = !!a; 309 | var details = { 310 | result: a, 311 | message: msg 312 | }; 313 | msg = escapeHtml(msg); 314 | QUnit.log(details); 315 | config.current.assertions.push({ 316 | result: a, 317 | message: msg 318 | }); 319 | }, 320 | 321 | /** 322 | * Checks that the first two arguments are equal, with an optional message. 323 | * Prints out both actual and expected values. 324 | * 325 | * Prefered to ok( actual == expected, message ) 326 | * 327 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 328 | * 329 | * @param Object actual 330 | * @param Object expected 331 | * @param String message (optional) 332 | */ 333 | equal: function(actual, expected, message) { 334 | QUnit.push(expected == actual, actual, expected, message); 335 | }, 336 | 337 | notEqual: function(actual, expected, message) { 338 | QUnit.push(expected != actual, actual, expected, message); 339 | }, 340 | 341 | deepEqual: function(actual, expected, message) { 342 | QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); 343 | }, 344 | 345 | notDeepEqual: function(actual, expected, message) { 346 | QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); 347 | }, 348 | 349 | strictEqual: function(actual, expected, message) { 350 | QUnit.push(expected === actual, actual, expected, message); 351 | }, 352 | 353 | notStrictEqual: function(actual, expected, message) { 354 | QUnit.push(expected !== actual, actual, expected, message); 355 | }, 356 | 357 | raises: function(block, expected, message) { 358 | var actual, ok = false; 359 | 360 | if (typeof expected === 'string') { 361 | message = expected; 362 | expected = null; 363 | } 364 | 365 | try { 366 | block(); 367 | } catch (e) { 368 | actual = e; 369 | } 370 | 371 | if (actual) { 372 | // we don't want to validate thrown error 373 | if (!expected) { 374 | ok = true; 375 | // expected is a regexp 376 | } else if (QUnit.objectType(expected) === "regexp") { 377 | ok = expected.test(actual); 378 | // expected is a constructor 379 | } else if (actual instanceof expected) { 380 | ok = true; 381 | // expected is a validation function which returns true is validation passed 382 | } else if (expected.call({}, actual) === true) { 383 | ok = true; 384 | } 385 | } 386 | 387 | QUnit.ok(ok, message); 388 | }, 389 | 390 | start: function() { 391 | config.semaphore--; 392 | if (config.semaphore > 0) { 393 | // don't start until equal number of stop-calls 394 | return; 395 | } 396 | if (config.semaphore < 0) { 397 | // ignore if start is called more often then stop 398 | config.semaphore = 0; 399 | } 400 | // A slight delay, to avoid any current callbacks 401 | if ( defined.setTimeout ) { 402 | window.setTimeout(function() { 403 | if (config.semaphore > 0) { 404 | return; 405 | } 406 | if ( config.timeout ) { 407 | clearTimeout(config.timeout); 408 | } 409 | 410 | config.blocking = false; 411 | process(); 412 | }, 13); 413 | } else { 414 | config.blocking = false; 415 | process(); 416 | } 417 | }, 418 | 419 | stop: function(timeout) { 420 | config.semaphore++; 421 | config.blocking = true; 422 | 423 | if ( timeout && defined.setTimeout ) { 424 | clearTimeout(config.timeout); 425 | config.timeout = window.setTimeout(function() { 426 | QUnit.ok( false, "Test timed out" ); 427 | QUnit.start(); 428 | }, timeout); 429 | } 430 | } 431 | }; 432 | 433 | // Backwards compatibility, deprecated 434 | QUnit.equals = QUnit.equal; 435 | QUnit.same = QUnit.deepEqual; 436 | 437 | // Maintain internal state 438 | var config = { 439 | // The queue of tests to run 440 | queue: [], 441 | 442 | // block until document ready 443 | blocking: true, 444 | 445 | // when enabled, show only failing tests 446 | // gets persisted through sessionStorage and can be changed in UI via checkbox 447 | hidepassed: false, 448 | 449 | // by default, run previously failed tests first 450 | // very useful in combination with "Hide passed tests" checked 451 | reorder: true, 452 | 453 | // by default, modify document.title when suite is done 454 | altertitle: true, 455 | 456 | noglobals: false, 457 | notrycatch: false 458 | }; 459 | 460 | // Load paramaters 461 | (function() { 462 | var location = window.location || { search: "", protocol: "file:" }, 463 | params = location.search.slice( 1 ).split( "&" ), 464 | length = params.length, 465 | urlParams = {}, 466 | current; 467 | 468 | if ( params[ 0 ] ) { 469 | for ( var i = 0; i < length; i++ ) { 470 | current = params[ i ].split( "=" ); 471 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 472 | // allow just a key to turn on a flag, e.g., test.html?noglobals 473 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 474 | urlParams[ current[ 0 ] ] = current[ 1 ]; 475 | if ( current[ 0 ] in config ) { 476 | config[ current[ 0 ] ] = current[ 1 ]; 477 | } 478 | } 479 | } 480 | 481 | QUnit.urlParams = urlParams; 482 | config.filter = urlParams.filter; 483 | 484 | // Figure out if we're running the tests from a server or not 485 | QUnit.isLocal = !!(location.protocol === 'file:'); 486 | })(); 487 | 488 | // Expose the API as global variables, unless an 'exports' 489 | // object exists, in that case we assume we're in CommonJS 490 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 491 | extend(window, QUnit); 492 | window.QUnit = QUnit; 493 | } else { 494 | extend(exports, QUnit); 495 | exports.QUnit = QUnit; 496 | } 497 | 498 | // define these after exposing globals to keep them in these QUnit namespace only 499 | extend(QUnit, { 500 | config: config, 501 | 502 | // Initialize the configuration options 503 | init: function() { 504 | extend(config, { 505 | stats: { all: 0, bad: 0 }, 506 | moduleStats: { all: 0, bad: 0 }, 507 | started: +new Date, 508 | updateRate: 1000, 509 | blocking: false, 510 | autostart: true, 511 | autorun: false, 512 | filter: "", 513 | queue: [], 514 | semaphore: 0 515 | }); 516 | 517 | var tests = id( "qunit-tests" ), 518 | banner = id( "qunit-banner" ), 519 | result = id( "qunit-testresult" ); 520 | 521 | if ( tests ) { 522 | tests.innerHTML = ""; 523 | } 524 | 525 | if ( banner ) { 526 | banner.className = ""; 527 | } 528 | 529 | if ( result ) { 530 | result.parentNode.removeChild( result ); 531 | } 532 | 533 | if ( tests ) { 534 | result = document.createElement( "p" ); 535 | result.id = "qunit-testresult"; 536 | result.className = "result"; 537 | tests.parentNode.insertBefore( result, tests ); 538 | result.innerHTML = 'Running...
     '; 539 | } 540 | }, 541 | 542 | /** 543 | * Resets the test setup. Useful for tests that modify the DOM. 544 | * 545 | * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. 546 | */ 547 | reset: function() { 548 | if ( window.jQuery ) { 549 | jQuery( "#qunit-fixture" ).html( config.fixture ); 550 | } else { 551 | var main = id( 'qunit-fixture' ); 552 | if ( main ) { 553 | main.innerHTML = config.fixture; 554 | } 555 | } 556 | }, 557 | 558 | /** 559 | * Trigger an event on an element. 560 | * 561 | * @example triggerEvent( document.body, "click" ); 562 | * 563 | * @param DOMElement elem 564 | * @param String type 565 | */ 566 | triggerEvent: function( elem, type, event ) { 567 | if ( document.createEvent ) { 568 | event = document.createEvent("MouseEvents"); 569 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 570 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 571 | elem.dispatchEvent( event ); 572 | 573 | } else if ( elem.fireEvent ) { 574 | elem.fireEvent("on"+type); 575 | } 576 | }, 577 | 578 | // Safe object type checking 579 | is: function( type, obj ) { 580 | return QUnit.objectType( obj ) == type; 581 | }, 582 | 583 | objectType: function( obj ) { 584 | if (typeof obj === "undefined") { 585 | return "undefined"; 586 | 587 | // consider: typeof null === object 588 | } 589 | if (obj === null) { 590 | return "null"; 591 | } 592 | 593 | var type = Object.prototype.toString.call( obj ) 594 | .match(/^\[object\s(.*)\]$/)[1] || ''; 595 | 596 | switch (type) { 597 | case 'Number': 598 | if (isNaN(obj)) { 599 | return "nan"; 600 | } else { 601 | return "number"; 602 | } 603 | case 'String': 604 | case 'Boolean': 605 | case 'Array': 606 | case 'Date': 607 | case 'RegExp': 608 | case 'Function': 609 | return type.toLowerCase(); 610 | } 611 | if (typeof obj === "object") { 612 | return "object"; 613 | } 614 | return undefined; 615 | }, 616 | 617 | push: function(result, actual, expected, message) { 618 | var details = { 619 | result: result, 620 | message: message, 621 | actual: actual, 622 | expected: expected 623 | }; 624 | 625 | message = escapeHtml(message) || (result ? "okay" : "failed"); 626 | message = '' + message + ""; 627 | expected = escapeHtml(QUnit.jsDump.parse(expected)); 628 | actual = escapeHtml(QUnit.jsDump.parse(actual)); 629 | var output = message + ''; 630 | if (actual != expected) { 631 | output += ''; 632 | output += ''; 633 | } 634 | if (!result) { 635 | var source = sourceFromStacktrace(); 636 | if (source) { 637 | details.source = source; 638 | output += ''; 639 | } 640 | } 641 | output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + escapeHtml(source) + '
    "; 642 | 643 | QUnit.log(details); 644 | 645 | config.current.assertions.push({ 646 | result: !!result, 647 | message: output 648 | }); 649 | }, 650 | 651 | url: function( params ) { 652 | params = extend( extend( {}, QUnit.urlParams ), params ); 653 | var querystring = "?", 654 | key; 655 | for ( key in params ) { 656 | querystring += encodeURIComponent( key ) + "=" + 657 | encodeURIComponent( params[ key ] ) + "&"; 658 | } 659 | return window.location.pathname + querystring.slice( 0, -1 ); 660 | }, 661 | 662 | extend: extend, 663 | id: id, 664 | addEvent: addEvent, 665 | 666 | // Logging callbacks; all receive a single argument with the listed properties 667 | // run test/logs.html for any related changes 668 | begin: function() {}, 669 | // done: { failed, passed, total, runtime } 670 | done: function() {}, 671 | // log: { result, actual, expected, message } 672 | log: function() {}, 673 | // testStart: { name } 674 | testStart: function() {}, 675 | // testDone: { name, failed, passed, total } 676 | testDone: function() {}, 677 | // moduleStart: { name } 678 | moduleStart: function() {}, 679 | // moduleDone: { name, failed, passed, total } 680 | moduleDone: function() {} 681 | }); 682 | 683 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 684 | config.autorun = true; 685 | } 686 | 687 | QUnit.load = function() { 688 | QUnit.begin({}); 689 | 690 | // Initialize the config, saving the execution queue 691 | var oldconfig = extend({}, config); 692 | QUnit.init(); 693 | extend(config, oldconfig); 694 | 695 | config.blocking = false; 696 | 697 | var userAgent = id("qunit-userAgent"); 698 | if ( userAgent ) { 699 | userAgent.innerHTML = navigator.userAgent; 700 | } 701 | var banner = id("qunit-header"); 702 | if ( banner ) { 703 | banner.innerHTML = ' ' + banner.innerHTML + ' ' + 704 | '' + 705 | ''; 706 | addEvent( banner, "change", function( event ) { 707 | var params = {}; 708 | params[ event.target.name ] = event.target.checked ? true : undefined; 709 | window.location = QUnit.url( params ); 710 | }); 711 | } 712 | 713 | var toolbar = id("qunit-testrunner-toolbar"); 714 | if ( toolbar ) { 715 | var filter = document.createElement("input"); 716 | filter.type = "checkbox"; 717 | filter.id = "qunit-filter-pass"; 718 | addEvent( filter, "click", function() { 719 | var ol = document.getElementById("qunit-tests"); 720 | if ( filter.checked ) { 721 | ol.className = ol.className + " hidepass"; 722 | } else { 723 | var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 724 | ol.className = tmp.replace(/ hidepass /, " "); 725 | } 726 | if ( defined.sessionStorage ) { 727 | if (filter.checked) { 728 | sessionStorage.setItem("qunit-filter-passed-tests", "true"); 729 | } else { 730 | sessionStorage.removeItem("qunit-filter-passed-tests"); 731 | } 732 | } 733 | }); 734 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { 735 | filter.checked = true; 736 | var ol = document.getElementById("qunit-tests"); 737 | ol.className = ol.className + " hidepass"; 738 | } 739 | toolbar.appendChild( filter ); 740 | 741 | var label = document.createElement("label"); 742 | label.setAttribute("for", "qunit-filter-pass"); 743 | label.innerHTML = "Hide passed tests"; 744 | toolbar.appendChild( label ); 745 | } 746 | 747 | var main = id('qunit-fixture'); 748 | if ( main ) { 749 | config.fixture = main.innerHTML; 750 | } 751 | 752 | if (config.autostart) { 753 | QUnit.start(); 754 | } 755 | }; 756 | 757 | addEvent(window, "load", QUnit.load); 758 | 759 | function done() { 760 | config.autorun = true; 761 | 762 | // Log the last module results 763 | if ( config.currentModule ) { 764 | QUnit.moduleDone( { 765 | name: config.currentModule, 766 | failed: config.moduleStats.bad, 767 | passed: config.moduleStats.all - config.moduleStats.bad, 768 | total: config.moduleStats.all 769 | } ); 770 | } 771 | 772 | var banner = id("qunit-banner"), 773 | tests = id("qunit-tests"), 774 | runtime = +new Date - config.started, 775 | passed = config.stats.all - config.stats.bad, 776 | html = [ 777 | 'Tests completed in ', 778 | runtime, 779 | ' milliseconds.
    ', 780 | '', 781 | passed, 782 | ' tests of ', 783 | config.stats.all, 784 | ' passed, ', 785 | config.stats.bad, 786 | ' failed.' 787 | ].join(''); 788 | 789 | if ( banner ) { 790 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 791 | } 792 | 793 | if ( tests ) { 794 | id( "qunit-testresult" ).innerHTML = html; 795 | } 796 | 797 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 798 | // show ✖ for good, ✔ for bad suite result in title 799 | // use escape sequences in case file gets loaded with non-utf-8-charset 800 | document.title = [ 801 | (config.stats.bad ? "\u2716" : "\u2714"), 802 | document.title.replace(/^[\u2714\u2716] /i, "") 803 | ].join(" "); 804 | } 805 | 806 | QUnit.done( { 807 | failed: config.stats.bad, 808 | passed: passed, 809 | total: config.stats.all, 810 | runtime: runtime 811 | } ); 812 | } 813 | 814 | function validTest( name ) { 815 | var filter = config.filter, 816 | run = false; 817 | 818 | if ( !filter ) { 819 | return true; 820 | } 821 | 822 | var not = filter.charAt( 0 ) === "!"; 823 | if ( not ) { 824 | filter = filter.slice( 1 ); 825 | } 826 | 827 | if ( name.indexOf( filter ) !== -1 ) { 828 | return !not; 829 | } 830 | 831 | if ( not ) { 832 | run = true; 833 | } 834 | 835 | return run; 836 | } 837 | 838 | // so far supports only Firefox, Chrome and Opera (buggy) 839 | // could be extended in the future to use something like https://github.com/csnover/TraceKit 840 | function sourceFromStacktrace() { 841 | try { 842 | throw new Error(); 843 | } catch ( e ) { 844 | if (e.stacktrace) { 845 | // Opera 846 | return e.stacktrace.split("\n")[6]; 847 | } else if (e.stack) { 848 | // Firefox, Chrome 849 | return e.stack.split("\n")[4]; 850 | } 851 | } 852 | } 853 | 854 | function escapeHtml(s) { 855 | if (!s) { 856 | return ""; 857 | } 858 | s = s + ""; 859 | return s.replace(/[\&"<>\\]/g, function(s) { 860 | switch(s) { 861 | case "&": return "&"; 862 | case "\\": return "\\\\"; 863 | case '"': return '\"'; 864 | case "<": return "<"; 865 | case ">": return ">"; 866 | default: return s; 867 | } 868 | }); 869 | } 870 | 871 | function synchronize( callback ) { 872 | config.queue.push( callback ); 873 | 874 | if ( config.autorun && !config.blocking ) { 875 | process(); 876 | } 877 | } 878 | 879 | function process() { 880 | var start = (new Date()).getTime(); 881 | 882 | while ( config.queue.length && !config.blocking ) { 883 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 884 | config.queue.shift()(); 885 | } else { 886 | window.setTimeout( process, 13 ); 887 | break; 888 | } 889 | } 890 | if (!config.blocking && !config.queue.length) { 891 | done(); 892 | } 893 | } 894 | 895 | function saveGlobal() { 896 | config.pollution = []; 897 | 898 | if ( config.noglobals ) { 899 | for ( var key in window ) { 900 | config.pollution.push( key ); 901 | } 902 | } 903 | } 904 | 905 | function checkPollution( name ) { 906 | var old = config.pollution; 907 | saveGlobal(); 908 | 909 | var newGlobals = diff( config.pollution, old ); 910 | if ( newGlobals.length > 0 ) { 911 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 912 | } 913 | 914 | var deletedGlobals = diff( old, config.pollution ); 915 | if ( deletedGlobals.length > 0 ) { 916 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 917 | } 918 | } 919 | 920 | // returns a new Array with the elements that are in a but not in b 921 | function diff( a, b ) { 922 | var result = a.slice(); 923 | for ( var i = 0; i < result.length; i++ ) { 924 | for ( var j = 0; j < b.length; j++ ) { 925 | if ( result[i] === b[j] ) { 926 | result.splice(i, 1); 927 | i--; 928 | break; 929 | } 930 | } 931 | } 932 | return result; 933 | } 934 | 935 | function fail(message, exception, callback) { 936 | if ( typeof console !== "undefined" && console.error && console.warn ) { 937 | console.error(message); 938 | console.error(exception); 939 | console.warn(callback.toString()); 940 | 941 | } else if ( window.opera && opera.postError ) { 942 | opera.postError(message, exception, callback.toString); 943 | } 944 | } 945 | 946 | function extend(a, b) { 947 | for ( var prop in b ) { 948 | if ( b[prop] === undefined ) { 949 | delete a[prop]; 950 | } else { 951 | a[prop] = b[prop]; 952 | } 953 | } 954 | 955 | return a; 956 | } 957 | 958 | function addEvent(elem, type, fn) { 959 | if ( elem.addEventListener ) { 960 | elem.addEventListener( type, fn, false ); 961 | } else if ( elem.attachEvent ) { 962 | elem.attachEvent( "on" + type, fn ); 963 | } else { 964 | fn(); 965 | } 966 | } 967 | 968 | function id(name) { 969 | return !!(typeof document !== "undefined" && document && document.getElementById) && 970 | document.getElementById( name ); 971 | } 972 | 973 | // Test for equality any JavaScript type. 974 | // Discussions and reference: http://philrathe.com/articles/equiv 975 | // Test suites: http://philrathe.com/tests/equiv 976 | // Author: Philippe Rathé 977 | QUnit.equiv = function () { 978 | 979 | var innerEquiv; // the real equiv function 980 | var callers = []; // stack to decide between skip/abort functions 981 | var parents = []; // stack to avoiding loops from circular referencing 982 | 983 | // Call the o related callback with the given arguments. 984 | function bindCallbacks(o, callbacks, args) { 985 | var prop = QUnit.objectType(o); 986 | if (prop) { 987 | if (QUnit.objectType(callbacks[prop]) === "function") { 988 | return callbacks[prop].apply(callbacks, args); 989 | } else { 990 | return callbacks[prop]; // or undefined 991 | } 992 | } 993 | } 994 | 995 | var callbacks = function () { 996 | 997 | // for string, boolean, number and null 998 | function useStrictEquality(b, a) { 999 | if (b instanceof a.constructor || a instanceof b.constructor) { 1000 | // to catch short annotaion VS 'new' annotation of a 1001 | // declaration 1002 | // e.g. var i = 1; 1003 | // var j = new Number(1); 1004 | return a == b; 1005 | } else { 1006 | return a === b; 1007 | } 1008 | } 1009 | 1010 | return { 1011 | "string" : useStrictEquality, 1012 | "boolean" : useStrictEquality, 1013 | "number" : useStrictEquality, 1014 | "null" : useStrictEquality, 1015 | "undefined" : useStrictEquality, 1016 | 1017 | "nan" : function(b) { 1018 | return isNaN(b); 1019 | }, 1020 | 1021 | "date" : function(b, a) { 1022 | return QUnit.objectType(b) === "date" 1023 | && a.valueOf() === b.valueOf(); 1024 | }, 1025 | 1026 | "regexp" : function(b, a) { 1027 | return QUnit.objectType(b) === "regexp" 1028 | && a.source === b.source && // the regex itself 1029 | a.global === b.global && // and its modifers 1030 | // (gmi) ... 1031 | a.ignoreCase === b.ignoreCase 1032 | && a.multiline === b.multiline; 1033 | }, 1034 | 1035 | // - skip when the property is a method of an instance (OOP) 1036 | // - abort otherwise, 1037 | // initial === would have catch identical references anyway 1038 | "function" : function() { 1039 | var caller = callers[callers.length - 1]; 1040 | return caller !== Object && typeof caller !== "undefined"; 1041 | }, 1042 | 1043 | "array" : function(b, a) { 1044 | var i, j, loop; 1045 | var len; 1046 | 1047 | // b could be an object literal here 1048 | if (!(QUnit.objectType(b) === "array")) { 1049 | return false; 1050 | } 1051 | 1052 | len = a.length; 1053 | if (len !== b.length) { // safe and faster 1054 | return false; 1055 | } 1056 | 1057 | // track reference to avoid circular references 1058 | parents.push(a); 1059 | for (i = 0; i < len; i++) { 1060 | loop = false; 1061 | for (j = 0; j < parents.length; j++) { 1062 | if (parents[j] === a[i]) { 1063 | loop = true;// dont rewalk array 1064 | } 1065 | } 1066 | if (!loop && !innerEquiv(a[i], b[i])) { 1067 | parents.pop(); 1068 | return false; 1069 | } 1070 | } 1071 | parents.pop(); 1072 | return true; 1073 | }, 1074 | 1075 | "object" : function(b, a) { 1076 | var i, j, loop; 1077 | var eq = true; // unless we can proove it 1078 | var aProperties = [], bProperties = []; // collection of 1079 | // strings 1080 | 1081 | // comparing constructors is more strict than using 1082 | // instanceof 1083 | if (a.constructor !== b.constructor) { 1084 | return false; 1085 | } 1086 | 1087 | // stack constructor before traversing properties 1088 | callers.push(a.constructor); 1089 | // track reference to avoid circular references 1090 | parents.push(a); 1091 | 1092 | for (i in a) { // be strict: don't ensures hasOwnProperty 1093 | // and go deep 1094 | loop = false; 1095 | for (j = 0; j < parents.length; j++) { 1096 | if (parents[j] === a[i]) 1097 | loop = true; // don't go down the same path 1098 | // twice 1099 | } 1100 | aProperties.push(i); // collect a's properties 1101 | 1102 | if (!loop && !innerEquiv(a[i], b[i])) { 1103 | eq = false; 1104 | break; 1105 | } 1106 | } 1107 | 1108 | callers.pop(); // unstack, we are done 1109 | parents.pop(); 1110 | 1111 | for (i in b) { 1112 | bProperties.push(i); // collect b's properties 1113 | } 1114 | 1115 | // Ensures identical properties name 1116 | return eq 1117 | && innerEquiv(aProperties.sort(), bProperties 1118 | .sort()); 1119 | } 1120 | }; 1121 | }(); 1122 | 1123 | innerEquiv = function() { // can take multiple arguments 1124 | var args = Array.prototype.slice.apply(arguments); 1125 | if (args.length < 2) { 1126 | return true; // end transition 1127 | } 1128 | 1129 | return (function(a, b) { 1130 | if (a === b) { 1131 | return true; // catch the most you can 1132 | } else if (a === null || b === null || typeof a === "undefined" 1133 | || typeof b === "undefined" 1134 | || QUnit.objectType(a) !== QUnit.objectType(b)) { 1135 | return false; // don't lose time with error prone cases 1136 | } else { 1137 | return bindCallbacks(a, callbacks, [ b, a ]); 1138 | } 1139 | 1140 | // apply transition with (1..n) arguments 1141 | })(args[0], args[1]) 1142 | && arguments.callee.apply(this, args.splice(1, 1143 | args.length - 1)); 1144 | }; 1145 | 1146 | return innerEquiv; 1147 | 1148 | }(); 1149 | 1150 | /** 1151 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1152 | * http://flesler.blogspot.com Licensed under BSD 1153 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1154 | * 1155 | * @projectDescription Advanced and extensible data dumping for Javascript. 1156 | * @version 1.0.0 1157 | * @author Ariel Flesler 1158 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1159 | */ 1160 | QUnit.jsDump = (function() { 1161 | function quote( str ) { 1162 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 1163 | }; 1164 | function literal( o ) { 1165 | return o + ''; 1166 | }; 1167 | function join( pre, arr, post ) { 1168 | var s = jsDump.separator(), 1169 | base = jsDump.indent(), 1170 | inner = jsDump.indent(1); 1171 | if ( arr.join ) 1172 | arr = arr.join( ',' + s + inner ); 1173 | if ( !arr ) 1174 | return pre + post; 1175 | return [ pre, inner + arr, base + post ].join(s); 1176 | }; 1177 | function array( arr, stack ) { 1178 | var i = arr.length, ret = Array(i); 1179 | this.up(); 1180 | while ( i-- ) 1181 | ret[i] = this.parse( arr[i] , undefined , stack); 1182 | this.down(); 1183 | return join( '[', ret, ']' ); 1184 | }; 1185 | 1186 | var reName = /^function (\w+)/; 1187 | 1188 | var jsDump = { 1189 | parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1190 | stack = stack || [ ]; 1191 | var parser = this.parsers[ type || this.typeOf(obj) ]; 1192 | type = typeof parser; 1193 | var inStack = inArray(obj, stack); 1194 | if (inStack != -1) { 1195 | return 'recursion('+(inStack - stack.length)+')'; 1196 | } 1197 | //else 1198 | if (type == 'function') { 1199 | stack.push(obj); 1200 | var res = parser.call( this, obj, stack ); 1201 | stack.pop(); 1202 | return res; 1203 | } 1204 | // else 1205 | return (type == 'string') ? parser : this.parsers.error; 1206 | }, 1207 | typeOf:function( obj ) { 1208 | var type; 1209 | if ( obj === null ) { 1210 | type = "null"; 1211 | } else if (typeof obj === "undefined") { 1212 | type = "undefined"; 1213 | } else if (QUnit.is("RegExp", obj)) { 1214 | type = "regexp"; 1215 | } else if (QUnit.is("Date", obj)) { 1216 | type = "date"; 1217 | } else if (QUnit.is("Function", obj)) { 1218 | type = "function"; 1219 | } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { 1220 | type = "window"; 1221 | } else if (obj.nodeType === 9) { 1222 | type = "document"; 1223 | } else if (obj.nodeType) { 1224 | type = "node"; 1225 | } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) { 1226 | type = "array"; 1227 | } else { 1228 | type = typeof obj; 1229 | } 1230 | return type; 1231 | }, 1232 | separator:function() { 1233 | return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; 1234 | }, 1235 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1236 | if ( !this.multiline ) 1237 | return ''; 1238 | var chr = this.indentChar; 1239 | if ( this.HTML ) 1240 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 1241 | return Array( this._depth_ + (extra||0) ).join(chr); 1242 | }, 1243 | up:function( a ) { 1244 | this._depth_ += a || 1; 1245 | }, 1246 | down:function( a ) { 1247 | this._depth_ -= a || 1; 1248 | }, 1249 | setParser:function( name, parser ) { 1250 | this.parsers[name] = parser; 1251 | }, 1252 | // The next 3 are exposed so you can use them 1253 | quote:quote, 1254 | literal:literal, 1255 | join:join, 1256 | // 1257 | _depth_: 1, 1258 | // This is the list of parsers, to modify them, use jsDump.setParser 1259 | parsers:{ 1260 | window: '[Window]', 1261 | document: '[Document]', 1262 | error:'[ERROR]', //when no parser is found, shouldn't happen 1263 | unknown: '[Unknown]', 1264 | 'null':'null', 1265 | 'undefined':'undefined', 1266 | 'function':function( fn ) { 1267 | var ret = 'function', 1268 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1269 | if ( name ) 1270 | ret += ' ' + name; 1271 | ret += '('; 1272 | 1273 | ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); 1274 | return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); 1275 | }, 1276 | array: array, 1277 | nodelist: array, 1278 | arguments: array, 1279 | object:function( map, stack ) { 1280 | var ret = [ ]; 1281 | QUnit.jsDump.up(); 1282 | for ( var key in map ) { 1283 | var val = map[key]; 1284 | ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); 1285 | } 1286 | QUnit.jsDump.down(); 1287 | return join( '{', ret, '}' ); 1288 | }, 1289 | node:function( node ) { 1290 | var open = QUnit.jsDump.HTML ? '<' : '<', 1291 | close = QUnit.jsDump.HTML ? '>' : '>'; 1292 | 1293 | var tag = node.nodeName.toLowerCase(), 1294 | ret = open + tag; 1295 | 1296 | for ( var a in QUnit.jsDump.DOMAttrs ) { 1297 | var val = node[QUnit.jsDump.DOMAttrs[a]]; 1298 | if ( val ) 1299 | ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); 1300 | } 1301 | return ret + close + open + '/' + tag + close; 1302 | }, 1303 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1304 | var l = fn.length; 1305 | if ( !l ) return ''; 1306 | 1307 | var args = Array(l); 1308 | while ( l-- ) 1309 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1310 | return ' ' + args.join(', ') + ' '; 1311 | }, 1312 | key:quote, //object calls it internally, the key part of an item in a map 1313 | functionCode:'[code]', //function calls it internally, it's the content of the function 1314 | attribute:quote, //node calls it internally, it's an html attribute value 1315 | string:quote, 1316 | date:quote, 1317 | regexp:literal, //regex 1318 | number:literal, 1319 | 'boolean':literal 1320 | }, 1321 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1322 | id:'id', 1323 | name:'name', 1324 | 'class':'className' 1325 | }, 1326 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1327 | indentChar:' ',//indentation unit 1328 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1329 | }; 1330 | 1331 | return jsDump; 1332 | })(); 1333 | 1334 | // from Sizzle.js 1335 | function getText( elems ) { 1336 | var ret = "", elem; 1337 | 1338 | for ( var i = 0; elems[i]; i++ ) { 1339 | elem = elems[i]; 1340 | 1341 | // Get the text from text nodes and CDATA nodes 1342 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1343 | ret += elem.nodeValue; 1344 | 1345 | // Traverse everything else, except comment nodes 1346 | } else if ( elem.nodeType !== 8 ) { 1347 | ret += getText( elem.childNodes ); 1348 | } 1349 | } 1350 | 1351 | return ret; 1352 | }; 1353 | 1354 | //from jquery.js 1355 | function inArray( elem, array ) { 1356 | if ( array.indexOf ) { 1357 | return array.indexOf( elem ); 1358 | } 1359 | 1360 | for ( var i = 0, length = array.length; i < length; i++ ) { 1361 | if ( array[ i ] === elem ) { 1362 | return i; 1363 | } 1364 | } 1365 | 1366 | return -1; 1367 | } 1368 | 1369 | /* 1370 | * Javascript Diff Algorithm 1371 | * By John Resig (http://ejohn.org/) 1372 | * Modified by Chu Alan "sprite" 1373 | * 1374 | * Released under the MIT license. 1375 | * 1376 | * More Info: 1377 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1378 | * 1379 | * Usage: QUnit.diff(expected, actual) 1380 | * 1381 | * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" 1382 | */ 1383 | QUnit.diff = (function() { 1384 | function diff(o, n) { 1385 | var ns = new Object(); 1386 | var os = new Object(); 1387 | 1388 | for (var i = 0; i < n.length; i++) { 1389 | if (ns[n[i]] == null) 1390 | ns[n[i]] = { 1391 | rows: new Array(), 1392 | o: null 1393 | }; 1394 | ns[n[i]].rows.push(i); 1395 | } 1396 | 1397 | for (var i = 0; i < o.length; i++) { 1398 | if (os[o[i]] == null) 1399 | os[o[i]] = { 1400 | rows: new Array(), 1401 | n: null 1402 | }; 1403 | os[o[i]].rows.push(i); 1404 | } 1405 | 1406 | for (var i in ns) { 1407 | if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { 1408 | n[ns[i].rows[0]] = { 1409 | text: n[ns[i].rows[0]], 1410 | row: os[i].rows[0] 1411 | }; 1412 | o[os[i].rows[0]] = { 1413 | text: o[os[i].rows[0]], 1414 | row: ns[i].rows[0] 1415 | }; 1416 | } 1417 | } 1418 | 1419 | for (var i = 0; i < n.length - 1; i++) { 1420 | if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && 1421 | n[i + 1] == o[n[i].row + 1]) { 1422 | n[i + 1] = { 1423 | text: n[i + 1], 1424 | row: n[i].row + 1 1425 | }; 1426 | o[n[i].row + 1] = { 1427 | text: o[n[i].row + 1], 1428 | row: i + 1 1429 | }; 1430 | } 1431 | } 1432 | 1433 | for (var i = n.length - 1; i > 0; i--) { 1434 | if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && 1435 | n[i - 1] == o[n[i].row - 1]) { 1436 | n[i - 1] = { 1437 | text: n[i - 1], 1438 | row: n[i].row - 1 1439 | }; 1440 | o[n[i].row - 1] = { 1441 | text: o[n[i].row - 1], 1442 | row: i - 1 1443 | }; 1444 | } 1445 | } 1446 | 1447 | return { 1448 | o: o, 1449 | n: n 1450 | }; 1451 | } 1452 | 1453 | return function(o, n) { 1454 | o = o.replace(/\s+$/, ''); 1455 | n = n.replace(/\s+$/, ''); 1456 | var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); 1457 | 1458 | var str = ""; 1459 | 1460 | var oSpace = o.match(/\s+/g); 1461 | if (oSpace == null) { 1462 | oSpace = [" "]; 1463 | } 1464 | else { 1465 | oSpace.push(" "); 1466 | } 1467 | var nSpace = n.match(/\s+/g); 1468 | if (nSpace == null) { 1469 | nSpace = [" "]; 1470 | } 1471 | else { 1472 | nSpace.push(" "); 1473 | } 1474 | 1475 | if (out.n.length == 0) { 1476 | for (var i = 0; i < out.o.length; i++) { 1477 | str += '' + out.o[i] + oSpace[i] + ""; 1478 | } 1479 | } 1480 | else { 1481 | if (out.n[0].text == null) { 1482 | for (n = 0; n < out.o.length && out.o[n].text == null; n++) { 1483 | str += '' + out.o[n] + oSpace[n] + ""; 1484 | } 1485 | } 1486 | 1487 | for (var i = 0; i < out.n.length; i++) { 1488 | if (out.n[i].text == null) { 1489 | str += '' + out.n[i] + nSpace[i] + ""; 1490 | } 1491 | else { 1492 | var pre = ""; 1493 | 1494 | for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { 1495 | pre += '' + out.o[n] + oSpace[n] + ""; 1496 | } 1497 | str += " " + out.n[i].text + nSpace[i] + pre; 1498 | } 1499 | } 1500 | } 1501 | 1502 | return str; 1503 | }; 1504 | })(); 1505 | 1506 | })(this); 1507 | --------------------------------------------------------------------------------