diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineDescriptions.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineDescriptions.class
new file mode 100755
index 0000000..5420d00
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineDescriptions.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.class
new file mode 100755
index 0000000..b8d4f02
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.class
new file mode 100755
index 0000000..22d3b20
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.class
new file mode 100755
index 0000000..08ba281
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$1.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$1.class
new file mode 100755
index 0000000..b0ef823
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$1.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$JasmineSpecStatus.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$JasmineSpecStatus.class
new file mode 100755
index 0000000..b578554
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec$JasmineSpecStatus.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec.class
new file mode 100755
index 0000000..392b9ad
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpec.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecFailureException.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecFailureException.class
new file mode 100755
index 0000000..e05e09c
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecFailureException.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator$TemplatePlaceholders.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator$TemplatePlaceholders.class
new file mode 100755
index 0000000..7d128bb
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator$TemplatePlaceholders.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.class
new file mode 100755
index 0000000..b099888
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuite.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuite.class
new file mode 100755
index 0000000..1352c11
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuite.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.class
new file mode 100755
index 0000000..5fc62da
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner$DefaultSuite.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner$DefaultSuite.class
new file mode 100755
index 0000000..d4b64a3
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner$DefaultSuite.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner.class
new file mode 100755
index 0000000..66439d3
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunner.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.class b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.class
new file mode 100755
index 0000000..9ba4bbc
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.class
new file mode 100755
index 0000000..e7f47ba
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.class
new file mode 100755
index 0000000..bb2421e
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.class
new file mode 100755
index 0000000..45e197e
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.class
new file mode 100755
index 0000000..e5b1fbe
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.class
new file mode 100755
index 0000000..c2dbcb6
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.class
new file mode 100755
index 0000000..47b79c8
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.class differ
diff --git a/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.class b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.class
new file mode 100755
index 0000000..4ba7fee
Binary files /dev/null and b/eclipsecompiled/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/ChainedErrorReporter.class b/eclipsecompiled/be/cegeka/rhino/ChainedErrorReporter.class
new file mode 100755
index 0000000..e38fdaf
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/ChainedErrorReporter.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/ClassInJS.class b/eclipsecompiled/be/cegeka/rhino/ClassInJS.class
new file mode 100755
index 0000000..fefb5b5
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/ClassInJS.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContext$1.class b/eclipsecompiled/be/cegeka/rhino/RhinoContext$1.class
new file mode 100755
index 0000000..09342dc
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContext$1.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContext$2.class b/eclipsecompiled/be/cegeka/rhino/RhinoContext$2.class
new file mode 100755
index 0000000..b888d8c
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContext$2.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContext.class b/eclipsecompiled/be/cegeka/rhino/RhinoContext.class
new file mode 100755
index 0000000..f0352f2
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContext.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContextClassExportingTest.class b/eclipsecompiled/be/cegeka/rhino/RhinoContextClassExportingTest.class
new file mode 100755
index 0000000..cc08b23
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContextClassExportingTest.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.class b/eclipsecompiled/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.class
new file mode 100755
index 0000000..7d4145d
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContextTest$1.class b/eclipsecompiled/be/cegeka/rhino/RhinoContextTest$1.class
new file mode 100755
index 0000000..6486eea
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContextTest$1.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoContextTest.class b/eclipsecompiled/be/cegeka/rhino/RhinoContextTest.class
new file mode 100755
index 0000000..8e8571c
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoContextTest.class differ
diff --git a/eclipsecompiled/be/cegeka/rhino/RhinoRunnable.class b/eclipsecompiled/be/cegeka/rhino/RhinoRunnable.class
new file mode 100755
index 0000000..4c229a9
Binary files /dev/null and b/eclipsecompiled/be/cegeka/rhino/RhinoRunnable.class differ
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..7c489da
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+
+
+ be.cegeka
+ jasminejunitrunner
+ 1.0-SNAPSHOT
+ Jasmine Junit Runner
+
+
+
+ rhino
+ js
+ 1.7R2
+
+
+ junit
+ junit
+ 4.8.2
+
+
+ commons-io
+ commons-io
+ 1.4
+
+
+ commons-lang
+ commons-lang
+ 2.4
+
+
+
+ org.mockito
+ mockito-all
+ 1.8.5
+ test
+
+
+ org.easytesting
+ fest-assert
+ 1.4
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-eclipse-plugin
+
+ eclipsecompiled
+ true
+ true
+
+ 2.5.1
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.6
+ true
+ true
+ UTF-8
+
+
+
+
+
+
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineDescriptions.java b/src/main/java/be/cegeka/junit/jasmine/JasmineDescriptions.java
new file mode 100755
index 0000000..9e37dc0
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineDescriptions.java
@@ -0,0 +1,38 @@
+package be.cegeka.junit.jasmine;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.junit.runner.Description;
+
+import be.cegeka.rhino.RhinoContext;
+
+class JasmineDescriptions {
+
+ private final Description rootDescription;
+ private final Map specsMap;
+ private final RhinoContext rhinoContext;
+
+ JasmineDescriptions(Description rootDescription, Map specsMap, RhinoContext context) {
+ this.rootDescription = rootDescription;
+ this.specsMap = specsMap;
+ this.rhinoContext = context;
+ }
+
+ public Description getRootDescription() {
+ return rootDescription;
+ }
+
+ public Collection getAllSpecs() {
+ return specsMap.values();
+ }
+
+ public void executeSpec(Description description) {
+ getSpec(description).execute(rhinoContext);
+ }
+
+ public JasmineSpec getSpec(Description description) {
+ return specsMap.get(description.getDisplayName());
+ }
+
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.java b/src/main/java/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.java
new file mode 100755
index 0000000..6b74cb0
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineJSSuiteConverter.java
@@ -0,0 +1,57 @@
+package be.cegeka.junit.jasmine;
+
+import java.lang.annotation.Annotation;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.runner.Description;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.NativeObject;
+
+import be.cegeka.rhino.RhinoContext;
+
+class JasmineJSSuiteConverter {
+
+ private final NativeArray baseSuites;
+ private final RhinoContext context;
+
+ JasmineJSSuiteConverter(NativeArray baseSuites, RhinoContext context) {
+ this.baseSuites = baseSuites;
+ this.context = context;
+ }
+
+ public JasmineDescriptions convertToJunitDescriptions(Class> testClass) {
+ Description rootDescription = Description.createSuiteDescription(testClass);
+ Map specsMap = convertSuiteArrayToDescriptions(this.baseSuites, rootDescription);
+ return new JasmineDescriptions(rootDescription, specsMap, context);
+ }
+
+ private Map convertSuiteArrayToDescriptions(NativeArray suiteArray, Description rootDescription) {
+ Map specsMap = new HashMap();
+ for (Object idObj : suiteArray.getIds()) {
+ NativeObject suite = (NativeObject) suiteArray.get((Integer) idObj, suiteArray);
+
+ Description suiteDescription = Description
+ .createSuiteDescription((String) suite.get("description", suite), (Annotation[]) null);
+ rootDescription.addChild(suiteDescription);
+ specsMap.putAll(convertToJunitDescription(suite, suiteDescription));
+ }
+
+ return specsMap;
+ }
+
+ private Map convertToJunitDescription(NativeObject suite, Description description) {
+ Map specsMap = new HashMap();
+ NativeArray specsArray = (NativeArray) context.executeFunction(suite, "specs");
+ for (Object idObj : specsArray.getIds()) {
+ NativeObject spec = (NativeObject) specsArray.get((Integer) idObj, specsArray);
+
+ JasmineSpec jasmineSpec = new JasmineSpec(spec);
+ specsMap.put(jasmineSpec.toString(), jasmineSpec);
+ description.addChild(jasmineSpec.getDescription());
+ }
+
+ return specsMap;
+ }
+
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineSpec.java b/src/main/java/be/cegeka/junit/jasmine/JasmineSpec.java
new file mode 100755
index 0000000..5f3763e
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineSpec.java
@@ -0,0 +1,101 @@
+package be.cegeka.junit.jasmine;
+
+import static junit.framework.Assert.assertTrue;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.NativeObject;
+
+import be.cegeka.rhino.RhinoContext;
+import be.cegeka.rhino.RhinoRunnable;
+
+// TODO rhinoContext als field zetten ipv altijd mee te geven?
+class JasmineSpec {
+
+ public enum JasmineSpecStatus {
+ PASSED,
+ FAILED,
+ SKIPPED
+ }
+
+ private final Description description;
+ private final NativeObject spec;
+
+ JasmineSpec(NativeObject spec) {
+ this.spec = spec;
+ String descriptionString = (String) spec.get("description", spec);
+ this.description = Description.createSuiteDescription(descriptionString);
+ }
+
+ public Description getDescription() {
+ return description;
+ }
+
+ public NativeObject getSpec() {
+ return spec;
+ }
+
+ public boolean isPassed(RhinoContext context) {
+ return getSpecResultStatus(context) == JasmineSpecStatus.PASSED;
+ }
+
+ public boolean isFailed(RhinoContext context) {
+ return getSpecResultStatus(context) == JasmineSpecStatus.FAILED;
+ }
+
+ public JasmineSpecStatus getSpecResultStatus(RhinoContext context) {
+ assertTrue(isDone());
+
+ NativeObject results = getSpecResults(context);
+ boolean passed = (Boolean) context.executeFunction(results, "passed");
+ boolean skipped = (Boolean) results.get("skipped", results);
+
+ if (skipped) {
+ return JasmineSpecStatus.SKIPPED;
+ }
+ return passed ? JasmineSpecStatus.PASSED : JasmineSpecStatus.FAILED;
+ }
+
+ public Failure getJunitFailure(RhinoContext context) {
+ assertTrue(isFailed(context));
+ return new Failure(description, getFirstFailedStacktrace(context));
+ }
+
+ private Throwable getFirstFailedStacktrace(RhinoContext context) {
+ NativeArray resultItems = (NativeArray) context.executeFunction(getSpecResults(context), "getItems");
+ for (Object resultItemId : resultItems.getIds()) {
+ NativeObject resultItem = (NativeObject) resultItems.get((Integer) resultItemId, resultItems);
+
+ if (!((Boolean) context.executeFunction(resultItem, "passed"))) {
+ return new JasmineSpecFailureException(resultItem);
+ }
+ }
+
+ return null;
+ }
+
+ private NativeObject getSpecResults(RhinoContext context) {
+ return (NativeObject) context.executeFunction(spec, "results");
+ }
+
+ public boolean isDone() {
+ Object doneResult = spec.get("done", spec);
+ return doneResult instanceof Boolean && ((Boolean) doneResult);
+ }
+
+ public void execute(RhinoContext baseContext) {
+ baseContext.runAsync(new RhinoRunnable() {
+
+ @Override
+ public void run(RhinoContext context) {
+ context.executeFunction(spec, "execute");
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return description.getDisplayName();
+ }
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineSpecFailureException.java b/src/main/java/be/cegeka/junit/jasmine/JasmineSpecFailureException.java
new file mode 100755
index 0000000..d60c63b
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineSpecFailureException.java
@@ -0,0 +1,19 @@
+package be.cegeka.junit.jasmine;
+
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.ScriptableObject;
+
+public class JasmineSpecFailureException extends Exception {
+
+ private final ScriptableObject trace;
+
+ public JasmineSpecFailureException(NativeObject specResultItem) {
+ this.trace = (ScriptableObject) specResultItem.get("trace", specResultItem);
+ }
+
+ @Override
+ public String getMessage() {
+ return (String) trace.get("message", trace);
+ }
+
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.java b/src/main/java/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.java
new file mode 100755
index 0000000..e6f2088
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineSpecRunnerGenerator.java
@@ -0,0 +1,77 @@
+package be.cegeka.junit.jasmine;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+
+public class JasmineSpecRunnerGenerator {
+
+ private enum TemplatePlaceholders {
+ RELATIVE_PATH(""),
+ SOURCE_FILES_TO_INCLUDE(""),
+ SPEC_FILES_TO_INCLUDE("");
+
+ private final String placeholder;
+
+ private TemplatePlaceholders(String placeholder) {
+ this.placeholder = placeholder;
+ }
+
+ public String getPlaceholder() {
+ return placeholder;
+ }
+
+ }
+
+ private final JasmineSuite suite;
+ private final String[] jasmineSpecs;
+ private final String outputPath;
+ private final String outputFileName;
+
+ public JasmineSpecRunnerGenerator(String[] jasmineSpecs, JasmineSuite suite, String outputPath, String outputFileName) {
+ this.jasmineSpecs = jasmineSpecs;
+ this.suite = suite;
+ this.outputPath = outputPath;
+ this.outputFileName = outputFileName;
+ }
+
+ public void generate() {
+ // TODO hardcoded relative path stuff wat configureerbaar maken
+ String template = loadTemplate();
+ template = replaceRelativePathsForLibs(template);
+ template = template.replaceAll(TemplatePlaceholders.SOURCE_FILES_TO_INCLUDE.getPlaceholder(),
+ getJavascriptFileIncludes("./../../../main/webapp/js", suite.sources()));
+ template = template.replaceAll(TemplatePlaceholders.SPEC_FILES_TO_INCLUDE.getPlaceholder(),
+ getJavascriptFileIncludes("./../specs", jasmineSpecs));
+
+ try {
+ FileUtils.writeStringToFile(new File(outputPath + "/" + outputFileName), template);
+ } catch (IOException e) {
+ throw new RuntimeException("unable to write spec runner contents to destination", e);
+ }
+ }
+
+ private String replaceRelativePathsForLibs(String template) {
+ return template.replaceAll(TemplatePlaceholders.RELATIVE_PATH.getPlaceholder(), suite.jsRootDir());
+ }
+
+ private String getJavascriptFileIncludes(String path, String[] jsFiles) {
+ StringBuilder sourceFileIncludes = new StringBuilder();
+ for (String sourceFile : jsFiles) {
+ sourceFileIncludes.append("\t\t\r\n");
+ }
+ return sourceFileIncludes.toString();
+ }
+
+ private String loadTemplate() {
+ String template = null;
+ try {
+ template = FileUtils.readFileToString(new File(suite.jsRootDir() + "/lib/specRunner.tpl"));
+ } catch (IOException e) {
+ throw new RuntimeException("spec runner template file not found!", e);
+ }
+ return template;
+ }
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineSuite.java b/src/main/java/be/cegeka/junit/jasmine/JasmineSuite.java
new file mode 100755
index 0000000..2135dcf
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineSuite.java
@@ -0,0 +1,20 @@
+package be.cegeka.junit.jasmine;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JasmineSuite {
+
+ String jsRootDir() default "src/test/javascript";
+
+ String sourcesRootDir() default "src/main/webapp/js";
+
+ String[] specs() default {};
+
+ String[] sources() default {};
+
+ boolean generateSpecRunner() default false;
+
+ boolean debug() default false;
+}
diff --git a/src/main/java/be/cegeka/junit/jasmine/JasmineTestRunner.java b/src/main/java/be/cegeka/junit/jasmine/JasmineTestRunner.java
new file mode 100755
index 0000000..d562bdf
--- /dev/null
+++ b/src/main/java/be/cegeka/junit/jasmine/JasmineTestRunner.java
@@ -0,0 +1,190 @@
+package be.cegeka.junit.jasmine;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang.StringUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.mozilla.javascript.NativeArray;
+import org.mozilla.javascript.tools.debugger.Main;
+
+import be.cegeka.rhino.RhinoContext;
+
+public class JasmineTestRunner extends Runner {
+
+ private static final int SLEEP_TIME_MILISECONDS = 50;
+ private static final String JASMINE_LIB_DIR = "/lib/jasmine-1.0.2/";
+
+ private JasmineDescriptions jasmineSuite;
+
+ private final RhinoContext rhinoContext;
+ private final JasmineSuite suiteAnnotation;
+ private final Class> testClass;
+
+ @JasmineSuite
+ private class DefaultSuite {
+ }
+
+ public JasmineTestRunner(Class> testClass) {
+ this.testClass = testClass;
+ this.suiteAnnotation = getJasmineSuiteAnnotationFromTestClass();
+
+ Main debugger = null;
+ if (this.suiteAnnotation.debug()) {
+ debugger = this.rhinoContext.createDebugger();
+ }
+
+ this.rhinoContext = setUpRhinoScope();
+
+ if (this.suiteAnnotation.debug()) {
+ debugger.doBreak();
+ }
+ }
+
+ private RhinoContext setUpRhinoScope() {
+ RhinoContext context = new RhinoContext();
+ context.loadEnv(suiteAnnotation.jsRootDir());
+ setUpJasmine(context);
+
+ context.load(suiteAnnotation.sourcesRootDir() + "/", suiteAnnotation.sources());
+ context.load(suiteAnnotation.jsRootDir() + "/specs/", getJasmineSpecs(suiteAnnotation));
+ return context;
+ }
+
+ private void setUpJasmine(RhinoContext context) {
+ context.load(getJsLibDir() + "jasmine.js");
+ context.load(getJsLibDir() + "jasmine.delegator_reporter.js");
+
+ context.evalJS("jasmine.getEnv().addReporter(new jasmine.DelegatorJUnitReporter());");
+ }
+
+ private JasmineSuite getJasmineSuiteAnnotationFromTestClass() {
+ JasmineSuite suiteAnnotation = testClass.getAnnotation(JasmineSuite.class);
+ if (suiteAnnotation == null) {
+ suiteAnnotation = DefaultSuite.class.getAnnotation(JasmineSuite.class);
+ }
+ return suiteAnnotation;
+ }
+
+ private String[] getJasmineSpecs(JasmineSuite suiteAnnotation) {
+ if (suiteAnnotation.specs().length == 0) {
+ return new String[] { StringUtils.uncapitalize(testClass.getSimpleName()).replace("Test", "Spec") + ".js" };
+ }
+ return suiteAnnotation.specs();
+ }
+
+ private void resetEnvjsWindowSpace() {
+ this.rhinoContext.evalJS("window.location = '" + suiteAnnotation.jsRootDir() + "/lib/blank.html';");
+ }
+
+ private String getJsLibDir() {
+ return suiteAnnotation.jsRootDir() + JASMINE_LIB_DIR;
+ }
+
+ private JasmineDescriptions getJasmineDescriptions() {
+ if (this.jasmineSuite == null) {
+ NativeArray baseSuites = (NativeArray) rhinoContext.evalJS("jasmine.getEnv().currentRunner().suites()");
+ this.jasmineSuite = new JasmineJSSuiteConverter(baseSuites, rhinoContext).convertToJunitDescriptions(testClass);
+ }
+ return this.jasmineSuite;
+ }
+
+ @Override
+ public Description getDescription() {
+ return getJasmineDescriptions().getRootDescription();
+ }
+
+ @Override
+ public void run(RunNotifier notifier) {
+ generateSpecRunnerIfNeeded();
+
+ for (JasmineSpec spec : getJasmineDescriptions().getAllSpecs()) {
+ Object testClassInstance = createTestClassInstance();
+ fireMethodsWithSpecifiedAnnotationIfAny(testClassInstance, Before.class);
+
+ try {
+ notifier.fireTestStarted(spec.getDescription());
+ spec.execute(rhinoContext);
+ while (!spec.isDone()) {
+ waitALittle();
+ }
+
+ reportSpecResultToNotifier(notifier, spec);
+ resetEnvjsWindowSpace();
+ } finally {
+ fireMethodsWithSpecifiedAnnotationIfAny(testClassInstance, After.class);
+ }
+ }
+
+ this.rhinoContext.exit();
+ }
+
+ private Object createTestClassInstance() {
+ try {
+ return testClass.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException("Unable to create a new instance of testClass " + testClass.getSimpleName()
+ + " using a no-arg constructor", ex);
+ }
+ }
+
+ private void fireMethodsWithSpecifiedAnnotationIfAny(Object testClassInstance, Class extends Annotation> annotation) {
+ for (Method method : testClass.getDeclaredMethods()) {
+
+ try {
+ if (method.getAnnotation(annotation) != null) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ throw new IllegalStateException("Annotated method should be public!");
+ }
+
+ method.setAccessible(true);
+ Class>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length == 0) {
+ method.invoke(testClassInstance, (Object[]) null);
+ } else if (parameterTypes.length == 1 && RhinoContext.class.isAssignableFrom(parameterTypes[0])) {
+ method.invoke(testClassInstance, new Object[] { this.rhinoContext });
+ } else {
+ throw new IllegalStateException("Annotated method does not have zero or rhinoContext as parameterTypes");
+ }
+ }
+ } catch (Exception ex) {
+ throw new RuntimeException(
+ "Exception while firing " + annotation.getSimpleName() + " method: " + method.getName(), ex);
+ }
+ }
+ }
+
+ private void generateSpecRunnerIfNeeded() {
+ if (suiteAnnotation.generateSpecRunner()) {
+ String[] jasmineSpecs = getJasmineSpecs(suiteAnnotation);
+ new JasmineSpecRunnerGenerator(jasmineSpecs, suiteAnnotation, suiteAnnotation.jsRootDir() + "/runners",
+ testClass.getSimpleName()
+ + "Runner.html")
+ .generate();
+ }
+ }
+
+ private void reportSpecResultToNotifier(RunNotifier notifier, JasmineSpec spec) {
+ if (spec.isPassed(rhinoContext)) {
+ notifier.fireTestFinished(spec.getDescription());
+ } else if (spec.isFailed(rhinoContext)) {
+ notifier.fireTestFailure(spec.getJunitFailure(rhinoContext));
+ } else {
+ throw new IllegalStateException("Unexpected spec status received: " + spec);
+ }
+ }
+
+ private void waitALittle() {
+ try {
+ Thread.sleep(SLEEP_TIME_MILISECONDS);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/be/cegeka/rhino/ChainedErrorReporter.java b/src/main/java/be/cegeka/rhino/ChainedErrorReporter.java
new file mode 100755
index 0000000..229a246
--- /dev/null
+++ b/src/main/java/be/cegeka/rhino/ChainedErrorReporter.java
@@ -0,0 +1,58 @@
+package be.cegeka.rhino;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mozilla.javascript.ErrorReporter;
+import org.mozilla.javascript.EvaluatorException;
+import org.mozilla.javascript.tools.ToolErrorReporter;
+
+class ChainedErrorReporter implements ErrorReporter {
+
+ private List chainedReporters = new ArrayList();
+
+ ChainedErrorReporter(ErrorReporter chainedDefaultReporter) {
+ chainedReporters.add(chainedDefaultReporter);
+ chainedReporters.add(new ToolErrorReporter(true, System.err));
+ }
+
+ @Override
+ public void error(String message, String sourceName, int line, String lineSource, int lineOffset) {
+ EvaluatorException ex = null;
+ for (ErrorReporter reporter : chainedReporters) {
+ try {
+ reporter.error(message, sourceName, line, lineSource, lineOffset);
+ } catch (EvaluatorException thrownByChainEx) {
+ ex = thrownByChainEx;
+ }
+ }
+
+ if (ex != null) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) {
+ EvaluatorException ex = null;
+ for (ErrorReporter reporter : chainedReporters) {
+ EvaluatorException returnedByChainEx = reporter.runtimeError(message, sourceName, line, lineSource, lineOffset);
+ if (returnedByChainEx != null) {
+ ex = returnedByChainEx;
+ }
+ }
+
+ if (ex != null) {
+ throw ex;
+ }
+ return null;
+ }
+
+ @Override
+ public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {
+ for (ErrorReporter reporter : chainedReporters) {
+ reporter.warning(message, sourceName, line, lineSource, lineOffset);
+ }
+ }
+
+}
diff --git a/src/main/java/be/cegeka/rhino/RhinoContext.java b/src/main/java/be/cegeka/rhino/RhinoContext.java
new file mode 100755
index 0000000..3e4d639
--- /dev/null
+++ b/src/main/java/be/cegeka/rhino/RhinoContext.java
@@ -0,0 +1,154 @@
+package be.cegeka.rhino;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.ContextFactory;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.tools.debugger.Main;
+import org.mozilla.javascript.tools.shell.Global;
+
+public class RhinoContext {
+
+ private Context jsContext;
+ private Scriptable jsScope;
+
+ public RhinoContext() {
+ this.jsContext = createJavascriptContext();
+ this.jsScope = createJavascriptScopeForContext(this.jsContext);
+ }
+
+ public RhinoContext(Scriptable sharedScope) {
+ this.jsContext = createJavascriptContext();
+ Scriptable newScope = this.jsContext.newObject(sharedScope);
+ newScope.setPrototype(sharedScope);
+ newScope.setParentScope(null);
+
+ this.jsScope = newScope;
+ }
+
+ private RhinoContext createNewRhinoContextBasedOnPrevious() {
+ return new RhinoContext(this.jsScope);
+ }
+
+ public void runAsync(final RhinoRunnable runnable) {
+ new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ RhinoContext newRhinoContextBasedOnPrevious = createNewRhinoContextBasedOnPrevious();
+ try {
+ runnable.run(newRhinoContextBasedOnPrevious);
+ } finally {
+ newRhinoContextBasedOnPrevious.exit();
+ }
+ }
+ }).start();
+ }
+
+ public Object evalJS(String js) {
+ return this.jsContext.evaluateString(this.jsScope, js, "script", 1, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public T createClassInJS(Class classToExport) {
+ exportClass(classToExport);
+ T newObj = (T) jsContext.newObject(jsScope, classToExport.getSimpleName());
+ return newObj;
+ }
+
+ public void setProperty(String objectToReceiveProperty, String property, Object value) {
+ Object obj = evalJS(objectToReceiveProperty);
+ if (obj == null || !(obj instanceof ScriptableObject)) {
+ throw new IllegalStateException("object to receive property is no ScriptableObject but a "
+ + (obj == null ? "" : obj.getClass().getSimpleName()));
+ }
+
+ ScriptableObject objectToReceive = (ScriptableObject) obj;
+ objectToReceive.put(property, objectToReceive, value);
+ }
+
+ private void exportClass(Class extends ScriptableObject> classToExport) {
+ try {
+ ScriptableObject.defineClass(this.jsScope, classToExport);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void load(String path, String... jsFiles) {
+ for (String jsFile : jsFiles) {
+ load(path + jsFile);
+ }
+ }
+
+ public void load(String fileName) {
+ evalJS("load('" + fileName + "')");
+ // Main.processFile(this.jsContext, this.jsScope, fileName);
+ }
+
+ public Object executeFunction(ScriptableObject object, String fnName, Object[] arguments) {
+ Object fnPointer = object.get(fnName, object);
+ if (fnPointer == null || !(fnPointer instanceof Function)) {
+ fnPointer = object.getPrototype().get(fnName, object);
+ }
+
+ return ((Function) fnPointer).call(jsContext, jsScope, object, arguments);
+ }
+
+ public Object executeFunction(ScriptableObject object, String fnName) {
+ return executeFunction(object, fnName, new Object[] {});
+ }
+
+ public Context getJsContext() {
+ return jsContext;
+ }
+
+ public Scriptable getJsScope() {
+ return jsScope;
+ }
+
+ public void loadEnv(String jsDir) {
+ // TODO ensure rhino 1.7R3 instead of R2 -> geen shim nodig + paths
+ // gedoe in orde zetten hier
+ load(jsDir + "/lib/es5-shim-0.0.4.min.js");
+ load(jsDir + "/lib/env.rhino.1.2.js");
+ load(jsDir + "/lib/env.utils.js");
+ load(jsDir + "/envJsOptions.js");
+ }
+
+ private Global createJavascriptScopeForContext(Context jsContext) {
+ Global scope = new Global();
+ scope.init(jsContext);
+ return scope;
+ }
+
+ private Context createJavascriptContext() {
+ Context jsContext = ContextFactory.getGlobal().enterContext();
+ jsContext.setOptimizationLevel(-1);
+ jsContext.setLanguageVersion(Context.VERSION_1_5); // TODO 1.8 plx
+ jsContext.setErrorReporter(new ChainedErrorReporter(jsContext.getErrorReporter()));
+ return jsContext;
+ }
+
+ public void exit() {
+ Context.exit();
+ }
+
+ public Main createDebugger() {
+ Main debugger = new Main("JS Rhino Debugger");
+
+ debugger.setExitAction(new Runnable() {
+
+ public void run() {
+ System.exit(0);
+ }
+ });
+
+ debugger.attachTo(ContextFactory.getGlobal());
+ debugger.pack();
+ debugger.setSize(600, 460);
+ debugger.setVisible(true);
+ return debugger;
+ }
+}
diff --git a/src/main/java/be/cegeka/rhino/RhinoRunnable.java b/src/main/java/be/cegeka/rhino/RhinoRunnable.java
new file mode 100755
index 0000000..28437fb
--- /dev/null
+++ b/src/main/java/be/cegeka/rhino/RhinoRunnable.java
@@ -0,0 +1,6 @@
+package be.cegeka.rhino;
+
+public interface RhinoRunnable {
+
+ public void run(RhinoContext context);
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.java b/src/test/java/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.java
new file mode 100755
index 0000000..f1d6926
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/JasmineFailingSpecsTest.java
@@ -0,0 +1,67 @@
+package be.cegeka.junit.jasmine;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.mozilla.javascript.EvaluatorException;
+
+import be.cegeka.junit.jasmine.classes.JasmineTestRunnerExceptionInJSCode;
+import be.cegeka.junit.jasmine.classes.JasmineTestRunnerExceptionInSpec;
+import be.cegeka.junit.jasmine.classes.JasmineTestRunnerFailingSpec;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JasmineFailingSpecsTest {
+
+ @Mock
+ private RunNotifier notifierMock;
+
+ @Test
+ public void shouldNotifyOfSingleFailure() {
+ new JasmineTestRunner(JasmineTestRunnerFailingSpec.class).run(notifierMock);
+
+ ArgumentCaptor failureCaptor = ArgumentCaptor.forClass(Failure.class);
+ ArgumentCaptor descriptionCaptor = ArgumentCaptor.forClass(Description.class);
+ verify(notifierMock).fireTestStarted(descriptionCaptor.capture());
+ verify(notifierMock).fireTestFailure(failureCaptor.capture());
+ verifyNoMoreInteractions(notifierMock);
+
+ Failure failure = failureCaptor.getValue();
+ Description startedDescription = descriptionCaptor.getValue();
+
+ assertThat(failure.getDescription()).isEqualTo(startedDescription);
+ assertThat(failure.getDescription().getDisplayName()).isEqualTo("will always fail");
+ assertThat(failure.getMessage()).isEqualTo("Expected true to be false.");
+ }
+
+ @Test
+ public void shouldNotifyOfSingleExceptionWithinSpecFunction() {
+ new JasmineTestRunner(JasmineTestRunnerExceptionInSpec.class).run(notifierMock);
+
+ ArgumentCaptor failureCaptor = ArgumentCaptor.forClass(Failure.class);
+ ArgumentCaptor descriptionCaptor = ArgumentCaptor.forClass(Description.class);
+ verify(notifierMock).fireTestStarted(descriptionCaptor.capture());
+ verify(notifierMock).fireTestFailure(failureCaptor.capture());
+ verifyNoMoreInteractions(notifierMock);
+
+ Failure failure = failureCaptor.getValue();
+ Description startedDescription = descriptionCaptor.getValue();
+
+ assertThat(failure.getDescription()).isEqualTo(startedDescription);
+ assertThat(failure.getDescription().getDisplayName()).isEqualTo("will always crash");
+ assertThat(failure.getMessage()).isEqualTo("ReferenceError: \"OEIWANU\" is not defined. in src/test/javascript/specs/crashingSpec.js (line 3)");
+ }
+
+ @Test(expected = EvaluatorException.class)
+ public void shouldCrashWhileTryingToLoadFaultyJSSpecFile() {
+ new JasmineTestRunner(JasmineTestRunnerExceptionInJSCode.class);
+ }
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.java b/src/test/java/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.java
new file mode 100755
index 0000000..b437ccb
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/JasmineFinishedSpecsTest.java
@@ -0,0 +1,40 @@
+package be.cegeka.junit.jasmine;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import be.cegeka.junit.jasmine.classes.JasmineTestRunnerSuccessSpec;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JasmineFinishedSpecsTest {
+
+ @Mock
+ private RunNotifier notifierMock;
+
+ @Test
+ public void shouldNotifyOfSingleSuccess() {
+ new JasmineTestRunner(JasmineTestRunnerSuccessSpec.class).run(notifierMock);
+
+ ArgumentCaptor descriptionStartedCaptor = ArgumentCaptor.forClass(Description.class);
+ ArgumentCaptor descriptionFinishedCaptor = ArgumentCaptor.forClass(Description.class);
+ verify(notifierMock).fireTestStarted(descriptionStartedCaptor.capture());
+ verify(notifierMock).fireTestFinished(descriptionFinishedCaptor.capture());
+ verifyNoMoreInteractions(notifierMock);
+
+ Description startedDescription = descriptionStartedCaptor.getValue();
+ Description finishedDescription = descriptionFinishedCaptor.getValue();
+
+ assertThat(startedDescription).isSameAs(finishedDescription);
+ assertThat(startedDescription.getDisplayName()).isEqualTo("will always run");
+ }
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.java b/src/test/java/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.java
new file mode 100755
index 0000000..1a8cc03
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/JasmineSuiteGeneratesRunnerTest.java
@@ -0,0 +1,67 @@
+package be.cegeka.junit.jasmine;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import be.cegeka.junit.jasmine.classes.JasmineSuiteGeneratorClassWithRunner;
+import be.cegeka.junit.jasmine.classes.JasmineSuiteGeneratorClassWithoutRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JasmineSuiteGeneratesRunnerTest {
+
+ private static final String RUNNERS_OUTPUT_DIR = "src/test/javascript/runners/";
+ @Mock
+ private RunNotifier notifierMock;
+
+ @Before
+ public void clearRunnersOutputDirectory() throws IOException {
+ FileUtils.cleanDirectory(new File(RUNNERS_OUTPUT_DIR));
+ }
+
+ @Test
+ public void byDefaultDoNotGenerateJasmineTestRunner() {
+ Class testClass = JasmineSuiteGeneratorClassWithoutRunner.class;
+ new JasmineTestRunner(testClass).run(notifierMock);
+
+ File runnerResult = getTestRunnerResultFile(testClass);
+ assertThat(runnerResult.isFile()).isFalse();
+ }
+
+ @Test
+ public void generateJasmineTestRunnerAfterRunningTests() throws IOException {
+ Class testClass = JasmineSuiteGeneratorClassWithRunner.class;
+ new JasmineTestRunner(testClass).run(notifierMock);
+
+ File runnerResult = getTestRunnerResultFile(testClass);
+ assertThat(runnerResult.isFile()).isTrue();
+
+ String runnerContent = FileUtils.readFileToString(runnerResult);
+
+ assertThat(runnerContent).contains("jasmine.getEnv().addReporter(new jasmine.TrivialReporter());");
+ assertJSFileIncluded(runnerContent,
+ "./../../../main/webapp/js/source1.js",
+ "./../../../main/webapp/js/source2.js",
+ "./../specs/spec1.js",
+ "./../specs/spec2.js");
+ }
+
+ private File getTestRunnerResultFile(Class> testClass) {
+ return new File(RUNNERS_OUTPUT_DIR + testClass.getSimpleName() + "Runner.html");
+ }
+
+ private void assertJSFileIncluded(String rawContent, String... files) {
+ for (String file : files) {
+ assertThat(rawContent).contains("");
+ }
+ }
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.java b/src/test/java/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.java
new file mode 100755
index 0000000..8a01dc4
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/JasmineTestRunnerBeforeAndAfterTest.java
@@ -0,0 +1,22 @@
+package be.cegeka.junit.jasmine;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import be.cegeka.junit.jasmine.JasmineTestRunner;
+import be.cegeka.junit.jasmine.classes.JasmineTestRunnerBeforeAndAfterClass;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JasmineTestRunnerBeforeAndAfterTest {
+
+ @Mock
+ private RunNotifier notifierMock;
+
+ @Test
+ public void useJasmineRunnerOnJasmineTestRunnerBeforeAndAfterClass() {
+ new JasmineTestRunner(JasmineTestRunnerBeforeAndAfterClass.class).run(notifierMock);
+ }
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.java
new file mode 100755
index 0000000..7a62e19
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithRunner.java
@@ -0,0 +1,12 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(
+ specs = { "spec1.js", "spec2.js" },
+ sources = { "source1.js", "source2.js" },
+ sourcesRootDir = "src/test/javascript/sources/",
+ generateSpecRunner = true)
+public class JasmineSuiteGeneratorClassWithRunner {
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.java
new file mode 100755
index 0000000..ff6bd55
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineSuiteGeneratorClassWithoutRunner.java
@@ -0,0 +1,8 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(specs = { "spec1.js", "spec2.js" }, sources = { "source1.js", "source2.js" }, sourcesRootDir = "src/test/javascript/sources/")
+public class JasmineSuiteGeneratorClassWithoutRunner {
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.java
new file mode 100755
index 0000000..af3b9d0
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerBeforeAndAfterClass.java
@@ -0,0 +1,50 @@
+package be.cegeka.junit.jasmine.classes;
+
+import static junit.framework.Assert.fail;
+import static org.fest.assertions.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+import be.cegeka.rhino.RhinoContext;
+
+@JasmineSuite(specs = { "emptySpec.js" })
+public class JasmineTestRunnerBeforeAndAfterClass {
+
+ private static final int RUN_MIJ_FLAG = 0;
+ private static final int RUN_MIJ_OOK_FLAG = 1;
+
+ List runs = new ArrayList();
+
+ @Before
+ public void runMij() {
+ assertThat(runs).isEmpty();
+ runs.add(RUN_MIJ_FLAG);
+ }
+
+ @Before
+ public void runMijOok(RhinoContext context) {
+ assertThat(runs).containsOnly(RUN_MIJ_FLAG);
+ runs.add(RUN_MIJ_OOK_FLAG);
+ }
+
+ @After
+ public void runMijAfter() {
+ assertThat(runs).containsOnly(RUN_MIJ_FLAG, RUN_MIJ_OOK_FLAG);
+ runs.remove((Object) RUN_MIJ_FLAG);
+ }
+
+ @After
+ public void runMijAfterOok(RhinoContext context) {
+ assertThat(runs).containsOnly(RUN_MIJ_OOK_FLAG);
+ runs.remove((Object) RUN_MIJ_OOK_FLAG);
+ }
+
+ public void runMijNiet() {
+ fail("should not be run");
+ }
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.java
new file mode 100755
index 0000000..f34e457
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInJSCode.java
@@ -0,0 +1,8 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(specs = { "crashingJSCode.js" })
+public class JasmineTestRunnerExceptionInJSCode {
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.java
new file mode 100755
index 0000000..2fd6386
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerExceptionInSpec.java
@@ -0,0 +1,8 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(specs = { "crashingSpec.js" })
+public class JasmineTestRunnerExceptionInSpec {
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.java
new file mode 100755
index 0000000..a7d556f
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerFailingSpec.java
@@ -0,0 +1,8 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(specs = { "failingSpec.js" })
+public class JasmineTestRunnerFailingSpec {
+
+}
diff --git a/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.java b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.java
new file mode 100755
index 0000000..d3543df
--- /dev/null
+++ b/src/test/java/be/cegeka/junit/jasmine/classes/JasmineTestRunnerSuccessSpec.java
@@ -0,0 +1,8 @@
+package be.cegeka.junit.jasmine.classes;
+
+import be.cegeka.junit.jasmine.JasmineSuite;
+
+@JasmineSuite(specs = { "emptySpec.js" })
+public class JasmineTestRunnerSuccessSpec {
+
+}
diff --git a/src/test/java/be/cegeka/rhino/ClassInJS.java b/src/test/java/be/cegeka/rhino/ClassInJS.java
new file mode 100755
index 0000000..210be1d
--- /dev/null
+++ b/src/test/java/be/cegeka/rhino/ClassInJS.java
@@ -0,0 +1,28 @@
+package be.cegeka.rhino;
+
+import org.mozilla.javascript.ScriptableObject;
+
+public class ClassInJS extends ScriptableObject {
+
+ private int prop = 0;
+
+ public ClassInJS() {
+ }
+
+ public void increaseProp() {
+ prop++;
+ }
+
+ public String jsFunction_fn() {
+ return "fn";
+ }
+
+ public int jsGet_prop() {
+ return prop;
+ }
+
+ @Override
+ public String getClassName() {
+ return "ClassInJS";
+ }
+}
diff --git a/src/test/java/be/cegeka/rhino/RhinoContextClassExportingTest.java b/src/test/java/be/cegeka/rhino/RhinoContextClassExportingTest.java
new file mode 100755
index 0000000..5b7c7f9
--- /dev/null
+++ b/src/test/java/be/cegeka/rhino/RhinoContextClassExportingTest.java
@@ -0,0 +1,23 @@
+package be.cegeka.rhino;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import org.junit.Test;
+
+public class RhinoContextClassExportingTest {
+
+ @Test
+ public void exposingClassInJS() {
+ RhinoContext context = new RhinoContext();
+
+ ClassInJS newDefaultInstance = context.createClassInJS(ClassInJS.class);
+ assertThat(newDefaultInstance.jsGet_prop()).isEqualTo(0);
+
+ ClassInJS objInJava = (ClassInJS) context.evalJS("var obj = new ClassInJS(); obj");
+
+ objInJava.increaseProp();
+ assertThat(newDefaultInstance.jsGet_prop()).isEqualTo(0);
+ assertThat(context.evalJS("obj.prop")).isEqualTo(1);
+ assertThat(context.evalJS("obj.fn()")).isEqualTo("fn");
+ }
+}
diff --git a/src/test/java/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.java b/src/test/java/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.java
new file mode 100755
index 0000000..0584017
--- /dev/null
+++ b/src/test/java/be/cegeka/rhino/RhinoContextEnvjsLoadingTest.java
@@ -0,0 +1,28 @@
+package be.cegeka.rhino;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import org.junit.Test;
+import org.mozilla.javascript.EcmaError;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.tools.shell.Global;
+
+public class RhinoContextEnvjsLoadingTest {
+
+ @Test
+ public void loadEnvShouldSetWindowSpaceAndBeES5Complaint() {
+ RhinoContext context = new RhinoContext();
+
+ context.loadEnv("src/test/javascript");
+ assertThat(context.evalJS("window")).isInstanceOf(Global.class);
+
+ assertThat(context.evalJS("Object.create({ test: 'test' });")).isInstanceOf(NativeObject.class);
+ }
+
+ @Test(expected = EcmaError.class)
+ public void failWithoutLoadingEnvAndManipulatingDOMStuff() {
+ RhinoContext context = new RhinoContext();
+ context.evalJS("document.getElementById");
+ }
+
+}
diff --git a/src/test/java/be/cegeka/rhino/RhinoContextTest.java b/src/test/java/be/cegeka/rhino/RhinoContextTest.java
new file mode 100755
index 0000000..bb6055f
--- /dev/null
+++ b/src/test/java/be/cegeka/rhino/RhinoContextTest.java
@@ -0,0 +1,80 @@
+package be.cegeka.rhino;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+import org.junit.Test;
+import org.mozilla.javascript.NativeObject;
+import org.mozilla.javascript.ScriptableObject;
+
+public class RhinoContextTest {
+
+ @Test
+ public void executeFunctionOnPrototypeAndActualObject() {
+ RhinoContext context = new RhinoContext();
+ String js = "" +
+ "var obj = function() {" +
+ " this.actual = function() { " +
+ " return 5; " +
+ " }" +
+ "};" +
+ "obj.prototype = {" +
+ " go : function() {" +
+ " return 3; " +
+ " }" +
+ "}";
+
+ context.evalJS(js);
+ ScriptableObject obj = (ScriptableObject) context.evalJS("new obj()");
+
+ assertThat(context.executeFunction(obj, "go")).isEqualTo(3.0);
+ assertThat(context.executeFunction(obj, "actual")).isEqualTo(5.0);
+ }
+
+ @Test
+ public void runAsyncUsesTheSameSharedGlobalScope() throws InterruptedException {
+ RhinoContext baseContext = new RhinoContext();
+ baseContext.evalJS("var base = 'base'");
+
+ baseContext.runAsync(new RhinoRunnable() {
+
+ @Override
+ public void run(RhinoContext context) {
+ assertThat(context.evalJS("base")).isEqualTo("base");
+ }
+ });
+
+ Thread.sleep(500);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void setPropertyOnUndefinedNotPossible() {
+ RhinoContext context = new RhinoContext();
+ context.evalJS("var zever");
+ context.setProperty("zever", "prop", 1);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void setPropertyOnNullNotPossible() {
+ RhinoContext context = new RhinoContext();
+ context.setProperty(null, null, null);
+ }
+
+ @Test
+ public void setPropertyOnSomeObj() {
+ RhinoContext context = new RhinoContext();
+
+ NativeObject anObj = (NativeObject) context.evalJS("var obj = { a: 'a'}; obj");
+ context.setProperty("obj", "b", "b");
+
+ assertThat(anObj.get("b", anObj)).isEqualTo("b");
+ }
+
+ @Test
+ public void loadMultipleJSFiles() {
+ RhinoContext context = new RhinoContext();
+ context.load("src/test/javascript/", "loadTest.js", "loadTestTwo.js");
+
+ assertThat(context.evalJS("loaded")).isEqualTo(true);
+ assertThat(context.evalJS("loadedTwo")).isEqualTo(true);
+ }
+}
diff --git a/src/test/javascript/envJsOptions.js b/src/test/javascript/envJsOptions.js
new file mode 100755
index 0000000..165127f
--- /dev/null
+++ b/src/test/javascript/envJsOptions.js
@@ -0,0 +1,12 @@
+/**
+ * Basic Envjs options set here (not part of a hack or util)
+ * See http://www.envjs.com/doc/apis
+ */
+Envjs({
+ scriptTypes : {
+ '': true, //inline and anonymous
+ 'text/javascript': true,
+ 'text/envjs': false
+ },
+ appCodeName: 'Mozilla'
+});
diff --git a/src/test/javascript/lib/blank.html b/src/test/javascript/lib/blank.html
new file mode 100755
index 0000000..e69de29
diff --git a/src/test/javascript/lib/env.rhino.1.2.js b/src/test/javascript/lib/env.rhino.1.2.js
new file mode 100755
index 0000000..8f09b47
--- /dev/null
+++ b/src/test/javascript/lib/env.rhino.1.2.js
@@ -0,0 +1,13989 @@
+/*
+ * Envjs core-env.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+var Envjs = function(){
+ var i,
+ name,
+ override = function(){
+ for(i=0;i and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author john resig
+ */
+// Helper method for extending one object with another.
+function __extend__(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+ if ( g || s ) {
+ if ( g ) { a.__defineGetter__(i, g); }
+ if ( s ) { a.__defineSetter__(i, s); }
+ } else {
+ a[i] = b[i];
+ }
+ } return a;
+}
+
+/**
+ * Writes message to system out
+ * @param {String} message
+ */
+Envjs.log = function(message){};
+
+/**
+ * Constants providing enumerated levels for logging in modules
+ */
+Envjs.DEBUG = 1;
+Envjs.INFO = 2;
+Envjs.WARN = 3;
+Envjs.ERROR = 3;
+Envjs.NONE = 3;
+
+/**
+ * Writes error info out to console
+ * @param {Error} e
+ */
+Envjs.lineSource = function(e){};
+
+
+/**
+ * TODO: used in ./event/eventtarget.js
+ * @param {Object} event
+ */
+Envjs.defaultEventBehaviors = {};
+
+
+/**
+ * describes which script src values will trigger Envjs to load
+ * the script like a browser would
+ */
+Envjs.scriptTypes = {
+ "text/javascript" :false,
+ "text/envjs" :true
+};
+
+/**
+ * will be called when loading a script throws an error
+ * @param {Object} script
+ * @param {Object} e
+ */
+Envjs.onScriptLoadError = function(script, e){
+ console.log('error loading script %s %s', script, e);
+};
+
+
+/**
+ * load and execute script tag text content
+ * @param {Object} script
+ */
+Envjs.loadInlineScript = function(script){
+ var tmpFile;
+ tmpFile = Envjs.writeToTempFile(script.text, 'js') ;
+ load(tmpFile);
+};
+
+/**
+ * Should evaluate script in some context
+ * @param {Object} context
+ * @param {Object} source
+ * @param {Object} name
+ */
+Envjs.eval = function(context, source, name){};
+
+
+/**
+ * Executes a script tag
+ * @param {Object} script
+ * @param {Object} parser
+ */
+Envjs.loadLocalScript = function(script){
+ //console.log("loading script %s", script);
+ var types,
+ src,
+ i,
+ base,
+ filename,
+ xhr;
+
+ if(script.type){
+ types = script.type.split(";");
+ for(i=0;i
+ * - Via an innerHTML parse of a
+ * - A modificiation of the 'src' attribute of an Image/HTMLImageElement
+ *
+ * NOTE: this is optional API. If this doesn't exist then the default
+ * 'loaded' event occurs.
+ *
+ * @param node {Object} the node
+ * @param node the src value
+ * @return 'true' to indicate the 'load' succeed, false otherwise
+ */
+Envjs.loadImage = function(node, src) {
+ return true;
+};
+
+
+/**
+ * A 'link' was requested by the document. Typically this occurs when:
+ * - During inital parse of a
+ * - Via an innerHTML parse of a
+ * - A modificiation of the 'href' attribute on a node in the tree
+ *
+ * @param node {Object} is the link node in question
+ * @param href {String} is the href.
+ *
+ * Return 'true' to indicate that the 'load' was successful, or false
+ * otherwise. The appropriate event is then triggered.
+ *
+ * NOTE: this is optional API. If this doesn't exist then the default
+ * 'loaded' event occurs
+ */
+Envjs.loadLink = function(node, href) {
+ return true;
+};
+
+(function(){
+
+
+/*
+ * cookie handling
+ * Private internal helper class used to save/retreive cookies
+ */
+
+/**
+ * Specifies the location of the cookie file
+ */
+Envjs.cookieFile = function(){
+ return 'file://'+Envjs.homedir+'/.cookies';
+};
+
+/**
+ * saves cookies to a local file
+ * @param {Object} htmldoc
+ */
+Envjs.saveCookies = function(){
+ var cookiejson = JSON.stringify(Envjs.cookies.peristent,null,'\t');
+ //console.log('persisting cookies %s', cookiejson);
+ Envjs.writeToFile(cookiejson, Envjs.cookieFile());
+};
+
+/**
+ * loads cookies from a local file
+ * @param {Object} htmldoc
+ */
+Envjs.loadCookies = function(){
+ var cookiejson,
+ js;
+ try{
+ cookiejson = Envjs.readFromFile(Envjs.cookieFile())
+ js = JSON.parse(cookiejson, null, '\t');
+ }catch(e){
+ //console.log('failed to load cookies %s', e);
+ js = {};
+ }
+ return js;
+};
+
+Envjs.cookies = {
+ persistent:{
+ //domain - key on domain name {
+ //path - key on path {
+ //name - key on name {
+ //value : cookie value
+ //other cookie properties
+ //}
+ //}
+ //}
+ //expire - provides a timestamp for expiring the cookie
+ //cookie - the cookie!
+ },
+ temporary:{//transient is a reserved word :(
+ //like above
+ }
+};
+
+var __cookies__;
+
+//HTMLDocument cookie
+Envjs.setCookie = function(url, cookie){
+ var i,
+ index,
+ name,
+ value,
+ properties = {},
+ attr,
+ attrs;
+ url = Envjs.urlsplit(url);
+ if(cookie)
+ attrs = cookie.split(";");
+ else
+ return;
+
+ //for now the strategy is to simply create a json object
+ //and post it to a file in the .cookies.js file. I hate parsing
+ //dates so I decided not to implement support for 'expires'
+ //(which is deprecated) and instead focus on the easier 'max-age'
+ //(which succeeds 'expires')
+ cookie = {};//keyword properties of the cookie
+ cookie['domain'] = url.hostname;
+ cookie['path'] = url.path||'/';
+ for(i=0;i -1){
+ name = __trim__(attrs[i].slice(0,index));
+ value = __trim__(attrs[i].slice(index+1));
+ if(name=='max-age'){
+ //we'll have to when to check these
+ //and garbage collect expired cookies
+ cookie[name] = parseInt(value, 10);
+ } else if( name == 'domain' ){
+ if(__domainValid__(url, value)){
+ cookie['domain'] = value;
+ }
+ } else if( name == 'path' ){
+ //not sure of any special logic for path
+ cookie['path'] = value;
+ } else {
+ //its not a cookie keyword so store it in our array of properties
+ //and we'll serialize individually in a moment
+ properties[name] = value;
+ }
+ }else{
+ if( attrs[i] == 'secure' ){
+ cookie[attrs[i]] = true;
+ }
+ }
+ }
+ if(!('max-age' in cookie)){
+ //it's a transient cookie so it only lasts as long as
+ //the window.location remains the same (ie in-memory cookie)
+ __mergeCookie__(Envjs.cookies.temporary, cookie, properties);
+ }else{
+ //the cookie is persistent
+ __mergeCookie__(Envjs.cookies.persistent, cookie, properties);
+ Envjs.saveCookies();
+ }
+};
+
+function __domainValid__(url, value){
+ var i,
+ domainParts = url.hostname.split('.').reverse(),
+ newDomainParts = value.split('.').reverse();
+ if(newDomainParts.length > 1){
+ for(i=0;i -1) {
+ for (name in cookies[domain][path]) {
+ // console.log('cookie domain path name %s', name);
+ cookieString +=
+ ((i++ > 0)?'; ':'') +
+ name + "=" +
+ cookies[domain][path][name].value;
+ }
+ }
+ }
+ }
+ }
+ return cookieString;
+};
+
+function __mergeCookie__(target, cookie, properties){
+ var name, now;
+ if(!target[cookie.domain]){
+ target[cookie.domain] = {};
+ }
+ if(!target[cookie.domain][cookie.path]){
+ target[cookie.domain][cookie.path] = {};
+ }
+ for(name in properties){
+ now = new Date().getTime();
+ target[cookie.domain][cookie.path][name] = {
+ "value":properties[name],
+ "secure":cookie.secure,
+ "max-age":cookie['max-age'],
+ "date-created":now,
+ "expiration":(cookie['max-age']===0) ?
+ 0 :
+ now + cookie['max-age']
+ };
+ //console.log('cookie is %o',target[cookie.domain][cookie.path][name]);
+ }
+};
+
+})();//end cookies
+/*
+ http://www.JSON.org/json2.js
+ 2008-07-15
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+try{ JSON; }catch(e){
+JSON = function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ String.prototype.toJSON = function (key) {
+ return String(this);
+ };
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+ escapeable.lastIndex = 0;
+ return escapeable.test(string) ?
+ '"' + string.replace(escapeable, function (a) {
+ var c = meta[a];
+ if (typeof c === 'string') {
+ return c;
+ }
+ return '\\u' + ('0000' +
+ (+(a.charCodeAt(0))).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+ return String(value);
+
+ case 'object':
+
+ if (!value) {
+ return 'null';
+ }
+ gap += indent;
+ partial = [];
+
+ if (typeof value.length === 'number' &&
+ !(value.propertyIsEnumerable('length'))) {
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+ return {
+ stringify: function (value, replacer, space) {
+
+ var i;
+ gap = '';
+ indent = '';
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+ return str('', {'': value});
+ },
+
+
+ parse: function (text, reviver) {
+ var j;
+ function walk(holder, key) {
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' + ('0000' +
+ (+(a.charCodeAt(0))).toString(16)).slice(-4);
+ });
+ }
+
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ j = eval('(' + text + ')');
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+ throw new SyntaxError('JSON.parse');
+ }
+ };
+}();
+
+}
+
+/**
+ * synchronizes thread modifications
+ * @param {Function} fn
+ */
+Envjs.sync = function(fn){};
+
+/**
+ * sleep thread for specified duration
+ * @param {Object} millseconds
+ */
+Envjs.sleep = function(millseconds){};
+
+/**
+ * Interval to wait on event loop when nothing is happening
+ */
+Envjs.WAIT_INTERVAL = 20;//milliseconds
+
+/*
+ * Copyright (c) 2010 Nick Galbreath
+ * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * 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.
+ */
+
+/*
+ * url processing in the spirit of python's urlparse module
+ * see `pydoc urlparse` or
+ * http://docs.python.org/library/urlparse.html
+ *
+ * urlsplit: break apart a URL into components
+ * urlunsplit: reconsistute a URL from componets
+ * urljoin: join an absolute and another URL
+ * urldefrag: remove the fragment from a URL
+ *
+ * Take a look at the tests in urlparse-test.html
+ *
+ * On URL Normalization:
+ *
+ * urlsplit only does minor normalization the components Only scheme
+ * and hostname are lowercased urljoin does a bit more, normalizing
+ * paths with "." and "..".
+
+ * urlnormalize adds additional normalization
+ *
+ * * removes default port numbers
+ * http://abc.com:80/ -> http://abc.com/, etc
+ * * normalizes path
+ * http://abc.com -> http://abc.com/
+ * and other "." and ".." cleanups
+ * * if file, remove query and fragment
+ *
+ * It does not do:
+ * * normalizes escaped hex values
+ * http://abc.com/%7efoo -> http://abc.com/%7Efoo
+ * * normalize '+' <--> '%20'
+ *
+ * Differences with Python
+ *
+ * The javascript urlsplit returns a normal object with the following
+ * properties: scheme, netloc, hostname, port, path, query, fragment.
+ * All properties are read-write.
+ *
+ * In python, the resulting object is not a dict, but a specialized,
+ * read-only, and has alternative tuple interface (e.g. obj[0] ==
+ * obj.scheme). It's not clear why such a simple function requires
+ * a unique datastructure.
+ *
+ * urlunsplit in javascript takes an duck-typed object,
+ * { scheme: 'http', netloc: 'abc.com', ...}
+ * while in * python it takes a list-like object.
+ * ['http', 'abc.com'... ]
+ *
+ * For all functions, the javascript version use
+ * hostname+port if netloc is missing. In python
+ * hostname+port were always ignored.
+ *
+ * Similar functionality in different languages:
+ *
+ * http://php.net/manual/en/function.parse-url.php
+ * returns assocative array but cannot handle relative URL
+ *
+ * TODO: test allowfragments more
+ * TODO: test netloc missing, but hostname present
+ */
+
+var urlparse = {};
+
+// Unlike to be useful standalone
+//
+// NORMALIZE PATH with "../" and "./"
+// http://en.wikipedia.org/wiki/URL_normalization
+// http://tools.ietf.org/html/rfc3986#section-5.2.3
+//
+urlparse.normalizepath = function(path)
+{
+ if (!path || path === '/') {
+ return '/';
+ }
+
+ var parts = path.split('/');
+
+ var newparts = [];
+ // make sure path always starts with '/'
+ if (parts[0]) {
+ newparts.push('');
+ }
+
+ for (var i = 0; i < parts.length; ++i) {
+ if (parts[i] === '..') {
+ if (newparts.length > 1) {
+ newparts.pop();
+ } else {
+ newparts.push(parts[i]);
+ }
+ } else if (parts[i] != '.') {
+ newparts.push(parts[i]);
+ }
+ }
+
+ path = newparts.join('/');
+ if (!path) {
+ path = '/';
+ }
+ return path;
+};
+
+//
+// Does many of the normalizations that the stock
+// python urlsplit/urlunsplit/urljoin neglects
+//
+// Doesn't do hex-escape normalization on path or query
+// %7e -> %7E
+// Nor, '+' <--> %20 translation
+//
+urlparse.urlnormalize = function(url)
+{
+ var parts = urlparse.urlsplit(url);
+ switch (parts.scheme) {
+ case 'file':
+ // files can't have query strings
+ // and we don't bother with fragments
+ parts.query = '';
+ parts.fragment = '';
+ break;
+ case 'http':
+ case 'https':
+ // remove default port
+ if ((parts.scheme === 'http' && parts.port == 80) ||
+ (parts.scheme === 'https' && parts.port == 443)) {
+ parts.port = null;
+ // hostname is already lower case
+ parts.netloc = parts.hostname;
+ }
+ break;
+ default:
+ // if we don't have specific normalizations for this
+ // scheme, return the original url unmolested
+ return url;
+ }
+
+ // for [file|http|https]. Not sure about other schemes
+ parts.path = urlparse.normalizepath(parts.path);
+
+ return urlparse.urlunsplit(parts);
+};
+
+urlparse.urldefrag = function(url)
+{
+ var idx = url.indexOf('#');
+ if (idx == -1) {
+ return [ url, '' ];
+ } else {
+ return [ url.substr(0,idx), url.substr(idx+1) ];
+ }
+};
+
+urlparse.urlsplit = function(url, default_scheme, allow_fragments)
+{
+ var leftover;
+
+ if (typeof allow_fragments === 'undefined') {
+ allow_fragments = true;
+ }
+
+ // scheme (optional), host, port
+ var fullurl = /^([A-Za-z]+)?(:?\/\/)([0-9.\-A-Za-z]*)(?::(\d+))?(.*)$/;
+ // path, query, fragment
+ var parse_leftovers = /([^?#]*)?(?:\?([^#]*))?(?:#(.*))?$/;
+
+ var o = {};
+
+ var parts = url.match(fullurl);
+ if (parts) {
+ o.scheme = parts[1] || default_scheme || '';
+ o.hostname = parts[3].toLowerCase() || '';
+ o.port = parseInt(parts[4],10) || '';
+ // Probably should grab the netloc from regexp
+ // and then parse again for hostname/port
+
+ o.netloc = parts[3];
+ if (parts[4]) {
+ o.netloc += ':' + parts[4];
+ }
+
+ leftover = parts[5];
+ } else {
+ o.scheme = default_scheme || '';
+ o.netloc = '';
+ o.hostname = '';
+ leftover = url;
+ }
+ o.scheme = o.scheme.toLowerCase();
+
+ parts = leftover.match(parse_leftovers);
+
+ o.path = parts[1] || '';
+ o.query = parts[2] || '';
+
+ if (allow_fragments) {
+ o.fragment = parts[3] || '';
+ } else {
+ o.fragment = '';
+ }
+
+ return o;
+};
+
+urlparse.urlunsplit = function(o) {
+ var s = '';
+ if (o.scheme) {
+ s += o.scheme + '://';
+ }
+
+ if (o.netloc) {
+ if (s == '') {
+ s += '//';
+ }
+ s += o.netloc;
+ } else if (o.hostname) {
+ // extension. Python only uses netloc
+ if (s == '') {
+ s += '//';
+ }
+ s += o.hostname;
+ if (o.port) {
+ s += ':' + o.port;
+ }
+ }
+
+ if (o.path) {
+ s += o.path;
+ }
+
+ if (o.query) {
+ s += '?' + o.query;
+ }
+ if (o.fragment) {
+ s += '#' + o.fragment;
+ }
+ return s;
+};
+
+urlparse.urljoin = function(base, url, allow_fragments)
+{
+ if (typeof allow_fragments === 'undefined') {
+ allow_fragments = true;
+ }
+
+ var url_parts = urlparse.urlsplit(url);
+
+ // if url parts has a scheme (i.e. absolute)
+ // then nothing to do
+ if (url_parts.scheme) {
+ if (! allow_fragments) {
+ return url;
+ } else {
+ return urlparse.urldefrag(url)[0];
+ }
+ }
+ var base_parts = urlparse.urlsplit(base);
+
+ // copy base, only if not present
+ if (!base_parts.scheme) {
+ base_parts.scheme = url_parts.scheme;
+ }
+
+ // copy netloc, only if not present
+ if (!base_parts.netloc || !base_parts.hostname) {
+ base_parts.netloc = url_parts.netloc;
+ base_parts.hostname = url_parts.hostname;
+ base_parts.port = url_parts.port;
+ }
+
+ // paths
+ if (url_parts.path.length > 0) {
+ if (url_parts.path.charAt(0) == '/') {
+ base_parts.path = url_parts.path;
+ } else {
+ // relative path.. get rid of "current filename" and
+ // replace. Same as var parts =
+ // base_parts.path.split('/'); parts[parts.length-1] =
+ // url_parts.path; base_parts.path = parts.join('/');
+ var idx = base_parts.path.lastIndexOf('/');
+ if (idx == -1) {
+ base_parts.path = url_parts.path;
+ } else {
+ base_parts.path = base_parts.path.substr(0,idx) + '/' +
+ url_parts.path;
+ }
+ }
+ }
+
+ // clean up path
+ base_parts.path = urlparse.normalizepath(base_parts.path);
+
+ // copy query string
+ base_parts.query = url_parts.query;
+
+ // copy fragments
+ if (allow_fragments) {
+ base_parts.fragment = url_parts.fragment;
+ } else {
+ base_parts.fragment = '';
+ }
+
+ return urlparse.urlunsplit(base_parts);
+};
+
+/**
+ * getcwd - named after posix call of same name (see 'man 2 getcwd')
+ *
+ */
+Envjs.getcwd = function() {
+ return '.';
+};
+
+/**
+ * resolves location relative to doc location
+ *
+ * @param {Object} path Relative or absolute URL
+ * @param {Object} base (semi-optional) The base url used in resolving "path" above
+ */
+Envjs.uri = function(path, base) {
+ //console.log('constructing uri from path %s and base %s', path, base);
+
+ // Semi-common trick is to make an iframe with src='javascript:false'
+ // (or some equivalent). By returning '', the load is skipped
+ if (path.indexOf('javascript') === 0) {
+ return '';
+ }
+
+ // if path is absolute, then just normalize and return
+ if (path.match('^[a-zA-Z]+://')) {
+ return urlparse.urlnormalize(path);
+ }
+
+ // interesting special case, a few very large websites use
+ // '//foo/bar/' to mean 'http://foo/bar'
+ if (path.match('^//')) {
+ path = 'http:' + path;
+ }
+
+ // if base not passed in, try to get it from document
+ // Ideally I would like the caller to pass in document.baseURI to
+ // make this more self-sufficient and testable
+ if (!base && document) {
+ base = document.baseURI;
+ }
+
+ // about:blank doesn't count
+ if (base === 'about:blank'){
+ base = '';
+ }
+
+ // if base is still empty, then we are in QA mode loading local
+ // files. Get current working directory
+ if (!base) {
+ base = 'file://' + Envjs.getcwd() + '/';
+ }
+ // handles all cases if path is abosulte or relative to base
+ // 3rd arg is "false" --> remove fragments
+ var newurl = urlparse.urlnormalize(urlparse.urljoin(base, path, false));
+
+ return newurl;
+};
+
+
+
+/**
+ * Used in the XMLHttpRquest implementation to run a
+ * request in a seperate thread
+ * @param {Object} fn
+ */
+Envjs.runAsync = function(fn){};
+
+
+/**
+ * Used to write to a local file
+ * @param {Object} text
+ * @param {Object} url
+ */
+Envjs.writeToFile = function(text, url){};
+
+
+/**
+ * Used to write to a local file
+ * @param {Object} text
+ * @param {Object} suffix
+ */
+Envjs.writeToTempFile = function(text, suffix){};
+
+/**
+ * Used to read the contents of a local file
+ * @param {Object} url
+ */
+Envjs.readFromFile = function(url){};
+
+/**
+ * Used to delete a local file
+ * @param {Object} url
+ */
+Envjs.deleteFile = function(url){};
+
+/**
+ * establishes connection and calls responsehandler
+ * @param {Object} xhr
+ * @param {Object} responseHandler
+ * @param {Object} data
+ */
+Envjs.connection = function(xhr, responseHandler, data){};
+
+
+__extend__(Envjs, urlparse);
+
+/**
+ * Makes an object window-like by proxying object accessors
+ * @param {Object} scope
+ * @param {Object} parent
+ */
+Envjs.proxy = function(scope, parent, aliasList){};
+
+Envjs.javaEnabled = false;
+
+Envjs.homedir = '';
+Envjs.tmpdir = '';
+Envjs.os_name = '';
+Envjs.os_arch = '';
+Envjs.os_version = '';
+Envjs.lang = '';
+Envjs.platform = '';
+
+/**
+ *
+ * @param {Object} frameElement
+ * @param {Object} url
+ */
+Envjs.loadFrame = function(frame, url){
+ try {
+ if(frame.contentWindow){
+ //mark for garbage collection
+ frame.contentWindow = null;
+ }
+
+ //create a new scope for the window proxy
+ //platforms will need to override this function
+ //to make sure the scope is global-like
+ frame.contentWindow = (function(){return this;})();
+ new Window(frame.contentWindow, window);
+
+ //I dont think frames load asynchronously in firefox
+ //and I think the tests have verified this but for
+ //some reason I'm less than confident... Are there cases?
+ frame.contentDocument = frame.contentWindow.document;
+ frame.contentDocument.async = false;
+ if(url){
+ //console.log('envjs.loadFrame async %s', frame.contentDocument.async);
+ frame.contentWindow.location = url;
+ }
+ } catch(e) {
+ console.log("failed to load frame content: from %s %s", url, e);
+ }
+};
+
+
+// The following are in rhino/window.js
+// TODO: Envjs.unloadFrame
+// TODO: Envjs.proxy
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+/*
+ * Envjs rhino-env.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+var __context__ = Packages.org.mozilla.javascript.Context.getCurrentContext();
+
+Envjs.platform = "Rhino";
+Envjs.revision = "1.7.0.rc2";
+
+/*
+ * Envjs rhino-env.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author john resig
+ */
+// Helper method for extending one object with another.
+function __extend__(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+ if ( g || s ) {
+ if ( g ) { a.__defineGetter__(i, g); }
+ if ( s ) { a.__defineSetter__(i, s); }
+ } else {
+ a[i] = b[i];
+ }
+ } return a;
+}
+
+/**
+ * Writes message to system out.
+ *
+ * Some sites redefine 'print' as in 'window.print', so instead of
+ * printing to stdout, you are popping open a new window, which might
+ * call print, etc, etc,etc This can cause infinite loops and can
+ * exhausing all memory.
+ *
+ * By defining this upfront now, Envjs.log will always call the native 'print'
+ * function
+ *
+ * @param {Object} message
+ */
+Envjs.log = print;
+
+Envjs.lineSource = function(e){
+ return e&&e.rhinoException?e.rhinoException.lineSource():"(line ?)";
+};
+/**
+ * load and execute script tag text content
+ * @param {Object} script
+ */
+Envjs.loadInlineScript = function(script){
+ if(script.ownerDocument.ownerWindow){
+ Envjs.eval(
+ script.ownerDocument.ownerWindow,
+ script.text,
+ 'eval('+script.text.substring(0,16)+'...):'+new Date().getTime()
+ );
+ }else{
+ Envjs.eval(
+ __this__,
+ script.text,
+ 'eval('+script.text.substring(0,16)+'...):'+new Date().getTime()
+ );
+ }
+ //console.log('evaluated at scope %s \n%s',
+ // script.ownerDocument.ownerWindow.guid, script.text);
+};
+
+
+Envjs.eval = function(context, source, name){
+ __context__.evaluateString(
+ context,
+ source,
+ name,
+ 0,
+ null
+ );
+};
+
+//Temporary patch for parser module
+Packages.org.mozilla.javascript.Context.
+ getCurrentContext().setOptimizationLevel(-1);
+
+/**
+ * Rhino provides a very succinct 'sync'
+ * @param {Function} fn
+ */
+try{
+ Envjs.sync = sync;
+ Envjs.spawn = spawn;
+} catch(e){
+ //sync unavailable on AppEngine
+ Envjs.sync = function(fn){
+ //console.log('Threadless platform, sync is safe');
+ return fn;
+ };
+
+ Envjs.spawn = function(fn){
+ //console.log('Threadless platform, spawn shares main thread.');
+ return fn();
+ };
+}
+
+/**
+ * sleep thread for specified duration
+ * @param {Object} millseconds
+ */
+Envjs.sleep = function(millseconds){
+ try{
+ java.lang.Thread.currentThread().sleep(millseconds);
+ }catch(e){
+ console.log('Threadless platform, cannot sleep.');
+ }
+};
+
+/**
+ * provides callback hook for when the system exits
+ */
+Envjs.onExit = function(callback){
+ var rhino = Packages.org.mozilla.javascript,
+ contextFactory = __context__.getFactory(),
+ listener = new rhino.ContextFactory.Listener({
+ contextReleased: function(context){
+ if(context === __context__)
+ console.log('context released', context);
+ contextFactory.removeListener(this);
+ if(callback)
+ callback();
+ }
+ });
+ contextFactory.addListener(listener);
+};
+
+/**
+ * Get 'Current Working Directory'
+ */
+Envjs.getcwd = function() {
+ return java.lang.System.getProperty('user.dir');
+}
+
+/**
+ *
+ * @param {Object} fn
+ * @param {Object} onInterupt
+ */
+Envjs.runAsync = function(fn, onInterupt){
+ ////Envjs.debug("running async");
+ var running = true,
+ run;
+
+ try{
+ run = Envjs.sync(function(){
+ fn();
+ Envjs.wait();
+ });
+ Envjs.spawn(run);
+ }catch(e){
+ console.log("error while running async operation", e);
+ try{if(onInterrupt)onInterrupt(e)}catch(ee){};
+ }
+};
+
+/**
+ * Used to write to a local file
+ * @param {Object} text
+ * @param {Object} url
+ */
+Envjs.writeToFile = function(text, url){
+ //Envjs.debug("writing text to url : " + url);
+ var out = new java.io.FileWriter(
+ new java.io.File(
+ new java.net.URI(url.toString())));
+ out.write( text, 0, text.length );
+ out.flush();
+ out.close();
+};
+
+/**
+ * Used to write to a local file
+ * @param {Object} text
+ * @param {Object} suffix
+ */
+Envjs.writeToTempFile = function(text, suffix){
+ //Envjs.debug("writing text to temp url : " + suffix);
+ // Create temp file.
+ var temp = java.io.File.createTempFile("envjs-tmp", suffix);
+
+ // Delete temp file when program exits.
+ temp.deleteOnExit();
+
+ // Write to temp file
+ var out = new java.io.FileWriter(temp);
+ out.write(text, 0, text.length);
+ out.close();
+ return temp.getAbsolutePath().toString()+'';
+};
+
+
+/**
+ * Used to read the contents of a local file
+ * @param {Object} url
+ */
+Envjs.readFromFile = function( url ){
+ var fileReader = new java.io.FileReader(
+ new java.io.File(
+ new java.net.URI( url )));
+
+ var stringwriter = new java.io.StringWriter(),
+ buffer = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 1024),
+ length;
+
+ while ((length = fileReader.read(buffer, 0, 1024)) != -1) {
+ stringwriter.write(buffer, 0, length);
+ }
+
+ stringwriter.close();
+ return stringwriter.toString()+"";
+};
+
+
+/**
+ * Used to delete a local file
+ * @param {Object} url
+ */
+Envjs.deleteFile = function(url){
+ var file = new java.io.File( new java.net.URI( url ) );
+ file["delete"]();
+};
+
+/**
+ * establishes connection and calls responsehandler
+ * @param {Object} xhr
+ * @param {Object} responseHandler
+ * @param {Object} data
+ */
+Envjs.connection = function(xhr, responseHandler, data){
+ var url = java.net.URL(xhr.url),
+ connection,
+ header,
+ outstream,
+ buffer,
+ length,
+ binary = false,
+ name, value,
+ contentEncoding,
+ instream,
+ responseXML,
+ i;
+ if ( /^file\:/.test(url) ) {
+ try{
+ if ( "PUT" == xhr.method || "POST" == xhr.method ) {
+ data = data || "" ;
+ Envjs.writeToFile(data, url);
+ xhr.readyState = 4;
+ //could be improved, I just cant recall the correct http codes
+ xhr.status = 200;
+ xhr.statusText = "";
+ } else if ( xhr.method == "DELETE" ) {
+ Envjs.deleteFile(url);
+ xhr.readyState = 4;
+ //could be improved, I just cant recall the correct http codes
+ xhr.status = 200;
+ xhr.statusText = "";
+ } else {
+ connection = url.openConnection();
+ connection.connect();
+ //try to add some canned headers that make sense
+
+ try{
+ if(xhr.url.match(/html$/)){
+ xhr.responseHeaders["Content-Type"] = 'text/html';
+ }else if(xhr.url.match(/.xml$/)){
+ xhr.responseHeaders["Content-Type"] = 'text/xml';
+ }else if(xhr.url.match(/.js$/)){
+ xhr.responseHeaders["Content-Type"] = 'text/javascript';
+ }else if(xhr.url.match(/.json$/)){
+ xhr.responseHeaders["Content-Type"] = 'application/json';
+ }else{
+ xhr.responseHeaders["Content-Type"] = 'text/plain';
+ }
+ //xhr.responseHeaders['Last-Modified'] = connection.getLastModified();
+ //xhr.responseHeaders['Content-Length'] = headerValue+'';
+ //xhr.responseHeaders['Date'] = new Date()+'';*/
+ }catch(e){
+ console.log('failed to load response headers',e);
+ }
+ }
+ }catch(e){
+ console.log('failed to open file %s %s', url, e);
+ connection = null;
+ xhr.readyState = 4;
+ xhr.statusText = "Local File Protocol Error";
+ xhr.responseText = ""+ e+ "
";
+ }
+ } else {
+ connection = url.openConnection();
+ connection.setRequestMethod( xhr.method );
+
+ // Add headers to Java connection
+ for (header in xhr.headers){
+ connection.addRequestProperty(header+'', xhr.headers[header]+'');
+ }
+
+ //write data to output stream if required
+ if(data){
+ if(data instanceof Document){
+ if ( xhr.method == "PUT" || xhr.method == "POST" ) {
+ connection.setDoOutput(true);
+ outstream = connection.getOutputStream(),
+ xml = (new XMLSerializer()).serializeToString(data);
+ buffer = new java.lang.String(xml).getBytes('UTF-8');
+ outstream.write(buffer, 0, buffer.length);
+ outstream.close();
+ }
+ }else if(data.length&&data.length>0){
+ if ( xhr.method == "PUT" || xhr.method == "POST" ) {
+ connection.setDoOutput(true);
+ outstream = connection.getOutputStream();
+ buffer = new java.lang.String(data).getBytes('UTF-8');
+ outstream.write(buffer, 0, buffer.length);
+ outstream.close();
+ }
+ }
+ connection.connect();
+ }else{
+ connection.connect();
+ }
+ }
+
+ if(connection){
+ try{
+ length = connection.getHeaderFields().size();
+ // Stick the response headers into responseHeaders
+ for (i = 0; i < length; i++) {
+ name = connection.getHeaderFieldKey(i);
+ value = connection.getHeaderField(i);
+ if (name)
+ xhr.responseHeaders[name+''] = value+'';
+ }
+ }catch(e){
+ console.log('failed to load response headers \n%s',e);
+ }
+
+ xhr.readyState = 4;
+ xhr.status = parseInt(connection.responseCode,10) || undefined;
+ xhr.statusText = connection.responseMessage || "";
+
+ contentEncoding = connection.getContentEncoding() || "utf-8";
+ instream = null;
+ responseXML = null;
+
+ try{
+ //console.log('contentEncoding %s', contentEncoding);
+ if( contentEncoding.equalsIgnoreCase("gzip") ||
+ contentEncoding.equalsIgnoreCase("decompress")){
+ //zipped content
+ binary = true;
+ outstream = new java.io.ByteArrayOutputStream();
+ buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024);
+ instream = new java.util.zip.GZIPInputStream(connection.getInputStream())
+ }else{
+ //this is a text file
+ outstream = new java.io.StringWriter();
+ buffer = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 1024);
+ instream = new java.io.InputStreamReader(connection.getInputStream());
+ }
+ }catch(e){
+ if (connection.getResponseCode() == 404){
+ console.log('failed to open connection stream \n %s %s',
+ e.toString(), e);
+ }else{
+ console.log('failed to open connection stream \n %s %s',
+ e.toString(), e);
+ }
+ instream = connection.getErrorStream();
+ }
+
+ while ((length = instream.read(buffer, 0, 1024)) != -1) {
+ outstream.write(buffer, 0, length);
+ }
+
+ outstream.close();
+ instream.close();
+
+ if(binary){
+ xhr.responseText = new String(outstream.toByteArray(), 'UTF-8') + '';
+ }else{
+ xhr.responseText = outstream.toString() + '';
+ }
+
+ }
+ if(responseHandler){
+ //Envjs.debug('calling ajax response handler');
+ responseHandler();
+ }
+};
+
+//Since we're running in rhino I guess we can safely assume
+//java is 'enabled'. I'm sure this requires more thought
+//than I've given it here
+Envjs.javaEnabled = true;
+
+Envjs.homedir = java.lang.System.getProperty("user.home");
+Envjs.tmpdir = java.lang.System.getProperty("java.io.tmpdir");
+Envjs.os_name = java.lang.System.getProperty("os.name");
+Envjs.os_arch = java.lang.System.getProperty("os.arch");
+Envjs.os_version = java.lang.System.getProperty("os.version");
+Envjs.lang = java.lang.System.getProperty("user.lang");
+
+
+/**
+ *
+ * @param {Object} frameElement
+ * @param {Object} url
+ */
+Envjs.loadFrame = function(frame, url){
+ try {
+ if(frame.contentWindow){
+ //mark for garbage collection
+ frame.contentWindow = null;
+ }
+
+ //create a new scope for the window proxy
+ frame.contentWindow = Envjs.proxy();
+ new Window(frame.contentWindow, window);
+
+ //I dont think frames load asynchronously in firefox
+ //and I think the tests have verified this but for
+ //some reason I'm less than confident... Are there cases?
+ frame.contentDocument = frame.contentWindow.document;
+ frame.contentDocument.async = false;
+ if(url){
+ //console.log('envjs.loadFrame async %s', frame.contentDocument.async);
+ frame.contentWindow.location = url;
+ }
+ } catch(e) {
+ console.log("failed to load frame content: from %s %s", url, e);
+ }
+};
+
+/**
+ * unloadFrame
+ * @param {Object} frame
+ */
+Envjs.unloadFrame = function(frame){
+ var all, length, i;
+ try{
+ //TODO: probably self-referencing structures within a document tree
+ //preventing it from being entirely garbage collected once orphaned.
+ //Should have code to walk tree and break all links between contained
+ //objects.
+ frame.contentDocument = null;
+ if(frame.contentWindow){
+ frame.contentWindow.close();
+ }
+ gc();
+ }catch(e){
+ console.log(e);
+ }
+};
+
+/**
+ * Makes an object window-like by proxying object accessors
+ * @param {Object} scope
+ * @param {Object} parent
+ */
+Envjs.proxy = function(scope, parent) {
+ try{
+ if(scope+'' == '[object global]'){
+ return scope
+ }else{
+ return __context__.initStandardObjects();
+ }
+ }catch(e){
+ console.log('failed to init standard objects %s %s \n%s', scope, parent, e);
+ }
+
+};
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+
+/**
+ * @author envjs team
+ */
+var Console,
+ console;
+
+/*
+ * Envjs console.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author envjs team
+ * borrowed 99%-ish with love from firebug-lite
+ *
+ * http://wiki.commonjs.org/wiki/Console
+ */
+Console = function(module){
+ var $level,
+ $logger,
+ $null = function(){};
+
+
+ if(Envjs[module] && Envjs[module].loglevel){
+ $level = Envjs.module.loglevel;
+ $logger = {
+ log: function(level){
+ logFormatted(arguments, (module)+" ");
+ },
+ debug: $level>1 ? $null: function() {
+ logFormatted(arguments, (module)+" debug");
+ },
+ info: $level>2 ? $null:function(){
+ logFormatted(arguments, (module)+" info");
+ },
+ warn: $level>3 ? $null:function(){
+ logFormatted(arguments, (module)+" warning");
+ },
+ error: $level>4 ? $null:function(){
+ logFormatted(arguments, (module)+" error");
+ }
+ };
+ } else {
+ $logger = {
+ log: function(level){
+ logFormatted(arguments, "");
+ },
+ debug: $null,
+ info: $null,
+ warn: $null,
+ error: $null
+ };
+ }
+
+ return $logger;
+};
+
+console = new Console("console",1);
+
+function logFormatted(objects, className)
+{
+ var html = [];
+
+ var format = objects[0];
+ var objIndex = 0;
+
+ if (typeof(format) != "string")
+ {
+ format = "";
+ objIndex = -1;
+ }
+
+ var parts = parseFormat(format);
+ for (var i = 0; i < parts.length; ++i)
+ {
+ var part = parts[i];
+ if (part && typeof(part) == "object")
+ {
+ var object = objects[++objIndex];
+ part.appender(object, html);
+ }
+ else {
+ appendText(part, html);
+ }
+ }
+
+ for (var i = objIndex+1; i < objects.length; ++i)
+ {
+ appendText(" ", html);
+
+ var object = objects[i];
+ if (typeof(object) == "string") {
+ appendText(object, html);
+ } else {
+ appendObject(object, html);
+ }
+ }
+
+ Envjs.log(html.join(' '));
+}
+
+function parseFormat(format)
+{
+ var parts = [];
+
+ var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/;
+ var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat};
+
+ for (var m = reg.exec(format); m; m = reg.exec(format))
+ {
+ var type = m[8] ? m[8] : m[5];
+ var appender = type in appenderMap ? appenderMap[type] : appendObject;
+ var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
+
+ parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
+ parts.push({appender: appender, precision: precision});
+
+ format = format.substr(m.index+m[0].length);
+ }
+
+ parts.push(format);
+
+ return parts;
+}
+
+function escapeHTML(value)
+{
+ return value;
+}
+
+function objectToString(object)
+{
+ try
+ {
+ return object+"";
+ }
+ catch (exc)
+ {
+ return null;
+ }
+}
+
+// ********************************************************************************************
+
+function appendText(object, html)
+{
+ html.push(escapeHTML(objectToString(object)));
+}
+
+function appendNull(object, html)
+{
+ html.push(escapeHTML(objectToString(object)));
+}
+
+function appendString(object, html)
+{
+ html.push(escapeHTML(objectToString(object)));
+}
+
+function appendInteger(object, html)
+{
+ html.push(escapeHTML(objectToString(object)));
+}
+
+function appendFloat(object, html)
+{
+ html.push(escapeHTML(objectToString(object)));
+}
+
+function appendFunction(object, html)
+{
+ var reName = /function ?(.*?)\(/;
+ var m = reName.exec(objectToString(object));
+ var name = m ? m[1] : "function";
+ html.push(escapeHTML(name));
+}
+
+function appendObject(object, html)
+{
+ try
+ {
+ if (object == undefined) {
+ appendNull("undefined", html);
+ } else if (object == null) {
+ appendNull("null", html);
+ } else if (typeof object == "string") {
+ appendString(object, html);
+ } else if (typeof object == "number") {
+ appendInteger(object, html);
+ } else if (typeof object == "function") {
+ appendFunction(object, html);
+ } else if (object.nodeType == 1) {
+ appendSelector(object, html);
+ } else if (typeof object == "object") {
+ appendObjectFormatted(object, html);
+ } else {
+ appendText(object, html);
+ }
+ }
+ catch (exc)
+ {
+ }
+}
+
+function appendObjectFormatted(object, html)
+{
+ var text = objectToString(object);
+ var reObject = /\[object (.*?)\]/;
+
+ var m = reObject.exec(text);
+ html.push( m ? m[1] : text);
+}
+
+function appendSelector(object, html)
+{
+
+ html.push(escapeHTML(object.nodeName.toLowerCase()));
+ if (object.id) {
+ html.push(escapeHTML(object.id));
+ }
+ if (object.className) {
+ html.push(escapeHTML(object.className));
+ }
+}
+
+function appendNode(node, html)
+{
+ if (node.nodeType == 1)
+ {
+ html.push( node.nodeName.toLowerCase());
+
+ for (var i = 0; i < node.attributes.length; ++i)
+ {
+ var attr = node.attributes[i];
+ if (!attr.specified) {
+ continue;
+ }
+
+ html.push( attr.nodeName.toLowerCase(),escapeHTML(attr.nodeValue));
+ }
+
+ if (node.firstChild)
+ {
+ for (var child = node.firstChild; child; child = child.nextSibling) {
+ appendNode(child, html);
+ }
+
+ html.push( node.nodeName.toLowerCase());
+ }
+ }
+ else if (node.nodeType === 3)
+ {
+ html.push(escapeHTML(node.nodeValue));
+ }
+};
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+/*
+ * Envjs dom.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ *
+ * Parts of the implementation were originally written by:\
+ * and Jon van Noort (jon@webarcana.com.au) \
+ * and David Joham (djoham@yahoo.com)",\
+ * and Scott Severtson
+ *
+ * This file simply provides the global definitions we need to \
+ * be able to correctly implement to core browser DOM interfaces."
+ */
+
+var Attr,
+ CDATASection,
+ CharacterData,
+ Comment,
+ Document,
+ DocumentFragment,
+ DocumentType,
+ DOMException,
+ DOMImplementation,
+ Element,
+ Entity,
+ EntityReference,
+ NamedNodeMap,
+ Namespace,
+ Node,
+ NodeList,
+ Notation,
+ ProcessingInstruction,
+ Text,
+ Range,
+ XMLSerializer,
+ DOMParser;
+
+
+
+/*
+ * Envjs dom.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author john resig
+ */
+// Helper method for extending one object with another.
+function __extend__(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+ if ( g || s ) {
+ if ( g ) { a.__defineGetter__(i, g); }
+ if ( s ) { a.__defineSetter__(i, s); }
+ } else {
+ a[i] = b[i];
+ }
+ } return a;
+}
+
+/**
+ * @author john resig
+ */
+//from jQuery
+function __setArray__( target, array ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ target.length = 0;
+ Array.prototype.push.apply( target, array );
+}
+
+/**
+ * @class NodeList -
+ * provides the abstraction of an ordered collection of nodes
+ *
+ * @param ownerDocument : Document - the ownerDocument
+ * @param parentNode : Node - the node that the NodeList is attached to (or null)
+ */
+NodeList = function(ownerDocument, parentNode) {
+ this.length = 0;
+ this.parentNode = parentNode;
+ this.ownerDocument = ownerDocument;
+ this._readonly = false;
+ __setArray__(this, []);
+};
+
+__extend__(NodeList.prototype, {
+ item : function(index) {
+ var ret = null;
+ if ((index >= 0) && (index < this.length)) {
+ // bounds check
+ ret = this[index];
+ }
+ // if the index is out of bounds, default value null is returned
+ return ret;
+ },
+ get xml() {
+ var ret = "",
+ i;
+
+ // create string containing the concatenation of the string values of each child
+ for (i=0; i < this.length; i++) {
+ if(this[i]){
+ if(this[i].nodeType == Node.TEXT_NODE && i>0 &&
+ this[i-1].nodeType == Node.TEXT_NODE){
+ //add a single space between adjacent text nodes
+ ret += " "+this[i].xml;
+ }else{
+ ret += this[i].xml;
+ }
+ }
+ }
+ return ret;
+ },
+ toArray: function () {
+ var children = [],
+ i;
+ for ( i=0; i < this.length; i++) {
+ children.push (this[i]);
+ }
+ return children;
+ },
+ toString: function(){
+ return "[object NodeList]";
+ }
+});
+
+
+/**
+ * @method __findItemIndex__
+ * find the item index of the node
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param node : Node
+ * @return : int
+ */
+var __findItemIndex__ = function (nodelist, node) {
+ var ret = -1, i;
+ for (i=0; i= 0) && (refChildIndex <= nodelist.length)) {
+ // bounds check
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // node is a DocumentFragment
+ // append the children of DocumentFragment
+ Array.prototype.splice.apply(nodelist,
+ [refChildIndex, 0].concat(newChild.childNodes.toArray()));
+ }
+ else {
+ // append the newChild
+ Array.prototype.splice.apply(nodelist,[refChildIndex, 0, newChild]);
+ }
+ }
+};
+
+/**
+ * @method __replaceChild__
+ * replace the specified Node in the NodeList at the specified index
+ * Used by Node.replaceChild(). Note: Node.replaceChild() is responsible
+ * for Node Pointer surgery __replaceChild__ simply modifies the internal
+ * data structure (Array).
+ *
+ * @param newChild : Node - the Node to be inserted
+ * @param refChildIndex : int - the array index to hold the Node
+ */
+var __replaceChild__ = function(nodelist, newChild, refChildIndex) {
+ var ret = null;
+
+ // bounds check
+ if ((refChildIndex >= 0) && (refChildIndex < nodelist.length)) {
+ // preserve old child for return
+ ret = nodelist[refChildIndex];
+
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // node is a DocumentFragment
+ // get array containing children prior to refChild
+ Array.prototype.splice.apply(nodelist,
+ [refChildIndex, 1].concat(newChild.childNodes.toArray()));
+ }
+ else {
+ // simply replace node in array (links between Nodes are
+ // made at higher level)
+ nodelist[refChildIndex] = newChild;
+ }
+ }
+ // return replaced node
+ return ret;
+};
+
+/**
+ * @method __removeChild__
+ * remove the specified Node in the NodeList at the specified index
+ * Used by Node.removeChild(). Note: Node.removeChild() is responsible
+ * for Node Pointer surgery __removeChild__ simply modifies the internal
+ * data structure (Array).
+ * @param refChildIndex : int - the array index holding the Node to be removed
+ */
+var __removeChild__ = function(nodelist, refChildIndex) {
+ var ret = null;
+
+ if (refChildIndex > -1) {
+ // found it!
+ // return removed node
+ ret = nodelist[refChildIndex];
+
+ // rebuild array without removed child
+ Array.prototype.splice.apply(nodelist,[refChildIndex, 1]);
+ }
+ // return removed node
+ return ret;
+};
+
+/**
+ * @method __appendChild__
+ * append the specified Node to the NodeList. Used by Node.appendChild().
+ * Note: Node.appendChild() is responsible for Node Pointer surgery
+ * __appendChild__ simply modifies the internal data structure (Array).
+ * @param newChild : Node - the Node to be inserted
+ */
+var __appendChild__ = function(nodelist, newChild) {
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // node is a DocumentFragment
+ // append the children of DocumentFragment
+ Array.prototype.push.apply(nodelist, newChild.childNodes.toArray() );
+ } else {
+ // simply add node to array (links between Nodes are made at higher level)
+ Array.prototype.push.apply(nodelist, [newChild]);
+ }
+
+};
+
+/**
+ * @method __cloneNodes__ -
+ * Returns a NodeList containing clones of the Nodes in this NodeList
+ * @param deep : boolean -
+ * If true, recursively clone the subtree under each of the nodes;
+ * if false, clone only the nodes themselves (and their attributes,
+ * if it is an Element).
+ * @param parentNode : Node - the new parent of the cloned NodeList
+ * @return : NodeList - NodeList containing clones of the Nodes in this NodeList
+ */
+var __cloneNodes__ = function(nodelist, deep, parentNode) {
+ var cloneNodeList = new NodeList(nodelist.ownerDocument, parentNode);
+
+ // create list containing clones of each child
+ for (var i=0; i < nodelist.length; i++) {
+ __appendChild__(cloneNodeList, nodelist[i].cloneNode(deep));
+ }
+
+ return cloneNodeList;
+};
+
+
+var __ownerDocument__ = function(node){
+ return (node.nodeType == Node.DOCUMENT_NODE)?node:node.ownerDocument;
+};
+
+/**
+ * @class Node -
+ * The Node interface is the primary datatype for the entire
+ * Document Object Model. It represents a single node in the
+ * document tree.
+ * @param ownerDocument : Document - The Document object associated with this node.
+ */
+
+Node = function(ownerDocument) {
+ this.baseURI = 'about:blank';
+ this.namespaceURI = null;
+ this.nodeName = "";
+ this.nodeValue = null;
+
+ // A NodeList that contains all children of this node. If there are no
+ // children, this is a NodeList containing no nodes. The content of the
+ // returned NodeList is "live" in the sense that, for instance, changes to
+ // the children of the node object that it was created from are immediately
+ // reflected in the nodes returned by the NodeList accessors; it is not a
+ // static snapshot of the content of the node. This is true for every
+ // NodeList, including the ones returned by the getElementsByTagName method.
+ this.childNodes = new NodeList(ownerDocument, this);
+
+ // The first child of this node. If there is no such node, this is null
+ this.firstChild = null;
+ // The last child of this node. If there is no such node, this is null.
+ this.lastChild = null;
+ // The node immediately preceding this node. If there is no such node,
+ // this is null.
+ this.previousSibling = null;
+ // The node immediately following this node. If there is no such node,
+ // this is null.
+ this.nextSibling = null;
+
+ this.attributes = null;
+ // The namespaces in scope for this node
+ this._namespaces = new NamespaceNodeMap(ownerDocument, this);
+ this._readonly = false;
+
+ //IMPORTANT: These must come last so rhino will not iterate parent
+ // properties before child properties. (qunit.equiv issue)
+
+ // The parent of this node. All nodes, except Document, DocumentFragment,
+ // and Attr may have a parent. However, if a node has just been created
+ // and not yet added to the tree, or if it has been removed from the tree,
+ // this is null
+ this.parentNode = null;
+ // The Document object associated with this node
+ this.ownerDocument = ownerDocument;
+
+};
+
+// nodeType constants
+Node.ELEMENT_NODE = 1;
+Node.ATTRIBUTE_NODE = 2;
+Node.TEXT_NODE = 3;
+Node.CDATA_SECTION_NODE = 4;
+Node.ENTITY_REFERENCE_NODE = 5;
+Node.ENTITY_NODE = 6;
+Node.PROCESSING_INSTRUCTION_NODE = 7;
+Node.COMMENT_NODE = 8;
+Node.DOCUMENT_NODE = 9;
+Node.DOCUMENT_TYPE_NODE = 10;
+Node.DOCUMENT_FRAGMENT_NODE = 11;
+Node.NOTATION_NODE = 12;
+Node.NAMESPACE_NODE = 13;
+
+Node.DOCUMENT_POSITION_EQUAL = 0x00;
+Node.DOCUMENT_POSITION_DISCONNECTED = 0x01;
+Node.DOCUMENT_POSITION_PRECEDING = 0x02;
+Node.DOCUMENT_POSITION_FOLLOWING = 0x04;
+Node.DOCUMENT_POSITION_CONTAINS = 0x08;
+Node.DOCUMENT_POSITION_CONTAINED_BY = 0x10;
+Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
+
+
+__extend__(Node.prototype, {
+ get localName(){
+ return this.prefix?
+ this.nodeName.substring(this.prefix.length+1, this.nodeName.length):
+ this.nodeName;
+ },
+ get prefix(){
+ return this.nodeName.split(':').length>1?
+ this.nodeName.split(':')[0]:
+ null;
+ },
+ set prefix(value){
+ if(value === null){
+ this.nodeName = this.localName;
+ }else{
+ this.nodeName = value+':'+this.localName;
+ }
+ },
+ hasAttributes : function() {
+ if (this.attributes.length == 0) {
+ return false;
+ }else{
+ return true;
+ }
+ },
+ get textContent(){
+ return __recursivelyGatherText__(this);
+ },
+ set textContent(newText){
+ while(this.firstChild != null){
+ this.removeChild( this.firstChild );
+ }
+ var text = this.ownerDocument.createTextNode(newText);
+ this.appendChild(text);
+ },
+ insertBefore : function(newChild, refChild) {
+ var prevNode;
+
+ if(newChild==null){
+ return newChild;
+ }
+ if(refChild==null){
+ this.appendChild(newChild);
+ return this.newChild;
+ }
+
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Node is readonly
+ if (this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if newChild was not created by this Document
+ if (__ownerDocument__(this) != __ownerDocument__(newChild)) {
+ throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
+ }
+
+ // throw Exception if the node is an ancestor
+ if (__isAncestor__(this, newChild)) {
+ throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
+ }
+ }
+
+ // if refChild is specified, insert before it
+ if (refChild) {
+ // find index of refChild
+ var itemIndex = __findItemIndex__(this.childNodes, refChild);
+ // throw Exception if there is no child node with this id
+ if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ // if the newChild is already in the tree,
+ var newChildParent = newChild.parentNode;
+ if (newChildParent) {
+ // remove it
+ newChildParent.removeChild(newChild);
+ }
+
+ // insert newChild into childNodes
+ __insertBefore__(this.childNodes, newChild, itemIndex);
+
+ // do node pointer surgery
+ prevNode = refChild.previousSibling;
+
+ // handle DocumentFragment
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ if (newChild.childNodes.length > 0) {
+ // set the parentNode of DocumentFragment's children
+ for (var ind = 0; ind < newChild.childNodes.length; ind++) {
+ newChild.childNodes[ind].parentNode = this;
+ }
+
+ // link refChild to last child of DocumentFragment
+ refChild.previousSibling = newChild.childNodes[newChild.childNodes.length-1];
+ }
+ }else {
+ // set the parentNode of the newChild
+ newChild.parentNode = this;
+ // link refChild to newChild
+ refChild.previousSibling = newChild;
+ }
+
+ }else {
+ // otherwise, append to end
+ prevNode = this.lastChild;
+ this.appendChild(newChild);
+ }
+
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // do node pointer surgery for DocumentFragment
+ if (newChild.childNodes.length > 0) {
+ if (prevNode) {
+ prevNode.nextSibling = newChild.childNodes[0];
+ }else {
+ // this is the first child in the list
+ this.firstChild = newChild.childNodes[0];
+ }
+ newChild.childNodes[0].previousSibling = prevNode;
+ newChild.childNodes[newChild.childNodes.length-1].nextSibling = refChild;
+ }
+ }else {
+ // do node pointer surgery for newChild
+ if (prevNode) {
+ prevNode.nextSibling = newChild;
+ }else {
+ // this is the first child in the list
+ this.firstChild = newChild;
+ }
+ newChild.previousSibling = prevNode;
+ newChild.nextSibling = refChild;
+ }
+
+ return newChild;
+ },
+ replaceChild : function(newChild, oldChild) {
+ var ret = null;
+
+ if(newChild==null || oldChild==null){
+ return oldChild;
+ }
+
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Node is readonly
+ if (this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if newChild was not created by this Document
+ if (__ownerDocument__(this) != __ownerDocument__(newChild)) {
+ throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
+ }
+
+ // throw Exception if the node is an ancestor
+ if (__isAncestor__(this, newChild)) {
+ throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
+ }
+ }
+
+ // get index of oldChild
+ var index = __findItemIndex__(this.childNodes, oldChild);
+
+ // throw Exception if there is no child node with this id
+ if (__ownerDocument__(this).implementation.errorChecking && (index < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ // if the newChild is already in the tree,
+ var newChildParent = newChild.parentNode;
+ if (newChildParent) {
+ // remove it
+ newChildParent.removeChild(newChild);
+ }
+
+ // add newChild to childNodes
+ ret = __replaceChild__(this.childNodes,newChild, index);
+
+
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // do node pointer surgery for Document Fragment
+ if (newChild.childNodes.length > 0) {
+ for (var ind = 0; ind < newChild.childNodes.length; ind++) {
+ newChild.childNodes[ind].parentNode = this;
+ }
+
+ if (oldChild.previousSibling) {
+ oldChild.previousSibling.nextSibling = newChild.childNodes[0];
+ } else {
+ this.firstChild = newChild.childNodes[0];
+ }
+
+ if (oldChild.nextSibling) {
+ oldChild.nextSibling.previousSibling = newChild;
+ } else {
+ this.lastChild = newChild.childNodes[newChild.childNodes.length-1];
+ }
+
+ newChild.childNodes[0].previousSibling = oldChild.previousSibling;
+ newChild.childNodes[newChild.childNodes.length-1].nextSibling = oldChild.nextSibling;
+ }
+ } else {
+ // do node pointer surgery for newChild
+ newChild.parentNode = this;
+
+ if (oldChild.previousSibling) {
+ oldChild.previousSibling.nextSibling = newChild;
+ }else{
+ this.firstChild = newChild;
+ }
+ if (oldChild.nextSibling) {
+ oldChild.nextSibling.previousSibling = newChild;
+ }else{
+ this.lastChild = newChild;
+ }
+ newChild.previousSibling = oldChild.previousSibling;
+ newChild.nextSibling = oldChild.nextSibling;
+ }
+
+ return ret;
+ },
+ removeChild : function(oldChild) {
+ if(!oldChild){
+ return null;
+ }
+ // throw Exception if NamedNodeMap is readonly
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ (this._readonly || oldChild._readonly)) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // get index of oldChild
+ var itemIndex = __findItemIndex__(this.childNodes, oldChild);
+
+ // throw Exception if there is no child node with this id
+ if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ // remove oldChild from childNodes
+ __removeChild__(this.childNodes, itemIndex);
+
+ // do node pointer surgery
+ oldChild.parentNode = null;
+
+ if (oldChild.previousSibling) {
+ oldChild.previousSibling.nextSibling = oldChild.nextSibling;
+ }else {
+ this.firstChild = oldChild.nextSibling;
+ }
+ if (oldChild.nextSibling) {
+ oldChild.nextSibling.previousSibling = oldChild.previousSibling;
+ }else {
+ this.lastChild = oldChild.previousSibling;
+ }
+
+ oldChild.previousSibling = null;
+ oldChild.nextSibling = null;
+
+ return oldChild;
+ },
+ appendChild : function(newChild) {
+ if(!newChild){
+ return null;
+ }
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Node is readonly
+ if (this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if arg was not created by this Document
+ if (__ownerDocument__(this) != __ownerDocument__(this)) {
+ throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
+ }
+
+ // throw Exception if the node is an ancestor
+ if (__isAncestor__(this, newChild)) {
+ throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
+ }
+ }
+
+ // if the newChild is already in the tree,
+ var newChildParent = newChild.parentNode;
+ if (newChildParent) {
+ // remove it
+ //console.debug('removing node %s', newChild);
+ newChildParent.removeChild(newChild);
+ }
+
+ // add newChild to childNodes
+ __appendChild__(this.childNodes, newChild);
+
+ if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // do node pointer surgery for DocumentFragment
+ if (newChild.childNodes.length > 0) {
+ for (var ind = 0; ind < newChild.childNodes.length; ind++) {
+ newChild.childNodes[ind].parentNode = this;
+ }
+
+ if (this.lastChild) {
+ this.lastChild.nextSibling = newChild.childNodes[0];
+ newChild.childNodes[0].previousSibling = this.lastChild;
+ this.lastChild = newChild.childNodes[newChild.childNodes.length-1];
+ } else {
+ this.lastChild = newChild.childNodes[newChild.childNodes.length-1];
+ this.firstChild = newChild.childNodes[0];
+ }
+ }
+ } else {
+ // do node pointer surgery for newChild
+ newChild.parentNode = this;
+ if (this.lastChild) {
+ this.lastChild.nextSibling = newChild;
+ newChild.previousSibling = this.lastChild;
+ this.lastChild = newChild;
+ } else {
+ this.lastChild = newChild;
+ this.firstChild = newChild;
+ }
+ }
+ return newChild;
+ },
+ hasChildNodes : function() {
+ return (this.childNodes.length > 0);
+ },
+ cloneNode: function(deep) {
+ // use importNode to clone this Node
+ //do not throw any exceptions
+ try {
+ return __ownerDocument__(this).importNode(this, deep);
+ } catch (e) {
+ //there shouldn't be any exceptions, but if there are, return null
+ // may want to warn: $debug("could not clone node: "+e.code);
+ return null;
+ }
+ },
+ normalize : function() {
+ var i;
+ var inode;
+ var nodesToRemove = new NodeList();
+
+ if (this.nodeType == Node.ELEMENT_NODE || this.nodeType == Node.DOCUMENT_NODE) {
+ var adjacentTextNode = null;
+
+ // loop through all childNodes
+ for(i = 0; i < this.childNodes.length; i++) {
+ inode = this.childNodes.item(i);
+
+ if (inode.nodeType == Node.TEXT_NODE) {
+ // this node is a text node
+ if (inode.length < 1) {
+ // this text node is empty
+ // add this node to the list of nodes to be remove
+ __appendChild__(nodesToRemove, inode);
+ }else {
+ if (adjacentTextNode) {
+ // previous node was also text
+ adjacentTextNode.appendData(inode.data);
+ // merge the data in adjacent text nodes
+ // add this node to the list of nodes to be removed
+ __appendChild__(nodesToRemove, inode);
+ } else {
+ // remember this node for next cycle
+ adjacentTextNode = inode;
+ }
+ }
+ } else {
+ // (soon to be) previous node is not a text node
+ adjacentTextNode = null;
+ // normalize non Text childNodes
+ inode.normalize();
+ }
+ }
+
+ // remove redundant Text Nodes
+ for(i = 0; i < nodesToRemove.length; i++) {
+ inode = nodesToRemove.item(i);
+ inode.parentNode.removeChild(inode);
+ }
+ }
+ },
+ isSupported : function(feature, version) {
+ // use Implementation.hasFeature to determine if this feature is supported
+ return __ownerDocument__(this).implementation.hasFeature(feature, version);
+ },
+ getElementsByTagName : function(tagname) {
+ // delegate to _getElementsByTagNameRecursive
+ // recurse childNodes
+ var nodelist = new NodeList(__ownerDocument__(this));
+ for (var i = 0; i < this.childNodes.length; i++) {
+ __getElementsByTagNameRecursive__(this.childNodes.item(i),
+ tagname,
+ nodelist);
+ }
+ return nodelist;
+ },
+ getElementsByTagNameNS : function(namespaceURI, localName) {
+ // delegate to _getElementsByTagNameNSRecursive
+ return __getElementsByTagNameNSRecursive__(this, namespaceURI, localName,
+ new NodeList(__ownerDocument__(this)));
+ },
+ importNode : function(importedNode, deep) {
+ var i;
+ var importNode;
+
+ //there is no need to perform namespace checks since everything has already gone through them
+ //in order to have gotten into the DOM in the first place. The following line
+ //turns namespace checking off in ._isValidNamespace
+ __ownerDocument__(this).importing = true;
+
+ if (importedNode.nodeType == Node.ELEMENT_NODE) {
+ if (!__ownerDocument__(this).implementation.namespaceAware) {
+ // create a local Element (with the name of the importedNode)
+ importNode = __ownerDocument__(this).createElement(importedNode.tagName);
+
+ // create attributes matching those of the importedNode
+ for(i = 0; i < importedNode.attributes.length; i++) {
+ importNode.setAttribute(importedNode.attributes.item(i).name, importedNode.attributes.item(i).value);
+ }
+ } else {
+ // create a local Element (with the name & namespaceURI of the importedNode)
+ importNode = __ownerDocument__(this).createElementNS(importedNode.namespaceURI, importedNode.nodeName);
+
+ // create attributes matching those of the importedNode
+ for(i = 0; i < importedNode.attributes.length; i++) {
+ importNode.setAttributeNS(importedNode.attributes.item(i).namespaceURI,
+ importedNode.attributes.item(i).name, importedNode.attributes.item(i).value);
+ }
+
+ // create namespace definitions matching those of the importedNode
+ for(i = 0; i < importedNode._namespaces.length; i++) {
+ importNode._namespaces[i] = __ownerDocument__(this).createNamespace(importedNode._namespaces.item(i).localName);
+ importNode._namespaces[i].value = importedNode._namespaces.item(i).value;
+ }
+ }
+ } else if (importedNode.nodeType == Node.ATTRIBUTE_NODE) {
+ if (!__ownerDocument__(this).implementation.namespaceAware) {
+ // create a local Attribute (with the name of the importedAttribute)
+ importNode = __ownerDocument__(this).createAttribute(importedNode.name);
+ } else {
+ // create a local Attribute (with the name & namespaceURI of the importedAttribute)
+ importNode = __ownerDocument__(this).createAttributeNS(importedNode.namespaceURI, importedNode.nodeName);
+
+ // create namespace definitions matching those of the importedAttribute
+ for(i = 0; i < importedNode._namespaces.length; i++) {
+ importNode._namespaces[i] = __ownerDocument__(this).createNamespace(importedNode._namespaces.item(i).localName);
+ importNode._namespaces[i].value = importedNode._namespaces.item(i).value;
+ }
+ }
+
+ // set the value of the local Attribute to match that of the importedAttribute
+ importNode.value = importedNode.value;
+
+ } else if (importedNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE) {
+ // create a local DocumentFragment
+ importNode = __ownerDocument__(this).createDocumentFragment();
+ } else if (importedNode.nodeType == Node.NAMESPACE_NODE) {
+ // create a local NamespaceNode (with the same name & value as the importedNode)
+ importNode = __ownerDocument__(this).createNamespace(importedNode.nodeName);
+ importNode.value = importedNode.value;
+ } else if (importedNode.nodeType == Node.TEXT_NODE) {
+ // create a local TextNode (with the same data as the importedNode)
+ importNode = __ownerDocument__(this).createTextNode(importedNode.data);
+ } else if (importedNode.nodeType == Node.CDATA_SECTION_NODE) {
+ // create a local CDATANode (with the same data as the importedNode)
+ importNode = __ownerDocument__(this).createCDATASection(importedNode.data);
+ } else if (importedNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
+ // create a local ProcessingInstruction (with the same target & data as the importedNode)
+ importNode = __ownerDocument__(this).createProcessingInstruction(importedNode.target, importedNode.data);
+ } else if (importedNode.nodeType == Node.COMMENT_NODE) {
+ // create a local Comment (with the same data as the importedNode)
+ importNode = __ownerDocument__(this).createComment(importedNode.data);
+ } else { // throw Exception if nodeType is not supported
+ throw(new DOMException(DOMException.NOT_SUPPORTED_ERR));
+ }
+
+ if (deep) {
+ // recurse childNodes
+ for(i = 0; i < importedNode.childNodes.length; i++) {
+ importNode.appendChild(__ownerDocument__(this).importNode(importedNode.childNodes.item(i), true));
+ }
+ }
+
+ //reset importing
+ __ownerDocument__(this).importing = false;
+ return importNode;
+
+ },
+ contains : function(node){
+ while(node && node != this ){
+ node = node.parentNode;
+ }
+ return !!node;
+ },
+ compareDocumentPosition : function(b){
+ //console.log("comparing document position %s %s", this, b);
+ var i,
+ length,
+ a = this,
+ parent,
+ aparents,
+ bparents;
+ //handle a couple simpler case first
+ if(a === b) {
+ return Node.DOCUMENT_POSITION_EQUAL;
+ }
+ if(a.ownerDocument !== b.ownerDocument) {
+ return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|
+ Node.DOCUMENT_POSITION_FOLLOWING|
+ Node.DOCUMENT_POSITION_DISCONNECTED;
+ }
+ if(a.parentNode === b.parentNode){
+ length = a.parentNode.childNodes.length;
+ for(i=0;i aparents.length){
+ return Node.DOCUMENT_POSITION_FOLLOWING;
+ }else if(bparents.length < aparents.length){
+ return Node.DOCUMENT_POSITION_PRECEDING;
+ }else{
+ //common ancestor diverge point
+ if (i === 0) {
+ return Node.DOCUMENT_POSITION_FOLLOWING;
+ } else {
+ parent = aparents[i-1];
+ }
+ return parent.compareDocumentPosition(bparents.pop());
+ }
+ }
+ }
+
+ return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC|
+ Node.DOCUMENT_POSITION_DISCONNECTED;
+
+ },
+ toString : function() {
+ return '[object Node]';
+ }
+
+});
+
+
+
+/**
+ * @method __getElementsByTagNameRecursive__ - implements getElementsByTagName()
+ * @param elem : Element - The element which are checking and then recursing into
+ * @param tagname : string - The name of the tag to match on. The special value "*" matches all tags
+ * @param nodeList : NodeList - The accumulating list of matching nodes
+ *
+ * @return : NodeList
+ */
+var __getElementsByTagNameRecursive__ = function (elem, tagname, nodeList) {
+
+ if (elem.nodeType == Node.ELEMENT_NODE || elem.nodeType == Node.DOCUMENT_NODE) {
+
+ if(elem.nodeType !== Node.DOCUMENT_NODE &&
+ ((elem.nodeName.toUpperCase() == tagname.toUpperCase()) ||
+ (tagname == "*")) ){
+ // add matching node to nodeList
+ __appendChild__(nodeList, elem);
+ }
+
+ // recurse childNodes
+ for(var i = 0; i < elem.childNodes.length; i++) {
+ nodeList = __getElementsByTagNameRecursive__(elem.childNodes.item(i), tagname, nodeList);
+ }
+ }
+
+ return nodeList;
+};
+
+/**
+ * @method __getElementsByTagNameNSRecursive__
+ * implements getElementsByTagName()
+ *
+ * @param elem : Element - The element which are checking and then recursing into
+ * @param namespaceURI : string - the namespace URI of the required node
+ * @param localName : string - the local name of the required node
+ * @param nodeList : NodeList - The accumulating list of matching nodes
+ *
+ * @return : NodeList
+ */
+var __getElementsByTagNameNSRecursive__ = function(elem, namespaceURI, localName, nodeList) {
+ if (elem.nodeType == Node.ELEMENT_NODE || elem.nodeType == Node.DOCUMENT_NODE) {
+
+ if (((elem.namespaceURI == namespaceURI) || (namespaceURI == "*")) &&
+ ((elem.localName == localName) || (localName == "*"))) {
+ // add matching node to nodeList
+ __appendChild__(nodeList, elem);
+ }
+
+ // recurse childNodes
+ for(var i = 0; i < elem.childNodes.length; i++) {
+ nodeList = __getElementsByTagNameNSRecursive__(
+ elem.childNodes.item(i), namespaceURI, localName, nodeList);
+ }
+ }
+
+ return nodeList;
+};
+
+/**
+ * @method __isAncestor__ - returns true if node is ancestor of target
+ * @param target : Node - The node we are using as context
+ * @param node : Node - The candidate ancestor node
+ * @return : boolean
+ */
+var __isAncestor__ = function(target, node) {
+ // if this node matches, return true,
+ // otherwise recurse up (if there is a parentNode)
+ return ((target == node) || ((target.parentNode) && (__isAncestor__(target.parentNode, node))));
+};
+
+
+
+var __recursivelyGatherText__ = function(aNode) {
+ var accumulateText = "",
+ idx,
+ node;
+ for (idx=0;idx < aNode.childNodes.length;idx++){
+ node = aNode.childNodes.item(idx);
+ if(node.nodeType == Node.TEXT_NODE)
+ accumulateText += node.data;
+ else
+ accumulateText += __recursivelyGatherText__(node);
+ }
+ return accumulateText;
+};
+
+/**
+ * function __escapeXML__
+ * @param str : string - The string to be escaped
+ * @return : string - The escaped string
+ */
+var escAmpRegEx = /&(?!(amp;|lt;|gt;|quot|apos;))/g;
+var escLtRegEx = //g;
+var quotRegEx = /"/g;
+var aposRegEx = /'/g;
+
+function __escapeXML__(str) {
+ str = str.replace(escAmpRegEx, "&").
+ replace(escLtRegEx, "<").
+ replace(escGtRegEx, ">").
+ replace(quotRegEx, """).
+ replace(aposRegEx, "'");
+
+ return str;
+};
+
+/*
+function __escapeHTML5__(str) {
+ str = str.replace(escAmpRegEx, "&").
+ replace(escLtRegEx, "<").
+ replace(escGtRegEx, ">");
+
+ return str;
+};
+function __escapeHTML5Atribute__(str) {
+ str = str.replace(escAmpRegEx, "&").
+ replace(escLtRegEx, "<").
+ replace(escGtRegEx, ">").
+ replace(quotRegEx, """).
+ replace(aposRegEx, "'");
+
+ return str;
+};
+*/
+
+/**
+ * function __unescapeXML__
+ * @param str : string - The string to be unescaped
+ * @return : string - The unescaped string
+ */
+var unescAmpRegEx = /&/g;
+var unescLtRegEx = /</g;
+var unescGtRegEx = />/g;
+var unquotRegEx = /"/g;
+var unaposRegEx = /'/g;
+function __unescapeXML__(str) {
+ str = str.replace(unescAmpRegEx, "&").
+ replace(unescLtRegEx, "<").
+ replace(unescGtRegEx, ">").
+ replace(unquotRegEx, "\"").
+ replace(unaposRegEx, "'");
+
+ return str;
+};
+
+/**
+ * @class NamedNodeMap -
+ * used to represent collections of nodes that can be accessed by name
+ * typically a set of Element attributes
+ *
+ * @extends NodeList -
+ * note W3C spec says that this is not the case, but we need an item()
+ * method identical to NodeList's, so why not?
+ * @param ownerDocument : Document - the ownerDocument
+ * @param parentNode : Node - the node that the NamedNodeMap is attached to (or null)
+ */
+NamedNodeMap = function(ownerDocument, parentNode) {
+ NodeList.apply(this, arguments);
+ __setArray__(this, []);
+};
+NamedNodeMap.prototype = new NodeList();
+__extend__(NamedNodeMap.prototype, {
+ add: function(name){
+ this[this.length] = name;
+ },
+ getNamedItem : function(name) {
+ var ret = null;
+ //console.log('NamedNodeMap getNamedItem %s', name);
+ // test that Named Node exists
+ var itemIndex = __findNamedItemIndex__(this, name);
+
+ if (itemIndex > -1) {
+ // found it!
+ ret = this[itemIndex];
+ }
+ // if node is not found, default value null is returned
+ return ret;
+ },
+ setNamedItem : function(arg) {
+ //console.log('setNamedItem %s', arg);
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if arg was not created by this Document
+ if (this.ownerDocument != arg.ownerDocument) {
+ throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
+ }
+
+ // throw Exception if DOMNamedNodeMap is readonly
+ if (this._readonly || (this.parentNode && this.parentNode._readonly)) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if arg is already an attribute of another Element object
+ if (arg.ownerElement && (arg.ownerElement != this.parentNode)) {
+ throw(new DOMException(DOMException.INUSE_ATTRIBUTE_ERR));
+ }
+ }
+
+ //console.log('setNamedItem __findNamedItemIndex__ ');
+ // get item index
+ var itemIndex = __findNamedItemIndex__(this, arg.name);
+ var ret = null;
+
+ //console.log('setNamedItem __findNamedItemIndex__ %s', itemIndex);
+ if (itemIndex > -1) { // found it!
+ ret = this[itemIndex]; // use existing Attribute
+
+ // throw Exception if DOMAttr is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && ret._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ } else {
+ this[itemIndex] = arg; // over-write existing NamedNode
+ this[arg.name.toLowerCase()] = arg;
+ }
+ } else {
+ // add new NamedNode
+ //console.log('setNamedItem add new named node map (by index)');
+ Array.prototype.push.apply(this, [arg]);
+ //console.log('setNamedItem add new named node map (by name) %s %s', arg, arg.name);
+ this[arg.name] = arg;
+ //console.log('finsished setNamedItem add new named node map (by name) %s', arg.name);
+
+ }
+
+ //console.log('setNamedItem parentNode');
+ arg.ownerElement = this.parentNode; // update ownerElement
+ // return old node or new node
+ //console.log('setNamedItem exit');
+ return ret;
+ },
+ removeNamedItem : function(name) {
+ var ret = null;
+ // test for exceptions
+ // throw Exception if NamedNodeMap is readonly
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ (this._readonly || (this.parentNode && this.parentNode._readonly))) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // get item index
+ var itemIndex = __findNamedItemIndex__(this, name);
+
+ // throw Exception if there is no node named name in this map
+ if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ // get Node
+ var oldNode = this[itemIndex];
+ //this[oldNode.name] = undefined;
+
+ // throw Exception if Node is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && oldNode._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // return removed node
+ return __removeChild__(this, itemIndex);
+ },
+ getNamedItemNS : function(namespaceURI, localName) {
+ var ret = null;
+
+ // test that Named Node exists
+ var itemIndex = __findNamedItemNSIndex__(this, namespaceURI, localName);
+
+ if (itemIndex > -1) {
+ // found it! return NamedNode
+ ret = this[itemIndex];
+ }
+ // if node is not found, default value null is returned
+ return ret;
+ },
+ setNamedItemNS : function(arg) {
+ //console.log('setNamedItemNS %s', arg);
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if NamedNodeMap is readonly
+ if (this._readonly || (this.parentNode && this.parentNode._readonly)) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if arg was not created by this Document
+ if (__ownerDocument__(this) != __ownerDocument__(arg)) {
+ throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
+ }
+
+ // throw Exception if arg is already an attribute of another Element object
+ if (arg.ownerElement && (arg.ownerElement != this.parentNode)) {
+ throw(new DOMException(DOMException.INUSE_ATTRIBUTE_ERR));
+ }
+ }
+
+ // get item index
+ var itemIndex = __findNamedItemNSIndex__(this, arg.namespaceURI, arg.localName);
+ var ret = null;
+
+ if (itemIndex > -1) {
+ // found it!
+ // use existing Attribute
+ ret = this[itemIndex];
+ // throw Exception if Attr is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && ret._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ } else {
+ // over-write existing NamedNode
+ this[itemIndex] = arg;
+ }
+ }else {
+ // add new NamedNode
+ Array.prototype.push.apply(this, [arg]);
+ }
+ arg.ownerElement = this.parentNode;
+
+ // return old node or null
+ return ret;
+ //console.log('finished setNamedItemNS %s', arg);
+ },
+ removeNamedItemNS : function(namespaceURI, localName) {
+ var ret = null;
+
+ // test for exceptions
+ // throw Exception if NamedNodeMap is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && (this._readonly || (this.parentNode && this.parentNode._readonly))) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // get item index
+ var itemIndex = __findNamedItemNSIndex__(this, namespaceURI, localName);
+
+ // throw Exception if there is no matching node in this map
+ if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ // get Node
+ var oldNode = this[itemIndex];
+
+ // throw Exception if Node is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && oldNode._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ return __removeChild__(this, itemIndex); // return removed node
+ },
+ get xml() {
+ var ret = "";
+
+ // create string containing concatenation of all (but last) Attribute string values (separated by spaces)
+ for (var i=0; i < this.length -1; i++) {
+ ret += this[i].xml +" ";
+ }
+
+ // add last Attribute to string (without trailing space)
+ if (this.length > 0) {
+ ret += this[this.length -1].xml;
+ }
+
+ return ret;
+ },
+ toString : function(){
+ return "[object NamedNodeMap]";
+ }
+
+});
+
+/**
+ * @method __findNamedItemIndex__
+ * find the item index of the node with the specified name
+ *
+ * @param name : string - the name of the required node
+ * @param isnsmap : if its a NamespaceNodeMap
+ * @return : int
+ */
+var __findNamedItemIndex__ = function(namednodemap, name, isnsmap) {
+ var ret = -1;
+ // loop through all nodes
+ for (var i=0; i -1) {
+ // found it!
+ ret = true;
+ }
+ // if node is not found, default value false is returned
+ return ret;
+}
+
+/**
+ * @method __hasAttributeNS__
+ * Returns true if specified node exists
+ *
+ * @param namespaceURI : string - the namespace URI of the required node
+ * @param localName : string - the local name of the required node
+ * @return : boolean
+ */
+var __hasAttributeNS__ = function(namednodemap, namespaceURI, localName) {
+ var ret = false;
+ // test that Named Node exists
+ var itemIndex = __findNamedItemNSIndex__(namednodemap, namespaceURI, localName);
+ if (itemIndex > -1) {
+ // found it!
+ ret = true;
+ }
+ // if node is not found, default value false is returned
+ return ret;
+}
+
+/**
+ * @method __cloneNamedNodes__
+ * Returns a NamedNodeMap containing clones of the Nodes in this NamedNodeMap
+ *
+ * @param parentNode : Node - the new parent of the cloned NodeList
+ * @param isnsmap : bool - is this a NamespaceNodeMap
+ * @return NamedNodeMap containing clones of the Nodes in this NamedNodeMap
+ */
+var __cloneNamedNodes__ = function(namednodemap, parentNode, isnsmap) {
+ var cloneNamedNodeMap = isnsmap?
+ new NamespaceNodeMap(namednodemap.ownerDocument, parentNode):
+ new NamedNodeMap(namednodemap.ownerDocument, parentNode);
+
+ // create list containing clones of all children
+ for (var i=0; i < namednodemap.length; i++) {
+ __appendChild__(cloneNamedNodeMap, namednodemap[i].cloneNode(false));
+ }
+
+ return cloneNamedNodeMap;
+};
+
+
+/**
+ * @class NamespaceNodeMap -
+ * used to represent collections of namespace nodes that can be
+ * accessed by name typically a set of Element attributes
+ *
+ * @extends NamedNodeMap
+ *
+ * @param ownerDocument : Document - the ownerDocument
+ * @param parentNode : Node - the node that the NamespaceNodeMap is attached to (or null)
+ */
+var NamespaceNodeMap = function(ownerDocument, parentNode) {
+ this.NamedNodeMap = NamedNodeMap;
+ this.NamedNodeMap(ownerDocument, parentNode);
+ __setArray__(this, []);
+};
+NamespaceNodeMap.prototype = new NamedNodeMap();
+__extend__(NamespaceNodeMap.prototype, {
+ get xml() {
+ var ret = "",
+ ns,
+ ind;
+ // identify namespaces declared local to this Element (ie, not inherited)
+ for (ind = 0; ind < this.length; ind++) {
+ // if namespace declaration does not exist in the containing node's, parentNode's namespaces
+ ns = null;
+ try {
+ var ns = this.parentNode.parentNode._namespaces.
+ getNamedItem(this[ind].localName);
+ }catch (e) {
+ //breaking to prevent default namespace being inserted into return value
+ break;
+ }
+ if (!(ns && (""+ ns.nodeValue == ""+ this[ind].nodeValue))) {
+ // display the namespace declaration
+ ret += this[ind].xml +" ";
+ }
+ }
+ return ret;
+ }
+});
+
+/**
+ * @class Namespace -
+ * The Namespace interface represents an namespace in an Element object
+ *
+ * @param ownerDocument : The Document object associated with this node.
+ */
+Namespace = function(ownerDocument) {
+ Node.apply(this, arguments);
+ // the name of this attribute
+ this.name = "";
+
+ // If this attribute was explicitly given a value in the original document,
+ // this is true; otherwise, it is false.
+ // Note that the implementation is in charge of this attribute, not the user.
+ // If the user changes the value of the attribute (even if it ends up having
+ // the same value as the default value) then the specified flag is
+ // automatically flipped to true
+ this.specified = false;
+};
+Namespace.prototype = new Node();
+__extend__(Namespace.prototype, {
+ get value(){
+ // the value of the attribute is returned as a string
+ return this.nodeValue;
+ },
+ set value(value){
+ this.nodeValue = value+'';
+ },
+ get nodeType(){
+ return Node.NAMESPACE_NODE;
+ },
+ get xml(){
+ var ret = "";
+
+ // serialize Namespace Declaration
+ if (this.nodeName != "") {
+ ret += this.nodeName +"=\""+ __escapeXML__(this.nodeValue) +"\"";
+ }
+ else { // handle default namespace
+ ret += "xmlns=\""+ __escapeXML__(this.nodeValue) +"\"";
+ }
+
+ return ret;
+ },
+ toString: function(){
+ return '[object Namespace]';
+ }
+});
+
+
+/**
+ * @class CharacterData - parent abstract class for Text and Comment
+ * @extends Node
+ * @param ownerDocument : The Document object associated with this node.
+ */
+CharacterData = function(ownerDocument) {
+ Node.apply(this, arguments);
+};
+CharacterData.prototype = new Node();
+__extend__(CharacterData.prototype,{
+ get data(){
+ return this.nodeValue;
+ },
+ set data(data){
+ this.nodeValue = data;
+ },
+ get textContent(){
+ return this.nodeValue;
+ },
+ set textContent(newText){
+ this.nodeValue = newText;
+ },
+ get length(){return this.nodeValue.length;},
+ appendData: function(arg){
+ // throw Exception if CharacterData is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+ // append data
+ this.data = "" + this.data + arg;
+ },
+ deleteData: function(offset, count){
+ // throw Exception if CharacterData is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+ if (this.data) {
+ // throw Exception if offset is negative or greater than the data length,
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ ((offset < 0) || (offset > this.data.length) || (count < 0))) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+
+ // delete data
+ if(!count || (offset + count) > this.data.length) {
+ this.data = this.data.substring(0, offset);
+ }else {
+ this.data = this.data.substring(0, offset).
+ concat(this.data.substring(offset + count));
+ }
+ }
+ },
+ insertData: function(offset, arg){
+ // throw Exception if CharacterData is readonly
+ if(__ownerDocument__(this).implementation.errorChecking && this._readonly){
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ if(this.data){
+ // throw Exception if offset is negative or greater than the data length,
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ ((offset < 0) || (offset > this.data.length))) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+
+ // insert data
+ this.data = this.data.substring(0, offset).concat(arg, this.data.substring(offset));
+ }else {
+ // throw Exception if offset is negative or greater than the data length,
+ if (__ownerDocument__(this).implementation.errorChecking && (offset !== 0)) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+
+ // set data
+ this.data = arg;
+ }
+ },
+ replaceData: function(offset, count, arg){
+ // throw Exception if CharacterData is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ if (this.data) {
+ // throw Exception if offset is negative or greater than the data length,
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ ((offset < 0) || (offset > this.data.length) || (count < 0))) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+
+ // replace data
+ this.data = this.data.substring(0, offset).
+ concat(arg, this.data.substring(offset + count));
+ }else {
+ // set data
+ this.data = arg;
+ }
+ },
+ substringData: function(offset, count){
+ var ret = null;
+ if (this.data) {
+ // throw Exception if offset is negative or greater than the data length,
+ // or the count is negative
+ if (__ownerDocument__(this).implementation.errorChecking &&
+ ((offset < 0) || (offset > this.data.length) || (count < 0))) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+ // if count is not specified
+ if (!count) {
+ ret = this.data.substring(offset); // default to 'end of string'
+ }else{
+ ret = this.data.substring(offset, offset + count);
+ }
+ }
+ return ret;
+ },
+ toString : function(){
+ return "[object CharacterData]";
+ }
+});
+
+/**
+ * @class Text
+ * The Text interface represents the textual content (termed
+ * character data in XML) of an Element or Attr.
+ * If there is no markup inside an element's content, the text is
+ * contained in a single object implementing the Text interface that
+ * is the only child of the element. If there is markup, it is
+ * parsed into a list of elements and Text nodes that form the
+ * list of children of the element.
+ * @extends CharacterData
+ * @param ownerDocument The Document object associated with this node.
+ */
+Text = function(ownerDocument) {
+ CharacterData.apply(this, arguments);
+ this.nodeName = "#text";
+};
+Text.prototype = new CharacterData();
+__extend__(Text.prototype,{
+ get localName(){
+ return null;
+ },
+ // Breaks this Text node into two Text nodes at the specified offset,
+ // keeping both in the tree as siblings. This node then only contains
+ // all the content up to the offset point. And a new Text node, which
+ // is inserted as the next sibling of this node, contains all the
+ // content at and after the offset point.
+ splitText : function(offset) {
+ var data,
+ inode;
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Node is readonly
+ if (this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+ // throw Exception if offset is negative or greater than the data length,
+ if ((offset < 0) || (offset > this.data.length)) {
+ throw(new DOMException(DOMException.INDEX_SIZE_ERR));
+ }
+ }
+ if (this.parentNode) {
+ // get remaining string (after offset)
+ data = this.substringData(offset);
+ // create new TextNode with remaining string
+ inode = __ownerDocument__(this).createTextNode(data);
+ // attach new TextNode
+ if (this.nextSibling) {
+ this.parentNode.insertBefore(inode, this.nextSibling);
+ } else {
+ this.parentNode.appendChild(inode);
+ }
+ // remove remaining string from original TextNode
+ this.deleteData(offset);
+ }
+ return inode;
+ },
+ get nodeType(){
+ return Node.TEXT_NODE;
+ },
+ get xml(){
+ return __escapeXML__(""+ this.nodeValue);
+ },
+ toString: function(){
+ return "[object Text]";
+ }
+});
+
+/**
+ * @class CDATASection
+ * CDATA sections are used to escape blocks of text containing
+ * characters that would otherwise be regarded as markup.
+ * The only delimiter that is recognized in a CDATA section is
+ * the "\]\]\>" string that ends the CDATA section
+ * @extends Text
+ * @param ownerDocument : The Document object associated with this node.
+ */
+CDATASection = function(ownerDocument) {
+ Text.apply(this, arguments);
+ this.nodeName = '#cdata-section';
+};
+CDATASection.prototype = new Text();
+__extend__(CDATASection.prototype,{
+ get nodeType(){
+ return Node.CDATA_SECTION_NODE;
+ },
+ get xml(){
+ return "";
+ },
+ toString : function(){
+ return "[object CDATASection]";
+ }
+});
+/**
+ * @class Comment
+ * This represents the content of a comment, i.e., all the
+ * characters between the starting ''
+ * @extends CharacterData
+ * @param ownerDocument : The Document object associated with this node.
+ */
+Comment = function(ownerDocument) {
+ CharacterData.apply(this, arguments);
+ this.nodeName = "#comment";
+};
+Comment.prototype = new CharacterData();
+__extend__(Comment.prototype, {
+ get localName(){
+ return null;
+ },
+ get nodeType(){
+ return Node.COMMENT_NODE;
+ },
+ get xml(){
+ return "";
+ },
+ toString : function(){
+ return "[object Comment]";
+ }
+});
+
+
+/**
+ * @author envjs team
+ * @param {Document} onwnerDocument
+ */
+DocumentType = function(ownerDocument) {
+ Node.apply(this, arguments);
+ this.systemId = null;
+ this.publicId = null;
+};
+DocumentType.prototype = new Node();
+__extend__({
+ get name(){
+ return this.nodeName;
+ },
+ get entities(){
+ return null;
+ },
+ get internalSubsets(){
+ return null;
+ },
+ get notations(){
+ return null;
+ },
+ toString : function(){
+ return "[object DocumentType]";
+ }
+});
+
+/**
+ * @class Attr
+ * The Attr interface represents an attribute in an Element object
+ * @extends Node
+ * @param ownerDocument : The Document object associated with this node.
+ */
+Attr = function(ownerDocument) {
+ Node.apply(this, arguments);
+ // set when Attr is added to NamedNodeMap
+ this.ownerElement = null;
+ //TODO: our implementation of Attr is incorrect because we don't
+ // treat the value of the attribute as a child text node.
+};
+Attr.prototype = new Node();
+__extend__(Attr.prototype, {
+ // the name of this attribute
+ get name(){
+ return this.nodeName;
+ },
+ // the value of the attribute is returned as a string
+ get value(){
+ return this.nodeValue||'';
+ },
+ set value(value){
+ // throw Exception if Attribute is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+ // delegate to node
+ this.nodeValue = value;
+ },
+ get textContent(){
+ return this.nodeValue;
+ },
+ set textContent(newText){
+ this.nodeValue = newText;
+ },
+ get specified(){
+ return (this !== null && this !== undefined);
+ },
+ get nodeType(){
+ return Node.ATTRIBUTE_NODE;
+ },
+ get xml() {
+ if (this.nodeValue) {
+ return __escapeXML__(this.nodeValue+"");
+ } else {
+ return '';
+ }
+ },
+ toString : function() {
+ return '[object Attr]';
+ }
+});
+
+
+/**
+ * @class Element -
+ * By far the vast majority of objects (apart from text)
+ * that authors encounter when traversing a document are
+ * Element nodes.
+ * @extends Node
+ * @param ownerDocument : The Document object associated with this node.
+ */
+Element = function(ownerDocument) {
+ Node.apply(this, arguments);
+ this.attributes = new NamedNodeMap(this.ownerDocument, this);
+};
+Element.prototype = new Node();
+__extend__(Element.prototype, {
+ // The name of the element.
+ get tagName(){
+ return this.nodeName;
+ },
+
+ getAttribute: function(name) {
+ var ret = null;
+ // if attribute exists, use it
+ var attr = this.attributes.getNamedItem(name);
+ if (attr) {
+ ret = attr.value;
+ }
+ // if Attribute exists, return its value, otherwise, return null
+ return ret;
+ },
+ setAttribute : function (name, value) {
+ // if attribute exists, use it
+ var attr = this.attributes.getNamedItem(name);
+ //console.log('attr %s', attr);
+ //I had to add this check because as the script initializes
+ //the id may be set in the constructor, and the html element
+ //overrides the id property with a getter/setter.
+ if(__ownerDocument__(this)){
+ if (attr===null||attr===undefined) {
+ // otherwise create it
+ attr = __ownerDocument__(this).createAttribute(name);
+ //console.log('attr %s', attr);
+ }
+
+
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Attribute is readonly
+ if (attr._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if the value string contains an illegal character
+ if (!__isValidString__(value+'')) {
+ throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
+ }
+ }
+
+ // assign values to properties (and aliases)
+ attr.value = value + '';
+
+ // add/replace Attribute in NamedNodeMap
+ this.attributes.setNamedItem(attr);
+ //console.log('element setNamedItem %s', attr);
+ }else{
+ console.warn('Element has no owner document '+this.tagName+
+ '\n\t cant set attribute ' + name + ' = '+value );
+ }
+ },
+ removeAttribute : function removeAttribute(name) {
+ // delegate to NamedNodeMap.removeNamedItem
+ return this.attributes.removeNamedItem(name);
+ },
+ getAttributeNode : function getAttributeNode(name) {
+ // delegate to NamedNodeMap.getNamedItem
+ return this.attributes.getNamedItem(name);
+ },
+ setAttributeNode: function(newAttr) {
+ // if this Attribute is an ID
+ if (__isIdDeclaration__(newAttr.name)) {
+ this.id = newAttr.value; // cache ID for getElementById()
+ }
+ // delegate to NamedNodeMap.setNamedItem
+ return this.attributes.setNamedItem(newAttr);
+ },
+ removeAttributeNode: function(oldAttr) {
+ // throw Exception if Attribute is readonly
+ if (__ownerDocument__(this).implementation.errorChecking && oldAttr._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // get item index
+ var itemIndex = this.attributes._findItemIndex(oldAttr._id);
+
+ // throw Exception if node does not exist in this map
+ if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) {
+ throw(new DOMException(DOMException.NOT_FOUND_ERR));
+ }
+
+ return this.attributes._removeChild(itemIndex);
+ },
+ getAttributeNS : function(namespaceURI, localName) {
+ var ret = "";
+ // delegate to NAmedNodeMap.getNamedItemNS
+ var attr = this.attributes.getNamedItemNS(namespaceURI, localName);
+ if (attr) {
+ ret = attr.value;
+ }
+ return ret; // if Attribute exists, return its value, otherwise return ""
+ },
+ setAttributeNS : function(namespaceURI, qualifiedName, value) {
+ // call NamedNodeMap.getNamedItem
+ //console.log('setAttributeNS %s %s %s', namespaceURI, qualifiedName, value);
+ var attr = this.attributes.getNamedItem(namespaceURI, qualifiedName);
+
+ if (!attr) { // if Attribute exists, use it
+ // otherwise create it
+ attr = __ownerDocument__(this).createAttributeNS(namespaceURI, qualifiedName);
+ }
+
+ value = '' + value;
+
+ // test for exceptions
+ if (__ownerDocument__(this).implementation.errorChecking) {
+ // throw Exception if Attribute is readonly
+ if (attr._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ // throw Exception if the Namespace is invalid
+ if (!__isValidNamespace__(this.ownerDocument, namespaceURI, qualifiedName, true)) {
+ throw(new DOMException(DOMException.NAMESPACE_ERR));
+ }
+
+ // throw Exception if the value string contains an illegal character
+ if (!__isValidString__(value)) {
+ throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
+ }
+ }
+
+ // if this Attribute is an ID
+ //if (__isIdDeclaration__(name)) {
+ // this.id = value;
+ //}
+
+ // assign values to properties (and aliases)
+ attr.value = value;
+ attr.nodeValue = value;
+
+ // delegate to NamedNodeMap.setNamedItem
+ this.attributes.setNamedItemNS(attr);
+ },
+ removeAttributeNS : function(namespaceURI, localName) {
+ // delegate to NamedNodeMap.removeNamedItemNS
+ return this.attributes.removeNamedItemNS(namespaceURI, localName);
+ },
+ getAttributeNodeNS : function(namespaceURI, localName) {
+ // delegate to NamedNodeMap.getNamedItemNS
+ return this.attributes.getNamedItemNS(namespaceURI, localName);
+ },
+ setAttributeNodeNS : function(newAttr) {
+ // if this Attribute is an ID
+ if ((newAttr.prefix == "") && __isIdDeclaration__(newAttr.name)) {
+ this.id = newAttr.value+''; // cache ID for getElementById()
+ }
+
+ // delegate to NamedNodeMap.setNamedItemNS
+ return this.attributes.setNamedItemNS(newAttr);
+ },
+ hasAttribute : function(name) {
+ // delegate to NamedNodeMap._hasAttribute
+ return __hasAttribute__(this.attributes,name);
+ },
+ hasAttributeNS : function(namespaceURI, localName) {
+ // delegate to NamedNodeMap._hasAttributeNS
+ return __hasAttributeNS__(this.attributes, namespaceURI, localName);
+ },
+ get nodeType(){
+ return Node.ELEMENT_NODE;
+ },
+ get xml() {
+ var ret = "",
+ ns = "",
+ attrs,
+ attrstring,
+ i;
+
+ // serialize namespace declarations
+ if (this.namespaceURI ){
+ if((this === this.ownerDocument.documentElement) ||
+ (!this.parentNode)||
+ (this.parentNode && (this.parentNode.namespaceURI !== this.namespaceURI))) {
+ ns = ' xmlns' + (this.prefix?(':'+this.prefix):'') +
+ '="' + this.namespaceURI + '"';
+ }
+ }
+
+ // serialize Attribute declarations
+ attrs = this.attributes;
+ attrstring = "";
+ for(i=0;i< attrs.length;i++){
+ if(attrs[i].name.match('xmlns:')) {
+ attrstring += " "+attrs[i].name+'="'+attrs[i].xml+'"';
+ }
+ }
+ for(i=0;i< attrs.length;i++){
+ if(!attrs[i].name.match('xmlns:')) {
+ attrstring += " "+attrs[i].name+'="'+attrs[i].xml+'"';
+ }
+ }
+
+ if(this.hasChildNodes()){
+ // serialize this Element
+ ret += "<" + this.tagName + ns + attrstring +">";
+ ret += this.childNodes.xml;
+ ret += "" + this.tagName + ">";
+ }else{
+ ret += "<" + this.tagName + ns + attrstring +"/>";
+ }
+
+ return ret;
+ },
+ toString : function(){
+ return '[object Element]';
+ }
+});
+/**
+ * @class DOMException - raised when an operation is impossible to perform
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param code : int - the exception code (one of the DOMException constants)
+ */
+DOMException = function(code) {
+ this.code = code;
+};
+
+// DOMException constants
+// Introduced in DOM Level 1:
+DOMException.INDEX_SIZE_ERR = 1;
+DOMException.DOMSTRING_SIZE_ERR = 2;
+DOMException.HIERARCHY_REQUEST_ERR = 3;
+DOMException.WRONG_DOCUMENT_ERR = 4;
+DOMException.INVALID_CHARACTER_ERR = 5;
+DOMException.NO_DATA_ALLOWED_ERR = 6;
+DOMException.NO_MODIFICATION_ALLOWED_ERR = 7;
+DOMException.NOT_FOUND_ERR = 8;
+DOMException.NOT_SUPPORTED_ERR = 9;
+DOMException.INUSE_ATTRIBUTE_ERR = 10;
+
+// Introduced in DOM Level 2:
+DOMException.INVALID_STATE_ERR = 11;
+DOMException.SYNTAX_ERR = 12;
+DOMException.INVALID_MODIFICATION_ERR = 13;
+DOMException.NAMESPACE_ERR = 14;
+DOMException.INVALID_ACCESS_ERR = 15;
+
+/**
+ * @class DocumentFragment -
+ * DocumentFragment is a "lightweight" or "minimal" Document object.
+ * @extends Node
+ * @param ownerDocument : The Document object associated with this node.
+ */
+DocumentFragment = function(ownerDocument) {
+ Node.apply(this, arguments);
+ this.nodeName = "#document-fragment";
+};
+DocumentFragment.prototype = new Node();
+__extend__(DocumentFragment.prototype,{
+ get nodeType(){
+ return Node.DOCUMENT_FRAGMENT_NODE;
+ },
+ get xml(){
+ var xml = "",
+ count = this.childNodes.length;
+
+ // create string concatenating the serialized ChildNodes
+ for (var i = 0; i < count; i++) {
+ xml += this.childNodes.item(i).xml;
+ }
+
+ return xml;
+ },
+ toString : function(){
+ return "[object DocumentFragment]";
+ },
+ get localName(){
+ return null;
+ }
+});
+
+
+/**
+ * @class ProcessingInstruction -
+ * The ProcessingInstruction interface represents a
+ * "processing instruction", used in XML as a way to
+ * keep processor-specific information in the text of
+ * the document
+ * @extends Node
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param ownerDocument : The Document object associated with this node.
+ */
+ProcessingInstruction = function(ownerDocument) {
+ Node.apply(this, arguments);
+};
+ProcessingInstruction.prototype = new Node();
+__extend__(ProcessingInstruction.prototype, {
+ get data(){
+ return this.nodeValue;
+ },
+ set data(data){
+ // throw Exception if Node is readonly
+ if (__ownerDocument__(this).errorChecking && this._readonly) {
+ throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
+ }
+ this.nodeValue = data;
+ },
+ get textContent(){
+ return this.data;
+ },
+ get localName(){
+ return null;
+ },
+ get target(){
+ // The target of this processing instruction.
+ // XML defines this as being the first token following the markup that begins the processing instruction.
+ // The content of this processing instruction.
+ return this.nodeName;
+ },
+ set target(value){
+ // The target of this processing instruction.
+ // XML defines this as being the first token following the markup that begins the processing instruction.
+ // The content of this processing instruction.
+ this.nodeName = value;
+ },
+ get nodeType(){
+ return Node.PROCESSING_INSTRUCTION_NODE;
+ },
+ get xml(){
+ return "" + this.nodeName +" "+ this.nodeValue + "?>";
+ },
+ toString : function(){
+ return "[object ProcessingInstruction]";
+ }
+});
+
+
+/**
+ * @author envjs team
+ */
+
+Entity = function() {
+ throw new Error("Entity Not Implemented" );
+};
+
+Entity.constants = {
+ // content taken from W3C "HTML 4.01 Specification"
+ // "W3C Recommendation 24 December 1999"
+
+ nbsp: "\u00A0",
+ iexcl: "\u00A1",
+ cent: "\u00A2",
+ pound: "\u00A3",
+ curren: "\u00A4",
+ yen: "\u00A5",
+ brvbar: "\u00A6",
+ sect: "\u00A7",
+ uml: "\u00A8",
+ copy: "\u00A9",
+ ordf: "\u00AA",
+ laquo: "\u00AB",
+ not: "\u00AC",
+ shy: "\u00AD",
+ reg: "\u00AE",
+ macr: "\u00AF",
+ deg: "\u00B0",
+ plusmn: "\u00B1",
+ sup2: "\u00B2",
+ sup3: "\u00B3",
+ acute: "\u00B4",
+ micro: "\u00B5",
+ para: "\u00B6",
+ middot: "\u00B7",
+ cedil: "\u00B8",
+ sup1: "\u00B9",
+ ordm: "\u00BA",
+ raquo: "\u00BB",
+ frac14: "\u00BC",
+ frac12: "\u00BD",
+ frac34: "\u00BE",
+ iquest: "\u00BF",
+ Agrave: "\u00C0",
+ Aacute: "\u00C1",
+ Acirc: "\u00C2",
+ Atilde: "\u00C3",
+ Auml: "\u00C4",
+ Aring: "\u00C5",
+ AElig: "\u00C6",
+ Ccedil: "\u00C7",
+ Egrave: "\u00C8",
+ Eacute: "\u00C9",
+ Ecirc: "\u00CA",
+ Euml: "\u00CB",
+ Igrave: "\u00CC",
+ Iacute: "\u00CD",
+ Icirc: "\u00CE",
+ Iuml: "\u00CF",
+ ETH: "\u00D0",
+ Ntilde: "\u00D1",
+ Ograve: "\u00D2",
+ Oacute: "\u00D3",
+ Ocirc: "\u00D4",
+ Otilde: "\u00D5",
+ Ouml: "\u00D6",
+ times: "\u00D7",
+ Oslash: "\u00D8",
+ Ugrave: "\u00D9",
+ Uacute: "\u00DA",
+ Ucirc: "\u00DB",
+ Uuml: "\u00DC",
+ Yacute: "\u00DD",
+ THORN: "\u00DE",
+ szlig: "\u00DF",
+ agrave: "\u00E0",
+ aacute: "\u00E1",
+ acirc: "\u00E2",
+ atilde: "\u00E3",
+ auml: "\u00E4",
+ aring: "\u00E5",
+ aelig: "\u00E6",
+ ccedil: "\u00E7",
+ egrave: "\u00E8",
+ eacute: "\u00E9",
+ ecirc: "\u00EA",
+ euml: "\u00EB",
+ igrave: "\u00EC",
+ iacute: "\u00ED",
+ icirc: "\u00EE",
+ iuml: "\u00EF",
+ eth: "\u00F0",
+ ntilde: "\u00F1",
+ ograve: "\u00F2",
+ oacute: "\u00F3",
+ ocirc: "\u00F4",
+ otilde: "\u00F5",
+ ouml: "\u00F6",
+ divide: "\u00F7",
+ oslash: "\u00F8",
+ ugrave: "\u00F9",
+ uacute: "\u00FA",
+ ucirc: "\u00FB",
+ uuml: "\u00FC",
+ yacute: "\u00FD",
+ thorn: "\u00FE",
+ yuml: "\u00FF",
+ fnof: "\u0192",
+ Alpha: "\u0391",
+ Beta: "\u0392",
+ Gamma: "\u0393",
+ Delta: "\u0394",
+ Epsilon: "\u0395",
+ Zeta: "\u0396",
+ Eta: "\u0397",
+ Theta: "\u0398",
+ Iota: "\u0399",
+ Kappa: "\u039A",
+ Lambda: "\u039B",
+ Mu: "\u039C",
+ Nu: "\u039D",
+ Xi: "\u039E",
+ Omicron: "\u039F",
+ Pi: "\u03A0",
+ Rho: "\u03A1",
+ Sigma: "\u03A3",
+ Tau: "\u03A4",
+ Upsilon: "\u03A5",
+ Phi: "\u03A6",
+ Chi: "\u03A7",
+ Psi: "\u03A8",
+ Omega: "\u03A9",
+ alpha: "\u03B1",
+ beta: "\u03B2",
+ gamma: "\u03B3",
+ delta: "\u03B4",
+ epsilon: "\u03B5",
+ zeta: "\u03B6",
+ eta: "\u03B7",
+ theta: "\u03B8",
+ iota: "\u03B9",
+ kappa: "\u03BA",
+ lambda: "\u03BB",
+ mu: "\u03BC",
+ nu: "\u03BD",
+ xi: "\u03BE",
+ omicron: "\u03BF",
+ pi: "\u03C0",
+ rho: "\u03C1",
+ sigmaf: "\u03C2",
+ sigma: "\u03C3",
+ tau: "\u03C4",
+ upsilon: "\u03C5",
+ phi: "\u03C6",
+ chi: "\u03C7",
+ psi: "\u03C8",
+ omega: "\u03C9",
+ thetasym: "\u03D1",
+ upsih: "\u03D2",
+ piv: "\u03D6",
+ bull: "\u2022",
+ hellip: "\u2026",
+ prime: "\u2032",
+ Prime: "\u2033",
+ oline: "\u203E",
+ frasl: "\u2044",
+ weierp: "\u2118",
+ image: "\u2111",
+ real: "\u211C",
+ trade: "\u2122",
+ alefsym: "\u2135",
+ larr: "\u2190",
+ uarr: "\u2191",
+ rarr: "\u2192",
+ darr: "\u2193",
+ harr: "\u2194",
+ crarr: "\u21B5",
+ lArr: "\u21D0",
+ uArr: "\u21D1",
+ rArr: "\u21D2",
+ dArr: "\u21D3",
+ hArr: "\u21D4",
+ forall: "\u2200",
+ part: "\u2202",
+ exist: "\u2203",
+ empty: "\u2205",
+ nabla: "\u2207",
+ isin: "\u2208",
+ notin: "\u2209",
+ ni: "\u220B",
+ prod: "\u220F",
+ sum: "\u2211",
+ minus: "\u2212",
+ lowast: "\u2217",
+ radic: "\u221A",
+ prop: "\u221D",
+ infin: "\u221E",
+ ang: "\u2220",
+ and: "\u2227",
+ or: "\u2228",
+ cap: "\u2229",
+ cup: "\u222A",
+ intXX: "\u222B",
+ there4: "\u2234",
+ sim: "\u223C",
+ cong: "\u2245",
+ asymp: "\u2248",
+ ne: "\u2260",
+ equiv: "\u2261",
+ le: "\u2264",
+ ge: "\u2265",
+ sub: "\u2282",
+ sup: "\u2283",
+ nsub: "\u2284",
+ sube: "\u2286",
+ supe: "\u2287",
+ oplus: "\u2295",
+ otimes: "\u2297",
+ perp: "\u22A5",
+ sdot: "\u22C5",
+ lceil: "\u2308",
+ rceil: "\u2309",
+ lfloor: "\u230A",
+ rfloor: "\u230B",
+ lang: "\u2329",
+ rang: "\u232A",
+ loz: "\u25CA",
+ spades: "\u2660",
+ clubs: "\u2663",
+ hearts: "\u2665",
+ diams: "\u2666",
+ quot: "\u0022",
+ amp: "\u0026",
+ lt: "\u003C",
+ gt: "\u003E",
+ OElig: "\u0152",
+ oelig: "\u0153",
+ Scaron: "\u0160",
+ scaron: "\u0161",
+ Yuml: "\u0178",
+ circ: "\u02C6",
+ tilde: "\u02DC",
+ ensp: "\u2002",
+ emsp: "\u2003",
+ thinsp: "\u2009",
+ zwnj: "\u200C",
+ zwj: "\u200D",
+ lrm: "\u200E",
+ rlm: "\u200F",
+ ndash: "\u2013",
+ mdash: "\u2014",
+ lsquo: "\u2018",
+ rsquo: "\u2019",
+ sbquo: "\u201A",
+ ldquo: "\u201C",
+ rdquo: "\u201D",
+ bdquo: "\u201E",
+ dagger: "\u2020",
+ Dagger: "\u2021",
+ permil: "\u2030",
+ lsaquo: "\u2039",
+ rsaquo: "\u203A",
+ euro: "\u20AC",
+
+ // non-standard entities
+ apos: "'"
+};
+
+/**
+ * @author envjs team
+ */
+
+EntityReference = function() {
+ throw new Error("EntityReference Not Implemented" );
+};
+
+/**
+ * @class DOMImplementation -
+ * provides a number of methods for performing operations
+ * that are independent of any particular instance of the
+ * document object model.
+ *
+ * @author Jon van Noort (jon@webarcana.com.au)
+ */
+DOMImplementation = function() {
+ this.preserveWhiteSpace = false; // by default, ignore whitespace
+ this.namespaceAware = true; // by default, handle namespaces
+ this.errorChecking = true; // by default, test for exceptions
+};
+
+__extend__(DOMImplementation.prototype,{
+ // @param feature : string - The package name of the feature to test.
+ // the legal only values are "XML" and "CORE" (case-insensitive).
+ // @param version : string - This is the version number of the package
+ // name to test. In Level 1, this is the string "1.0".*
+ // @return : boolean
+ hasFeature : function(feature, version) {
+ var ret = false;
+ if (feature.toLowerCase() == "xml") {
+ ret = (!version || (version == "1.0") || (version == "2.0"));
+ }
+ else if (feature.toLowerCase() == "core") {
+ ret = (!version || (version == "2.0"));
+ }
+ else if (feature == "http://www.w3.org/TR/SVG11/feature#BasicStructure") {
+ ret = (version == "1.1");
+ }
+ return ret;
+ },
+ createDocumentType : function(qname, publicId, systemId){
+ var doctype = new DocumentType();
+ doctype.nodeName = qname?qname.toUpperCase():null;
+ doctype.publicId = publicId?publicId:null;
+ doctype.systemId = systemId?systemId:null;
+ return doctype;
+ },
+ createDocument : function(nsuri, qname, doctype){
+
+ var doc = null, documentElement;
+
+ doc = new Document(this, null);
+ if(doctype){
+ doc.doctype = doctype;
+ }
+
+ if(nsuri && qname){
+ documentElement = doc.createElementNS(nsuri, qname);
+ }else if(qname){
+ documentElement = doc.createElement(qname);
+ }
+ if(documentElement){
+ doc.appendChild(documentElement);
+ }
+ return doc;
+ },
+ createHTMLDocument : function(title){
+ var doc = new HTMLDocument($implementation, null, "");
+ var html = doc.createElement("html"); doc.appendChild(html);
+ var head = doc.createElement("head"); html.appendChild(head);
+ var body = doc.createElement("body"); html.appendChild(body);
+ var t = doc.createElement("title"); head.appendChild(t);
+ if( title) {
+ t.appendChild(doc.createTextNode(title));
+ }
+ return doc;
+ },
+ translateErrCode : function(code) {
+ //convert DOMException Code to human readable error message;
+ var msg = "";
+
+ switch (code) {
+ case DOMException.INDEX_SIZE_ERR : // 1
+ msg = "INDEX_SIZE_ERR: Index out of bounds";
+ break;
+
+ case DOMException.DOMSTRING_SIZE_ERR : // 2
+ msg = "DOMSTRING_SIZE_ERR: The resulting string is too long to fit in a DOMString";
+ break;
+
+ case DOMException.HIERARCHY_REQUEST_ERR : // 3
+ msg = "HIERARCHY_REQUEST_ERR: The Node can not be inserted at this location";
+ break;
+
+ case DOMException.WRONG_DOCUMENT_ERR : // 4
+ msg = "WRONG_DOCUMENT_ERR: The source and the destination Documents are not the same";
+ break;
+
+ case DOMException.INVALID_CHARACTER_ERR : // 5
+ msg = "INVALID_CHARACTER_ERR: The string contains an invalid character";
+ break;
+
+ case DOMException.NO_DATA_ALLOWED_ERR : // 6
+ msg = "NO_DATA_ALLOWED_ERR: This Node / NodeList does not support data";
+ break;
+
+ case DOMException.NO_MODIFICATION_ALLOWED_ERR : // 7
+ msg = "NO_MODIFICATION_ALLOWED_ERR: This object cannot be modified";
+ break;
+
+ case DOMException.NOT_FOUND_ERR : // 8
+ msg = "NOT_FOUND_ERR: The item cannot be found";
+ break;
+
+ case DOMException.NOT_SUPPORTED_ERR : // 9
+ msg = "NOT_SUPPORTED_ERR: This implementation does not support function";
+ break;
+
+ case DOMException.INUSE_ATTRIBUTE_ERR : // 10
+ msg = "INUSE_ATTRIBUTE_ERR: The Attribute has already been assigned to another Element";
+ break;
+
+ // Introduced in DOM Level 2:
+ case DOMException.INVALID_STATE_ERR : // 11
+ msg = "INVALID_STATE_ERR: The object is no longer usable";
+ break;
+
+ case DOMException.SYNTAX_ERR : // 12
+ msg = "SYNTAX_ERR: Syntax error";
+ break;
+
+ case DOMException.INVALID_MODIFICATION_ERR : // 13
+ msg = "INVALID_MODIFICATION_ERR: Cannot change the type of the object";
+ break;
+
+ case DOMException.NAMESPACE_ERR : // 14
+ msg = "NAMESPACE_ERR: The namespace declaration is incorrect";
+ break;
+
+ case DOMException.INVALID_ACCESS_ERR : // 15
+ msg = "INVALID_ACCESS_ERR: The object does not support this function";
+ break;
+
+ default :
+ msg = "UNKNOWN: Unknown Exception Code ("+ code +")";
+ }
+
+ return msg;
+ },
+ toString : function(){
+ return "[object DOMImplementation]";
+ }
+});
+
+
+
+/**
+ * @method DOMImplementation._isNamespaceDeclaration - Return true, if attributeName is a namespace declaration
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param attributeName : string - the attribute name
+ * @return : boolean
+ */
+function __isNamespaceDeclaration__(attributeName) {
+ // test if attributeName is 'xmlns'
+ return (attributeName.indexOf('xmlns') > -1);
+}
+
+/**
+ * @method DOMImplementation._isIdDeclaration - Return true, if attributeName is an id declaration
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param attributeName : string - the attribute name
+ * @return : boolean
+ */
+function __isIdDeclaration__(attributeName) {
+ // test if attributeName is 'id' (case insensitive)
+ return attributeName?(attributeName.toLowerCase() == 'id'):false;
+}
+
+/**
+ * @method DOMImplementation._isValidName - Return true,
+ * if name contains no invalid characters
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param name : string - the candidate name
+ * @return : boolean
+ */
+function __isValidName__(name) {
+ // test if name contains only valid characters
+ return name.match(re_validName);
+}
+var re_validName = /^[a-zA-Z_:][a-zA-Z0-9\.\-_:]*$/;
+
+/**
+ * @method DOMImplementation._isValidString - Return true, if string does not contain any illegal chars
+ * All of the characters 0 through 31 and character 127 are nonprinting control characters.
+ * With the exception of characters 09, 10, and 13, (Ox09, Ox0A, and Ox0D)
+ * Note: different from _isValidName in that ValidStrings may contain spaces
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param name : string - the candidate string
+ * @return : boolean
+ */
+function __isValidString__(name) {
+ // test that string does not contains invalid characters
+ return (name.search(re_invalidStringChars) < 0);
+}
+var re_invalidStringChars = /\x01|\x02|\x03|\x04|\x05|\x06|\x07|\x08|\x0B|\x0C|\x0E|\x0F|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1A|\x1B|\x1C|\x1D|\x1E|\x1F|\x7F/;
+
+/**
+ * @method DOMImplementation._parseNSName - parse the namespace name.
+ * if there is no colon, the
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param qualifiedName : string - The qualified name
+ * @return : NSName - [
+ .prefix : string - The prefix part of the qname
+ .namespaceName : string - The namespaceURI part of the qname
+ ]
+ */
+function __parseNSName__(qualifiedName) {
+ var resultNSName = {};
+ // unless the qname has a namespaceName, the prefix is the entire String
+ resultNSName.prefix = qualifiedName;
+ resultNSName.namespaceName = "";
+ // split on ':'
+ var delimPos = qualifiedName.indexOf(':');
+ if (delimPos > -1) {
+ // get prefix
+ resultNSName.prefix = qualifiedName.substring(0, delimPos);
+ // get namespaceName
+ resultNSName.namespaceName = qualifiedName.substring(delimPos +1, qualifiedName.length);
+ }
+ return resultNSName;
+}
+
+/**
+ * @method DOMImplementation._parseQName - parse the qualified name
+ * @author Jon van Noort (jon@webarcana.com.au)
+ * @param qualifiedName : string - The qualified name
+ * @return : QName
+ */
+function __parseQName__(qualifiedName) {
+ var resultQName = {};
+ // unless the qname has a prefix, the local name is the entire String
+ resultQName.localName = qualifiedName;
+ resultQName.prefix = "";
+ // split on ':'
+ var delimPos = qualifiedName.indexOf(':');
+ if (delimPos > -1) {
+ // get prefix
+ resultQName.prefix = qualifiedName.substring(0, delimPos);
+ // get localName
+ resultQName.localName = qualifiedName.substring(delimPos +1, qualifiedName.length);
+ }
+ return resultQName;
+}
+/**
+ * @author envjs team
+ */
+Notation = function() {
+ throw new Error("Notation Not Implemented" );
+};/**
+ * @author thatcher
+ */
+Range = function(){
+
+};
+
+__extend__(Range.prototype, {
+ get startContainer(){
+
+ },
+ get endContainer(){
+
+ },
+ get startOffset(){
+
+ },
+ get endOffset(){
+
+ },
+ get collapsed(){
+
+ },
+ get commonAncestorContainer(){
+
+ },
+ setStart: function(refNode, offset){//throws RangeException
+
+ },
+ setEnd: function(refNode, offset){//throws RangeException
+
+ },
+ setStartBefore: function(refNode){//throws RangeException
+
+ },
+ setStartAfter: function(refNode){//throws RangeException
+
+ },
+ setEndBefore: function(refNode){//throws RangeException
+
+ },
+ setEndAfter: function(refNode){//throws RangeException
+
+ },
+ collapse: function(toStart){//throws RangeException
+
+ },
+ selectNode: function(refNode){//throws RangeException
+
+ },
+ selectNodeContents: function(refNode){//throws RangeException
+
+ },
+ compareBoundaryPoints: function(how, sourceRange){
+
+ },
+ deleteContents: function(){
+
+ },
+ extractContents: function(){
+
+ },
+ cloneContents: function(){
+
+ },
+ insertNode: function(newNode){
+
+ },
+ surroundContents: function(newParent){
+
+ },
+ cloneRange: function(){
+
+ },
+ toString: function(){
+ return '[object Range]';
+ },
+ detach: function(){
+
+ }
+});
+
+
+ // CompareHow
+Range.START_TO_START = 0;
+Range.START_TO_END = 1;
+Range.END_TO_END = 2;
+Range.END_TO_START = 3;
+
+/*
+ * Forward declarations
+ */
+var __isValidNamespace__;
+
+/**
+ * @class Document - The Document interface represents the entire HTML
+ * or XML document. Conceptually, it is the root of the document tree,
+ * and provides the primary access to the document's data.
+ *
+ * @extends Node
+ * @param implementation : DOMImplementation - the creator Implementation
+ */
+Document = function(implementation, docParentWindow) {
+ Node.apply(this, arguments);
+
+ //TODO: Temporary!!! Cnage back to true!!!
+ this.async = true;
+ // The Document Type Declaration (see DocumentType) associated with this document
+ this.doctype = null;
+ // The DOMImplementation object that handles this document.
+ this.implementation = implementation;
+
+ this.nodeName = "#document";
+ // initially false, set to true by parser
+ this.parsing = false;
+ this.baseURI = 'about:blank';
+
+ this.ownerDocument = null;
+
+ this.importing = false;
+};
+
+Document.prototype = new Node();
+__extend__(Document.prototype,{
+ get localName(){
+ return null;
+ },
+ get textContent(){
+ return null;
+ },
+ get all(){
+ return this.getElementsByTagName("*");
+ },
+ get documentElement(){
+ var i, length = this.childNodes?this.childNodes.length:0;
+ for(i=0;i -1 ){
+ valid = false;
+ }
+
+ if ((valid) && (!isAttribute)) {
+ // if the namespaceURI is not null
+ if (!namespaceURI) {
+ valid = false;
+ }
+ }
+
+ // if the qualifiedName has a prefix
+ if ((valid) && (qName.prefix === "")) {
+ valid = false;
+ }
+ }
+
+ // if the qualifiedName has a prefix that is "xml" and the namespaceURI is
+ // different from "http://www.w3.org/XML/1998/namespace" [Namespaces].
+ if ((valid) && (qName.prefix === "xml") && (namespaceURI !== "http://www.w3.org/XML/1998/namespace")) {
+ valid = false;
+ }
+
+ return valid;
+};
+/**
+ *
+ * This file only handles XML parser.
+ * It is extended by parser/domparser.js (and parser/htmlparser.js)
+ *
+ * This depends on e4x, which some engines may not have.
+ *
+ * @author thatcher
+ */
+DOMParser = function(principle, documentURI, baseURI) {
+ // TODO: why/what should these 3 args do?
+};
+__extend__(DOMParser.prototype,{
+ parseFromString: function(xmlstring, mimetype){
+ var doc = new Document(new DOMImplementation()),
+ e4;
+
+ // The following are e4x directives.
+ // Full spec is here:
+ // http://www.ecma-international.org/publications/standards/Ecma-357.htm
+ //
+ // that is pretty gross, so checkout this summary
+ // http://rephrase.net/days/07/06/e4x
+ //
+ // also see the Mozilla Developer Center:
+ // https://developer.mozilla.org/en/E4X
+ //
+ XML.ignoreComments = false;
+ XML.ignoreProcessingInstructions = false;
+ XML.ignoreWhitespace = false;
+
+ // for some reason e4x can't handle initial xml declarations
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=336551
+ // The official workaround is the big regexp below
+ // but simpler one seems to be ok
+ // xmlstring = xmlstring.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
+ //
+ xmlstring = xmlstring.replace(/<\?xml.*\?>/);
+
+ e4 = new XMLList(xmlstring);
+
+ __toDomNode__(e4, doc, doc);
+
+ //console.log('xml \n %s', doc.documentElement.xml);
+ return doc;
+ }
+});
+
+var __toDomNode__ = function(e4, parent, doc){
+ var xnode,
+ domnode,
+ children,
+ target,
+ value,
+ length,
+ element,
+ kind,
+ item;
+ //console.log('converting e4x node list \n %s', e4)
+
+ // not using the for each(item in e4) since some engines can't
+ // handle the syntax (i.e. says syntax error)
+ //
+ // for each(xnode in e4) {
+ for (item in e4) {
+ // NO do not do this if (e4.hasOwnProperty(item)) {
+ // breaks spidermonkey
+ xnode = e4[item];
+
+ kind = xnode.nodeKind();
+ //console.log('treating node kind %s', kind);
+ switch(kind){
+ case 'element':
+ // add node
+ //console.log('creating element %s %s', xnode.localName(), xnode.namespace());
+ if(xnode.namespace() && (xnode.namespace()+'') !== ''){
+ //console.log('createElementNS %s %s',xnode.namespace()+'', xnode.localName() );
+ domnode = doc.createElementNS(xnode.namespace()+'', xnode.localName());
+ }else{
+ domnode = doc.createElement(xnode.name()+'');
+ }
+ parent.appendChild(domnode);
+
+ // add attributes
+ __toDomNode__(xnode.attributes(), domnode, doc);
+
+ // add children
+ children = xnode.children();
+ length = children.length();
+ //console.log('recursing? %s', length ? 'yes' : 'no');
+ if (length > 0) {
+ __toDomNode__(children, domnode, doc);
+ }
+ break;
+ case 'attribute':
+ // console.log('setting attribute %s %s %s',
+ // xnode.localName(), xnode.namespace(), xnode.valueOf());
+
+ //
+ // cross-platform alert. The original code used
+ // xnode.text() to get the attribute value
+ // This worked in Rhino, but did not in Spidermonkey
+ // valueOf seemed to work in both
+ //
+ if(xnode.namespace() && xnode.namespace().prefix){
+ //console.log("%s", xnode.namespace().prefix);
+ parent.setAttributeNS(xnode.namespace()+'',
+ xnode.namespace().prefix+':'+xnode.localName(),
+ xnode.valueOf());
+ }else if((xnode.name()+'').match('http://www.w3.org/2000/xmlns/::')){
+ if(xnode.localName()!=='xmlns'){
+ parent.setAttributeNS('http://www.w3.org/2000/xmlns/',
+ 'xmlns:'+xnode.localName(),
+ xnode.valueOf());
+ }
+ }else{
+ parent.setAttribute(xnode.localName()+'', xnode.valueOf());
+ }
+ break;
+ case 'text':
+ //console.log('creating text node : %s', xnode);
+ domnode = doc.createTextNode(xnode+'');
+ parent.appendChild(domnode);
+ break;
+ case 'comment':
+ //console.log('creating comment node : %s', xnode);
+ value = xnode+'';
+ domnode = doc.createComment(value.substring(4,value.length-3));
+ parent.appendChild(domnode);
+ break;
+ case 'processing-instruction':
+ //console.log('creating processing-instruction node : %s', xnode);
+ value = xnode+'';
+ target = value.split(' ')[0].substring(2);
+ value = value.split(' ').splice(1).join(' ').replace('?>','');
+ //console.log('creating processing-instruction data : %s', value);
+ domnode = doc.createProcessingInstruction(target, value);
+ parent.appendChild(domnode);
+ break;
+ default:
+ console.log('e4x DOM ERROR');
+ throw new Error("Assertion failed in xml parser");
+ }
+ }
+};
+/**
+ * @author envjs team
+ * @class XMLSerializer
+ */
+
+XMLSerializer = function() {};
+
+__extend__(XMLSerializer.prototype, {
+ serializeToString: function(node){
+ return node.xml;
+ },
+ toString : function(){
+ return "[object XMLSerializer]";
+ }
+});
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+/*
+ * Envjs event.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ *
+ * This file simply provides the global definitions we need to
+ * be able to correctly implement to core browser DOM Event interfaces.
+ */
+var Event,
+ MouseEvent,
+ UIEvent,
+ KeyboardEvent,
+ MutationEvent,
+ DocumentEvent,
+ EventTarget,
+ EventException,
+ //nonstandard but very useful for implementing mutation events
+ //among other things like general profiling
+ Aspect;
+/*
+ * Envjs event.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author john resig
+ */
+// Helper method for extending one object with another.
+function __extend__(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+ if ( g || s ) {
+ if ( g ) { a.__defineGetter__(i, g); }
+ if ( s ) { a.__defineSetter__(i, s); }
+ } else {
+ a[i] = b[i];
+ }
+ } return a;
+}
+
+/**
+ * @author john resig
+ */
+//from jQuery
+function __setArray__( target, array ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ target.length = 0;
+ Array.prototype.push.apply( target, array );
+}
+/**
+ * Borrowed with love from:
+ *
+ * jQuery AOP - jQuery plugin to add features of aspect-oriented programming (AOP) to jQuery.
+ * http://jquery-aop.googlecode.com/
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Version: 1.1
+ */
+(function() {
+
+ var _after = 1;
+ var _before = 2;
+ var _around = 3;
+ var _intro = 4;
+ var _regexEnabled = true;
+
+ /**
+ * Private weaving function.
+ */
+ var weaveOne = function(source, method, advice) {
+
+ var old = source[method];
+
+ var aspect;
+ if (advice.type == _after)
+ aspect = function() {
+ var returnValue = old.apply(this, arguments);
+ return advice.value.apply(this, [returnValue, method]);
+ };
+ else if (advice.type == _before)
+ aspect = function() {
+ advice.value.apply(this, [arguments, method]);
+ return old.apply(this, arguments);
+ };
+ else if (advice.type == _intro)
+ aspect = function() {
+ return advice.value.apply(this, arguments);
+ };
+ else if (advice.type == _around) {
+ aspect = function() {
+ var invocation = { object: this, args: arguments };
+ return advice.value.apply(invocation.object, [{ arguments: invocation.args, method: method, proceed :
+ function() {
+ return old.apply(invocation.object, invocation.args);
+ }
+ }] );
+ };
+ }
+
+ aspect.unweave = function() {
+ source[method] = old;
+ pointcut = source = aspect = old = null;
+ };
+
+ source[method] = aspect;
+
+ return aspect;
+
+ };
+
+
+ /**
+ * Private weaver and pointcut parser.
+ */
+ var weave = function(pointcut, advice)
+ {
+
+ var source = (typeof(pointcut.target.prototype) != 'undefined') ? pointcut.target.prototype : pointcut.target;
+ var advices = [];
+
+ // If it's not an introduction and no method was found, try with regex...
+ if (advice.type != _intro && typeof(source[pointcut.method]) == 'undefined')
+ {
+
+ for (var method in source)
+ {
+ if (source[method] != null && source[method] instanceof Function && method.match(pointcut.method))
+ {
+ advices[advices.length] = weaveOne(source, method, advice);
+ }
+ }
+
+ if (advices.length == 0)
+ throw 'No method: ' + pointcut.method;
+
+ }
+ else
+ {
+ // Return as an array of one element
+ advices[0] = weaveOne(source, pointcut.method, advice);
+ }
+
+ return _regexEnabled ? advices : advices[0];
+
+ };
+
+ Aspect =
+ {
+ /**
+ * Creates an advice after the defined point-cut. The advice will be executed after the point-cut method
+ * has completed execution successfully, and will receive one parameter with the result of the execution.
+ * This function returns an array of weaved aspects (Function).
+ *
+ * @example jQuery.aop.after( {target: window, method: 'MyGlobalMethod'}, function(result) { alert('Returned: ' + result); } );
+ * @result Array
+ *
+ * @example jQuery.aop.after( {target: String, method: 'indexOf'}, function(index) { alert('Result found at: ' + index + ' on:' + this); } );
+ * @result Array
+ *
+ * @name after
+ * @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
+ * @option Object target Target object to be weaved.
+ * @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
+ * @param Function advice Function containing the code that will get called after the execution of the point-cut. It receives one parameter
+ * with the result of the point-cut's execution.
+ *
+ * @type Array
+ * @cat Plugins/General
+ */
+ after : function(pointcut, advice)
+ {
+ return weave( pointcut, { type: _after, value: advice } );
+ },
+
+ /**
+ * Creates an advice before the defined point-cut. The advice will be executed before the point-cut method
+ * but cannot modify the behavior of the method, or prevent its execution.
+ * This function returns an array of weaved aspects (Function).
+ *
+ * @example jQuery.aop.before( {target: window, method: 'MyGlobalMethod'}, function() { alert('About to execute MyGlobalMethod'); } );
+ * @result Array
+ *
+ * @example jQuery.aop.before( {target: String, method: 'indexOf'}, function(index) { alert('About to execute String.indexOf on: ' + this); } );
+ * @result Array
+ *
+ * @name before
+ * @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
+ * @option Object target Target object to be weaved.
+ * @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
+ * @param Function advice Function containing the code that will get called before the execution of the point-cut.
+ *
+ * @type Array
+ * @cat Plugins/General
+ */
+ before : function(pointcut, advice)
+ {
+ return weave( pointcut, { type: _before, value: advice } );
+ },
+
+
+ /**
+ * Creates an advice 'around' the defined point-cut. This type of advice can control the point-cut method execution by calling
+ * the functions '.proceed()' on the 'invocation' object, and also, can modify the arguments collection before sending them to the function call.
+ * This function returns an array of weaved aspects (Function).
+ *
+ * @example jQuery.aop.around( {target: window, method: 'MyGlobalMethod'}, function(invocation) {
+ * alert('# of Arguments: ' + invocation.arguments.length);
+ * return invocation.proceed();
+ * } );
+ * @result Array
+ *
+ * @example jQuery.aop.around( {target: String, method: 'indexOf'}, function(invocation) {
+ * alert('Searching: ' + invocation.arguments[0] + ' on: ' + this);
+ * return invocation.proceed();
+ * } );
+ * @result Array
+ *
+ * @example jQuery.aop.around( {target: window, method: /Get(\d+)/}, function(invocation) {
+ * alert('Executing ' + invocation.method);
+ * return invocation.proceed();
+ * } );
+ * @desc Matches all global methods starting with 'Get' and followed by a number.
+ * @result Array
+ *
+ *
+ * @name around
+ * @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
+ * @option Object target Target object to be weaved.
+ * @option String method Name of the function to be weaved. Regex are supported, but not on built-in objects.
+ * @param Function advice Function containing the code that will get called around the execution of the point-cut. This advice will be called with one
+ * argument containing one function '.proceed()', the collection of arguments '.arguments', and the matched method name '.method'.
+ *
+ * @type Array
+ * @cat Plugins/General
+ */
+ around : function(pointcut, advice)
+ {
+ return weave( pointcut, { type: _around, value: advice } );
+ },
+
+ /**
+ * Creates an introduction on the defined point-cut. This type of advice replaces any existing methods with the same
+ * name. To restore them, just unweave it.
+ * This function returns an array with only one weaved aspect (Function).
+ *
+ * @example jQuery.aop.introduction( {target: window, method: 'MyGlobalMethod'}, function(result) { alert('Returned: ' + result); } );
+ * @result Array
+ *
+ * @example jQuery.aop.introduction( {target: String, method: 'log'}, function() { alert('Console: ' + this); } );
+ * @result Array
+ *
+ * @name introduction
+ * @param Map pointcut Definition of the point-cut to apply the advice. A point-cut is the definition of the object/s and method/s to be weaved.
+ * @option Object target Target object to be weaved.
+ * @option String method Name of the function to be weaved.
+ * @param Function advice Function containing the code that will be executed on the point-cut.
+ *
+ * @type Array
+ * @cat Plugins/General
+ */
+ introduction : function(pointcut, advice)
+ {
+ return weave( pointcut, { type: _intro, value: advice } );
+ },
+
+ /**
+ * Configures global options.
+ *
+ * @name setup
+ * @param Map settings Configuration options.
+ * @option Boolean regexMatch Enables/disables regex matching of method names.
+ *
+ * @example jQuery.aop.setup( { regexMatch: false } );
+ * @desc Disable regex matching.
+ *
+ * @type Void
+ * @cat Plugins/General
+ */
+ setup: function(settings)
+ {
+ _regexEnabled = settings.regexMatch;
+ }
+ };
+
+})();
+
+
+
+
+/**
+ * @name EventTarget
+ * @w3c:domlevel 2
+ * @uri -//TODO: paste dom event level 2 w3c spc uri here
+ */
+EventTarget = function(){};
+EventTarget.prototype.addEventListener = function(type, fn, phase){
+ __addEventListener__(this, type, fn, phase);
+};
+EventTarget.prototype.removeEventListener = function(type, fn){
+ __removeEventListener__(this, type, fn);
+};
+EventTarget.prototype.dispatchEvent = function(event, bubbles){
+ __dispatchEvent__(this, event, bubbles);
+};
+
+__extend__(Node.prototype, EventTarget.prototype);
+
+
+var $events = [{}];
+
+function __addEventListener__(target, type, fn, phase){
+ phase = !!phase?"CAPTURING":"BUBBLING";
+ if ( !target.uuid ) {
+ //console.log('event uuid %s %s', target, target.uuid);
+ target.uuid = $events.length+'';
+ }
+ if ( !$events[target.uuid] ) {
+ //console.log('creating listener for target: %s %s', target, target.uuid);
+ $events[target.uuid] = {};
+ }
+ if ( !$events[target.uuid][type] ){
+ //console.log('creating listener for type: %s %s %s', target, target.uuid, type);
+ $events[target.uuid][type] = {
+ CAPTURING:[],
+ BUBBLING:[]
+ };
+ }
+ if ( $events[target.uuid][type][phase].indexOf( fn ) < 0 ){
+ //console.log('adding event listener %s %s %s %s %s %s', target, target.uuid, type, phase,
+ // $events[target.uuid][type][phase].length, $events[target.uuid][type][phase].indexOf( fn ));
+ //console.log('creating listener for function: %s %s %s', target, target.uuid, phase);
+ $events[target.uuid][type][phase].push( fn );
+ //console.log('adding event listener %s %s %s %s %s %s', target, target.uuid, type, phase,
+ // $events[target.uuid][type][phase].length, $events[target.uuid][type][phase].indexOf( fn ));
+ }
+ //console.log('registered event listeners %s', $events.length);
+}
+
+function __removeEventListener__(target, type, fn, phase){
+
+ phase = !!phase?"CAPTURING":"BUBBLING";
+ if ( !target.uuid ) {
+ return;
+ }
+ if ( !$events[target.uuid] ) {
+ return;
+ }
+ if(type == '*'){
+ //used to clean all event listeners for a given node
+ //console.log('cleaning all event listeners for node %s %s',target, target.uuid);
+ delete $events[target.uuid];
+ return;
+ }else if ( !$events[target.uuid][type] ){
+ return;
+ }
+ $events[target.uuid][type][phase] =
+ $events[target.uuid][type][phase].filter(function(f){
+ //console.log('removing event listener %s %s %s %s', target, type, phase, fn);
+ return f != fn;
+ });
+}
+
+var __eventuuid__ = 0;
+function __dispatchEvent__(target, event, bubbles){
+
+ if (!event.uuid) {
+ event.uuid = __eventuuid__++;
+ }
+ //the window scope defines the $event object, for IE(^^^) compatibility;
+ //$event = event;
+ //console.log('dispatching event %s', event.uuid);
+ if (bubbles === undefined || bubbles === null) {
+ bubbles = true;
+ }
+
+ if (!event.target) {
+ event.target = target;
+ }
+
+ //console.log('dispatching? %s %s %s', target, event.type, bubbles);
+ if ( event.type && (target.nodeType || target === window )) {
+
+ //console.log('dispatching event %s %s %s', target, event.type, bubbles);
+ __captureEvent__(target, event);
+
+ event.eventPhase = Event.AT_TARGET;
+ if ( target.uuid && $events[target.uuid] && $events[target.uuid][event.type] ) {
+ event.currentTarget = target;
+ //console.log('dispatching %s %s %s %s', target, event.type,
+ // $events[target.uuid][event.type]['CAPTURING'].length);
+ $events[target.uuid][event.type].CAPTURING.forEach(function(fn){
+ //console.log('AT_TARGET (CAPTURING) event %s', fn);
+ var returnValue = fn( event );
+ //console.log('AT_TARGET (CAPTURING) return value %s', returnValue);
+ if(returnValue === false){
+ event.stopPropagation();
+ }
+ });
+ //console.log('dispatching %s %s %s %s', target, event.type,
+ // $events[target.uuid][event.type]['BUBBLING'].length);
+ $events[target.uuid][event.type].BUBBLING.forEach(function(fn){
+ //console.log('AT_TARGET (BUBBLING) event %s', fn);
+ var returnValue = fn( event );
+ //console.log('AT_TARGET (BUBBLING) return value %s', returnValue);
+ if(returnValue === false){
+ event.stopPropagation();
+ }
+ });
+ }
+ if (target["on" + event.type]) {
+ target["on" + event.type](event);
+ }
+ if (bubbles && !event.cancelled){
+ __bubbleEvent__(target, event);
+ }
+ if(!event._preventDefault){
+ //At this point I'm guessing that just HTMLEvents are concerned
+ //with default behavior being executed in a browser but I could be
+ //wrong as usual. The goal is much more to filter at this point
+ //what events have no need to be handled
+ //console.log('triggering default behavior for %s', event.type);
+ if(event.type in Envjs.defaultEventBehaviors){
+ Envjs.defaultEventBehaviors[event.type](event);
+ }
+ }
+ //console.log('deleting event %s', event.uuid);
+ event.target = null;
+ event = null;
+ }else{
+ throw new EventException(EventException.UNSPECIFIED_EVENT_TYPE_ERR);
+ }
+}
+
+function __captureEvent__(target, event){
+ var ancestorStack = [],
+ parent = target.parentNode;
+
+ event.eventPhase = Event.CAPTURING_PHASE;
+ while(parent){
+ if(parent.uuid && $events[parent.uuid] && $events[parent.uuid][event.type]){
+ ancestorStack.push(parent);
+ }
+ parent = parent.parentNode;
+ }
+ while(ancestorStack.length && !event.cancelled){
+ event.currentTarget = ancestorStack.pop();
+ if($events[event.currentTarget.uuid] && $events[event.currentTarget.uuid][event.type]){
+ $events[event.currentTarget.uuid][event.type].CAPTURING.forEach(function(fn){
+ var returnValue = fn( event );
+ if(returnValue === false){
+ event.stopPropagation();
+ }
+ });
+ }
+ }
+}
+
+function __bubbleEvent__(target, event){
+ var parent = target.parentNode;
+ event.eventPhase = Event.BUBBLING_PHASE;
+ while(parent){
+ if(parent.uuid && $events[parent.uuid] && $events[parent.uuid][event.type] ){
+ event.currentTarget = parent;
+ $events[event.currentTarget.uuid][event.type].BUBBLING.forEach(function(fn){
+ var returnValue = fn( event );
+ if(returnValue === false){
+ event.stopPropagation();
+ }
+ });
+ }
+ parent = parent.parentNode;
+ }
+}
+
+/**
+ * @class Event
+ */
+Event = function(options){
+ // event state is kept read-only by forcing
+ // a new object for each event. This may not
+ // be appropriate in the long run and we'll
+ // have to decide if we simply dont adhere to
+ // the read-only restriction of the specification
+ this._bubbles = true;
+ this._cancelable = true;
+ this._cancelled = false;
+ this._currentTarget = null;
+ this._target = null;
+ this._eventPhase = Event.AT_TARGET;
+ this._timeStamp = new Date().getTime();
+ this._preventDefault = false;
+ this._stopPropogation = false;
+};
+
+__extend__(Event.prototype,{
+ get bubbles(){return this._bubbles;},
+ get cancelable(){return this._cancelable;},
+ get currentTarget(){return this._currentTarget;},
+ set currentTarget(currentTarget){ this._currentTarget = currentTarget; },
+ get eventPhase(){return this._eventPhase;},
+ set eventPhase(eventPhase){this._eventPhase = eventPhase;},
+ get target(){return this._target;},
+ set target(target){ this._target = target;},
+ get timeStamp(){return this._timeStamp;},
+ get type(){return this._type;},
+ initEvent: function(type, bubbles, cancelable){
+ this._type=type?type:'';
+ this._bubbles=!!bubbles;
+ this._cancelable=!!cancelable;
+ },
+ preventDefault: function(){
+ this._preventDefault = true;
+ },
+ stopPropagation: function(){
+ if(this._cancelable){
+ this._cancelled = true;
+ this._bubbles = false;
+ }
+ },
+ get cancelled(){
+ return this._cancelled;
+ },
+ toString: function(){
+ return '[object Event]';
+ }
+});
+
+__extend__(Event,{
+ CAPTURING_PHASE : 1,
+ AT_TARGET : 2,
+ BUBBLING_PHASE : 3
+});
+
+
+
+/**
+ * @name UIEvent
+ * @param {Object} options
+ */
+UIEvent = function(options) {
+ this._view = null;
+ this._detail = 0;
+};
+
+UIEvent.prototype = new Event();
+__extend__(UIEvent.prototype,{
+ get view(){
+ return this._view;
+ },
+ get detail(){
+ return this._detail;
+ },
+ initUIEvent: function(type, bubbles, cancelable, windowObject, detail){
+ this.initEvent(type, bubbles, cancelable);
+ this._detail = 0;
+ this._view = windowObject;
+ }
+});
+
+var $onblur,
+ $onfocus,
+ $onresize;
+
+
+/**
+ * @name MouseEvent
+ * @w3c:domlevel 2
+ * @uri http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
+ */
+MouseEvent = function(options) {
+ this._screenX= 0;
+ this._screenY= 0;
+ this._clientX= 0;
+ this._clientY= 0;
+ this._ctrlKey= false;
+ this._metaKey= false;
+ this._altKey= false;
+ this._button= null;
+ this._relatedTarget= null;
+};
+MouseEvent.prototype = new UIEvent();
+__extend__(MouseEvent.prototype,{
+ get screenX(){
+ return this._screenX;
+ },
+ get screenY(){
+ return this._screenY;
+ },
+ get clientX(){
+ return this._clientX;
+ },
+ get clientY(){
+ return this._clientY;
+ },
+ get ctrlKey(){
+ return this._ctrlKey;
+ },
+ get altKey(){
+ return this._altKey;
+ },
+ get shiftKey(){
+ return this._shiftKey;
+ },
+ get metaKey(){
+ return this._metaKey;
+ },
+ get button(){
+ return this._button;
+ },
+ get relatedTarget(){
+ return this._relatedTarget;
+ },
+ initMouseEvent: function(type, bubbles, cancelable, windowObject, detail,
+ screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey,
+ metaKey, button, relatedTarget){
+ this.initUIEvent(type, bubbles, cancelable, windowObject, detail);
+ this._screenX = screenX;
+ this._screenY = screenY;
+ this._clientX = clientX;
+ this._clientY = clientY;
+ this._ctrlKey = ctrlKey;
+ this._altKey = altKey;
+ this._shiftKey = shiftKey;
+ this._metaKey = metaKey;
+ this._button = button;
+ this._relatedTarget = relatedTarget;
+ }
+});
+
+/**
+ * Interface KeyboardEvent (introduced in DOM Level 3)
+ */
+KeyboardEvent = function(options) {
+ this._keyIdentifier = 0;
+ this._keyLocation = 0;
+ this._ctrlKey = false;
+ this._metaKey = false;
+ this._altKey = false;
+ this._metaKey = false;
+};
+KeyboardEvent.prototype = new UIEvent();
+
+__extend__(KeyboardEvent.prototype,{
+
+ get ctrlKey(){
+ return this._ctrlKey;
+ },
+ get altKey(){
+ return this._altKey;
+ },
+ get shiftKey(){
+ return this._shiftKey;
+ },
+ get metaKey(){
+ return this._metaKey;
+ },
+ get button(){
+ return this._button;
+ },
+ get relatedTarget(){
+ return this._relatedTarget;
+ },
+ getModifiersState: function(keyIdentifier){
+
+ },
+ initMouseEvent: function(type, bubbles, cancelable, windowObject,
+ keyIdentifier, keyLocation, modifiersList, repeat){
+ this.initUIEvent(type, bubbles, cancelable, windowObject, 0);
+ this._keyIdentifier = keyIdentifier;
+ this._keyLocation = keyLocation;
+ this._modifiersList = modifiersList;
+ this._repeat = repeat;
+ }
+});
+
+KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0;
+KeyboardEvent.DOM_KEY_LOCATION_LEFT = 1;
+KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 2;
+KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 3;
+KeyboardEvent.DOM_KEY_LOCATION_MOBILE = 4;
+KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK = 5;
+
+
+
+//We dont fire mutation events until someone has registered for them
+var __supportedMutations__ = /DOMSubtreeModified|DOMNodeInserted|DOMNodeRemoved|DOMAttrModified|DOMCharacterDataModified/;
+
+var __fireMutationEvents__ = Aspect.before({
+ target: EventTarget,
+ method: 'addEventListener'
+}, function(target, type){
+ if(type && type.match(__supportedMutations__)){
+ //unweaving removes the __addEventListener__ aspect
+ __fireMutationEvents__.unweave();
+ // These two methods are enough to cover all dom 2 manipulations
+ Aspect.around({
+ target: Node,
+ method:"removeChild"
+ }, function(invocation){
+ var event,
+ node = invocation.arguments[0];
+ event = node.ownerDocument.createEvent('MutationEvents');
+ event.initEvent('DOMNodeRemoved', true, false, node.parentNode, null, null, null, null);
+ node.dispatchEvent(event, false);
+ return invocation.proceed();
+
+ });
+ Aspect.around({
+ target: Node,
+ method:"appendChild"
+ }, function(invocation) {
+ var event,
+ node = invocation.proceed();
+ event = node.ownerDocument.createEvent('MutationEvents');
+ event.initEvent('DOMNodeInserted', true, false, node.parentNode, null, null, null, null);
+ node.dispatchEvent(event, false);
+ return node;
+ });
+ }
+});
+
+/**
+ * @name MutationEvent
+ * @param {Object} options
+ */
+MutationEvent = function(options) {
+ this._cancelable = false;
+ this._timeStamp = 0;
+};
+
+MutationEvent.prototype = new Event();
+__extend__(MutationEvent.prototype,{
+ get relatedNode(){
+ return this._relatedNode;
+ },
+ get prevValue(){
+ return this._prevValue;
+ },
+ get newValue(){
+ return this._newValue;
+ },
+ get attrName(){
+ return this._attrName;
+ },
+ get attrChange(){
+ return this._attrChange;
+ },
+ initMutationEvent: function( type, bubbles, cancelable,
+ relatedNode, prevValue, newValue, attrName, attrChange ){
+ this._relatedNode = relatedNode;
+ this._prevValue = prevValue;
+ this._newValue = newValue;
+ this._attrName = attrName;
+ this._attrChange = attrChange;
+ switch(type){
+ case "DOMSubtreeModified":
+ this.initEvent(type, true, false);
+ break;
+ case "DOMNodeInserted":
+ this.initEvent(type, true, false);
+ break;
+ case "DOMNodeRemoved":
+ this.initEvent(type, true, false);
+ break;
+ case "DOMNodeRemovedFromDocument":
+ this.initEvent(type, false, false);
+ break;
+ case "DOMNodeInsertedIntoDocument":
+ this.initEvent(type, false, false);
+ break;
+ case "DOMAttrModified":
+ this.initEvent(type, true, false);
+ break;
+ case "DOMCharacterDataModified":
+ this.initEvent(type, true, false);
+ break;
+ default:
+ this.initEvent(type, bubbles, cancelable);
+ }
+ }
+});
+
+// constants
+MutationEvent.ADDITION = 0;
+MutationEvent.MODIFICATION = 1;
+MutationEvent.REMOVAL = 2;
+
+
+/**
+ * @name EventException
+ */
+EventException = function(code) {
+ this.code = code;
+};
+EventException.UNSPECIFIED_EVENT_TYPE_ERR = 0;
+/**
+ *
+ * DOM Level 2: http://www.w3.org/TR/DOM-Level-2-Events/events.html
+ * DOM Level 3: http://www.w3.org/TR/DOM-Level-3-Events/
+ *
+ * interface DocumentEvent {
+ * Event createEvent (in DOMString eventType)
+ * raises (DOMException);
+ * };
+ *
+ * Firefox (3.6) exposes DocumentEvent
+ * Safari (4) does NOT.
+ */
+
+/**
+ * TODO: Not sure we need a full prototype. We not just an regular object?
+ */
+DocumentEvent = function(){};
+DocumentEvent.prototype.__EventMap__ = {
+ // Safari4: singular and plural forms accepted
+ // Firefox3.6: singular and plural forms accepted
+ 'Event' : Event,
+ 'Events' : Event,
+ 'UIEvent' : UIEvent,
+ 'UIEvents' : UIEvent,
+ 'MouseEvent' : MouseEvent,
+ 'MouseEvents' : MouseEvent,
+ 'MutationEvent' : MutationEvent,
+ 'MutationEvents' : MutationEvent,
+
+ // Safari4: accepts HTMLEvents, but not HTMLEvent
+ // Firefox3.6: accepts HTMLEvents, but not HTMLEvent
+ 'HTMLEvent' : Event,
+ 'HTMLEvents' : Event,
+
+ // Safari4: both not accepted
+ // Firefox3.6, only KeyEvents is accepted
+ 'KeyEvent' : KeyboardEvent,
+ 'KeyEvents' : KeyboardEvent,
+
+ // Safari4: both accepted
+ // Firefox3.6: none accepted
+ 'KeyboardEvent' : KeyboardEvent,
+ 'KeyboardEvents' : KeyboardEvent
+};
+
+DocumentEvent.prototype.createEvent = function(eventType) {
+ var Clazz = this.__EventMap__[eventType];
+ if (Clazz) {
+ return new Clazz();
+ }
+ throw(new DOMException(DOMException.NOT_SUPPORTED_ERR));
+};
+
+__extend__(Document.prototype, DocumentEvent.prototype);
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+
+/*
+ * Envjs timer.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ *
+ * Parts of the implementation were originally written by:\
+ * Steven Parkes
+ *
+ * requires Envjs.wait, Envjs.sleep, Envjs.WAIT_INTERVAL
+ */
+var setTimeout,
+ clearTimeout,
+ setInterval,
+ clearInterval;
+
+/*
+ * Envjs timer.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+/*
+* timer.js
+* implementation provided by Steven Parkes
+*/
+
+//private
+var $timers = [],
+ EVENT_LOOP_RUNNING = false;
+
+$timers.lock = function(fn){
+ Envjs.sync(fn)();
+};
+
+//private internal class
+var Timer = function(fn, interval){
+ this.fn = fn;
+ this.interval = interval;
+ this.at = Date.now() + interval;
+ // allows for calling wait() from callbacks
+ this.running = false;
+};
+
+Timer.prototype.start = function(){};
+Timer.prototype.stop = function(){};
+
+//static
+Timer.normalize = function(time) {
+ time = time*1;
+ if ( isNaN(time) || time < 0 ) {
+ time = 0;
+ }
+
+ if ( EVENT_LOOP_RUNNING && time < Timer.MIN_TIME ) {
+ time = Timer.MIN_TIME;
+ }
+ return time;
+};
+// html5 says this should be at least 4, but the parser is using
+// a setTimeout for the SAX stuff which messes up the world
+Timer.MIN_TIME = /* 4 */ 0;
+
+/**
+ * @function setTimeout
+ * @param {Object} fn
+ * @param {Object} time
+ */
+setTimeout = function(fn, time){
+ var num;
+ time = Timer.normalize(time);
+ $timers.lock(function(){
+ num = $timers.length+1;
+ var tfn;
+ if (typeof fn == 'string') {
+ tfn = function() {
+ try {
+ // eval in global scope
+ eval(fn, null);
+ } catch (e) {
+ console.log('timer error %s %s', fn, e);
+ } finally {
+ clearInterval(num);
+ }
+ };
+ } else {
+ tfn = function() {
+ try {
+ fn();
+ } catch (e) {
+ console.log('timer error %s %s', fn, e);
+ } finally {
+ clearInterval(num);
+ }
+ };
+ }
+ //console.log("Creating timer number %s", num);
+ $timers[num] = new Timer(tfn, time);
+ $timers[num].start();
+ });
+ return num;
+};
+
+/**
+ * @function setInterval
+ * @param {Object} fn
+ * @param {Object} time
+ */
+setInterval = function(fn, time){
+ //console.log('setting interval %s %s', time, fn.toString().substring(0,64));
+ time = Timer.normalize(time);
+ if ( time < 10 ) {
+ time = 10;
+ }
+ if (typeof fn == 'string') {
+ var fnstr = fn;
+ fn = function() {
+ eval(fnstr);
+ };
+ }
+ var num;
+ $timers.lock(function(){
+ num = $timers.length+1;
+ //Envjs.debug("Creating timer number "+num);
+ $timers[num] = new Timer(fn, time);
+ $timers[num].start();
+ });
+ return num;
+};
+
+/**
+ * clearInterval
+ * @param {Object} num
+ */
+clearInterval = clearTimeout = function(num){
+ //console.log("clearing interval "+num);
+ $timers.lock(function(){
+ if ( $timers[num] ) {
+ $timers[num].stop();
+ delete $timers[num];
+ }
+ });
+};
+
+// wait === null/undefined: execute any timers as they fire,
+// waiting until there are none left
+// wait(n) (n > 0): execute any timers as they fire until there
+// are none left waiting at least n ms but no more, even if there
+// are future events/current threads
+// wait(0): execute any immediately runnable timers and return
+// wait(-n): keep sleeping until the next event is more than n ms
+// in the future
+//
+// TODO: make a priority queue ...
+
+Envjs.wait = function(wait) {
+ //console.log('wait %s', wait);
+ var delta_wait,
+ start = Date.now(),
+ was_running = EVENT_LOOP_RUNNING;
+
+ if (wait < 0) {
+ delta_wait = -wait;
+ wait = 0;
+ }
+ EVENT_LOOP_RUNNING = true;
+ if (wait !== 0 && wait !== null && wait !== undefined){
+ wait += Date.now();
+ }
+
+ var earliest,
+ timer,
+ sleep,
+ index,
+ goal,
+ now,
+ nextfn;
+
+ for (;;) {
+ //console.log('timer loop');
+ earliest = sleep = goal = now = nextfn = null;
+ $timers.lock(function(){
+ for(index in $timers){
+ if( isNaN(index*0) ) {
+ continue;
+ }
+ timer = $timers[index];
+ // determine timer with smallest run-at time that is
+ // not already running
+ if( !timer.running && ( !earliest || timer.at < earliest.at) ) {
+ earliest = timer;
+ }
+ }
+ });
+ //next sleep time
+ sleep = earliest && earliest.at - Date.now();
+ if ( earliest && sleep <= 0 ) {
+ nextfn = earliest.fn;
+ try {
+ //console.log('running stack %s', nextfn.toString().substring(0,64));
+ earliest.running = true;
+ nextfn();
+ } catch (e) {
+ console.log('timer error %s %s', nextfn, e);
+ } finally {
+ earliest.running = false;
+ }
+ goal = earliest.at + earliest.interval;
+ now = Date.now();
+ if ( goal < now ) {
+ earliest.at = now;
+ } else {
+ earliest.at = goal;
+ }
+ continue;
+ }
+
+ // bunch of subtle cases here ...
+ if ( !earliest ) {
+ // no events in the queue (but maybe XHR will bring in events, so ...
+ if ( !wait || wait < Date.now() ) {
+ // Loop ends if there are no events and a wait hasn't been
+ // requested or has expired
+ break;
+ }
+ // no events, but a wait requested: fall through to sleep
+ } else {
+ // there are events in the queue, but they aren't firable now
+ /*if ( delta_wait && sleep <= delta_wait ) {
+ //TODO: why waste a check on a tight
+ // loop if it just falls through?
+ // if they will happen within the next delta, fall through to sleep
+ } else */if ( wait === 0 || ( wait > 0 && wait < Date.now () ) ) {
+ // loop ends even if there are events but the user
+ // specifcally asked not to wait too long
+ break;
+ }
+ // there are events and the user wants to wait: fall through to sleep
+ }
+
+ // Related to ajax threads ... hopefully can go away ..
+ var interval = Envjs.WAIT_INTERVAL || 100;
+ if ( !sleep || sleep > interval ) {
+ sleep = interval;
+ }
+ //console.log('sleeping %s', sleep);
+ Envjs.sleep(sleep);
+
+ }
+ EVENT_LOOP_RUNNING = was_running;
+};
+
+
+/**
+ * @author john resig & the envjs team
+ * @uri http://www.envjs.com/
+ * @copyright 2008-2010
+ * @license MIT
+ */
+//CLOSURE_END
+}());
+/*
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ *
+ * This file simply provides the global definitions we need to
+ * be able to correctly implement to core browser DOM HTML interfaces.
+ */
+var HTMLDocument,
+ HTMLElement,
+ HTMLCollection,
+ HTMLAnchorElement,
+ HTMLAreaElement,
+ HTMLBaseElement,
+ HTMLQuoteElement,
+ HTMLBodyElement,
+ HTMLBRElement,
+ HTMLButtonElement,
+ HTMLCanvasElement,
+ HTMLTableColElement,
+ HTMLModElement,
+ HTMLDivElement,
+ HTMLDListElement,
+ HTMLFieldSetElement,
+ HTMLFormElement,
+ HTMLFrameElement,
+ HTMLFrameSetElement,
+ HTMLHeadElement,
+ HTMLHeadingElement,
+ HTMLHRElement,
+ HTMLHtmlElement,
+ HTMLIFrameElement,
+ HTMLImageElement,
+ HTMLInputElement,
+ HTMLLabelElement,
+ HTMLLegendElement,
+ HTMLLIElement,
+ HTMLLinkElement,
+ HTMLMapElement,
+ HTMLMetaElement,
+ HTMLObjectElement,
+ HTMLOListElement,
+ HTMLOptGroupElement,
+ HTMLOptionElement,
+ HTMLParagraphElement,
+ HTMLParamElement,
+ HTMLPreElement,
+ HTMLScriptElement,
+ HTMLSelectElement,
+ HTMLSpanElement,
+ HTMLStyleElement,
+ HTMLTableElement,
+ HTMLTableSectionElement,
+ HTMLTableCellElement,
+ HTMLTableDataCellElement,
+ HTMLTableHeaderCellElement,
+ HTMLTableRowElement,
+ HTMLTextAreaElement,
+ HTMLTitleElement,
+ HTMLUListElement,
+ HTMLUnknownElement,
+ Image,
+ Option,
+ __loadImage__,
+ __loadLink__;
+
+/*
+ * Envjs html.1.2.13
+ * Pure JavaScript Browser Environment
+ * By John Resig and the Envjs Team
+ * Copyright 2008-2010 John Resig, under the MIT License
+ */
+
+//CLOSURE_START
+(function(){
+
+
+
+
+
+/**
+ * @author ariel flesler
+ * http://flesler.blogspot.com/2008/11/fast-trim-function-for-javascript.html
+ * @param {Object} str
+ */
+function __trim__( str ){
+ return (str || "").replace( /^\s+|\s+$/g, "" );
+}
+
+
+/**
+ * @author john resig
+ */
+// Helper method for extending one object with another.
+function __extend__(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+ if ( g || s ) {
+ if ( g ) { a.__defineGetter__(i, g); }
+ if ( s ) { a.__defineSetter__(i, s); }
+ } else {
+ a[i] = b[i];
+ }
+ } return a;
+}
+
+/**
+ * @author john resig
+ */
+//from jQuery
+function __setArray__( target, array ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ target.length = 0;
+ Array.prototype.push.apply( target, array );
+}
+
+/**
+ * @class HTMLDocument
+ * The Document interface represents the entire HTML or XML document.
+ * Conceptually, it is the root of the document tree, and provides
+ * the primary access to the document's data.
+ *
+ * @extends Document
+ */
+HTMLDocument = function(implementation, ownerWindow, referrer) {
+ Document.apply(this, arguments);
+ this.referrer = referrer || '';
+ this.baseURI = "about:blank";
+ this.ownerWindow = ownerWindow;
+};
+
+HTMLDocument.prototype = new Document();
+
+__extend__(HTMLDocument.prototype, {
+ createElement: function(tagName){
+ var node;
+ tagName = tagName.toUpperCase();
+ // create Element specifying 'this' as ownerDocument
+ // This is an html document so we need to use explicit interfaces per the
+ //TODO: would be much faster as a big switch
+ switch(tagName){
+ case "A":
+ node = new HTMLAnchorElement(this);break;
+ case "AREA":
+ node = new HTMLAreaElement(this);break;
+ case "BASE":
+ node = new HTMLBaseElement(this);break;
+ case "BLOCKQUOTE":
+ node = new HTMLQuoteElement(this);break;
+ case "CANVAS":
+ node = new HTMLCanvasElement(this);break;
+ case "Q":
+ node = new HTMLQuoteElement(this);break;
+ case "BODY":
+ node = new HTMLBodyElement(this);break;
+ case "BR":
+ node = new HTMLBRElement(this);break;
+ case "BUTTON":
+ node = new HTMLButtonElement(this);break;
+ case "CAPTION":
+ node = new HTMLElement(this);break;
+ case "COL":
+ node = new HTMLTableColElement(this);break;
+ case "COLGROUP":
+ node = new HTMLTableColElement(this);break;
+ case "DEL":
+ node = new HTMLModElement(this);break;
+ case "INS":
+ node = new HTMLModElement(this);break;
+ case "DIV":
+ node = new HTMLDivElement(this);break;
+ case "DL":
+ node = new HTMLDListElement(this);break;
+ case "DT":
+ node = new HTMLElement(this); break;
+ case "FIELDSET":
+ node = new HTMLFieldSetElement(this);break;
+ case "FORM":
+ node = new HTMLFormElement(this);break;
+ case "FRAME":
+ node = new HTMLFrameElement(this);break;
+ case "H1":
+ node = new HTMLHeadingElement(this);break;
+ case "H2":
+ node = new HTMLHeadingElement(this);break;
+ case "H3":
+ node = new HTMLHeadingElement(this);break;
+ case "H4":
+ node = new HTMLHeadingElement(this);break;
+ case "H5":
+ node = new HTMLHeadingElement(this);break;
+ case "H6":
+ node = new HTMLHeadingElement(this);break;
+ case "HEAD":
+ node = new HTMLHeadElement(this);break;
+ case "HR":
+ node = new HTMLHRElement(this);break;
+ case "HTML":
+ node = new HTMLHtmlElement(this);break;
+ case "IFRAME":
+ node = new HTMLIFrameElement(this);break;
+ case "IMG":
+ node = new HTMLImageElement(this);break;
+ case "INPUT":
+ node = new HTMLInputElement(this);break;
+ case "LABEL":
+ node = new HTMLLabelElement(this);break;
+ case "LEGEND":
+ node = new HTMLLegendElement(this);break;
+ case "LI":
+ node = new HTMLLIElement(this);break;
+ case "LINK":
+ node = new HTMLLinkElement(this);break;
+ case "MAP":
+ node = new HTMLMapElement(this);break;
+ case "META":
+ node = new HTMLMetaElement(this);break;
+ case "NOSCRIPT":
+ node = new HTMLElement(this);break;
+ case "OBJECT":
+ node = new HTMLObjectElement(this);break;
+ case "OPTGROUP":
+ node = new HTMLOptGroupElement(this);break;
+ case "OL":
+ node = new HTMLOListElement(this); break;
+ case "OPTION":
+ node = new HTMLOptionElement(this);break;
+ case "P":
+ node = new HTMLParagraphElement(this);break;
+ case "PARAM":
+ node = new HTMLParamElement(this);break;
+ case "PRE":
+ node = new HTMLPreElement(this);break;
+ case "SCRIPT":
+ node = new HTMLScriptElement(this);break;
+ case "SELECT":
+ node = new HTMLSelectElement(this);break;
+ case "SMALL":
+ node = new HTMLElement(this);break;
+ case "SPAN":
+ node = new HTMLSpanElement(this);break;
+ case "STRONG":
+ node = new HTMLElement(this);break;
+ case "STYLE":
+ node = new HTMLStyleElement(this);break;
+ case "TABLE":
+ node = new HTMLTableElement(this);break;
+ case "TBODY":
+ node = new HTMLTableSectionElement(this);break;
+ case "TFOOT":
+ node = new HTMLTableSectionElement(this);break;
+ case "THEAD":
+ node = new HTMLTableSectionElement(this);break;
+ case "TD":
+ node = new HTMLTableDataCellElement(this);break;
+ case "TH":
+ node = new HTMLTableHeaderCellElement(this);break;
+ case "TEXTAREA":
+ node = new HTMLTextAreaElement(this);break;
+ case "TITLE":
+ node = new HTMLTitleElement(this);break;
+ case "TR":
+ node = new HTMLTableRowElement(this);break;
+ case "UL":
+ node = new HTMLUListElement(this);break;
+ default:
+ node = new HTMLUnknownElement(this);
+ }
+ // assign values to properties (and aliases)
+ node.nodeName = tagName;
+ return node;
+ },
+ createElementNS : function (uri, local) {
+ //print('createElementNS :'+uri+" "+local);
+ if(!uri){
+ return this.createElement(local);
+ }else if ("http://www.w3.org/1999/xhtml" == uri) {
+ return this.createElement(local);
+ } else if ("http://www.w3.org/1998/Math/MathML" == uri) {
+ return this.createElement(local);
+ } else {
+ return Document.prototype.createElementNS.apply(this,[uri, local]);
+ }
+ },
+ get anchors(){
+ return new HTMLCollection(this.getElementsByTagName('a'));
+ },
+ get applets(){
+ return new HTMLCollection(this.getElementsByTagName('applet'));
+ },
+ get documentElement(){
+ var html = Document.prototype.__lookupGetter__('documentElement').apply(this,[]);
+ if( html === null){
+ html = this.createElement('html');
+ this.appendChild(html);
+ html.appendChild(this.createElement('head'));
+ html.appendChild(this.createElement('body'));
+ }
+ return html;
+ },
+ //document.head is non-standard
+ get head(){
+ //console.log('get head');
+ if (!this.documentElement) {
+ this.appendChild(this.createElement('html'));
+ }
+ var element = this.documentElement,
+ length = element.childNodes.length,
+ i;
+ //check for the presence of the head element in this html doc
+ for(i=0;i1?matches[1]:"";
+ },
+ set domain(value){
+ var i,
+ domainParts = this.domain.split('.').reverse(),
+ newDomainParts = value.split('.').reverse();
+ if(newDomainParts.length > 1){
+ for(i=0;i 0){
+ event = doc.createEvent('HTMLEvents');
+ event.initEvent( okay ? "load" : "error", false, false );
+ node.dispatchEvent( event, false );
+ }
+ }catch(e){
+ console.log('error loading html element %s %e', node, e.toString());
+ }
+ }
+ break;
+ case 'frame':
+ case 'iframe':
+ node.contentWindow = { };
+ node.contentDocument = new HTMLDocument(new DOMImplementation(), node.contentWindow);
+ node.contentWindow.document = node.contentDocument;
+ try{
+ Window;
+ }catch(e){
+ node.contentDocument.addEventListener('DOMContentLoaded', function(){
+ event = node.contentDocument.createEvent('HTMLEvents');
+ event.initEvent("load", false, false);
+ node.dispatchEvent( event, false );
+ });
+ }
+ try{
+ if (node.src && node.src.length > 0){
+ //console.log("getting content document for (i)frame from %s", node.src);
+ Envjs.loadFrame(node, Envjs.uri(node.src));
+ event = node.contentDocument.createEvent('HTMLEvents');
+ event.initEvent("load", false, false);
+ node.dispatchEvent( event, false );
+ }else{
+ //I dont like this being here:
+ //TODO: better mix-in strategy so the try/catch isnt required
+ try{
+ if(Window){
+ Envjs.loadFrame(node);
+ //console.log('src/html/document.js: triggering frame load');
+ event = node.contentDocument.createEvent('HTMLEvents');
+ event.initEvent("load", false, false);
+ node.dispatchEvent( event, false );
+ }
+ }catch(e){}
+ }
+ }catch(e){
+ console.log('error loading html element %s %e', node, e.toString());
+ }
+ break;
+
+ case 'link':
+ if (node.href && node.href.length > 0) {
+ __loadLink__(node, node.href);
+ }
+ break;
+ /*
+ case 'img':
+ if (node.src && node.src.length > 0){
+ // don't actually load anything, so we're "done" immediately:
+ event = doc.createEvent('HTMLEvents');
+ event.initEvent("load", false, false);
+ node.dispatchEvent( event, false );
+ }
+ break;
+ */
+ case 'option':
+ node._updateoptions();
+ break;
+ default:
+ if(node.getAttribute('onload')){
+ console.log('calling attribute onload %s | %s', node.onload, node.tagName);
+ node.onload();
+ }
+ break;
+ }//switch on name
+ default:
+ break;
+ }//switch on ns
+ break;
+ default:
+ // console.log('element appended: %s %s', node+'', node.namespaceURI);
+ }//switch on doc.parsing
+ return node;
+
+});
+
+Aspect.around({
+ target: Node,
+ method:"removeChild"
+}, function(invocation) {
+ var event,
+ okay,
+ node = invocation.proceed(),
+ doc = node.ownerDocument;
+ if((node.nodeType !== Node.ELEMENT_NODE)){
+ //for now we are only handling element insertions. probably we will need
+ //to handle text node changes to script tags and changes to src
+ //attributes
+ if(node.nodeType !== Node.DOCUMENT_NODE && node.uuid){
+ //console.log('removing event listeners, %s', node, node.uuid);
+ node.removeEventListener('*', null, null);
+ }
+ return node;
+ }
+ //console.log('appended html element %s %s %s', node.namespaceURI, node.nodeName, node);
+
+ switch(doc.parsing){
+ case true:
+ //handled by parser if included
+ break;
+ case false:
+ switch(node.namespaceURI){
+ case null:
+ //fall through
+ case "":
+ //fall through
+ case "http://www.w3.org/1999/xhtml":
+ //this is interesting dillema since our event engine is
+ //storing the registered events in an array accessed
+ //by the uuid property of the node. unforunately this
+ //means listeners hang out way after(forever ;)) the node
+ //has been removed and gone out of scope.
+ //console.log('removing event listeners, %s', node, node.uuid);
+ node.removeEventListener('*', null, null);
+ switch(node.tagName.toLowerCase()){
+ case 'frame':
+ case 'iframe':
+ try{
+ //console.log('removing iframe document');
+ try{
+ Envjs.unloadFrame(node);
+ }catch(e){
+ console.log('error freeing resources from frame %s', e);
+ }
+ node.contentWindow = null;
+ node.contentDocument = null;
+ }catch(e){
+ console.log('error unloading html element %s %e', node, e.toString());
+ }
+ break;
+ default:
+ break;
+ }//switch on name
+ default:
+ break;
+ }//switch on ns
+ break;
+ default:
+ console.log('element appended: %s %s', node+'', node.namespaceURI);
+ }//switch on doc.parsing
+ return node;
+
+});
+
+
+
+/**
+ * Named Element Support
+ *
+ *
+ */
+
+/*
+ *
+ * @returns 'name' if the node has a appropriate name
+ * null if node does not have a name
+ */
+
+var __isNamedElement__ = function(node) {
+ if (node.nodeType !== Node.ELEMENT_NODE) {
+ return null;
+ }
+ var tagName = node.tagName.toLowerCase();
+ var nodename = null;
+
+ switch (tagName) {
+ case 'embed':
+ case 'form':
+ case 'iframe':
+ nodename = node.getAttribute('name');
+ break;
+ case 'applet':
+ nodename = node.id;
+ break;
+ case 'object':
+ // TODO: object needs to be 'fallback free'
+ nodename = node.id;
+ break;
+ case 'img':
+ nodename = node.id;
+ if (!nodename || ! node.getAttribute('name')) {
+ nodename = null;
+ }
+ break;
+ }
+ return (nodename) ? nodename : null;
+};
+
+
+var __addNamedMap__ = function(target, node) {
+ var nodename = __isNamedElement__(node);
+ if (nodename) {
+ target.__defineGetter__(nodename, function() {
+ return node;
+ });
+ }
+};
+
+var __removeNamedMap__ = function(target, node) {
+ if (!node) {
+ return;
+ }
+ var nodename = __isNamedElement__(node);
+ if (nodename) {
+ delete target[nodename];
+ }
+};
+
+/**
+ * @name HTMLEvents
+ * @w3c:domlevel 2
+ * @uri http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
+ */
+
+var __eval__ = function(script, node){
+ if (!script == ""){
+ // don't assemble environment if no script...
+ try{
+ eval(script);
+ }catch(e){
+ console.log('error evaluating %s', e);
+ }
+ }
+};
+
+var HTMLEvents= function(){};
+HTMLEvents.prototype = {
+ onload: function(event){
+ __eval__(this.getAttribute('onload')||'', this);
+ },
+ onunload: function(event){
+ __eval__(this.getAttribute('onunload')||'', this);
+ },
+ onabort: function(event){
+ __eval__(this.getAttribute('onabort')||'', this);
+ },
+ onerror: function(event){
+ __eval__(this.getAttribute('onerror')||'', this);
+ },
+ onselect: function(event){
+ __eval__(this.getAttribute('onselect')||'', this);
+ },
+ onchange: function(event){
+ __eval__(this.getAttribute('onchange')||'', this);
+ },
+ onsubmit: function(event){
+ if (__eval__(this.getAttribute('onsubmit')||'', this)) {
+ this.submit();
+ }
+ },
+ onreset: function(event){
+ __eval__(this.getAttribute('onreset')||'', this);
+ },
+ onfocus: function(event){
+ __eval__(this.getAttribute('onfocus')||'', this);
+ },
+ onblur: function(event){
+ __eval__(this.getAttribute('onblur')||'', this);
+ },
+ onresize: function(event){
+ __eval__(this.getAttribute('onresize')||'', this);
+ },
+ onscroll: function(event){
+ __eval__(this.getAttribute('onscroll')||'', this);
+ }
+};
+
+//HTMLDocument, HTMLFramesetElement, HTMLObjectElement
+var __load__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("load", false, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HTMLFramesetElement, HTMLBodyElement
+var __unload__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("unload", false, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HTMLObjectElement
+var __abort__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("abort", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HTMLFramesetElement, HTMLObjectElement, HTMLBodyElement
+var __error__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("error", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HTMLInputElement, HTMLTextAreaElement
+var __select__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("select", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement
+var __change__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("change", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HtmlFormElement
+var __submit__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("submit", true, true);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//HtmlFormElement
+var __reset__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("reset", false, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//LABEL, INPUT, SELECT, TEXTAREA, and BUTTON
+var __focus__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("focus", false, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//LABEL, INPUT, SELECT, TEXTAREA, and BUTTON
+var __blur__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("blur", false, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//Window
+var __resize__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("resize", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+//Window
+var __scroll__ = function(element){
+ var event = new Event('HTMLEvents');
+ event.initEvent("scroll", true, false);
+ element.dispatchEvent(event);
+ return event;
+};
+
+/**
+ * @name KeyboardEvents
+ * @w3c:domlevel 2
+ * @uri http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
+ */
+var KeyboardEvents= function(){};
+KeyboardEvents.prototype = {
+ onkeydown: function(event){
+ __eval__(this.getAttribute('onkeydown')||'', this);
+ },
+ onkeypress: function(event){
+ __eval__(this.getAttribute('onkeypress')||'', this);
+ },
+ onkeyup: function(event){
+ __eval__(this.getAttribute('onkeyup')||'', this);
+ }
+};
+
+
+var __registerKeyboardEventAttrs__ = function(elm){
+ if(elm.hasAttribute('onkeydown')){
+ elm.addEventListener('keydown', elm.onkeydown, false);
+ }
+ if(elm.hasAttribute('onkeypress')){
+ elm.addEventListener('keypress', elm.onkeypress, false);
+ }
+ if(elm.hasAttribute('onkeyup')){
+ elm.addEventListener('keyup', elm.onkeyup, false);
+ }
+ return elm;
+};
+
+//HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement
+var __keydown__ = function(element){
+ var event = new Event('KeyboardEvents');
+ event.initEvent("keydown", false, false);
+ element.dispatchEvent(event);
+};
+
+//HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement
+var __keypress__ = function(element){
+ var event = new Event('KeyboardEvents');
+ event.initEvent("keypress", false, false);
+ element.dispatchEvent(event);
+};
+
+//HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement
+var __keyup__ = function(element){
+ var event = new Event('KeyboardEvents');
+ event.initEvent("keyup", false, false);
+ element.dispatchEvent(event);
+};
+
+/**
+ * @name MaouseEvents
+ * @w3c:domlevel 2
+ * @uri http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html
+ */
+var MouseEvents= function(){};
+MouseEvents.prototype = {
+ onclick: function(event){
+ __eval__(this.getAttribute('onclick')||'', this);
+ },
+ ondblclick: function(event){
+ __eval__(this.getAttribute('ondblclick')||'', this);
+ },
+ onmousedown: function(event){
+ __eval__(this.getAttribute('onmousedown')||'', this);
+ },
+ onmousemove: function(event){
+ __eval__(this.getAttribute('onmousemove')||'', this);
+ },
+ onmouseout: function(event){
+ __eval__(this.getAttribute('onmouseout')||'', this);
+ },
+ onmouseover: function(event){
+ __eval__(this.getAttribute('onmouseover')||'', this);
+ },
+ onmouseup: function(event){
+ __eval__(this.getAttribute('onmouseup')||'', this);
+ }
+};
+
+var __registerMouseEventAttrs__ = function(elm){
+ if(elm.hasAttribute('onclick')){
+ elm.addEventListener('click', elm.onclick, false);
+ }
+ if(elm.hasAttribute('ondblclick')){
+ elm.addEventListener('dblclick', elm.ondblclick, false);
+ }
+ if(elm.hasAttribute('onmousedown')){
+ elm.addEventListener('mousedown', elm.onmousedown, false);
+ }
+ if(elm.hasAttribute('onmousemove')){
+ elm.addEventListener('mousemove', elm.onmousemove, false);
+ }
+ if(elm.hasAttribute('onmouseout')){
+ elm.addEventListener('mouseout', elm.onmouseout, false);
+ }
+ if(elm.hasAttribute('onmouseover')){
+ elm.addEventListener('mouseover', elm.onmouseover, false);
+ }
+ if(elm.hasAttribute('onmouseup')){
+ elm.addEventListener('mouseup', elm.onmouseup, false);
+ }
+ return elm;
+};
+
+
+var __click__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("click", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+var __mousedown__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("mousedown", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+var __mouseup__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("mouseup", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+var __mouseover__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("mouseover", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+var __mousemove__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("mousemove", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+var __mouseout__ = function(element){
+ var event = new Event('MouseEvents');
+ event.initEvent("mouseout", true, true, null, 0,
+ 0, 0, 0, 0, false, false, false,
+ false, null, null);
+ element.dispatchEvent(event);
+};
+
+/**
+ * HTMLElement - DOM Level 2
+ */
+
+
+/* Hack for http://www.prototypejs.org/
+ *
+ * Prototype 1.6 (the library) creates a new global Element, which causes
+ * envjs to use the wrong Element.
+ *
+ * http://envjs.lighthouseapp.com/projects/21590/tickets/108-prototypejs-wont-load-due-it-clobbering-element
+ *
+ * Options:
+ * (1) Rename the dom/element to something else
+ * rejected: been done before. people want Element.
+ * (2) merge dom+html and not export Element to global namespace
+ * (meaning we would use a local var Element in a closure, so prototype
+ * can do what ever it wants)
+ * rejected: want dom and html separate
+ * (3) use global namespace (put everything under Envjs = {})
+ * rejected: massive change
+ * (4) use commonjs modules (similar to (3) in spirit)
+ * rejected: massive change
+ *
+ * or
+ *
+ * (5) take a reference to Element during initial loading ("compile
+ * time"), and use the reference instead of "Element". That's
+ * what the next line does. We use __DOMElement__ if we need to
+ * reference the parent class. Only this file explcity uses
+ * Element so this should work, and is the most minimal change I
+ * could think of with no external API changes.
+ *
+ */
+var __DOMElement__ = Element;
+
+HTMLElement = function(ownerDocument) {
+ __DOMElement__.apply(this, arguments);
+};
+
+HTMLElement.prototype = new Element();
+__extend__(HTMLElement.prototype, HTMLEvents.prototype);
+__extend__(HTMLElement.prototype, {
+ get className() {
+ return this.getAttribute("class")||'';
+ },
+ set className(value) {
+ return this.setAttribute("class",__trim__(value));
+ },
+ get dir() {
+ return this.getAttribute("dir")||"ltr";
+ },
+ set dir(val) {
+ return this.setAttribute("dir",val);
+ },
+ get id(){
+ return this.getAttribute('id');
+ },
+ set id(id){
+ this.setAttribute('id', id);
+ },
+ get innerHTML(){
+ var ret = "",
+ i;
+
+ // create string containing the concatenation of the string
+ // values of each child
+ for (i=0; i < this.childNodes.length; i++) {
+ if(this.childNodes[i]){
+ if(this.childNodes[i].nodeType === Node.ELEMENT_NODE){
+ ret += this.childNodes[i].xhtml;
+ } else if (this.childNodes[i].nodeType === Node.TEXT_NODE && i>0 &&
+ this.childNodes[i-1].nodeType === Node.TEXT_NODE){
+ //add a single space between adjacent text nodes
+ ret += " "+this.childNodes[i].xml;
+ }else{
+ ret += this.childNodes[i].xml;
+ }
+ }
+ }
+ return ret;
+ },
+ get lang() {
+ return this.getAttribute("lang");
+ },
+ set lang(val) {
+ return this.setAttribute("lang",val);
+ },
+ get offsetHeight(){
+ return Number((this.style.height || '').replace("px",""));
+ },
+ get offsetWidth(){
+ return Number((this.style.width || '').replace("px",""));
+ },
+ offsetLeft: 0,
+ offsetRight: 0,
+ get offsetParent(){
+ /* TODO */
+ return;
+ },
+ set offsetParent(element){
+ /* TODO */
+ return;
+ },
+ scrollHeight: 0,
+ scrollWidth: 0,
+ scrollLeft: 0,
+ scrollRight: 0,
+ get style(){
+ return this.getAttribute('style')||'';
+ },
+ get title() {
+ return this.getAttribute("title");
+ },
+ set title(value) {
+ return this.setAttribute("title", value);
+ },
+ get tabIndex(){
+ var tabindex = this.getAttribute('tabindex');
+ if(tabindex!==null){
+ return Number(tabindex);
+ } else {
+ return 0;
+ }
+ },
+ set tabIndex(value){
+ if (value === undefined || value === null) {
+ value = 0;
+ }
+ this.setAttribute('tabindex',Number(value));
+ },
+ get outerHTML(){
+ //Not in the specs but I'll leave it here for now.
+ return this.xhtml;
+ },
+ scrollIntoView: function(){
+ /*TODO*/
+ return;
+ },
+ toString: function(){
+ return '[object HTMLElement]';
+ },
+ get xhtml() {
+ // HTMLDocument.xhtml is non-standard
+ // This is exactly like Document.xml except the tagName has to be
+ // lower cased. I dont like to duplicate this but its really not
+ // a simple work around between xml and html serialization via
+ // XMLSerializer (which uppercases html tags) and innerHTML (which
+ // lowercases tags)
+
+ var ret = "",
+ ns = "",
+ name = (this.tagName+"").toLowerCase(),
+ attrs,
+ attrstring = "",
+ i;
+
+ // serialize namespace declarations
+ if (this.namespaceURI){
+ if((this === this.ownerDocument.documentElement) ||
+ (!this.parentNode) ||
+ (this.parentNode &&
+ (this.parentNode.namespaceURI !== this.namespaceURI))) {
+ ns = ' xmlns' + (this.prefix ? (':' + this.prefix) : '') +
+ '="' + this.namespaceURI + '"';
+ }
+ }
+
+ // serialize Attribute declarations
+ attrs = this.attributes;
+ for(i=0;i< attrs.length;i++){
+ attrstring += " "+attrs[i].name+'="'+attrs[i].xml+'"';
+ }
+
+ if(this.hasChildNodes()){
+ // serialize this Element
+ ret += "<" + name + ns + attrstring +">";
+ for(i=0;i< this.childNodes.length;i++){
+ ret += this.childNodes[i].xhtml ?
+ this.childNodes[i].xhtml :
+ this.childNodes[i].xml;
+ }
+ ret += "" + name + ">";
+ }else{
+ switch(name){
+ case 'script':
+ ret += "<" + name + ns + attrstring +">"+name+">";
+ break;
+ default:
+ ret += "<" + name + ns + attrstring +"/>";
+ }
+ }
+
+ return ret;
+ },
+
+ /**
+ * setAttribute use a dispatch table that other tags can set to
+ * "listen" to various values being set. The dispatch table
+ * and registration functions are at the end of the file.
+ *
+ */
+
+ setAttribute: function(name, value) {
+ var result = __DOMElement__.prototype.setAttribute.apply(this, arguments);
+ __addNamedMap__(this.ownerDocument, this);
+ var tagname = this.tagName;
+ var callback = HTMLElement.getAttributeCallback('set', tagname, name);
+ if (callback) {
+ callback(this, value);
+ }
+ },
+ setAttributeNS: function(namespaceURI, name, value) {
+ var result = __DOMElement__.prototype.setAttributeNS.apply(this, arguments);
+ __addNamedMap__(this.ownerDocument, this);
+ var tagname = this.tagName;
+ var callback = HTMLElement.getAttributeCallback('set', tagname, name);
+ if (callback) {
+ callback(this, value);
+ }
+
+ return result;
+ },
+ setAttributeNode: function(newnode) {
+ var result = __DOMElement__.prototype.setAttributeNode.apply(this, arguments);
+ __addNamedMap__(this.ownerDocument, this);
+ var tagname = this.tagName;
+ var callback = HTMLElement.getAttributeCallback('set', tagname, newnode.name);
+ if (callback) {
+ callback(this, node.value);
+ }
+ return result;
+ },
+ setAttributeNodeNS: function(newnode) {
+ var result = __DOMElement__.prototype.setAttributeNodeNS.apply(this, arguments);
+ __addNamedMap__(this.ownerDocument, this);
+ var tagname = this.tagName;
+ var callback = HTMLElement.getAttributeCallback('set', tagname, newnode.name);
+ if (callback) {
+ callback(this, node.value);
+ }
+ return result;
+ },
+ removeAttribute: function(name) {
+ __removeNamedMap__(this.ownerDocument, this);
+ return __DOMElement__.prototype.removeAttribute.apply(this, arguments);
+ },
+ removeAttributeNS: function(namespace, localname) {
+ __removeNamedMap__(this.ownerDocument, this);
+ return __DOMElement__.prototype.removeAttributeNS.apply(this, arguments);
+ },
+ removeAttributeNode: function(name) {
+ __removeNamedMap__(this.ownerDocument, this);
+ return __DOMElement__.prototype.removeAttribute.apply(this, arguments);
+ },
+ removeChild: function(oldChild) {
+ __removeNamedMap__(this.ownerDocument, oldChild);
+ return __DOMElement__.prototype.removeChild.apply(this, arguments);
+ },
+ importNode: function(othernode, deep) {
+ var newnode = __DOMElement__.prototype.importNode.apply(this, arguments);
+ __addNamedMap__(this.ownerDocument, newnode);
+ return newnode;
+ },
+
+ // not actually sure if this is needed or not
+ replaceNode: function(newchild, oldchild) {
+ var newnode = __DOMElement__.prototype.replaceNode.apply(this, arguments);
+ __removeNamedMap__(this.ownerDocument, oldchild);
+ __addNamedMap__(this.ownerDocument, newnode);
+ return newnode;
+ }
+});
+
+
+HTMLElement.attributeCallbacks = {};
+HTMLElement.registerSetAttribute = function(tag, attrib, callbackfn) {
+ HTMLElement.attributeCallbacks[tag + ':set:' + attrib] = callbackfn;
+};
+HTMLElement.registerRemoveAttribute = function(tag, attrib, callbackfn) {
+ HTMLElement.attributeCallbacks[tag + ':remove:' + attrib] = callbackfn;
+};
+
+/**
+ * This is really only useful internally
+ *
+ */
+HTMLElement.getAttributeCallback = function(type, tag, attrib) {
+ return HTMLElement.attributeCallbacks[tag + ':' + type + ':' + attrib] || null;
+};
+/*
+ * HTMLCollection
+ *
+ * HTML5 -- 2.7.2.1 HTMLCollection
+ * http://dev.w3.org/html5/spec/Overview.html#htmlcollection
+ * http://dev.w3.org/html5/spec/Overview.html#collections
+ */
+HTMLCollection = function(nodelist, type) {
+
+ __setArray__(this, []);
+ var n;
+ for (var i=0; i= 0) && (idx < this.length)) ? this[idx] : null;
+ },
+
+ namedItem: function (name) {
+ return this[name] || null;
+ },
+
+ toString: function() {
+ return '[object HTMLCollection]';
+ }
+};
+/*
+ * a set of convenience classes to centralize implementation of
+ * properties and methods across multiple in-form elements
+ *
+ * the hierarchy of related HTML elements and their members is as follows:
+ *
+ * Condensed Version
+ *
+ * HTMLInputCommon
+ * * legent (no value attr)
+ * * fieldset (no value attr)
+ * * label (no value attr)
+ * * option (custom value)
+ * HTMLTypeValueInputs (extends InputCommon)
+ * * select (custom value)
+ * * button (just sets value)
+ * HTMLInputAreaCommon (extends TypeValueIput)
+ * * input (custom)
+ * * textarea (just sets value)
+ *
+ * -----------------------
+ * HTMLInputCommon: common to all elements
+ * .form
+ *
+ *