This is an automated email from the ASF dual-hosted git repository.

zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 4d6e8109939 Render railroad diagrams in-browser without cross-domain 
requests (#37619)
4d6e8109939 is described below

commit 4d6e81099391eee2ec929c27761dd6d4f542796f
Author: Liang Zhang <[email protected]>
AuthorDate: Fri Jan 2 13:14:38 2026 +0800

    Render railroad diagrams in-browser without cross-domain requests (#37619)
---
 .../static/css/railroad-diagrams.css               |  47 ++
 .../themes/hugo-theme-learn/static/js/learn.js     | 242 ++++++++--
 .../static/js/railroad-diagrams.js                 | 524 +++++++++++++++++++++
 3 files changed, 763 insertions(+), 50 deletions(-)

diff --git 
a/docs/document/themes/hugo-theme-learn/static/css/railroad-diagrams.css 
b/docs/document/themes/hugo-theme-learn/static/css/railroad-diagrams.css
new file mode 100644
index 00000000000..57580c12dfe
--- /dev/null
+++ b/docs/document/themes/hugo-theme-learn/static/css/railroad-diagrams.css
@@ -0,0 +1,47 @@
+svg.railroad-diagram {
+    background-color: hsl(30,20%,95%);
+}
+svg.railroad-diagram path {
+    stroke-width: 3;
+    stroke: black;
+    fill: rgba(0,0,0,0);
+}
+svg.railroad-diagram text {
+    font: bold 14px monospace;
+    text-anchor: middle;
+    white-space: pre;
+}
+svg.railroad-diagram text.diagram-text {
+    font-size: 12px;
+}
+svg.railroad-diagram text.diagram-arrow {
+    font-size: 16px;
+}
+svg.railroad-diagram text.label {
+    text-anchor: start;
+}
+svg.railroad-diagram text.comment {
+    font: italic 12px monospace;
+}
+svg.railroad-diagram g.non-terminal text {
+    /*font-style: italic;*/
+}
+svg.railroad-diagram rect {
+    stroke-width: 3;
+    stroke: black;
+    fill: hsl(120,100%,90%);
+}
+svg.railroad-diagram rect.group-box {
+    stroke: gray;
+    stroke-dasharray: 10 5;
+    fill: none;
+}
+svg.railroad-diagram path.diagram-text {
+    stroke-width: 3;
+    stroke: black;
+    fill: white;
+    cursor: help;
+}
+svg.railroad-diagram g.diagram-text:hover path.diagram-text {
+    fill: #eee;
+}
diff --git a/docs/document/themes/hugo-theme-learn/static/js/learn.js 
b/docs/document/themes/hugo-theme-learn/static/js/learn.js
index 7f7c3bc5534..bf00b086687 100644
--- a/docs/document/themes/hugo-theme-learn/static/js/learn.js
+++ b/docs/document/themes/hugo-theme-learn/static/js/learn.js
@@ -401,71 +401,213 @@ jQuery(document).ready(function() {
     })(window.document, window.history, window.location);
 
 
-    //railroad diagram
-    if ($('.tab-panel').length) {
+    // railroad diagram (generate in-browser to avoid cross-domain CSP blocks)
+    (function () {
+        if (!$('.tab-panel').length) {
+            return;
+        }
         var codeBlock = $('.tab-panel code').first();
-        var diagram = $('#diagram');
-        var grammarText = codeBlock.text() || '';
-
-        if (grammarText && diagram.length) {
-            var loadingId = 'rr-loading';
-            diagram.before('<p id="' + loadingId + '">Loading ...</p>');
-
-            function baseRailroadPath() {
-                var parts = window.location.pathname.split('/');
-                var docIdx = parts.indexOf('document');
-                if (docIdx === -1) {
-                    return '/railroad/';
+        var grammarText = (codeBlock.text() || '').trim();
+        var diagramFrame = $('#diagram');
+        if (!grammarText || !diagramFrame.length) {
+            return;
+        }
+
+        var loadingId = 'rr-loading';
+        diagramFrame.before('<p id="' + loadingId + '">Loading ...</p>');
+
+        function resolveStaticPath(relPath) {
+            var parts = window.location.pathname.split('/');
+            var docIdx = parts.indexOf('document');
+            if (docIdx === -1) {
+                return '/' + relPath;
+            }
+            return '/' + parts.slice(1, docIdx + 2).join('/') + '/' + relPath;
+        }
+
+        var railroadAssetsPromise;
+        function ensureRailroadAssets() {
+            if (railroadAssetsPromise) {
+                return railroadAssetsPromise;
+            }
+            railroadAssetsPromise = new Promise(function (resolve, reject) {
+                function onReady() {
+                    resolve();
+                }
+
+                // load CSS once
+                if (!document.getElementById('railroad-diagrams-css')) {
+                    var link = document.createElement('link');
+                    link.id = 'railroad-diagrams-css';
+                    link.rel = 'stylesheet';
+                    link.href = resolveStaticPath('css/railroad-diagrams.css');
+                    document.head.appendChild(link);
                 }
-                // e.g. /document/current/... -> /document/current/railroad/
-                return '/' + parts.slice(1, docIdx + 2).join('/') + 
'/railroad/';
+
+                if (window.Diagram && window.NonTerminal && window.Terminal) {
+                    resolve();
+                    return;
+                }
+
+                var script = document.createElement('script');
+                script.src = resolveStaticPath('js/railroad-diagrams.js');
+                script.onload = onReady;
+                script.onerror = function () {
+                    reject(new Error('Railroad script load failed'));
+                };
+                document.head.appendChild(script);
+            });
+            return railroadAssetsPromise;
+        }
+
+        function tokenize(text) {
+            var regex = 
/::=|\\?|\\*|\\+|\\||\\(|\\)|\\[|\\]|'[^']*'|[A-Za-z_][A-Za-z0-9_-]*|,/g;
+            var tokens = [];
+            var match;
+            while ((match = regex.exec(text)) !== null) {
+                tokens.push(match[0]);
             }
+            return tokens;
+        }
+
+        function parseExpression(tokens, indexRef) {
+            var terms = [parseTerm(tokens, indexRef)];
+            while (tokens[indexRef.idx] === '|') {
+                indexRef.idx += 1;
+                terms.push(parseTerm(tokens, indexRef));
+            }
+            if (terms.length === 1) {
+                return terms[0];
+            }
+            return { type: 'choice', options: terms };
+        }
 
-            function toHex(buffer) {
-                var bytes = new Uint8Array(buffer);
-                var hex = '';
-                for (var i = 0; i < bytes.length; i++) {
-                    hex += ('00' + bytes[i].toString(16)).slice(-2);
+        function parseTerm(tokens, indexRef) {
+            var items = [];
+            while (indexRef.idx < tokens.length) {
+                var tok = tokens[indexRef.idx];
+                if (tok === '|' || tok === ')' || tok === ']') {
+                    break;
                 }
-                return hex;
+                items.push(parseFactor(tokens, indexRef));
+            }
+            if (items.length === 1) {
+                return items[0];
             }
+            return { type: 'sequence', items: items };
+        }
 
-            function sha256(text) {
-                if (!window.crypto || !window.crypto.subtle || 
!window.TextEncoder) {
-                    return Promise.reject(new Error('SHA-256 unsupported'));
+        function parseFactor(tokens, indexRef) {
+            var node = parsePrimary(tokens, indexRef);
+            var tok = tokens[indexRef.idx];
+            if (tok === '?' || tok === '*' || tok === '+') {
+                indexRef.idx += 1;
+                if (tok === '?') {
+                    node = { type: 'optional', item: node };
+                } else if (tok === '*') {
+                    node = { type: 'zeroOrMore', item: node };
+                } else if (tok === '+') {
+                    node = { type: 'oneOrMore', item: node };
                 }
-                var encoder = new TextEncoder();
-                return window.crypto.subtle.digest('SHA-256', 
encoder.encode(text)).then(toHex);
             }
+            return node;
+        }
+
+        function parsePrimary(tokens, indexRef) {
+            var tok = tokens[indexRef.idx];
+            indexRef.idx += 1;
+            if (!tok) {
+                return { type: 'terminal', value: '' };
+            }
+            if (tok === '(') {
+                var expr = parseExpression(tokens, indexRef);
+                indexRef.idx += 1; // skip ')'
+                return expr;
+            }
+            if (tok === '[') {
+                var optExpr = parseExpression(tokens, indexRef);
+                indexRef.idx += 1; // skip ']'
+                return { type: 'optional', item: optExpr };
+            }
+            if (tok[0] === "'" && tok.length >= 2) {
+                return { type: 'terminal', value: tok.slice(1, -1) };
+            }
+            return { type: 'nonterminal', value: tok };
+        }
 
-            sha256(grammarText).then(function (hash) {
-                var paths = [baseRailroadPath(), '/railroad/'];
-                var idx = 0;
+        function astToRailroad(node) {
+            switch (node.type) {
+                case 'terminal':
+                    return new Terminal(node.value);
+                case 'nonterminal':
+                    return new NonTerminal(node.value);
+                case 'sequence':
+                    return Sequence.apply(null, node.items.map(astToRailroad));
+                case 'choice':
+                    var opts = node.options.map(astToRailroad);
+                    return Choice.apply(null, [0].concat(opts));
+                case 'optional':
+                    return new Optional(astToRailroad(node.item), 'skip');
+                case 'zeroOrMore':
+                    return new ZeroOrMore(astToRailroad(node.item));
+                case 'oneOrMore':
+                    return new OneOrMore(astToRailroad(node.item));
+                default:
+                    return new Terminal('');
+            }
+        }
 
-                function loadNext() {
-                    if (idx >= paths.length) {
-                        $('#' + loadingId).text('Railroad diagram 
unavailable.');
-                        return;
-                    }
-                    var src = paths[idx] + hash + '.html';
-                    diagram.attr('src', src);
+        function parseDefinitions(text) {
+            var blocks = text.split(/\\n\\s*\\n/);
+            var defs = [];
+            for (var i = 0; i < blocks.length; i++) {
+                var block = blocks[i].trim();
+                if (!block) {
+                    continue;
+                }
+                var parts = block.split('::=');
+                if (parts.length < 2) {
+                    continue;
                 }
+                var name = parts[0].trim().split(/\\s+/)[0];
+                var rhs = parts.slice(1).join('::=').trim();
+                defs.push({ name: name, rhs: rhs });
+            }
+            return defs;
+        }
 
-                diagram.on('load', function () {
-                    $('#' + loadingId).remove();
-                    var codeHeight = codeBlock.height();
-                    diagram.height(codeHeight > 500 ? codeHeight + 'px' : 
'500px');
-                }).on('error', function () {
-                    idx += 1;
-                    loadNext();
-                });
+        function renderRailroad(grammar) {
+            var defs = parseDefinitions(grammar);
+            var htmlParts = ['<style>svg.railroad-diagram{background:#fff;} 
.rr-title{font:bold 14px Verdana, sans-serif;margin:10px 0 4px;}</style>'];
+            for (var i = 0; i < defs.length; i++) {
+                var def = defs[i];
+                try {
+                    var tokens = tokenize(def.rhs);
+                    var ast = parseExpression(tokens, { idx: 0 });
+                    var diagram = new Diagram(astToRailroad(ast));
+                    htmlParts.push('<p class=\"rr-title\">' + def.name + 
':</p>');
+                    htmlParts.push(diagram.toString());
+                } catch (e) {
+                    htmlParts.push('<p class=\"rr-title\">' + def.name + 
':</p><p>Railroad diagram unavailable.</p>');
+                }
+            }
+            return htmlParts.join('\\n');
+        }
 
-                loadNext();
-            }).catch(function () {
+        ensureRailroadAssets().then(function () {
+            try {
+                var container = $('<div class=\"railroad-diagrams\"></div>');
+                container.html(renderRailroad(grammarText));
+                diagramFrame.replaceWith(container);
+            } catch (e) {
                 $('#' + loadingId).text('Railroad diagram unavailable.');
-            });
-        }
-    }
+            } finally {
+                $('#' + loadingId).remove();
+            }
+        }).catch(function () {
+            $('#' + loadingId).text('Railroad diagram unavailable.');
+        });
+    })();
 
 });
 
diff --git 
a/docs/document/themes/hugo-theme-learn/static/js/railroad-diagrams.js 
b/docs/document/themes/hugo-theme-learn/static/js/railroad-diagrams.js
new file mode 100644
index 00000000000..afc45412e82
--- /dev/null
+++ b/docs/document/themes/hugo-theme-learn/static/js/railroad-diagrams.js
@@ -0,0 +1,524 @@
+/*
+Railroad Diagrams
+by Tab Atkins Jr. (and others)
+http://xanthir.com
+http://twitter.com/tabatkins
+http://github.com/tabatkins/railroad-diagrams
+
+This document and all associated files in the github project are licensed 
under CC0: http://creativecommons.org/publicdomain/zero/1.0/
+This means you can reuse, remix, or otherwise appropriate this project for 
your own use WITHOUT RESTRICTION.
+(The actual legal meaning can be found at the above link.)
+Don't ask me for permission to use any part of this project, JUST USE IT.
+I would appreciate attribution, but that is not required by the license.
+*/
+
+/*
+This file uses a module pattern to avoid leaking names into the global scope.
+The only accidental leakage is the name "temp".
+The exported names can be found at the bottom of this file;
+simply change the names in the array of strings to change what they are called 
in your application.
+
+As well, several configuration constants are passed into the module function 
at the bottom of this file.
+At runtime, these constants can be found on the Diagram class.
+*/
+
+(function(options) {
+       function subclassOf(baseClass, superClass) {
+               baseClass.prototype = Object.create(superClass.prototype);
+               baseClass.prototype.$super = superClass.prototype;
+       }
+
+       function unnull(/* children */) {
+               return [].slice.call(arguments).reduce(function(sofar, x) { 
return sofar !== undefined ? sofar : x; });
+       }
+
+       function determineGaps(outer, inner) {
+               var diff = outer - inner;
+               switch(Diagram.INTERNAL_ALIGNMENT) {
+                       case 'left': return [0, diff]; break;
+                       case 'right': return [diff, 0]; break;
+                       case 'center':
+                       default: return [diff/2, diff/2]; break;
+               }
+       }
+
+       function wrapString(value) {
+               return ((typeof value) == 'string') ? new Terminal(value) : 
value;
+       }
+
+
+       function SVG(name, attrs, text) {
+               attrs = attrs || {};
+               text = text || '';
+               var el = 
document.createElementNS("http://www.w3.org/2000/svg",name);
+               for(var attr in attrs) {
+                       el.setAttribute(attr, attrs[attr]);
+               }
+               el.textContent = text;
+               return el;
+       }
+
+       function FakeSVG(tagName, attrs, text){
+               if(!(this instanceof FakeSVG)) return new FakeSVG(tagName, 
attrs, text);
+               if(text) this.children = text;
+               else this.children = [];
+               this.tagName = tagName;
+               this.attrs = unnull(attrs, {});
+               return this;
+       };
+       FakeSVG.prototype.format = function(x, y, width) {
+               // Virtual
+       };
+       FakeSVG.prototype.addTo = function(parent) {
+               if(parent instanceof FakeSVG) {
+                       parent.children.push(this);
+                       return this;
+               } else {
+                       var svg = this.toSVG();
+                       parent.appendChild(svg);
+                       return svg;
+               }
+       };
+       FakeSVG.prototype.escapeString = function(string) {
+                // Escape markdown and HTML special characters
+               return string.replace(/[*_\`\[\]<&]/g, function(charString) {
+                       return '&#' + charString.charCodeAt(0) + ';';
+               });
+       };
+       FakeSVG.prototype.toSVG = function() {
+               var el = SVG(this.tagName, this.attrs);
+               if(typeof this.children == 'string') {
+                       el.innerHTML = 
FakeSVG.prototype.escapeString(this.children);
+               } else {
+                       this.children.forEach(function(e) {
+                               el.appendChild(e.toSVG());
+                       });
+               }
+               return el;
+       };
+       FakeSVG.prototype.toString = function() {
+               var str = '<' + this.tagName;
+               var group = this.tagName == "g" || this.tagName == "svg";
+               for(var attr in this.attrs) {
+                       str += ' ' + attr + '="' + 
(this.attrs[attr]+'').replace(/&/g, '&amp;').replace(/"/g, '&quot;') + '"';
+               }
+               str += '>';
+               if(group) str += "\n";
+               if(typeof this.children == 'string') {
+                       str += FakeSVG.prototype.escapeString(this.children);
+               } else {
+                       this.children.forEach(function(e) {
+                               str += e;
+                       });
+               }
+               str += '</' + this.tagName + '>\n';
+               return str;
+       }
+
+       function Path(x,y) {
+               if(!(this instanceof Path)) return new Path(x,y);
+               FakeSVG.call(this, 'path');
+               this.attrs.d = "M"+x+' '+y;
+       }
+       subclassOf(Path, FakeSVG);
+       Path.prototype.m = function(x,y) {
+               this.attrs.d += 'm'+x+' '+y;
+               return this;
+       }
+       Path.prototype.h = function(val) {
+               this.attrs.d += 'h'+val;
+               return this;
+       }
+       Path.prototype.right = Path.prototype.h;
+       Path.prototype.left = function(val) { return this.h(-val); }
+       Path.prototype.v = function(val) {
+               this.attrs.d += 'v'+val;
+               return this;
+       }
+       Path.prototype.down = Path.prototype.v;
+       Path.prototype.up = function(val) { return this.v(-val); }
+       Path.prototype.arc = function(sweep){
+               var x = Diagram.ARC_RADIUS;
+               var y = Diagram.ARC_RADIUS;
+               if(sweep[0] == 'e' || sweep[1] == 'w') {
+                       x *= -1;
+               }
+               if(sweep[0] == 's' || sweep[1] == 'n') {
+                       y *= -1;
+               }
+               if(sweep == 'ne' || sweep == 'es' || sweep == 'sw' || sweep == 
'wn') {
+                       var cw = 1;
+               } else {
+                       var cw = 0;
+               }
+               this.attrs.d += "a"+Diagram.ARC_RADIUS+" "+Diagram.ARC_RADIUS+" 
0 0 "+cw+' '+x+' '+y;
+               return this;
+       }
+       Path.prototype.format = function() {
+               // All paths in this library start/end horizontally.
+               // The extra .5 ensures a minor overlap, so there's no seams in 
bad rasterizers.
+               this.attrs.d += 'h.5';
+               return this;
+       }
+
+       function Diagram(items) {
+               if(!(this instanceof Diagram)) return new 
Diagram([].slice.call(arguments));
+               FakeSVG.call(this, 'svg', {class: Diagram.DIAGRAM_CLASS});
+               this.items = items.map(wrapString);
+               this.items.unshift(new Start);
+               this.items.push(new End);
+               this.width = this.items.reduce(function(sofar, el) { return 
sofar + el.width + (el.needsSpace?20:0)}, 0)+1;
+               this.up = Math.max.apply(null, this.items.map(function (x) { 
return x.up; }));
+               this.down = Math.max.apply(null, this.items.map(function (x) { 
return x.down; }));
+               this.formatted = false;
+       }
+       subclassOf(Diagram, FakeSVG);
+       for(var option in options) {
+               Diagram[option] = options[option];
+       }
+       Diagram.prototype.format = function(paddingt, paddingr, paddingb, 
paddingl) {
+               paddingt = unnull(paddingt, 20);
+               paddingr = unnull(paddingr, paddingt, 20);
+               paddingb = unnull(paddingb, paddingt, 20);
+               paddingl = unnull(paddingl, paddingr, 20);
+               var x = paddingl;
+               var y = paddingt;
+               y += this.up;
+               var g = FakeSVG('g', Diagram.STROKE_ODD_PIXEL_LENGTH ? 
{transform:'translate(.5 .5)'} : {});
+               for(var i = 0; i < this.items.length; i++) {
+                       var item = this.items[i];
+                       if(item.needsSpace) {
+                               Path(x,y).h(10).addTo(g);
+                               x += 10;
+                       }
+                       item.format(x, y, item.width).addTo(g);
+                       x += item.width;
+                       if(item.needsSpace) {
+                               Path(x,y).h(10).addTo(g);
+                               x += 10;
+                       }
+               }
+               this.attrs.width = this.width + paddingl + paddingr;
+               this.attrs.height = this.up + this.down + paddingt + paddingb;
+               this.attrs.viewBox = "0 0 "  + this.attrs.width + " " + 
this.attrs.height;
+               g.addTo(this);
+               this.formatted = true;
+               return this;
+       }
+       Diagram.prototype.addTo = function(parent) {
+               var scriptTag = document.getElementsByTagName('script');
+               scriptTag = scriptTag[scriptTag.length - 1];
+               var parentTag = scriptTag.parentNode;
+               parent = parent || parentTag;
+               return this.$super.addTo.call(this, parent);
+       }
+       Diagram.prototype.toSVG = function() {
+               if (!this.formatted) {
+                       this.format();
+               }
+               return this.$super.toSVG.call(this);
+       }
+       Diagram.prototype.toString = function() {
+               if (!this.formatted) {
+                       this.format();
+               }
+               return this.$super.toString.call(this);
+       }
+
+       function ComplexDiagram() {
+               var diagram = new Diagram([].slice.call(arguments));
+                var items = diagram.items;
+               items.shift();
+               items.pop();
+               items.unshift(new Start(false));
+               items.push(new End(false));
+               diagram.items = items;
+               return diagram;
+        }
+
+       function Sequence(items) {
+               if(!(this instanceof Sequence)) return new 
Sequence([].slice.call(arguments));
+               FakeSVG.call(this, 'g');
+               this.items = items.map(wrapString);
+               this.width = this.items.reduce(function(sofar, el) { return 
sofar + el.width + (el.needsSpace?20:0)}, 0);
+               this.up = this.items.reduce(function(sofar,el) { return 
Math.max(sofar, el.up)}, 0);
+               this.down = this.items.reduce(function(sofar,el) { return 
Math.max(sofar, el.down)}, 0);
+       }
+       subclassOf(Sequence, FakeSVG);
+       Sequence.prototype.format = function(x,y,width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               for(var i = 0; i < this.items.length; i++) {
+                       var item = this.items[i];
+                       if(item.needsSpace) {
+                               Path(x,y).h(10).addTo(this);
+                               x += 10;
+                       }
+                       item.format(x, y, item.width).addTo(this);
+                       x += item.width;
+                       if(item.needsSpace) {
+                               Path(x,y).h(10).addTo(this);
+                               x += 10;
+                       }
+               }
+               return this;
+       }
+
+       function Choice(normal, items) {
+               if(!(this instanceof Choice)) return new Choice(normal, 
[].slice.call(arguments,1));
+               FakeSVG.call(this, 'g');
+               if( typeof normal !== "number" || normal !== Math.floor(normal) 
) {
+                       throw new TypeError("The first argument of Choice() 
must be an integer.");
+               } else if(normal < 0 || normal >= items.length) {
+                       throw new RangeError("The first argument of Choice() 
must be an index for one of the items.");
+               } else {
+                       this.normal = normal;
+               }
+               this.items = items.map(wrapString);
+               this.width = this.items.reduce(function(sofar, el){return 
Math.max(sofar, el.width)},0) + Diagram.ARC_RADIUS*4;
+               this.up = this.down = 0;
+               for(var i = 0; i < this.items.length; i++) {
+                       var item = this.items[i];
+                       if(i < normal) { this.up += 
Math.max(Diagram.ARC_RADIUS,item.up + item.down + Diagram.VERTICAL_SEPARATION); 
}
+                       if(i == normal) { this.up += 
Math.max(Diagram.ARC_RADIUS, item.up); this.down += 
Math.max(Diagram.ARC_RADIUS, item.down); }
+                       if(i > normal) { this.down += 
Math.max(Diagram.ARC_RADIUS,Diagram.VERTICAL_SEPARATION + item.up + item.down); 
}
+               }
+       }
+       subclassOf(Choice, FakeSVG);
+       Choice.prototype.format = function(x,y,width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               var last = this.items.length -1;
+               var innerWidth = this.width - Diagram.ARC_RADIUS*4;
+
+               // Do the elements that curve above
+               for(var i = this.normal - 1; i >= 0; i--) {
+                       var item = this.items[i];
+                       if( i == this.normal - 1 ) {
+                               var distanceFromY = 
Math.max(Diagram.ARC_RADIUS*2, this.items[i+1].up + Diagram.VERTICAL_SEPARATION 
+ item.down);
+                       }
+                       Path(x,y).arc('se').up(distanceFromY - 
Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
+                       item.format(x+Diagram.ARC_RADIUS*2,y - 
distanceFromY,innerWidth).addTo(this);
+                       Path(x+Diagram.ARC_RADIUS*2+innerWidth, 
y-distanceFromY).arc('ne').down(distanceFromY - 
Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
+                       distanceFromY += Math.max(Diagram.ARC_RADIUS, item.up + 
Diagram.VERTICAL_SEPARATION + (i == 0 ? 0 : this.items[i-1].down));
+               }
+
+               // Do the straight-line path.
+               Path(x,y).right(Diagram.ARC_RADIUS*2).addTo(this);
+               this.items[this.normal].format(x+Diagram.ARC_RADIUS*2, y, 
innerWidth).addTo(this);
+               Path(x+Diagram.ARC_RADIUS*2+innerWidth, 
y).right(Diagram.ARC_RADIUS*2).addTo(this);
+
+               // Do the elements that curve below
+               for(var i = this.normal+1; i <= last; i++) {
+                       var item = this.items[i];
+                       if( i == this.normal + 1 ) {
+                               var distanceFromY = 
Math.max(Diagram.ARC_RADIUS*2, this.items[i-1].down + 
Diagram.VERTICAL_SEPARATION + item.up);
+                       }
+                       Path(x,y).arc('ne').down(distanceFromY - 
Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
+                       item.format(x+Diagram.ARC_RADIUS*2, y+distanceFromY, 
innerWidth).addTo(this);
+                       Path(x+Diagram.ARC_RADIUS*2+innerWidth, 
y+distanceFromY).arc('se').up(distanceFromY - 
Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
+                       distanceFromY += Math.max(Diagram.ARC_RADIUS, item.down 
+ Diagram.VERTICAL_SEPARATION + (i == last ? 0 : this.items[i+1].up));
+               }
+
+               return this;
+       }
+
+       function Optional(item, skip) {
+               if( skip === undefined )
+                       return Choice(1, Skip(), item);
+               else if ( skip === "skip" )
+                       return Choice(0, Skip(), item);
+               else
+                       throw "Unknown value for Optional()'s 'skip' argument.";
+       }
+
+       function OneOrMore(item, rep) {
+               if(!(this instanceof OneOrMore)) return new OneOrMore(item, 
rep);
+               FakeSVG.call(this, 'g');
+               rep = rep || (new Skip);
+               this.item = wrapString(item);
+               this.rep = wrapString(rep);
+               this.width = Math.max(this.item.width, this.rep.width) + 
Diagram.ARC_RADIUS*2;
+               this.up = this.item.up;
+               this.down = Math.max(Diagram.ARC_RADIUS*2, this.item.down + 
Diagram.VERTICAL_SEPARATION + this.rep.up + this.rep.down);
+       }
+       subclassOf(OneOrMore, FakeSVG);
+       OneOrMore.prototype.needsSpace = true;
+       OneOrMore.prototype.format = function(x,y,width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               // Draw item
+               Path(x,y).right(Diagram.ARC_RADIUS).addTo(this);
+               
this.item.format(x+Diagram.ARC_RADIUS,y,this.width-Diagram.ARC_RADIUS*2).addTo(this);
+               
Path(x+this.width-Diagram.ARC_RADIUS,y).right(Diagram.ARC_RADIUS).addTo(this);
+
+               // Draw repeat arc
+               var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, 
this.item.down+Diagram.VERTICAL_SEPARATION+this.rep.up);
+               
Path(x+Diagram.ARC_RADIUS,y).arc('nw').down(distanceFromY-Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
+               this.rep.format(x+Diagram.ARC_RADIUS, y+distanceFromY, 
this.width - Diagram.ARC_RADIUS*2).addTo(this);
+               Path(x+this.width-Diagram.ARC_RADIUS, 
y+distanceFromY).arc('se').up(distanceFromY-Diagram.ARC_RADIUS*2).arc('en').addTo(this);
+
+               return this;
+       }
+
+       function ZeroOrMore(item, rep, skip) {
+               return Optional(OneOrMore(item, rep), skip);
+       }
+
+       function Start(simpleType) {
+               if(!(this instanceof Start)) return new Start();
+               FakeSVG.call(this, 'path');
+               this.width = 20;
+               this.up = 10;
+               this.down = 10;
+               this.simpleType = simpleType;
+       }
+       subclassOf(Start, FakeSVG);
+       Start.prototype.format = function(x,y) {
+               if (this.simpleType === false) {
+                       this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 0 -10 h 20.5';
+               } else {
+                       this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 10 -20 v 20 m 
-10 -10 h 20.5';
+               }
+               return this;
+       }
+
+       function End(simpleType) {
+               if(!(this instanceof End)) return new End();
+               FakeSVG.call(this, 'path');
+               this.width = 20;
+               this.up = 10;
+               this.down = 10;
+               this.simpleType = simpleType;
+       }
+       subclassOf(End, FakeSVG);
+       End.prototype.format = function(x,y) {
+               if (this.simpleType === false) {
+                       this.attrs.d = 'M '+x+' '+y+' h 20 m 0 -10 v 20';
+               } else {
+                       this.attrs.d = 'M '+x+' '+y+' h 20 m -10 -10 v 20 m 10 
-20 v 20';
+               }
+               return this;
+       }
+
+       function Terminal(text) {
+               if(!(this instanceof Terminal)) return new Terminal(text);
+               FakeSVG.call(this, 'g');
+               this.text = text;
+               this.width = text.length * 8 + 20; /* Assume that each char is 
.5em, and that the em is 16px */
+               this.up = 11;
+               this.down = 11;
+       }
+       subclassOf(Terminal, FakeSVG);
+       Terminal.prototype.needsSpace = true;
+       Terminal.prototype.format = function(x, y, width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               FakeSVG('rect', {x:x, y:y-11, width:this.width, 
height:this.up+this.down, rx:10, ry:10}).addTo(this);
+               FakeSVG('text', {x:x+this.width/2, y:y+4}, 
this.text).addTo(this);
+               return this;
+       }
+
+       function NonTerminal(text) {
+               if(!(this instanceof NonTerminal)) return new NonTerminal(text);
+               FakeSVG.call(this, 'g');
+               this.text = text;
+               this.width = text.length * 8 + 20;
+               this.up = 11;
+               this.down = 11;
+       }
+       subclassOf(NonTerminal, FakeSVG);
+       NonTerminal.prototype.needsSpace = true;
+       NonTerminal.prototype.format = function(x, y, width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               FakeSVG('rect', {x:x, y:y-11, width:this.width, 
height:this.up+this.down}).addTo(this);
+               FakeSVG('text', {x:x+this.width/2, y:y+4}, 
this.text).addTo(this);
+               return this;
+       }
+
+       function Comment(text) {
+               if(!(this instanceof Comment)) return new Comment(text);
+               FakeSVG.call(this, 'g');
+               this.text = text;
+               this.width = text.length * 7 + 10;
+               this.up = 11;
+               this.down = 11;
+       }
+       subclassOf(Comment, FakeSVG);
+       Comment.prototype.needsSpace = true;
+       Comment.prototype.format = function(x, y, width) {
+               // Hook up the two sides if this is narrower than its stated 
width.
+               var gaps = determineGaps(width, this.width);
+               Path(x,y).h(gaps[0]).addTo(this);
+               Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
+               x += gaps[0];
+
+               FakeSVG('text', {x:x+this.width/2, y:y+5, class:'comment'}, 
this.text).addTo(this);
+               return this;
+       }
+
+       function Skip() {
+               if(!(this instanceof Skip)) return new Skip();
+               FakeSVG.call(this, 'g');
+               this.width = 0;
+               this.up = 0;
+               this.down = 0;
+       }
+       subclassOf(Skip, FakeSVG);
+       Skip.prototype.format = function(x, y, width) {
+               Path(x,y).right(width).addTo(this);
+               return this;
+       }
+       
+       var root;
+       if (typeof define === 'function' && define.amd) {
+               // AMD. Register as an anonymous module.
+               root = {};
+               define([], function() {
+                       return root;
+               });
+       } else if (typeof exports === 'object') {
+               // CommonJS for node
+               root = exports;
+       } else {
+               // Browser globals (root is window)
+               root = this;
+       }
+
+       var temp = [Diagram, ComplexDiagram, Sequence, Choice, Optional, 
OneOrMore, ZeroOrMore, Terminal, NonTerminal, Comment, Skip];
+       /*
+       These are the names that the internal classes are exported as.
+       If you would like different names, adjust them here.
+       */
+       ['Diagram', 'ComplexDiagram', 'Sequence', 'Choice', 'Optional', 
'OneOrMore', 'ZeroOrMore', 'Terminal', 'NonTerminal', 'Comment', 'Skip']
+               .forEach(function(e,i) { root[e] = temp[i]; });
+}).call(this,
+       {
+       VERTICAL_SEPARATION: 8,
+       ARC_RADIUS: 10,
+       DIAGRAM_CLASS: 'railroad-diagram',
+       STROKE_ODD_PIXEL_LENGTH: true,
+       INTERNAL_ALIGNMENT: 'center',
+       }
+);

Reply via email to