Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: internetarchive/heritrix3
base: 42e759c
...
head fork: internetarchive/heritrix3
compare: e4f12bb
  • 2 commits
  • 15 files changed
  • 0 commit comments
  • 2 contributors
Commits on Mar 31, 2012
@ato ato HER-2001: Use the CodeMirror editor for crawl config and script console
This enables syntax-highlighting, line-numbering and auto-indent for the crawl
configuration editor and the script console (for BeanShell, JavaScript and
Groovy).

* BaseResource.java
    add a more generic getStaticRef() in addition to getStylesheetRef()
* EditRepresentation.java
    wrap textarea with CodeMirror in purexml mode.
    disable the "save changes" button until config is edited.
    dynamically resize CodeMirror edit element to fit window height.
* ScriptResource.java
    wrap textarea with CodeMirror.
    switch CodeMirror language modes depending on selected script engine.
* resources/.../restlet/codemirror/
    include just the files from codemirror-2.23.zip necessary for the above
    plus the CodeMirror LICENSE file (MIT)
    plus a README linking to the codemirror website
f19a72e
Commits on Apr 25, 2012
@gojomo gojomo Merge pull request #6 from ato/codemirror
HER-2001: Use the CodeMirror editor for crawl config and script console
    (Thanks Alex!)
e4f12bb
Showing with 4,790 additions and 9 deletions.
  1. +6 −2 engine/src/main/java/org/archive/crawler/restlet/BaseResource.java
  2. +35 −5 engine/src/main/java/org/archive/crawler/restlet/EditRepresentation.java
  3. +22 −2 engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java
  4. +23 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/LICENSE
  5. +9 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/README
  6. +112 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/codemirror.css
  7. +2,972 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/codemirror.js
  8. +234 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/clike.js
  9. +210 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/groovy.js
  10. +360 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/javascript.js
  11. +490 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/xmlpure.js
  12. +23 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/dialog.css
  13. +63 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/dialog.js
  14. +114 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/search.js
  15. +117 −0 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/searchcursor.js
View
8 engine/src/main/java/org/archive/crawler/restlet/BaseResource.java
@@ -68,8 +68,12 @@ public Variant getPreferredVariant() {
return super.getPreferredVariant();
}
- protected String getStylesheetRef() {
+ protected String getStaticRef(String resource) {
String rootRef = getRequest().getRootRef().toString();
- return rootRef + "/engine/static/engine.css";
+ return rootRef + "/engine/static/" + resource;
+ }
+
+ protected String getStylesheetRef() {
+ return getStaticRef("engine.css");
}
}
View
40 engine/src/main/java/org/archive/crawler/restlet/EditRepresentation.java
@@ -58,20 +58,33 @@ public Reader getReader() throws IOException {
return new StringReader(writer.toString());
}
+ protected String getStaticRef(String resource) {
+ String rootRef = dirResource.getRequest().getRootRef().toString();
+ return rootRef + "/engine/static/" + resource;
+ }
+
@Override
public void write(Writer writer) throws IOException {
PrintWriter pw = new PrintWriter(writer);
pw.println("<!DOCTYPE html>");
pw.println("<html>");
- pw.println("<head><title>"+fileRepresentation.getFile().getName()+"</title></head>");
+ pw.println("<head><title>"+fileRepresentation.getFile().getName()+"</title>");
+ pw.println("<link rel='stylesheet' href='" + getStaticRef("codemirror/codemirror.css") + "'>");
+ pw.println("<link rel='stylesheet' href='" + getStaticRef("codemirror/util/dialog.css") + "'>");
+ pw.println("<script src='" + getStaticRef("codemirror/codemirror.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/mode/xmlpure.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/dialog.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/searchcursor.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/search.js") + "'></script>");
+ pw.println("<style>.CodeMirror { background: #fff; }</style>");
+ pw.println("</head>");
pw.println("<body style='background-color:#ddd'>");
pw.println("<form style='position:absolute;top:15px;bottom:15px;left:15px;right:15px;overflow:auto' method='POST'>");
- pw.println("<textarea style='width:98%;height:90%;font-family:monospace' name='contents'>");
+ pw.println("<textarea style='width:98%;height:90%;font-family:monospace' name='contents' id='editor'>");
StringEscapeUtils.escapeHtml(pw,fileRepresentation.getText());
pw.println("</textarea>");
- pw.println("<div>");
- // TODO: enable button on after changes made
- pw.println("<input type='submit' value='save changes'>");
+ pw.println("<div id='savebar'>");
+ pw.println("<input type='submit' value='save changes' id='savebutton'>");
pw.println(fileRepresentation.getFile());
Reference viewRef = dirResource.getRequest().getOriginalRef().clone();
viewRef.setQuery(null);
@@ -79,6 +92,23 @@ public void write(Writer writer) throws IOException {
Flash.renderFlashesHTML(pw, dirResource.getRequest());
pw.println("</div>");
pw.println("</form>");
+ pw.println("<script>");
+ pw.println("var editor = document.getElementById('editor');");
+ pw.println("var savebar = document.getElementById('savebar');");
+ pw.println("var savebutton = document.getElementById('savebutton');");
+ pw.println("var cmopts = {");
+ pw.println(" mode: {name: 'xmlpure'},");
+ pw.println(" indentUnit: 1, lineNumbers: true, autofocus: true,");
+ pw.println(" onChange: function() { savebutton.disabled = false; },");
+ pw.println("}");
+ pw.println("var cm = CodeMirror.fromTextArea(editor, cmopts);");
+ pw.println("window.onresize = function() {");
+ pw.println(" cm.getScrollerElement().style.height = innerHeight - savebar.offsetHeight - 30 + 'px';");
+ pw.println(" cm.refresh();");
+ pw.println("}");
+ pw.println("window.onresize();");
+ pw.println("savebutton.disabled = true;");
+ pw.println("</script>");
pw.println("</body>");
pw.println("</html>");
pw.close();
View
24 engine/src/main/java/org/archive/crawler/restlet/ScriptResource.java
@@ -250,6 +250,15 @@ protected void writeHtml(Writer writer) {
pw.println("<head>");
pw.println("<title>Script in "+cj.getShortName()+"</title>");
pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + getStylesheetRef() + "\">");
+ pw.println("<link rel='stylesheet' href='" + getStaticRef("codemirror/codemirror.css") + "'>");
+ pw.println("<link rel='stylesheet' href='" + getStaticRef("codemirror/util/dialog.css") + "'>");
+ pw.println("<script src='" + getStaticRef("codemirror/codemirror.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/mode/groovy.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/mode/clike.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/mode/javascript.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/dialog.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/searchcursor.js") + "'></script>");
+ pw.println("<script src='" + getStaticRef("codemirror/util/search.js") + "'></script>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h1>Execute script for job <i><a href='/engine/job/"
@@ -279,7 +288,7 @@ protected void writeHtml(Writer writer) {
pw.println("<form method='POST'>");
pw.println("<input type='submit' value='execute'>");
- pw.println("<select name='engine'>");;
+ pw.println("<select name='engine' id='selectEngine'>");;
for(ScriptEngineFactory f : FACTORIES) {
String opt = f.getNames().get(0);
pw.println("<option "
@@ -287,7 +296,7 @@ protected void writeHtml(Writer writer) {
+"value='"+opt+"'>"+f.getLanguageName()+"</option>");
}
pw.println("</select>");
- pw.println("<textarea rows='20' style='width:100%' name=\'script\'>"+script+"</textarea>");
+ pw.println("<textarea rows='20' style='width:100%' name=\'script\' id='editor'>"+script+"</textarea>");
pw.println("<input type='submit' value='execute'></input>");
pw.println("</form>");
pw.println(
@@ -300,6 +309,17 @@ protected void writeHtml(Writer writer) {
"<li><code>scriptResource</code>: the ScriptResource implementing this " +
"page, which offers utility methods</li>\n" +
"</ul>");
+ pw.println("<script>");
+ pw.println("var modemap = {beanshell: 'text/x-java', groovy: 'groovy', js: 'javascript'};");
+ pw.println("var selectEngine = document.getElementById('selectEngine');");
+ pw.println("var editor = document.getElementById('editor');");
+ pw.println("var cmopts = {");
+ pw.println(" mode: modemap[selectEngine.value],");
+ pw.println(" lineNumbers: true, autofocus: true, indentUnit: 4");
+ pw.println("}");
+ pw.println("var cm = CodeMirror.fromTextArea(editor, cmopts);");
+ pw.println("selectEngine.onchange = function(e) { cm.setOption('mode', modemap[selectEngine.value]); }");
+ pw.println("</script>");
pw.println("</body>");
pw.println("</html>");
View
23 engine/src/main/resources/org/archive/crawler/restlet/codemirror/LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2011 by Marijn Haverbeke <marijnh@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Please note that some subdirectories of the CodeMirror distribution
+include their own LICENSE files, and are released under different
+licences.
View
9 engine/src/main/resources/org/archive/crawler/restlet/codemirror/README
@@ -0,0 +1,9 @@
+CodeMirror: an in-browser code editor
+
+Provides a nicer editing experience for the crawl configuration and script
+console.
+
+We only include here the modes that Heritrix actually uses.
+
+License: MIT-style
+Website: http://codemirror.net/
View
112 engine/src/main/resources/org/archive/crawler/restlet/codemirror/codemirror.css
@@ -0,0 +1,112 @@
+.CodeMirror {
+ line-height: 1em;
+ font-family: monospace;
+}
+
+.CodeMirror-scroll {
+ overflow: auto;
+ height: 300px;
+ /* This is needed to prevent an IE[67] bug where the scrolled content
+ is visible outside of the scrolling box. */
+ position: relative;
+ outline: none;
+}
+
+.CodeMirror-gutter {
+ position: absolute; left: 0; top: 0;
+ z-index: 10;
+ background-color: #f7f7f7;
+ border-right: 1px solid #eee;
+ min-width: 2em;
+ height: 100%;
+}
+.CodeMirror-gutter-text {
+ color: #aaa;
+ text-align: right;
+ padding: .4em .2em .4em .4em;
+ white-space: pre !important;
+}
+.CodeMirror-lines {
+ padding: .4em;
+ white-space: pre;
+}
+
+.CodeMirror pre {
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ -o-border-radius: 0;
+ border-radius: 0;
+ border-width: 0; margin: 0; padding: 0; background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ padding: 0; margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+}
+
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+}
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror textarea {
+ outline: none !important;
+}
+
+.CodeMirror pre.CodeMirror-cursor {
+ z-index: 10;
+ position: absolute;
+ visibility: hidden;
+ border-left: 1px solid black;
+ border-right:none;
+ width:0;
+}
+.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
+.CodeMirror-focused pre.CodeMirror-cursor {
+ visibility: visible;
+}
+
+div.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
+
+.CodeMirror-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* Default theme */
+
+.cm-s-default span.cm-keyword {color: #708;}
+.cm-s-default span.cm-atom {color: #219;}
+.cm-s-default span.cm-number {color: #164;}
+.cm-s-default span.cm-def {color: #00f;}
+.cm-s-default span.cm-variable {color: black;}
+.cm-s-default span.cm-variable-2 {color: #05a;}
+.cm-s-default span.cm-variable-3 {color: #085;}
+.cm-s-default span.cm-property {color: black;}
+.cm-s-default span.cm-operator {color: black;}
+.cm-s-default span.cm-comment {color: #a50;}
+.cm-s-default span.cm-string {color: #a11;}
+.cm-s-default span.cm-string-2 {color: #f50;}
+.cm-s-default span.cm-meta {color: #555;}
+.cm-s-default span.cm-error {color: #f00;}
+.cm-s-default span.cm-qualifier {color: #555;}
+.cm-s-default span.cm-builtin {color: #30a;}
+.cm-s-default span.cm-bracket {color: #cc7;}
+.cm-s-default span.cm-tag {color: #170;}
+.cm-s-default span.cm-attribute {color: #00c;}
+.cm-s-default span.cm-header {color: #a0a;}
+.cm-s-default span.cm-quote {color: #090;}
+.cm-s-default span.cm-hr {color: #999;}
+.cm-s-default span.cm-link {color: #00c;}
+
+span.cm-header, span.cm-strong {font-weight: bold;}
+span.cm-em {font-style: italic;}
+span.cm-emstrong {font-style: italic; font-weight: bold;}
+span.cm-link {text-decoration: underline;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
View
2,972 engine/src/main/resources/org/archive/crawler/restlet/codemirror/codemirror.js
2,972 additions, 0 deletions not shown
View
234 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/clike.js
@@ -0,0 +1,234 @@
+CodeMirror.defineMode("clike", function(config, parserConfig) {
+ var indentUnit = config.indentUnit,
+ keywords = parserConfig.keywords || {},
+ blockKeywords = parserConfig.blockKeywords || {},
+ atoms = parserConfig.atoms || {},
+ hooks = parserConfig.hooks || {},
+ multiLineStrings = parserConfig.multiLineStrings;
+ var isOperatorChar = /[+\-*&%=<>!?|\/]/;
+
+ var curPunc;
+
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+ if (hooks[ch]) {
+ var result = hooks[ch](stream, state);
+ if (result !== false) return result;
+ }
+ if (ch == '"' || ch == "'") {
+ state.tokenize = tokenString(ch);
+ return state.tokenize(stream, state);
+ }
+ if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+ curPunc = ch;
+ return null
+ }
+ if (/\d/.test(ch)) {
+ stream.eatWhile(/[\w\.]/);
+ return "number";
+ }
+ if (ch == "/") {
+ if (stream.eat("*")) {
+ state.tokenize = tokenComment;
+ return tokenComment(stream, state);
+ }
+ if (stream.eat("/")) {
+ stream.skipToEnd();
+ return "comment";
+ }
+ }
+ if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return "operator";
+ }
+ stream.eatWhile(/[\w\$_]/);
+ var cur = stream.current();
+ if (keywords.propertyIsEnumerable(cur)) {
+ if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ return "keyword";
+ }
+ if (atoms.propertyIsEnumerable(cur)) return "atom";
+ return "word";
+ }
+
+ function tokenString(quote) {
+ return function(stream, state) {
+ var escaped = false, next, end = false;
+ while ((next = stream.next()) != null) {
+ if (next == quote && !escaped) {end = true; break;}
+ escaped = !escaped && next == "\\";
+ }
+ if (end || !(escaped || multiLineStrings))
+ state.tokenize = null;
+ return "string";
+ };
+ }
+
+ function tokenComment(stream, state) {
+ var maybeEnd = false, ch;
+ while (ch = stream.next()) {
+ if (ch == "/" && maybeEnd) {
+ state.tokenize = null;
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return "comment";
+ }
+
+ function Context(indented, column, type, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.align = align;
+ this.prev = prev;
+ }
+ function pushContext(state, col, type) {
+ return state.context = new Context(state.indented, col, type, null, state.context);
+ }
+ function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+ }
+
+ // Interface
+
+ return {
+ startState: function(basecolumn) {
+ return {
+ tokenize: null,
+ context: new Context((basecolumn || 0) - indentUnit, 0, "top", false),
+ indented: 0,
+ startOfLine: true
+ };
+ },
+
+ token: function(stream, state) {
+ var ctx = state.context;
+ if (stream.sol()) {
+ if (ctx.align == null) ctx.align = false;
+ state.indented = stream.indentation();
+ state.startOfLine = true;
+ }
+ if (stream.eatSpace()) return null;
+ curPunc = null;
+ var style = (state.tokenize || tokenBase)(stream, state);
+ if (style == "comment" || style == "meta") return style;
+ if (ctx.align == null) ctx.align = true;
+
+ if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
+ else if (curPunc == "{") pushContext(state, stream.column(), "}");
+ else if (curPunc == "[") pushContext(state, stream.column(), "]");
+ else if (curPunc == "(") pushContext(state, stream.column(), ")");
+ else if (curPunc == "}") {
+ while (ctx.type == "statement") ctx = popContext(state);
+ if (ctx.type == "}") ctx = popContext(state);
+ while (ctx.type == "statement") ctx = popContext(state);
+ }
+ else if (curPunc == ctx.type) popContext(state);
+ else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
+ pushContext(state, stream.column(), "statement");
+ state.startOfLine = false;
+ return style;
+ },
+
+ indent: function(state, textAfter) {
+ if (state.tokenize != tokenBase && state.tokenize != null) return 0;
+ var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
+ if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
+ var closing = firstChar == ctx.type;
+ if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : indentUnit);
+ else if (ctx.align) return ctx.column + (closing ? 0 : 1);
+ else return ctx.indented + (closing ? 0 : indentUnit);
+ },
+
+ electricChars: "{}"
+ };
+});
+
+(function() {
+ function words(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+ var cKeywords = "auto if break int case long char register continue return default short do sizeof " +
+ "double static else struct entry switch extern typedef float union for unsigned " +
+ "goto while enum void const signed volatile";
+
+ function cppHook(stream, state) {
+ if (!state.startOfLine) return false;
+ stream.skipToEnd();
+ return "meta";
+ }
+
+ // C#-style strings where "" escapes a quote.
+ function tokenAtString(stream, state) {
+ var next;
+ while ((next = stream.next()) != null) {
+ if (next == '"' && !stream.eat('"')) {
+ state.tokenize = null;
+ break;
+ }
+ }
+ return "string";
+ }
+
+ CodeMirror.defineMIME("text/x-csrc", {
+ name: "clike",
+ keywords: words(cKeywords),
+ blockKeywords: words("case do else for if switch while struct"),
+ atoms: words("null"),
+ hooks: {"#": cppHook}
+ });
+ CodeMirror.defineMIME("text/x-c++src", {
+ name: "clike",
+ keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " +
+ "static_cast typeid catch operator template typename class friend private " +
+ "this using const_cast inline public throw virtual delete mutable protected " +
+ "wchar_t"),
+ blockKeywords: words("catch class do else finally for if struct switch try while"),
+ atoms: words("true false null"),
+ hooks: {"#": cppHook}
+ });
+ CodeMirror.defineMIME("text/x-java", {
+ name: "clike",
+ keywords: words("abstract assert boolean break byte case catch char class const continue default " +
+ "do double else enum extends final finally float for goto if implements import " +
+ "instanceof int interface long native new package private protected public " +
+ "return short static strictfp super switch synchronized this throw throws transient " +
+ "try void volatile while"),
+ blockKeywords: words("catch class do else finally for if switch try while"),
+ atoms: words("true false null"),
+ hooks: {
+ "@": function(stream, state) {
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ }
+ }
+ });
+ CodeMirror.defineMIME("text/x-csharp", {
+ name: "clike",
+ keywords: words("abstract as base bool break byte case catch char checked class const continue decimal" +
+ " default delegate do double else enum event explicit extern finally fixed float for" +
+ " foreach goto if implicit in int interface internal is lock long namespace new object" +
+ " operator out override params private protected public readonly ref return sbyte sealed short" +
+ " sizeof stackalloc static string struct switch this throw try typeof uint ulong unchecked" +
+ " unsafe ushort using virtual void volatile while add alias ascending descending dynamic from get" +
+ " global group into join let orderby partial remove select set value var yield"),
+ blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
+ atoms: words("true false null"),
+ hooks: {
+ "@": function(stream, state) {
+ if (stream.eat('"')) {
+ state.tokenize = tokenAtString;
+ return tokenAtString(stream, state);
+ }
+ stream.eatWhile(/[\w\$_]/);
+ return "meta";
+ }
+ }
+ });
+}());
View
210 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/groovy.js
@@ -0,0 +1,210 @@
+CodeMirror.defineMode("groovy", function(config, parserConfig) {
+ function words(str) {
+ var obj = {}, words = str.split(" ");
+ for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
+ return obj;
+ }
+ var keywords = words(
+ "abstract as assert boolean break byte case catch char class const continue def default " +
+ "do double else enum extends final finally float for goto if implements import in " +
+ "instanceof int interface long native new package private protected public return " +
+ "short static strictfp super switch synchronized threadsafe throw throws transient " +
+ "try void volatile while");
+ var blockKeywords = words("catch class do else finally for if switch try while enum interface def");
+ var atoms = words("null true false this");
+
+ var curPunc;
+ function tokenBase(stream, state) {
+ var ch = stream.next();
+ if (ch == '"' || ch == "'") {
+ return startString(ch, stream, state);
+ }
+ if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
+ curPunc = ch;
+ return null
+ }
+ if (/\d/.test(ch)) {
+ stream.eatWhile(/[\w\.]/);
+ if (stream.eat(/eE/)) { stream.eat(/\+\-/); stream.eatWhile(/\d/); }
+ return "number";
+ }
+ if (ch == "/") {
+ if (stream.eat("*")) {
+ state.tokenize.push(tokenComment);
+ return tokenComment(stream, state);
+ }
+ if (stream.eat("/")) {
+ stream.skipToEnd();
+ return "comment";
+ }
+ if (expectExpression(state.lastToken)) {
+ return startString(ch, stream, state);
+ }
+ }
+ if (ch == "-" && stream.eat(">")) {
+ curPunc = "->";
+ return null;
+ }
+ if (/[+\-*&%=<>!?|\/~]/.test(ch)) {
+ stream.eatWhile(/[+\-*&%=<>|~]/);
+ return "operator";
+ }
+ stream.eatWhile(/[\w\$_]/);
+ if (ch == "@") { stream.eatWhile(/[\w\$_\.]/); return "meta"; }
+ if (state.lastToken == ".") return "property";
+ if (stream.eat(":")) { curPunc = "proplabel"; return "property"; }
+ var cur = stream.current();
+ if (atoms.propertyIsEnumerable(cur)) { return "atom"; }
+ if (keywords.propertyIsEnumerable(cur)) {
+ if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement";
+ return "keyword";
+ }
+ return "word";
+ }
+ tokenBase.isBase = true;
+
+ function startString(quote, stream, state) {
+ var tripleQuoted = false;
+ if (quote != "/" && stream.eat(quote)) {
+ if (stream.eat(quote)) tripleQuoted = true;
+ else return "string";
+ }
+ function t(stream, state) {
+ var escaped = false, next, end = !tripleQuoted;
+ while ((next = stream.next()) != null) {
+ if (next == quote && !escaped) {
+ if (!tripleQuoted) { break; }
+ if (stream.match(quote + quote)) { end = true; break; }
+ }
+ if (quote == '"' && next == "$" && !escaped && stream.eat("{")) {
+ state.tokenize.push(tokenBaseUntilBrace());
+ return "string";
+ }
+ escaped = !escaped && next == "\\";
+ }
+ if (end) state.tokenize.pop();
+ return "string";
+ }
+ state.tokenize.push(t);
+ return t(stream, state);
+ }
+
+ function tokenBaseUntilBrace() {
+ var depth = 1;
+ function t(stream, state) {
+ if (stream.peek() == "}") {
+ depth--;
+ if (depth == 0) {
+ state.tokenize.pop();
+ return state.tokenize[state.tokenize.length-1](stream, state);
+ }
+ } else if (stream.peek() == "{") {
+ depth++;
+ }
+ return tokenBase(stream, state);
+ }
+ t.isBase = true;
+ return t;
+ }
+
+ function tokenComment(stream, state) {
+ var maybeEnd = false, ch;
+ while (ch = stream.next()) {
+ if (ch == "/" && maybeEnd) {
+ state.tokenize.pop();
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return "comment";
+ }
+
+ function expectExpression(last) {
+ return !last || last == "operator" || last == "->" || /[\.\[\{\(,;:]/.test(last) ||
+ last == "newstatement" || last == "keyword" || last == "proplabel";
+ }
+
+ function Context(indented, column, type, align, prev) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.align = align;
+ this.prev = prev;
+ }
+ function pushContext(state, col, type) {
+ return state.context = new Context(state.indented, col, type, null, state.context);
+ }
+ function popContext(state) {
+ var t = state.context.type;
+ if (t == ")" || t == "]" || t == "}")
+ state.indented = state.context.indented;
+ return state.context = state.context.prev;
+ }
+
+ // Interface
+
+ return {
+ startState: function(basecolumn) {
+ return {
+ tokenize: [tokenBase],
+ context: new Context((basecolumn || 0) - config.indentUnit, 0, "top", false),
+ indented: 0,
+ startOfLine: true,
+ lastToken: null
+ };
+ },
+
+ token: function(stream, state) {
+ var ctx = state.context;
+ if (stream.sol()) {
+ if (ctx.align == null) ctx.align = false;
+ state.indented = stream.indentation();
+ state.startOfLine = true;
+ // Automatic semicolon insertion
+ if (ctx.type == "statement" && !expectExpression(state.lastToken)) {
+ popContext(state); ctx = state.context;
+ }
+ }
+ if (stream.eatSpace()) return null;
+ curPunc = null;
+ var style = state.tokenize[state.tokenize.length-1](stream, state);
+ if (style == "comment") return style;
+ if (ctx.align == null) ctx.align = true;
+
+ if ((curPunc == ";" || curPunc == ":") && ctx.type == "statement") popContext(state);
+ // Handle indentation for {x -> \n ... }
+ else if (curPunc == "->" && ctx.type == "statement" && ctx.prev.type == "}") {
+ popContext(state);
+ state.context.align = false;
+ }
+ else if (curPunc == "{") pushContext(state, stream.column(), "}");
+ else if (curPunc == "[") pushContext(state, stream.column(), "]");
+ else if (curPunc == "(") pushContext(state, stream.column(), ")");
+ else if (curPunc == "}") {
+ while (ctx.type == "statement") ctx = popContext(state);
+ if (ctx.type == "}") ctx = popContext(state);
+ while (ctx.type == "statement") ctx = popContext(state);
+ }
+ else if (curPunc == ctx.type) popContext(state);
+ else if (ctx.type == "}" || ctx.type == "top" || (ctx.type == "statement" && curPunc == "newstatement"))
+ pushContext(state, stream.column(), "statement");
+ state.startOfLine = false;
+ state.lastToken = curPunc || style;
+ return style;
+ },
+
+ indent: function(state, textAfter) {
+ if (!state.tokenize[state.tokenize.length-1].isBase) return 0;
+ var firstChar = textAfter && textAfter.charAt(0), ctx = state.context;
+ if (ctx.type == "statement" && !expectExpression(state.lastToken)) ctx = ctx.prev;
+ var closing = firstChar == ctx.type;
+ if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : config.indentUnit);
+ else if (ctx.align) return ctx.column + (closing ? 0 : 1);
+ else return ctx.indented + (closing ? 0 : config.indentUnit);
+ },
+
+ electricChars: "{}"
+ };
+});
+
+CodeMirror.defineMIME("text/x-groovy", "groovy");
View
360 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/javascript.js
@@ -0,0 +1,360 @@
+CodeMirror.defineMode("javascript", function(config, parserConfig) {
+ var indentUnit = config.indentUnit;
+ var jsonMode = parserConfig.json;
+
+ // Tokenizer
+
+ var keywords = function(){
+ function kw(type) {return {type: type, style: "keyword"};}
+ var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+ var operator = kw("operator"), atom = {type: "atom", style: "atom"};
+ return {
+ "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
+ "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
+ "var": kw("var"), "const": kw("var"), "let": kw("var"),
+ "function": kw("function"), "catch": kw("catch"),
+ "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
+ "in": operator, "typeof": operator, "instanceof": operator,
+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+ };
+ }();
+
+ var isOperatorChar = /[+\-*&%=<>!?|]/;
+
+ function chain(stream, state, f) {
+ state.tokenize = f;
+ return f(stream, state);
+ }
+
+ function nextUntilUnescaped(stream, end) {
+ var escaped = false, next;
+ while ((next = stream.next()) != null) {
+ if (next == end && !escaped)
+ return false;
+ escaped = !escaped && next == "\\";
+ }
+ return escaped;
+ }
+
+ // Used as scratch variables to communicate multiple values without
+ // consing up tons of objects.
+ var type, content;
+ function ret(tp, style, cont) {
+ type = tp; content = cont;
+ return style;
+ }
+
+ function jsTokenBase(stream, state) {
+ var ch = stream.next();
+ if (ch == '"' || ch == "'")
+ return chain(stream, state, jsTokenString(ch));
+ else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+ return ret(ch);
+ else if (ch == "0" && stream.eat(/x/i)) {
+ stream.eatWhile(/[\da-f]/i);
+ return ret("number", "number");
+ }
+ else if (/\d/.test(ch)) {
+ stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
+ return ret("number", "number");
+ }
+ else if (ch == "/") {
+ if (stream.eat("*")) {
+ return chain(stream, state, jsTokenComment);
+ }
+ else if (stream.eat("/")) {
+ stream.skipToEnd();
+ return ret("comment", "comment");
+ }
+ else if (state.reAllowed) {
+ nextUntilUnescaped(stream, "/");
+ stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
+ return ret("regexp", "string-2");
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", null, stream.current());
+ }
+ }
+ else if (ch == "#") {
+ stream.skipToEnd();
+ return ret("error", "error");
+ }
+ else if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", null, stream.current());
+ }
+ else {
+ stream.eatWhile(/[\w\$_]/);
+ var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
+ return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
+ ret("variable", "variable", word);
+ }
+ }
+
+ function jsTokenString(quote) {
+ return function(stream, state) {
+ if (!nextUntilUnescaped(stream, quote))
+ state.tokenize = jsTokenBase;
+ return ret("string", "string");
+ };
+ }
+
+ function jsTokenComment(stream, state) {
+ var maybeEnd = false, ch;
+ while (ch = stream.next()) {
+ if (ch == "/" && maybeEnd) {
+ state.tokenize = jsTokenBase;
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return ret("comment", "comment");
+ }
+
+ // Parser
+
+ var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
+
+ function JSLexical(indented, column, type, align, prev, info) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.prev = prev;
+ this.info = info;
+ if (align != null) this.align = align;
+ }
+
+ function inScope(state, varname) {
+ for (var v = state.localVars; v; v = v.next)
+ if (v.name == varname) return true;
+ }
+
+ function parseJS(state, style, type, content, stream) {
+ var cc = state.cc;
+ // Communicate our context to the combinators.
+ // (Less wasteful than consing up a hundred closures on every call.)
+ cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
+
+ if (!state.lexical.hasOwnProperty("align"))
+ state.lexical.align = true;
+
+ while(true) {
+ var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
+ if (combinator(type, content)) {
+ while(cc.length && cc[cc.length - 1].lex)
+ cc.pop()();
+ if (cx.marked) return cx.marked;
+ if (type == "variable" && inScope(state, content)) return "variable-2";
+ return style;
+ }
+ }
+ }
+
+ // Combinator utils
+
+ var cx = {state: null, column: null, marked: null, cc: null};
+ function pass() {
+ for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
+ }
+ function cont() {
+ pass.apply(null, arguments);
+ return true;
+ }
+ function register(varname) {
+ var state = cx.state;
+ if (state.context) {
+ cx.marked = "def";
+ for (var v = state.localVars; v; v = v.next)
+ if (v.name == varname) return;
+ state.localVars = {name: varname, next: state.localVars};
+ }
+ }
+
+ // Combinators
+
+ var defaultVars = {name: "this", next: {name: "arguments"}};
+ function pushcontext() {
+ if (!cx.state.context) cx.state.localVars = defaultVars;
+ cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
+ }
+ function popcontext() {
+ cx.state.localVars = cx.state.context.vars;
+ cx.state.context = cx.state.context.prev;
+ }
+ function pushlex(type, info) {
+ var result = function() {
+ var state = cx.state;
+ state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
+ };
+ result.lex = true;
+ return result;
+ }
+ function poplex() {
+ var state = cx.state;
+ if (state.lexical.prev) {
+ if (state.lexical.type == ")")
+ state.indented = state.lexical.indented;
+ state.lexical = state.lexical.prev;
+ }
+ }
+ poplex.lex = true;
+
+ function expect(wanted) {
+ return function expecting(type) {
+ if (type == wanted) return cont();
+ else if (wanted == ";") return pass();
+ else return cont(arguments.callee);
+ };
+ }
+
+ function statement(type) {
+ if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+ if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
+ if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+ if (type == "{") return cont(pushlex("}"), block, poplex);
+ if (type == ";") return cont();
+ if (type == "function") return cont(functiondef);
+ if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
+ poplex, statement, poplex);
+ if (type == "variable") return cont(pushlex("stat"), maybelabel);
+ if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
+ block, poplex, poplex);
+ if (type == "case") return cont(expression, expect(":"));
+ if (type == "default") return cont(expect(":"));
+ if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
+ statement, poplex, popcontext);
+ return pass(pushlex("stat"), expression, expect(";"), poplex);
+ }
+ function expression(type) {
+ if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
+ if (type == "function") return cont(functiondef);
+ if (type == "keyword c") return cont(maybeexpression);
+ if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
+ if (type == "operator") return cont(expression);
+ if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
+ if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
+ return cont();
+ }
+ function maybeexpression(type) {
+ if (type.match(/[;\}\)\],]/)) return pass();
+ return pass(expression);
+ }
+
+ function maybeoperator(type, value) {
+ if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
+ if (type == "operator") return cont(expression);
+ if (type == ";") return;
+ if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
+ if (type == ".") return cont(property, maybeoperator);
+ if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
+ }
+ function maybelabel(type) {
+ if (type == ":") return cont(poplex, statement);
+ return pass(maybeoperator, expect(";"), poplex);
+ }
+ function property(type) {
+ if (type == "variable") {cx.marked = "property"; return cont();}
+ }
+ function objprop(type) {
+ if (type == "variable") cx.marked = "property";
+ if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
+ }
+ function commasep(what, end) {
+ function proceed(type) {
+ if (type == ",") return cont(what, proceed);
+ if (type == end) return cont();
+ return cont(expect(end));
+ }
+ return function commaSeparated(type) {
+ if (type == end) return cont();
+ else return pass(what, proceed);
+ };
+ }
+ function block(type) {
+ if (type == "}") return cont();
+ return pass(statement, block);
+ }
+ function vardef1(type, value) {
+ if (type == "variable"){register(value); return cont(vardef2);}
+ return cont();
+ }
+ function vardef2(type, value) {
+ if (value == "=") return cont(expression, vardef2);
+ if (type == ",") return cont(vardef1);
+ }
+ function forspec1(type) {
+ if (type == "var") return cont(vardef1, forspec2);
+ if (type == ";") return pass(forspec2);
+ if (type == "variable") return cont(formaybein);
+ return pass(forspec2);
+ }
+ function formaybein(type, value) {
+ if (value == "in") return cont(expression);
+ return cont(maybeoperator, forspec2);
+ }
+ function forspec2(type, value) {
+ if (type == ";") return cont(forspec3);
+ if (value == "in") return cont(expression);
+ return cont(expression, expect(";"), forspec3);
+ }
+ function forspec3(type) {
+ if (type != ")") cont(expression);
+ }
+ function functiondef(type, value) {
+ if (type == "variable") {register(value); return cont(functiondef);}
+ if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
+ }
+ function funarg(type, value) {
+ if (type == "variable") {register(value); return cont();}
+ }
+
+ // Interface
+
+ return {
+ startState: function(basecolumn) {
+ return {
+ tokenize: jsTokenBase,
+ reAllowed: true,
+ kwAllowed: true,
+ cc: [],
+ lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
+ localVars: parserConfig.localVars,
+ context: parserConfig.localVars && {vars: parserConfig.localVars},
+ indented: 0
+ };
+ },
+
+ token: function(stream, state) {
+ if (stream.sol()) {
+ if (!state.lexical.hasOwnProperty("align"))
+ state.lexical.align = false;
+ state.indented = stream.indentation();
+ }
+ if (stream.eatSpace()) return null;
+ var style = state.tokenize(stream, state);
+ if (type == "comment") return style;
+ state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
+ state.kwAllowed = type != '.';
+ return parseJS(state, style, type, content, stream);
+ },
+
+ indent: function(state, textAfter) {
+ if (state.tokenize != jsTokenBase) return 0;
+ var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,
+ type = lexical.type, closing = firstChar == type;
+ if (type == "vardef") return lexical.indented + 4;
+ else if (type == "form" && firstChar == "{") return lexical.indented;
+ else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
+ else if (lexical.info == "switch" && !closing)
+ return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
+ else if (lexical.align) return lexical.column + (closing ? 0 : 1);
+ else return lexical.indented + (closing ? 0 : indentUnit);
+ },
+
+ electricChars: ":{}"
+ };
+});
+
+CodeMirror.defineMIME("text/javascript", "javascript");
+CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
View
490 engine/src/main/resources/org/archive/crawler/restlet/codemirror/mode/xmlpure.js
@@ -0,0 +1,490 @@
+/**
+ * xmlpure.js
+ *
+ * Building upon and improving the CodeMirror 2 XML parser
+ * @author: Dror BG (deebug.dev@gmail.com)
+ * @date: August, 2011
+ */
+
+CodeMirror.defineMode("xmlpure", function(config, parserConfig) {
+ // constants
+ var STYLE_ERROR = "error";
+ var STYLE_INSTRUCTION = "comment";
+ var STYLE_COMMENT = "comment";
+ var STYLE_ELEMENT_NAME = "tag";
+ var STYLE_ATTRIBUTE = "attribute";
+ var STYLE_WORD = "string";
+ var STYLE_TEXT = "atom";
+ var STYLE_ENTITIES = "string";
+
+ var TAG_INSTRUCTION = "!instruction";
+ var TAG_CDATA = "!cdata";
+ var TAG_COMMENT = "!comment";
+ var TAG_TEXT = "!text";
+
+ var doNotIndent = {
+ "!cdata": true,
+ "!comment": true,
+ "!text": true,
+ "!instruction": true
+ };
+
+ // options
+ var indentUnit = config.indentUnit;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // helper functions
+
+ // chain a parser to another parser
+ function chain(stream, state, parser) {
+ state.tokenize = parser;
+ return parser(stream, state);
+ }
+
+ // parse a block (comment, CDATA or text)
+ function inBlock(style, terminator, nextTokenize) {
+ return function(stream, state) {
+ while (!stream.eol()) {
+ if (stream.match(terminator)) {
+ popContext(state);
+ state.tokenize = nextTokenize;
+ break;
+ }
+ stream.next();
+ }
+ return style;
+ };
+ }
+
+ // go down a level in the document
+ // (hint: look at who calls this function to know what the contexts are)
+ function pushContext(state, tagName) {
+ var noIndent = doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.doIndent);
+ var newContext = {
+ tagName: tagName,
+ prev: state.context,
+ indent: state.context ? state.context.indent + indentUnit : 0,
+ lineNumber: state.lineNumber,
+ indented: state.indented,
+ noIndent: noIndent
+ };
+ state.context = newContext;
+ }
+
+ // go up a level in the document
+ function popContext(state) {
+ if (state.context) {
+ var oldContext = state.context;
+ state.context = oldContext.prev;
+ return oldContext;
+ }
+
+ // we shouldn't be here - it means we didn't have a context to pop
+ return null;
+ }
+
+ // return true if the current token is seperated from the tokens before it
+ // which means either this is the start of the line, or there is at least
+ // one space or tab character behind the token
+ // otherwise returns false
+ function isTokenSeparated(stream) {
+ return stream.sol() ||
+ stream.string.charAt(stream.start - 1) == " " ||
+ stream.string.charAt(stream.start - 1) == "\t";
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: document
+ //
+ // an XML document can contain:
+ // - a single declaration (if defined, it must be the very first line)
+ // - exactly one root element
+ // @todo try to actually limit the number of root elements to 1
+ // - zero or more comments
+ function parseDocument(stream, state) {
+ if(stream.eat("<")) {
+ if(stream.eat("?")) {
+ // processing instruction
+ pushContext(state, TAG_INSTRUCTION);
+ state.tokenize = parseProcessingInstructionStartTag;
+ return STYLE_INSTRUCTION;
+ } else if(stream.match("!--")) {
+ // new context: comment
+ pushContext(state, TAG_COMMENT);
+ return chain(stream, state, inBlock(STYLE_COMMENT, "-->", parseDocument));
+ } else if(stream.eatSpace() || stream.eol() ) {
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ } else {
+ // element
+ state.tokenize = parseElementTagName;
+ return STYLE_ELEMENT_NAME;
+ }
+ }
+
+ // error on line
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: XML element start-tag or end-tag
+ //
+ // - element start-tag can contain attributes
+ // - element start-tag may self-close (or start an element block if it doesn't)
+ // - element end-tag can contain only the tag name
+ function parseElementTagName(stream, state) {
+ // get the name of the tag
+ var startPos = stream.pos;
+ if(stream.match(/^[a-zA-Z_:][-a-zA-Z0-9_:.]*/)) {
+ // element start-tag
+ var tagName = stream.string.substring(startPos, stream.pos);
+ pushContext(state, tagName);
+ state.tokenize = parseElement;
+ return STYLE_ELEMENT_NAME;
+ } else if(stream.match(/^\/[a-zA-Z_:][-a-zA-Z0-9_:.]*( )*>/)) {
+ // element end-tag
+ var endTagName = stream.string.substring(startPos + 1, stream.pos - 1).trim();
+ var oldContext = popContext(state);
+ state.tokenize = state.context == null ? parseDocument : parseElementBlock;
+ if(oldContext == null || endTagName != oldContext.tagName) {
+ // the start and end tag names should match - error
+ return STYLE_ERROR;
+ }
+ return STYLE_ELEMENT_NAME;
+ } else {
+ // no tag name - error
+ state.tokenize = state.context == null ? parseDocument : parseElementBlock;
+ stream.eatWhile(/[^>]/);
+ stream.eat(">");
+ return STYLE_ERROR;
+ }
+
+ stream.skipToEnd();
+ return null;
+ }
+
+ function parseElement(stream, state) {
+ if(stream.match(/^\/>/)) {
+ // self-closing tag
+ popContext(state);
+ state.tokenize = state.context == null ? parseDocument : parseElementBlock;
+ return STYLE_ELEMENT_NAME;
+ } else if(stream.eat(/^>/)) {
+ state.tokenize = parseElementBlock;
+ return STYLE_ELEMENT_NAME;
+ } else if(isTokenSeparated(stream) && stream.match(/^[a-zA-Z_:][-a-zA-Z0-9_:.]*( )*=/)) {
+ // attribute
+ state.tokenize = parseAttribute;
+ return STYLE_ATTRIBUTE;
+ }
+
+ // no other options - this is an error
+ state.tokenize = state.context == null ? parseDocument : parseDocument;
+ stream.eatWhile(/[^>]/);
+ stream.eat(">");
+ return STYLE_ERROR;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: attribute
+ //
+ // attribute values may contain everything, except:
+ // - the ending quote (with ' or ") - this marks the end of the value
+ // - the character "<" - should never appear
+ // - ampersand ("&") - unless it starts a reference: a string that ends with a semi-colon (";")
+ // ---> note: this parser is lax in what may be put into a reference string,
+ // ---> consult http://www.w3.org/TR/REC-xml/#NT-Reference if you want to make it tighter
+ function parseAttribute(stream, state) {
+ var quote = stream.next();
+ if(quote != "\"" && quote != "'") {
+ // attribute must be quoted
+ stream.skipToEnd();
+ state.tokenize = parseElement;
+ return STYLE_ERROR;
+ }
+
+ state.tokParams.quote = quote;
+ state.tokenize = parseAttributeValue;
+ return STYLE_WORD;
+ }
+
+ // @todo: find out whether this attribute value spans multiple lines,
+ // and if so, push a context for it in order not to indent it
+ // (or something of the sort..)
+ function parseAttributeValue(stream, state) {
+ var ch = "";
+ while(!stream.eol()) {
+ ch = stream.next();
+ if(ch == state.tokParams.quote) {
+ // end quote found
+ state.tokenize = parseElement;
+ return STYLE_WORD;
+ } else if(ch == "<") {
+ // can't have less-than signs in an attribute value, ever
+ stream.skipToEnd()
+ state.tokenize = parseElement;
+ return STYLE_ERROR;
+ } else if(ch == "&") {
+ // reference - look for a semi-colon, or return error if none found
+ ch = stream.next();
+
+ // make sure that semi-colon isn't right after the ampersand
+ if(ch == ';') {
+ stream.skipToEnd()
+ state.tokenize = parseElement;
+ return STYLE_ERROR;
+ }
+
+ // make sure no less-than characters slipped in
+ while(!stream.eol() && ch != ";") {
+ if(ch == "<") {
+ // can't have less-than signs in an attribute value, ever
+ stream.skipToEnd()
+ state.tokenize = parseElement;
+ return STYLE_ERROR;
+ }
+ ch = stream.next();
+ }
+ if(stream.eol() && ch != ";") {
+ // no ampersand found - error
+ stream.skipToEnd();
+ state.tokenize = parseElement;
+ return STYLE_ERROR;
+ }
+ }
+ }
+
+ // attribute value continues to next line
+ return STYLE_WORD;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: element block
+ //
+ // a block can contain:
+ // - elements
+ // - text
+ // - CDATA sections
+ // - comments
+ function parseElementBlock(stream, state) {
+ if(stream.eat("<")) {
+ if(stream.match("?")) {
+ pushContext(state, TAG_INSTRUCTION);
+ state.tokenize = parseProcessingInstructionStartTag;
+ return STYLE_INSTRUCTION;
+ } else if(stream.match("!--")) {
+ // new context: comment
+ pushContext(state, TAG_COMMENT);
+ return chain(stream, state, inBlock(STYLE_COMMENT, "-->",
+ state.context == null ? parseDocument : parseElementBlock));
+ } else if(stream.match("![CDATA[")) {
+ // new context: CDATA section
+ pushContext(state, TAG_CDATA);
+ return chain(stream, state, inBlock(STYLE_TEXT, "]]>",
+ state.context == null ? parseDocument : parseElementBlock));
+ } else if(stream.eatSpace() || stream.eol() ) {
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ } else {
+ // element
+ state.tokenize = parseElementTagName;
+ return STYLE_ELEMENT_NAME;
+ }
+ } else if(stream.eat("&")) {
+ stream.eatWhile(/[^;]/);
+ stream.eat(";");
+ return STYLE_ENTITIES;
+ } else {
+ // new context: text
+ pushContext(state, TAG_TEXT);
+ state.tokenize = parseText;
+ return null;
+ }
+
+ state.tokenize = state.context == null ? parseDocument : parseElementBlock;
+ stream.skipToEnd();
+ return null;
+ }
+
+ function parseText(stream, state) {
+ stream.eatWhile(/[^<]/);
+ if(!stream.eol()) {
+ // we cannot possibly be in the document context,
+ // just inside an element block
+ popContext(state);
+ state.tokenize = parseElementBlock;
+ }
+ return STYLE_TEXT;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: XML processing instructions
+ //
+ // XML processing instructions (PIs) allow documents to contain instructions for applications.
+ // PI format: <?name data?>
+ // - 'name' can be anything other than 'xml' (case-insensitive)
+ // - 'data' can be anything which doesn't contain '?>'
+ // XML declaration is a special PI (see XML declaration context below)
+ function parseProcessingInstructionStartTag(stream, state) {
+ if(stream.match("xml", true, true)) {
+ // xml declaration
+ if(state.lineNumber > 1 || stream.pos > 5) {
+ state.tokenize = parseDocument;
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ } else {
+ state.tokenize = parseDeclarationVersion;
+ return STYLE_INSTRUCTION;
+ }
+ }
+
+ // regular processing instruction
+ if(isTokenSeparated(stream) || stream.match("?>")) {
+ // we have a space after the start-tag, or nothing but the end-tag
+ // either way - error!
+ state.tokenize = parseDocument;
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ }
+
+ state.tokenize = parseProcessingInstructionBody;
+ return STYLE_INSTRUCTION;
+ }
+
+ function parseProcessingInstructionBody(stream, state) {
+ stream.eatWhile(/[^?]/);
+ if(stream.eat("?")) {
+ if(stream.eat(">")) {
+ popContext(state);
+ state.tokenize = state.context == null ? parseDocument : parseElementBlock;
+ }
+ }
+ return STYLE_INSTRUCTION;
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // context: XML declaration
+ //
+ // XML declaration is of the following format:
+ // <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+ // - must start at the first character of the first line
+ // - may span multiple lines
+ // - must include 'version'
+ // - may include 'encoding' and 'standalone' (in that order after 'version')
+ // - attribute names must be lowercase
+ // - cannot contain anything else on the line
+ function parseDeclarationVersion(stream, state) {
+ state.tokenize = parseDeclarationEncoding;
+
+ if(isTokenSeparated(stream) && stream.match(/^version( )*=( )*"([a-zA-Z0-9_.:]|\-)+"/)) {
+ return STYLE_INSTRUCTION;
+ }
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ }
+
+ function parseDeclarationEncoding(stream, state) {
+ state.tokenize = parseDeclarationStandalone;
+
+ if(isTokenSeparated(stream) && stream.match(/^encoding( )*=( )*"[A-Za-z]([A-Za-z0-9._]|\-)*"/)) {
+ return STYLE_INSTRUCTION;
+ }
+ return null;
+ }
+
+ function parseDeclarationStandalone(stream, state) {
+ state.tokenize = parseDeclarationEndTag;
+
+ if(isTokenSeparated(stream) && stream.match(/^standalone( )*=( )*"(yes|no)"/)) {
+ return STYLE_INSTRUCTION;
+ }
+ return null;
+ }
+
+ function parseDeclarationEndTag(stream, state) {
+ state.tokenize = parseDocument;
+
+ if(stream.match("?>") && stream.eol()) {
+ popContext(state);
+ return STYLE_INSTRUCTION;
+ }
+ stream.skipToEnd();
+ return STYLE_ERROR;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // returned object
+ return {
+ electricChars: "/[",
+
+ startState: function() {
+ return {
+ tokenize: parseDocument,
+ tokParams: {},
+ lineNumber: 0,
+ lineError: false,
+ context: null,
+ indented: 0
+ };
+ },
+
+ token: function(stream, state) {
+ if(stream.sol()) {
+ // initialize a new line
+ state.lineNumber++;
+ state.lineError = false;
+ state.indented = stream.indentation();
+ }
+
+ // eat all (the spaces) you can
+ if(stream.eatSpace()) return null;
+
+ // run the current tokenize function, according to the state
+ var style = state.tokenize(stream, state);
+
+ // is there an error somewhere in the line?
+ state.lineError = (state.lineError || style == "error");
+
+ return style;
+ },
+
+ blankLine: function(state) {
+ // blank lines are lines too!
+ state.lineNumber++;
+ state.lineError = false;
+ },
+
+ indent: function(state, textAfter) {
+ if(state.context) {
+ if(state.context.noIndent == true) {
+ // do not indent - no return value at all
+ return;
+ }
+ if(textAfter.match(/^<\/.*/)) {
+ // end-tag - indent back to last context
+ return state.context.indent;
+ }
+ if(textAfter.match(/^<!\[CDATA\[/)) {
+ // a stand-alone CDATA start-tag - indent back to column 0
+ return 0;
+ }
+ // indent to last context + regular indent unit
+ return state.context.indent + indentUnit;
+ }
+ return 0;
+ },
+
+ compareStates: function(a, b) {
+ if (a.indented != b.indented) return false;
+ for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
+ if (!ca || !cb) return ca == cb;
+ if (ca.tagName != cb.tagName) return false;
+ }
+ }
+ };
+});
+
+CodeMirror.defineMIME("application/xml", "purexml");
+CodeMirror.defineMIME("text/xml", "purexml");
View
23 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/dialog.css
@@ -0,0 +1,23 @@
+.CodeMirror-dialog {
+ position: relative;
+}
+
+.CodeMirror-dialog > div {
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ background: white;
+ border-bottom: 1px solid #eee;
+ z-index: 15;
+ padding: .1em .8em;
+ overflow: hidden;
+ color: #333;
+}
+
+.CodeMirror-dialog input {
+ border: none;
+ outline: none;
+ background: transparent;
+ width: 20em;
+ color: inherit;
+ font-family: monospace;
+}
View
63 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/dialog.js
@@ -0,0 +1,63 @@
+// Open simple dialogs on top of an editor. Relies on dialog.css.
+
+(function() {
+ function dialogDiv(cm, template) {
+ var wrap = cm.getWrapperElement();
+ var dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
+ dialog.className = "CodeMirror-dialog";
+ dialog.innerHTML = '<div>' + template + '</div>';
+ return dialog;
+ }
+
+ CodeMirror.defineExtension("openDialog", function(template, callback) {
+ var dialog = dialogDiv(this, template);
+ var closed = false, me = this;
+ function close() {
+ if (closed) return;
+ closed = true;
+ dialog.parentNode.removeChild(dialog);
+ }
+ var inp = dialog.getElementsByTagName("input")[0];
+ if (inp) {
+ CodeMirror.connect(inp, "keydown", function(e) {
+ if (e.keyCode == 13 || e.keyCode == 27) {
+ CodeMirror.e_stop(e);
+ close();
+ me.focus();
+ if (e.keyCode == 13) callback(inp.value);
+ }
+ });
+ inp.focus();
+ CodeMirror.connect(inp, "blur", close);
+ }
+ return close;
+ });
+
+ CodeMirror.defineExtension("openConfirm", function(template, callbacks) {
+ var dialog = dialogDiv(this, template);
+ var buttons = dialog.getElementsByTagName("button");
+ var closed = false, me = this, blurring = 1;
+ function close() {
+ if (closed) return;
+ closed = true;
+ dialog.parentNode.removeChild(dialog);
+ me.focus();
+ }
+ buttons[0].focus();
+ for (var i = 0; i < buttons.length; ++i) {
+ var b = buttons[i];
+ (function(callback) {
+ CodeMirror.connect(b, "click", function(e) {
+ CodeMirror.e_preventDefault(e);
+ close();
+ if (callback) callback(me);
+ });
+ })(callbacks[i]);
+ CodeMirror.connect(b, "blur", function() {
+ --blurring;
+ setTimeout(function() { if (blurring <= 0) close(); }, 200);
+ });
+ CodeMirror.connect(b, "focus", function() { ++blurring; });
+ }
+ });
+})();
View
114 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/search.js
@@ -0,0 +1,114 @@
+// Define search commands. Depends on dialog.js or another
+// implementation of the openDialog method.
+
+// Replace works a little oddly -- it will do the replace on the next
+// Ctrl-G (or whatever is bound to findNext) press. You prevent a
+// replace by making sure the match is no longer selected when hitting
+// Ctrl-G.
+
+(function() {
+ function SearchState() {
+ this.posFrom = this.posTo = this.query = null;
+ this.marked = [];
+ }
+ function getSearchState(cm) {
+ return cm._searchState || (cm._searchState = new SearchState());
+ }
+ function dialog(cm, text, shortText, f) {
+ if (cm.openDialog) cm.openDialog(text, f);
+ else f(prompt(shortText, ""));
+ }
+ function confirmDialog(cm, text, shortText, fs) {
+ if (cm.openConfirm) cm.openConfirm(text, fs);
+ else if (confirm(shortText)) fs[0]();
+ }
+ function parseQuery(query) {
+ var isRE = query.match(/^\/(.*)\/$/);
+ return isRE ? new RegExp(isRE[1]) : query;
+ }
+ var queryDialog =
+ 'Search: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+ function doSearch(cm, rev) {
+ var state = getSearchState(cm);
+ if (state.query) return findNext(cm, rev);
+ dialog(cm, queryDialog, "Search for:", function(query) {
+ cm.operation(function() {
+ if (!query || state.query) return;
+ state.query = parseQuery(query);
+ if (cm.lineCount() < 2000) { // This is too expensive on big documents.
+ for (var cursor = cm.getSearchCursor(query); cursor.findNext();)
+ state.marked.push(cm.markText(cursor.from(), cursor.to(), "CodeMirror-searching"));
+ }
+ state.posFrom = state.posTo = cm.getCursor();
+ findNext(cm, rev);
+ });
+ });
+ }
+ function findNext(cm, rev) {cm.operation(function() {
+ var state = getSearchState(cm);
+ var cursor = cm.getSearchCursor(state.query, rev ? state.posFrom : state.posTo);
+ if (!cursor.find(rev)) {
+ cursor = cm.getSearchCursor(state.query, rev ? {line: cm.lineCount() - 1} : {line: 0, ch: 0});
+ if (!cursor.find(rev)) return;
+ }
+ cm.setSelection(cursor.from(), cursor.to());
+ state.posFrom = cursor.from(); state.posTo = cursor.to();
+ })}
+ function clearSearch(cm) {cm.operation(function() {
+ var state = getSearchState(cm);
+ if (!state.query) return;
+ state.query = null;
+ for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear();
+ state.marked.length = 0;
+ })}
+
+ var replaceQueryDialog =
+ 'Replace: <input type="text" style="width: 10em"> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+ var replacementQueryDialog = 'With: <input type="text" style="width: 10em">';
+ var doReplaceConfirm = "Replace? <button>Yes</button> <button>No</button> <button>Stop</button>";
+ function replace(cm, all) {
+ dialog(cm, replaceQueryDialog, "Replace:", function(query) {
+ if (!query) return;
+ query = parseQuery(query);
+ dialog(cm, replacementQueryDialog, "Replace with:", function(text) {
+ if (all) {
+ cm.operation(function() {
+ for (var cursor = cm.getSearchCursor(query); cursor.findNext();) {
+ if (typeof query != "string") {
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
+ cursor.replace(text.replace(/\$(\d)/, function(w, i) {return match[i];}));
+ } else cursor.replace(text);
+ }
+ });
+ } else {
+ clearSearch(cm);
+ var cursor = cm.getSearchCursor(query, cm.getCursor());
+ function advance() {
+ var start = cursor.from(), match;
+ if (!(match = cursor.findNext())) {
+ cursor = cm.getSearchCursor(query);
+ if (!(match = cursor.findNext()) ||
+ (cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
+ }
+ cm.setSelection(cursor.from(), cursor.to());
+ confirmDialog(cm, doReplaceConfirm, "Replace?",
+ [function() {doReplace(match);}, advance]);
+ }
+ function doReplace(match) {
+ cursor.replace(typeof query == "string" ? text :
+ text.replace(/\$(\d)/, function(w, i) {return match[i];}));
+ advance();
+ }
+ advance();
+ }
+ });
+ });
+ }
+
+ CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+ CodeMirror.commands.findNext = doSearch;
+ CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
+ CodeMirror.commands.clearSearch = clearSearch;
+ CodeMirror.commands.replace = replace;
+ CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
+})();
View
117 engine/src/main/resources/org/archive/crawler/restlet/codemirror/util/searchcursor.js
@@ -0,0 +1,117 @@
+(function(){
+ function SearchCursor(cm, query, pos, caseFold) {
+ this.atOccurrence = false; this.cm = cm;
+ if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase();
+
+ pos = pos ? cm.clipPos(pos) : {line: 0, ch: 0};
+ this.pos = {from: pos, to: pos};
+
+ // The matches method is filled in based on the type of query.
+ // It takes a position and a direction, and returns an object
+ // describing the next occurrence of the query, or null if no
+ // more matches were found.
+ if (typeof query != "string") // Regexp match
+ this.matches = function(reverse, pos) {
+ if (reverse) {
+ var line = cm.getLine(pos.line).slice(0, pos.ch), match = line.match(query), start = 0;
+ while (match) {
+ var ind = line.indexOf(match[0]);
+ start += ind;
+ line = line.slice(ind + 1);
+ var newmatch = line.match(query);
+ if (newmatch) match = newmatch;
+ else break;
+ start++;
+ }
+ }
+ else {
+ var line = cm.getLine(pos.line).slice(pos.ch), match = line.match(query),
+ start = match && pos.ch + line.indexOf(match[0]);
+ }
+ if (match)
+ return {from: {line: pos.line, ch: start},
+ to: {line: pos.line, ch: start + match[0].length},
+ match: match};
+ };
+ else { // String query
+ if (caseFold) query = query.toLowerCase();
+ var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
+ var target = query.split("\n");
+ // Different methods for single-line and multi-line queries
+ if (target.length == 1)
+ this.matches = function(reverse, pos) {
+ var line = fold(cm.getLine(pos.line)), len = query.length, match;
+ if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1)
+ : (match = line.indexOf(query, pos.ch)) != -1)
+ return {from: {line: pos.line, ch: match},
+ to: {line: pos.line, ch: match + len}};
+ };
+ else
+ this.matches = function(reverse, pos) {
+ var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln));
+ var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
+ if (reverse ? offsetA >= pos.ch || offsetA != match.length
+ : offsetA <= pos.ch || offsetA != line.length - match.length)
+ return;
+ for (;;) {
+ if (reverse ? !ln : ln == cm.lineCount() - 1) return;
+ line = fold(cm.getLine(ln += reverse ? -1 : 1));
+ match = target[reverse ? --idx : ++idx];
+ if (idx > 0 && idx < target.length - 1) {
+ if (line != match) return;
+ else continue;
+ }
+ var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
+ if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
+ return;
+ var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB};
+ return {from: reverse ? end : start, to: reverse ? start : end};
+ }
+ };
+ }
+ }
+
+ SearchCursor.prototype = {
+ findNext: function() {return this.find(false);},
+ findPrevious: function() {return this.find(true);},
+
+ find: function(reverse) {
+ var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to);
+ function savePosAndFail(line) {
+ var p