Index: /FCSEndpointTester/branches/non-vaadin-old/pom.xml
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/pom.xml	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/pom.xml	(revision 7116)
@@ -0,0 +1,86 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>eu.clarin.sru</groupId>
+	<artifactId>sru-endpoint-tester</artifactId>
+	<version>0.0.1-SNAPSHOT</version>
+	<name>SRU/CQL Tester for CLARIN FCS</name>
+	<description>A test-service to test CLARIN FCS endpoints for protocol conformance</description>
+	<organization>
+		<name>IDS Mannheim</name>
+		<url>http://www.ids-mannheim.de/</url>
+	</organization>
+    <packaging>war</packaging>
+
+	<properties>
+		<!-- project settings -->
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<!-- plug-in settings -->
+		<maven.compiler.source>1.6</maven.compiler.source>
+		<maven.compiler.target>1.6</maven.compiler.target>
+		<!-- versions of common dependencies -->
+		<slf4j.version>1.6.4</slf4j.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+			<version>${slf4j.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-jdk14</artifactId>
+			<!-- <artifactId>slf4j-log4j12</artifactId> -->
+			<version>${slf4j.version}</version>
+		</dependency>
+
+
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.6</version>
+		</dependency>
+
+		<dependency>
+			<groupId>joda-time</groupId>
+			<artifactId>joda-time</artifactId>
+			<version>2.1</version>
+		</dependency>
+
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+			<version>2.5</version>
+			<scope>provided</scope>
+		</dependency>
+
+		<dependency>
+			<groupId>eu.clarin.sru</groupId>
+			<artifactId>sru-client</artifactId>
+			<version>0.9.2-SNAPSHOT</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.reflections</groupId>
+			<artifactId>reflections</artifactId>
+			<version>0.9.8</version>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.1</version>
+				<configuration>
+					<source>${maven.compiler.source}</source>
+					<target>${maven.compiler.target}</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUEndpointTester.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUEndpointTester.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUEndpointTester.java	(revision 7116)
@@ -0,0 +1,304 @@
+package eu.clarin.fcs.test;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Annotation;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.reflections.Reflections;
+
+import eu.clarin.fcs.test.SRUTest.Result;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUVersion;
+import eu.clarin.sru.client.fcs.ClarinFCSRecordParser;
+
+
+@SuppressWarnings("serial")
+public class SRUEndpointTester extends HttpServlet {
+    private static final List<SRUTest> TESTS;
+    private final SRULoggingHandler logcapturehandler =
+            new SRULoggingHandler();
+    private DateTimeFormatter dateFmt =
+            DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
+
+
+    @Override
+    public void init() throws ServletException {
+        java.util.logging.Logger l =
+                java.util.logging.Logger.getLogger("eu.clarin.sru");
+        l.setLevel(Level.FINEST);
+        l.addHandler(logcapturehandler);
+    }
+
+
+    @Override
+    protected void doGet(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+        performTest(request, response);
+    }
+
+
+    @Override
+    protected void doPost(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+        performTest(request, response);
+    }
+
+
+    private SRUSimpleClient createClient(boolean strict) {
+        SRUSimpleClient client =
+                new SRUSimpleClient(SRUVersion.VERSION_1_2, strict);
+        client.registerRecordParser(new ClarinFCSRecordParser());
+        return client;
+    }
+
+
+    private void performTest(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+        String uri = request.getParameter("uri");
+        if (uri != null) {
+            uri = uri.trim();
+        }
+        if ((uri == null) || uri.isEmpty()) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing argument: uri");
+            return;
+        }
+
+        String searchTerm = request.getParameter("searchTerm");
+        if (searchTerm != null) {
+            searchTerm = searchTerm.trim();
+        }
+        if ((searchTerm == null) || searchTerm.isEmpty()) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "missing argument: searchTerm");
+            return;
+        }
+
+        /*
+         * ok, all well. commence checks ...
+         */
+        String strict = request.getParameter("strict");
+        if (strict != null) {
+            strict = strict.trim();
+            if (strict.isEmpty()) {
+                strict = null;
+            }
+        }
+        SRUSimpleClient client = createClient(strict != null);
+        PrintWriter writer = null;
+        try {
+            URI baseURI = new URI(uri);
+
+            response.setContentType("text/html");
+            response.setCharacterEncoding("utf-8");
+            writer = response.getWriter();
+            writer.println("<html>");
+            writer.println("<head><title>CLARIN FCS SRU/CQL Conformance Test</title></head>");
+            writer.println("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />");
+            writer.println("<link href=\"tester.css\" rel=\"stylesheet\" type=\"text/css\" />");
+            writer.println("<script  type=\"text/javascript\" src=\"jquery-1.8.0.js\"></script>");
+            writer.println("<script type=\"text/javascript\">$(document).ready(function() { $('.logview').toggle(false); });</script>");
+            writer.println("<body>");
+            writer.println("<h1>Results for " + baseURI + "</h1>");
+            SRUTestingHandler handler = new SRUTestingHandler();
+            int i = 0;
+            int success = 0;
+            int warning = 0;
+            int error = 0;
+            for (SRUTest test : TESTS) {
+                Result.Code result = performSingleTest(++i, writer,
+                        baseURI, searchTerm, client, handler, test);
+                handler.reset();
+                switch (result) {
+                case SUCCESS:
+                    success++;
+                    break;
+                case WARNING:
+                    warning++;
+                    break;
+                case ERROR:
+                    error++;
+                    break;
+                }
+            }
+            writer.println("<p>Success: " + success + ", Warning: " + warning +
+                    ", Error: " + error + "</p>");
+            if ((warning > 0) || (error > 0)) {
+                writer.println("<p class=\"error\">Problems detected!</p>");
+            }
+        } catch (URISyntaxException e) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+                    "illegal argument");
+        } finally {
+            if (writer != null) {
+                writer.println("</body>");
+                writer.println("</html>");
+                writer.flush();
+                writer.close();
+            }
+        }
+    }
+
+
+    private SRUTest.Result.Code performSingleTest(int testId,
+            PrintWriter writer, URI baseURI, String searchTerm,
+            SRUSimpleClient client, SRUTestingHandler handler, SRUTest test)
+            throws IOException {
+        writer.print("<h2 id=\"test" + testId + "\">");
+        StringEscapeUtils.escapeHtml(writer, test.getName());
+        writer.println("</h2>");
+        if (test.getDescription() != null) {
+            writer.print("<p class=\"small\">Test Case: ");
+            StringEscapeUtils.escapeHtml(writer, test.getDescription());
+            writer.print("</p>");
+        }
+        SRUTest.Result result = null;
+        try {
+            result = test.performTest(client, handler,
+                    baseURI.toString(), searchTerm);
+        } catch (SRUClientException e) {
+            String msg = e.getMessage();
+            if (msg == null) {
+                msg = "unclassified exception from SRUClient";
+            }
+            LogRecord record = new LogRecord(Level.SEVERE, msg);
+            record.setThrown(e);
+            logcapturehandler.publish(record);
+            result = new SRUTest.Result(SRUTest.Result.Code.ERROR, msg);
+        } finally {
+            if (result == null) {
+                result = SRUTest.ERROR;
+            }
+
+            boolean hasErrors = false;
+            List<LogRecord> records = logcapturehandler.getLogRecords();
+            writer.print("<p>Result: ");
+            switch (result.getCode()) {
+            case SUCCESS:
+                if (records != null) {
+                    for (LogRecord record : records) {
+                        if ((record.getLevel() == Level.WARNING) ||
+                                (record.getLevel() == Level.SEVERE)) {
+                            hasErrors = true;
+                            break;
+                        }
+                    }
+                }
+                writer.println("<span class=\"success\">SUCCESS</span>");
+                break;
+            case WARNING:
+                writer.println("<span class=\"warning\">WARNING</span>");
+                break;
+            case ERROR:
+                writer.println("<span class=\"error\">FAIL</span>");
+                break;
+            }
+            if (result.getMessage() != null) {
+                writer.print(" ");
+                writer.print(result.getMessage());
+                writer.print(" ");
+            }
+            writer.println("<a href=\"#test" + testId + "\" class=\"togglelink\" onclick=\"$('#log" + testId + "').toggle();\">(toggle log messages)</a>");
+            writer.println("</p>");
+
+            if (hasErrors) {
+                writer.println("<p class=\"error\"><em>NOTE</em>: Endpoint violates the SRU/CQL specification, but client was able to recover! See logs for details.</p>");
+            }
+
+            writer.print("<div id=\"log" + testId + "\" class=\"logview\">");
+            if (records != null) {
+                writer.println("<ul class=\"log\">");
+                for (LogRecord record : records) {
+                    String clazz = null;
+                    if (record.getLevel() == Level.SEVERE) {
+                        clazz = "error";
+                    } else if (record.getLevel() == Level.WARNING) {
+                        clazz = "warning";
+                    }
+                    writer.print("<li>");
+                    writer.print("[");
+                    writer.print(dateFmt.print(record.getMillis()));
+                    writer.print("] ");
+                    if (clazz != null) {
+                        writer.println("<span class=\"");
+                        writer.print(clazz);
+                        writer.print("\">");
+                    }
+                    StringEscapeUtils.escapeHtml(writer, record.getMessage());
+                    if (clazz != null) {
+                        writer.print("</span>");
+                    }
+                    if (record.getThrown() != null) {
+                        writer.print("<pre class=\"error\">");
+                        record.getThrown().printStackTrace(writer);
+                        writer.print("</pre>");
+                    }
+                    writer.println("</li>");
+                }
+                writer.println("</ul>");
+            }
+            writer.println("</div>");
+            writer.println("<hr />");
+        }
+
+        return result != null ? result.getCode() : Result.Code.ERROR;
+    }
+
+
+    private static int getPriority(Class<?> clazz) {
+        for (Annotation a : clazz.getDeclaredAnnotations()) {
+            if (a instanceof SRUTestCase) {
+                return ((SRUTestCase) a).priority();
+            }
+        }
+        return Integer.MIN_VALUE;
+    }
+
+
+    static {
+        Reflections reflections = new Reflections("eu.clarin.fcs.test.tests");
+
+        List<SRUTest> tests = null;
+        List<Class<?>> types = new ArrayList<Class<?>>(
+                reflections.getTypesAnnotatedWith(SRUTestCase.class));
+        Collections.sort(types, new Comparator<Class<?>>() {
+            @Override
+            public int compare(Class<?> o1, Class<?> o2) {
+                return getPriority(o1) - getPriority(o2);
+            }
+        });
+
+        for (Class<?> clazz : types) {
+            if (tests == null) {
+                tests = new ArrayList<SRUTest>();
+            }
+            try {
+                tests.add((SRUTest) clazz.newInstance());
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        if (tests == null) {
+            tests = Collections.emptyList();
+        }
+        TESTS = tests;
+    }
+
+} // class SRUEndpointTester
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRULoggingHandler.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRULoggingHandler.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRULoggingHandler.java	(revision 7116)
@@ -0,0 +1,46 @@
+package eu.clarin.fcs.test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+
+class SRULoggingHandler extends Handler {
+    private final Map<Long, List<LogRecord>> messages = new HashMap<Long, List<LogRecord>>();
+
+    @Override
+    public void publish(LogRecord record) {
+        Long id = Thread.currentThread().getId();
+
+        List<LogRecord> records;
+        synchronized (messages) {
+            records = messages.get(id);
+            if (records == null) {
+                records = new ArrayList<LogRecord>();
+                messages.put(id, records);
+            }
+        }
+        records.add(record);
+    }
+
+
+    @Override
+    public void flush() {
+    }
+
+
+    @Override
+    public void close() throws SecurityException {
+    }
+
+
+    List<LogRecord> getLogRecords() {
+        synchronized (messages) {
+            return messages.remove(Thread.currentThread().getId());
+        }
+    }
+
+} // class DiagnosticCapturingHandler
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTest.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTest.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTest.java	(revision 7116)
@@ -0,0 +1,56 @@
+package eu.clarin.fcs.test;
+
+import eu.clarin.fcs.test.SRUTest.Result.Code;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUClientException;
+
+
+public interface SRUTest {
+    public static class Result {
+        public static enum Code {
+            SUCCESS, WARNING, ERROR
+        }
+
+        private Code code;
+        private String message;
+
+
+        public Result(Code code, String message) {
+            this.code = code;
+            this.message = message;
+        }
+
+
+        public Result(Code code) {
+            this(code, null);
+        }
+
+
+        public Code getCode() {
+            return code;
+        }
+
+
+        public String getMessage() {
+            return message;
+        }
+    } // inner class Result
+    public static final Result SUCCESS = new Result(Code.SUCCESS);
+    public static final Result WARNING_DIAGNOSTICS = new Result(Code.WARNING,
+            "One or more unexpected diagnostic reported by endpoint");
+    public static final Result ERROR = new Result(Code.ERROR,
+            "Something went wrong");
+    public static final Result ERROR_NO_DIAGNOSTIC = new Result(Code.ERROR,
+            "Endpoint did not report expected diagnostic");
+
+
+    public String getName();
+
+
+    public String getDescription();
+
+
+    public Result performTest(SRUSimpleClient client, SRUTestingHandler handler,
+            String baseURI, String searchTerm) throws SRUClientException;
+
+} // class SRUEndpintTest
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestCase.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestCase.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestCase.java	(revision 7116)
@@ -0,0 +1,13 @@
+package eu.clarin.fcs.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SRUTestCase {
+    public int priority();
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestingHandler.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestingHandler.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/SRUTestingHandler.java	(revision 7116)
@@ -0,0 +1,143 @@
+package eu.clarin.fcs.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUDefaultHandlerAdapter;
+import eu.clarin.sru.client.SRUDiagnostic;
+import eu.clarin.sru.client.SRURecordData;
+import eu.clarin.sru.client.SRUSurrogateRecordData;
+import eu.clarin.sru.client.SRUWhereInList;
+
+
+public class SRUTestingHandler extends SRUDefaultHandlerAdapter {
+    public static class Record {
+        private final String identifier;
+        private final int position;
+        private final SRURecordData data;
+
+        public Record(String identifier, int position, SRURecordData data) {
+            this.identifier = identifier;
+            this.position   = position;
+            this.data       = data;
+        }
+
+        public String getIdentifier() {
+            return identifier;
+        }
+
+        public int getPosition() {
+            return position;
+        }
+
+        public SRURecordData getData() {
+            return data;
+        }
+    }
+    public static class SurrogateDiagnostic implements SRURecordData {
+        private final SRUDiagnostic diagnostic;
+
+        public SurrogateDiagnostic(SRUDiagnostic diagnostic) {
+            this.diagnostic = diagnostic;
+        }
+
+        @Override
+        public boolean isTransient() {
+            return false;
+        }
+
+        @Override
+        public String getRecordSchema() {
+            return SRUSurrogateRecordData.RECORD_SCHEMA;
+        }
+
+        public SRUDiagnostic getDiagnostic() {
+            return diagnostic;
+        }
+
+        public boolean isDiagnostic(String uri) {
+            if (uri == null) {
+                throw new NullPointerException("uri == null");
+            }
+            return uri.equals(diagnostic.getURI());
+        }
+    }
+    private List<String> termList = new ArrayList<String>();
+    private List<Record> recordList = new ArrayList<Record>(512);
+    private List<SRUDiagnostic> diagnostics;
+
+
+    @Override
+    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
+            throws SRUClientException {
+        if (this.diagnostics == null) {
+            this.diagnostics = diagnostics;
+        } else {
+            this.diagnostics.addAll(diagnostics);
+        }
+    }
+
+
+    @Override
+    public void onTerm(String value, int numberOfRecords, String displayTerm,
+            SRUWhereInList whereInList) throws SRUClientException {
+        termList.add(value);
+    }
+
+
+    @Override
+    public void onRecord(String identifier, int position, SRURecordData data)
+            throws SRUClientException {
+        recordList.add(new Record(identifier, position, data));
+    }
+
+
+    @Override
+    public void onSurrogateRecord(String identifier, int position,
+            SRUDiagnostic data) throws SRUClientException {
+        recordList.add(new Record(identifier, position,
+                new SurrogateDiagnostic(data)));
+    }
+
+
+    void reset() {
+        termList.clear();
+        recordList.clear();
+        this.diagnostics = null;
+    }
+
+
+    public boolean findDiagnostic(String uri) {
+        if (uri == null) {
+            throw new NullPointerException("uri == null");
+        }
+        if (diagnostics != null) {
+            for (SRUDiagnostic diagnostic : diagnostics) {
+                if (uri.equals(diagnostic.getURI())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    public int getDiagnosticCount() {
+        return diagnostics != null ? diagnostics.size() : 0;
+    }
+
+
+    public List<String> getTerms() {
+        return termList;
+    }
+
+    public int getRecordCount() {
+        return recordList.size();
+    }
+
+    public List<Record> getRecords() {
+        return recordList;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain1.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain1.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain1.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUVersion;
+
+@SRUTestCase(priority=1000)
+public class TestExplain1 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Regular explain request using SRU version 1.2";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUExplainRequest req = new SRUExplainRequest(baseURI);
+        req.setVersion(SRUVersion.VERSION_1_2);
+        client.explain(req, handler);
+        return SUCCESS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain2.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain2.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain2.java	(revision 7116)
@@ -0,0 +1,38 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=1010)
+public class TestExplain2 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'operation' and 'version' arguments";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUExplainRequest req = new SRUExplainRequest(baseURI);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_OPERATION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return SUCCESS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain3.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain3.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain3.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=1020)
+public class TestExplain3 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'version' argument";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUExplainRequest req = new SRUExplainRequest(baseURI);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return SUCCESS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain4.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain4.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain4.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=1040)
+public class TestExplain4 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'operation' argument";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUExplainRequest req = new SRUExplainRequest(baseURI);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_OPERATION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return SUCCESS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain5.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain5.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestExplain5.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=1050)
+public class TestExplain5 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain with invalid value for 'version' agrument (expecting diagnostic \"info:srw/diagnostic/1/5\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUExplainRequest req = new SRUExplainRequest(baseURI);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                "9.9");
+        client.explain(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/5") ? SUCCESS : new Result(Result.Code.WARNING, "Endpoint did not report diagnostic for invalid version number");
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan1.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan1.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan1.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=2000)
+public class TestScan1 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan with missing 'scanClause' argument (expecting diagnostic \"info:srw/diagnostic/1/7\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUScanRequest req = new SRUScanRequest(baseURI);
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_SCAN_CLAUSE,
+                SRUScanRequest.MALFORMED_OMIT);
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/7") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan2.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan2.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan2.java	(revision 7116)
@@ -0,0 +1,51 @@
+package eu.clarin.fcs.test.tests;
+
+import java.util.List;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=2010)
+public class TestScan2 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: scan on fcs.resource = root";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUScanRequest req = new SRUScanRequest(baseURI);
+        req.setScanClause("fcs.resource=root");
+        client.scan(req, handler);
+
+        if (handler.findDiagnostic("info:srw/diagnostic/1/4")) {
+            return new Result(Result.Code.WARNING, "Endpoint does not support 'scan' operation");
+        } else {
+            if (handler.getDiagnosticCount() == 0) {
+                List<String> terms = handler.getTerms();
+                if (terms.size() >= 1) {
+                    return SUCCESS;
+                } else {
+                    return new Result(Result.Code.WARNING, "Scan on 'fcs.resource = root' should yield at least one collection");
+                }
+            } else {
+                return WARNING_DIAGNOSTICS;
+            }
+        }
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan3.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan3.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan3.java	(revision 7116)
@@ -0,0 +1,53 @@
+package eu.clarin.fcs.test.tests;
+
+import java.util.List;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=2030)
+public class TestScan3 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: scan on 'fcs.resource = root' with 'maximumTerms' with value 1";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUScanRequest req = new SRUScanRequest(baseURI);
+        req.setScanClause("fcs.resource=root");
+        req.setMaximumTerms(1);
+        client.scan(req, handler);
+
+        if (handler.getDiagnosticCount() > 0) {
+            if (handler.findDiagnostic("info:srw/diagnostic/1/4")) {
+                return new Result(Result.Code.WARNING, "Endpoint does not support 'scan' operation");
+            } else {
+                return WARNING_DIAGNOSTICS;
+            }
+        } else {
+            List<String> terms = handler.getTerms();
+            if (terms.size() <= 1) {
+                return SUCCESS;
+            } else if (terms.size() > 1) {
+                return new Result(Result.Code.WARNING, "Endpoint did not honor 'maximumTerms' argument");
+            }
+        }
+        return ERROR;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan4.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan4.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan4.java	(revision 7116)
@@ -0,0 +1,37 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=2040)
+public class TestScan4 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: scan on 'fcs.resource = root' with bad 'maximumTerms' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUScanRequest req = new SRUScanRequest(baseURI);
+        req.setScanClause("fcs.resource=root");
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_MAXIMUM_TERMS,
+                "invalid");
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan5.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan5.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestScan5.java	(revision 7116)
@@ -0,0 +1,37 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=2050)
+public class TestScan5 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: scan on 'fcs.resource = root' with bad 'responsePosition' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUScanRequest req = new SRUScanRequest(baseURI);
+        req.setScanClause("fcs.resource=root");
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_RESPONSE_POSITION,
+                "invalid");
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch1.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch1.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch1.java	(revision 7116)
@@ -0,0 +1,40 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3000)
+public class TestSearch1 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: searching for random string '" + RANDOM_SEARCH_STRING +
+                "' (expecting no (fatal) non-surrogate diagnostics)";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        client.searchRetrieve(req, handler);
+        return handler.getDiagnosticCount() == 0 ? SUCCESS : WARNING_DIAGNOSTICS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch10.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch10.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch10.java	(revision 7116)
@@ -0,0 +1,40 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3100)
+public class TestSearch10 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search to provoke first record position out of range diagnostic (expecting diagnostic \"info:srw/diagnostic/1/61\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setStartRecord(Integer.MAX_VALUE);
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/61") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch11.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch11.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch11.java	(revision 7116)
@@ -0,0 +1,130 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.fcs.test.SRUTestingHandler.SurrogateDiagnostic;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUDiagnostic;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUSurrogateRecordData;
+import eu.clarin.sru.client.fcs.ClarinFCSRecordData;
+import eu.clarin.sru.client.fcs.DataView;
+import eu.clarin.sru.client.fcs.DataViewKWIC;
+import eu.clarin.sru.client.fcs.Resource;
+import eu.clarin.sru.client.fcs.Resource.ResourceFragment;
+
+@SRUTestCase(priority=3500)
+public class TestSearch11 implements SRUTest {
+    private static final String FCS_RECORD_SCHEMA = "http://clarin.eu/fcs/1.0";
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: Search for search term (expecting at least one result in CLARIN-FCS format)";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        if (searchTerm.indexOf(' ') != -1) {
+            searchTerm = '"' + searchTerm + '"';
+        }
+        req.setQuery(searchTerm);
+        req.setRecordSchema(FCS_RECORD_SCHEMA);
+        req.setMaximumRecords(5);
+        client.searchRetrieve(req, handler);
+        if (handler.findDiagnostic("info:srw/diagnostic/1/66")) {
+            return new Result(Result.Code.ERROR, "Endpoint claims to not " +
+                    "support FCS record schema (" + FCS_RECORD_SCHEMA + ")");
+        } else if (handler.getRecordCount() == 0) {
+            return new Result(Result.Code.WARNING, "Endpoint has no results " +"" +
+                    "for search term \"" + searchTerm +
+                    "\". Please supply a differnt search term.");
+        } else {
+            for (SRUTestingHandler.Record record : handler.getRecords()) {
+                String recordSchema = record.getData().getRecordSchema();
+                if (SRUSurrogateRecordData.RECORD_SCHEMA.equals(recordSchema)) {
+                    final SurrogateDiagnostic data =
+                            (SurrogateDiagnostic) record.getData();
+                    final SRUDiagnostic d = data.getDiagnostic();
+
+                    if (data.isDiagnostic("info:srw/diagnostic/1/67")) {
+                        return new Result(Result.Code.ERROR,
+                                "Endpoint cannot render record in CLARIN-FCS " +
+                                "record format and returned surrogate " +
+                                "diagnostic \"info:srw/diagnostic/1/67\" " +
+                                "instead.");
+                    } else if (data.isDiagnostic("info:clarin/sru/diagnostic/2")) {
+                        return new Result(Result.Code.ERROR,
+                                "Endpoint sent record with record " +
+                                        "schema of '" + d.getDetails() +
+                                        "' instead of '" + FCS_RECORD_SCHEMA +
+                                        "' which is " +
+                                        "required by CLARIN-FCS.");
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append("Endpoint returned unexpected surrgogate ")
+                                .append("diagnostic \"")
+                                .append(d.getURI()).append("\"");
+                        if (d.getDetails() != null) {
+                            sb.append("; details = \"")
+                                .append(d.getDetails()).append("\"");
+                        }
+                        if (d.getMessage() != null) {
+                            sb.append("; message = \"")
+                                .append(d.getMessage()).append("\"");
+                        }
+                        return new Result(Result.Code.ERROR, sb.toString());
+                    }
+                } else if (FCS_RECORD_SCHEMA.equals(recordSchema)) {
+                    final ClarinFCSRecordData data =
+                            (ClarinFCSRecordData) record.getData();
+                    final Resource resource = data.getResource();
+                    boolean foundKwic = false;
+                    if (resource.hasDataViews()) {
+                        for (DataView dataView : resource.getDataViews()) {
+                            if (dataView.isMimeType(DataViewKWIC.TYPE)) {
+                                foundKwic = true;
+                            }
+                        }
+                    }
+
+                    if (resource.hasResourceFragments()) {
+                        for (ResourceFragment fragment : resource.getResourceFragments()) {
+                            for (DataView dataView : fragment.getDataViews()) {
+                                if (dataView.isMimeType(DataViewKWIC.TYPE)) {
+                                    foundKwic = true;
+                                }
+                            }
+                        }
+                    }
+                    if (!foundKwic) {
+                        return new Result(Result.Code.ERROR, "Endpoint did not provide mandatory KWIC dataview in results");
+                    }
+
+                } else {
+                    return new Result(Result.Code.ERROR, "Endpoint does not supply results in FCS record schema (" + FCS_RECORD_SCHEMA + "\"");
+                }
+            }
+            if (handler.getRecordCount() > req.getMaximumRecords()) {
+                return new Result(Result.Code.ERROR,
+                        "Endpoint did not honor upper requested limit for " +
+                                "\"maximumRecords\" parameter (up to " +
+                                req.getMaximumRecords() +
+                                " records where requested and endpoint delivered " +
+                                handler.getRecordCount() + " results)");
+            }
+            return SUCCESS;
+        }
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch2.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch2.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch2.java	(revision 7116)
@@ -0,0 +1,44 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.fcs.ClarinFCSRecordData;
+
+@SRUTestCase(priority=3010)
+public class TestSearch2 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "CLARIN-FCS: searching for random string '" +
+                RANDOM_SEARCH_STRING + "' with CLARIN FSC record schema '" +
+                ClarinFCSRecordData.RECORD_SCHEMA +
+                "' (expecting no (fatal) non-surrogate diagnostics)";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA);
+        client.searchRetrieve(req, handler);
+        return handler.getDiagnosticCount() == 0 ? SUCCESS : WARNING_DIAGNOSTICS;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch3.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch3.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch3.java	(revision 7116)
@@ -0,0 +1,36 @@
+package eu.clarin.fcs.test.tests;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3030)
+public class TestSearch3 implements SRUTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with missing 'query' argument (expecting diagnostic \"info:srw/diagnostic/1/7\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_QUERY,
+                SRUSearchRetrieveRequest.MALFORMED_OMIT);
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/7") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch4.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch4.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch4.java	(revision 7116)
@@ -0,0 +1,41 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3040)
+public class TestSearch4 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid 'recordPacking' argument (expecting diagnostic \"info:srw/diagnostic/1/71\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_RECORD_PACKING,
+                "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/71") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch5.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch5.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch5.java	(revision 7116)
@@ -0,0 +1,41 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3050)
+public class TestSearch5 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value for 'startRecord' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_START_RECORD,
+                "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch6.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch6.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch6.java	(revision 7116)
@@ -0,0 +1,41 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3060)
+public class TestSearch6 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value of 0 for 'startRecord' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_START_RECORD,
+                "0");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch7.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch7.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch7.java	(revision 7116)
@@ -0,0 +1,41 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3070)
+public class TestSearch7 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value for 'maximumRecords' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_MAXIMUM_RECORDS,
+                "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch8.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch8.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch8.java	(revision 7116)
@@ -0,0 +1,41 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3080)
+public class TestSearch8 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value of -1 for 'maximumRecords' argument (expecting diagnostic \"info:srw/diagnostic/1/6\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery(RANDOM_SEARCH_STRING);
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_MAXIMUM_RECORDS,
+                "-1");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch9.java
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch9.java	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/java/eu/clarin/fcs/test/tests/TestSearch9.java	(revision 7116)
@@ -0,0 +1,39 @@
+package eu.clarin.fcs.test.tests;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.fcs.test.SRUTest;
+import eu.clarin.fcs.test.SRUTestCase;
+import eu.clarin.fcs.test.SRUTestingHandler;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@SRUTestCase(priority=3090)
+public class TestSearch9 implements SRUTest {
+    private static final String RANDOM_SEARCH_STRING =
+            RandomStringUtils.randomAlphanumeric(16);
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid query (expecting diagnostic \"info:srw/diagnostic/1/10\")";
+    }
+
+
+    @Override
+    public Result performTest(SRUSimpleClient client,
+            SRUTestingHandler handler, String baseURI, String searchTerm)
+            throws SRUClientException {
+        SRUSearchRetrieveRequest req = new SRUSearchRetrieveRequest(baseURI);
+        req.setQuery("\"" + RANDOM_SEARCH_STRING + "\" =");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/10") ? SUCCESS : ERROR_NO_DIAGNOSTIC;
+    }
+
+}
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/WEB-INF/web.xml
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/WEB-INF/web.xml	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/WEB-INF/web.xml	(revision 7116)
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+	id="eu.clarin.sru.client.fcs.SRUEndpointTester" version="2.5">
+	<display-name>CLARIN FCS SRU/CQL Conformance Tester</display-name>
+
+	<servlet>
+		<servlet-name>tester</servlet-name>
+		<servlet-class>eu.clarin.fcs.test.SRUEndpointTester</servlet-class>
+		<load-on-startup>1</load-on-startup>
+	</servlet>
+
+	<servlet-mapping>
+		<servlet-name>tester</servlet-name>
+		<url-pattern>/EndpointTester</url-pattern>
+	</servlet-mapping>
+
+	<welcome-file-list>
+		<welcome-file>index.jsp</welcome-file>
+	</welcome-file-list>
+</web-app>
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/index.jsp
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/index.jsp	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/index.jsp	(revision 7116)
@@ -0,0 +1,57 @@
+<%@page import="javax.naming.Context"%>
+<%@ page language="java" pageEncoding="UTF-8" %><%
+String uri = request.getParameter("uri");
+if (uri == null) {
+    uri = "";
+}
+String searchTerm = request.getParameter("searchTerm");
+if (searchTerm == null) {
+    searchTerm = "";
+}
+String location = request.getHeader("X-SERVLET-LOCATION");
+if (location == null) {
+    location = request.getContextPath();
+}
+if (!request.getRequestURI().endsWith("/")) {
+    StringBuilder sb = new StringBuilder(location);
+    if (!location.endsWith("/")) {
+        sb.append("/");
+    }
+    response.sendRedirect(sb.toString());
+} else {
+    StringBuilder testUri = new StringBuilder(location);
+    if (!location.endsWith("/")) {
+        testUri.append("/");
+    }
+    testUri.append("EndpointTester");
+%><html>
+    <head>
+        <title>CLARIN FCS SRU/CQL Conformance Test</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+        <link href="tester.css" rel="stylesheet" type="text/css" />
+    </head>
+    <body>
+        <h1>CLARIN FCS SRU/CQL Conformance Test</h1>
+        <form action="<%= testUri %>" method="get">
+            <label for="uri">Endpoint BaseURI</label>
+            <input id="uri" type="text" name="uri" value="<%= uri %>" size="64" maxlength="255"/>
+            <input id="cb_strict" type="checkbox" name="strict" value="strict" checked="checked" />
+            <label for="cb_strict">Strict Mode</label>
+            <input id="submit" type="submit" name="doit" value="Go!" /> <br />
+            <label for="searchTerm">Search term (e.g. a word)</label>
+            <input id="searchTerm" type="text" name="searchTerm" value="<%= searchTerm %>" size="32" maxlength="64" /> <br />
+            <small>Note: the <em>search term</em> must occur at least one in your data. It is used to check, if searchRetrieve is properly implemented. Please only use a single word.</small>
+        </form>
+        <hr />
+        <h2>SRU/CQL Resources</h2>
+        <ul>
+            <li><a href="http://www.loc.gov/standards/sru/index.html">SRU Specification</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/specs/explain.html">SRU Specification &ndash; Explain</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/specs/scan.html">SRU Specification &ndash; Scan</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/specs/search-retrieve.html">SRU Specification &ndash; Search Retrieve</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/resources/diagnostics-list.html">SRU Specification &ndash; Diagnostics List</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/sru1-1archive/xml-files/srw-types.xsd">SRU/SRW XSD (SRU Version 1.1)</a></li>
+            <li><a href="http://www.loc.gov/standards/sru/sru1-1archive/xml-files/diagnostics.xsd">SRU/SRW Diagnostic XSD</a></li>
+        </ul>
+    </body>
+</html><% } %>
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/jquery-1.8.0.js
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/jquery-1.8.0.js	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/jquery-1.8.0.js	(revision 7116)
@@ -0,0 +1,9227 @@
+/*!
+ * jQuery JavaScript Library v1.8.0
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Thu Aug 09 2012 16:24:48 GMT-0400 (Eastern Daylight Time)
+ */
+(function( window, undefined ) {
+var
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// Use the correct document accordingly with window argument (sandbox)
+	document = window.document,
+	location = window.location,
+	navigator = window.navigator,
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// Save a reference to some core methods
+	core_push = Array.prototype.push,
+	core_slice = Array.prototype.slice,
+	core_indexOf = Array.prototype.indexOf,
+	core_toString = Object.prototype.toString,
+	core_hasOwn = Object.prototype.hasOwnProperty,
+	core_trim = String.prototype.trim,
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Used for matching numbers
+	core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+	// Used for detecting and trimming whitespace
+	core_rnotwhite = /\S/,
+	core_rspace = /\s+/,
+
+	// IE doesn't match non-breaking spaces with \s
+	rtrim = core_rnotwhite.test("\xA0") ? (/^[\s\xA0]+|[\s\xA0]+$/g) : /^\s+|\s+$/g,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+	// Matches dashed string for camelizing
+	rmsPrefix = /^-ms-/,
+	rdashAlpha = /-([\da-z])/gi,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return ( letter + "" ).toUpperCase();
+	},
+
+	// The ready event handler and self cleanup method
+	DOMContentLoaded = function() {
+		if ( document.addEventListener ) {
+			document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+			jQuery.ready();
+		} else if ( document.readyState === "complete" ) {
+			// we're here because readyState === "complete" in oldIE
+			// which is good enough for us to call the dom ready!
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	},
+
+	// [[Class]] -> type pairs
+	class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem, ret, doc;
+
+		// Handle $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = rquickExpr.exec( selector );
+			}
+
+			// Match html or make sure no context is specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+					// scripts is true for back-compat
+					selector = jQuery.parseHTML( match[1], doc, true );
+					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+						this.attr.call( selector, context, true );
+					}
+
+					return jQuery.merge( this, selector );
+
+				// HANDLE: $(#id)
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id !== match[2] ) {
+							return rootjQuery.find( selector );
+						}
+
+						// Otherwise, we inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || rootjQuery ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.8.0",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	toArray: function() {
+		return core_slice.call( this );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+
+		// Build a new jQuery matched element set
+		var ret = jQuery.merge( this.constructor(), elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" ) {
+			ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+		} else if ( name ) {
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+		}
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Add the callback
+		jQuery.ready.promise().done( fn );
+
+		return this;
+	},
+
+	eq: function( i ) {
+		i = +i;
+		return i === -1 ?
+			this.slice( i ) :
+			this.slice( i, i + 1 );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	slice: function() {
+		return this.pushStack( core_slice.apply( this, arguments ),
+			"slice", core_slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: core_push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+
+		// Abort if there are pending holds or we're already ready
+		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+			return;
+		}
+
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( !document.body ) {
+			return setTimeout( jQuery.ready, 1 );
+		}
+
+		// Remember that the DOM is ready
+		jQuery.isReady = true;
+
+		// If a normal DOM Ready event fired, decrement, and wait if need be
+		if ( wait !== true && --jQuery.readyWait > 0 ) {
+			return;
+		}
+
+		// If there are functions bound, to execute
+		readyList.resolveWith( document, [ jQuery ] );
+
+		// Trigger any bound ready events
+		if ( jQuery.fn.trigger ) {
+			jQuery( document ).trigger("ready").off("ready");
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray || function( obj ) {
+		return jQuery.type(obj) === "array";
+	},
+
+	isWindow: function( obj ) {
+		return obj != null && obj == obj.window;
+	},
+
+	isNumeric: function( obj ) {
+		return !isNaN( parseFloat(obj) ) && isFinite( obj );
+	},
+
+	type: function( obj ) {
+		return obj == null ?
+			String( obj ) :
+			class2type[ core_toString.call(obj) ] || "object";
+	},
+
+	isPlainObject: function( obj ) {
+		// Must be an Object.
+		// Because of IE, we also have to check the presence of the constructor property.
+		// Make sure that DOM nodes and window objects don't pass through, as well
+		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		try {
+			// Not own constructor property must be Object
+			if ( obj.constructor &&
+				!core_hasOwn.call(obj, "constructor") &&
+				!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+				return false;
+			}
+		} catch ( e ) {
+			// IE8,9 Will throw exceptions on certain host objects #9897
+			return false;
+		}
+
+		// Own properties are enumerated firstly, so to speed up,
+		// if last one is own, then all properties are own.
+
+		var key;
+		for ( key in obj ) {}
+
+		return key === undefined || core_hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	// data: string of html
+	// context (optional): If specified, the fragment will be created in this context, defaults to document
+	// scripts (optional): If true, will include scripts passed in the html string
+	parseHTML: function( data, context, scripts ) {
+		var parsed;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		if ( typeof context === "boolean" ) {
+			scripts = context;
+			context = 0;
+		}
+		context = context || document;
+
+		// Single tag
+		if ( (parsed = rsingleTag.exec( data )) ) {
+			return [ context.createElement( parsed[1] ) ];
+		}
+
+		parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+		return jQuery.merge( [],
+			(parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+	},
+
+	parseJSON: function( data ) {
+		if ( !data || typeof data !== "string") {
+			return null;
+		}
+
+		// Make sure leading/trailing whitespace is removed (IE can't handle it)
+		data = jQuery.trim( data );
+
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( data );
+		}
+
+		// Make sure the incoming data is actual JSON
+		// Logic borrowed from http://json.org/json2.js
+		if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+			.replace( rvalidtokens, "]" )
+			.replace( rvalidbraces, "")) ) {
+
+			return ( new Function( "return " + data ) )();
+
+		}
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		var xml, tmp;
+		if ( !data || typeof data !== "string" ) {
+			return null;
+		}
+		try {
+			if ( window.DOMParser ) { // Standard
+				tmp = new DOMParser();
+				xml = tmp.parseFromString( data , "text/xml" );
+			} else { // IE
+				xml = new ActiveXObject( "Microsoft.XMLDOM" );
+				xml.async = "false";
+				xml.loadXML( data );
+			}
+		} catch( e ) {
+			xml = undefined;
+		}
+		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// Workarounds based on findings by Jim Driscoll
+	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+	globalEval: function( data ) {
+		if ( data && core_rnotwhite.test( data ) ) {
+			// We use execScript on Internet Explorer
+			// We use an anonymous function so that context is window
+			// rather than jQuery in Firefox
+			( window.execScript || function( data ) {
+				window[ "eval" ].call( window, data );
+			} )( data );
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( obj, callback, args ) {
+		var name,
+			i = 0,
+			length = obj.length,
+			isObj = length === undefined || jQuery.isFunction( obj );
+
+		if ( args ) {
+			if ( isObj ) {
+				for ( name in obj ) {
+					if ( callback.apply( obj[ name ], args ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.apply( obj[ i++ ], args ) === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isObj ) {
+				for ( name in obj ) {
+					if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: core_trim ?
+		function( text ) {
+			return text == null ?
+				"" :
+				core_trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		function( text ) {
+			return text == null ?
+				"" :
+				text.toString().replace( rtrim, "" );
+		},
+
+	// results is for internal usage only
+	makeArray: function( arr, results ) {
+		var type,
+			ret = results || [];
+
+		if ( arr != null ) {
+			// The window, strings (and functions) also have 'length'
+			// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+			type = jQuery.type( arr );
+
+			if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+				core_push.call( ret, arr );
+			} else {
+				jQuery.merge( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		var len;
+
+		if ( arr ) {
+			if ( core_indexOf ) {
+				return core_indexOf.call( arr, elem, i );
+			}
+
+			len = arr.length;
+			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+			for ( ; i < len; i++ ) {
+				// Skip accessing in sparse arrays
+				if ( i in arr && arr[ i ] === elem ) {
+					return i;
+				}
+			}
+		}
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		var l = second.length,
+			i = first.length,
+			j = 0;
+
+		if ( typeof l === "number" ) {
+			for ( ; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var retVal,
+			ret = [],
+			i = 0,
+			length = elems.length;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value, key,
+			ret = [],
+			i = 0,
+			length = elems.length,
+			// jquery objects are treated as arrays
+			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( key in elems ) {
+				value = callback( elems[ key ], key, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		var tmp, args, proxy;
+
+		if ( typeof context === "string" ) {
+			tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		args = core_slice.call( arguments, 2 );
+		proxy = function() {
+			return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+		};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Multifunctional method to get and set values of a collection
+	// The value/s can optionally be executed if it's a function
+	access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+		var exec,
+			bulk = key == null,
+			i = 0,
+			length = elems.length;
+
+		// Sets many values
+		if ( key && typeof key === "object" ) {
+			for ( i in key ) {
+				jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+			}
+			chainable = 1;
+
+		// Sets one value
+		} else if ( value !== undefined ) {
+			// Optionally, function values get executed if exec is true
+			exec = pass === undefined && jQuery.isFunction( value );
+
+			if ( bulk ) {
+				// Bulk operations only iterate when executing function values
+				if ( exec ) {
+					exec = fn;
+					fn = function( elem, key, value ) {
+						return exec.call( jQuery( elem ), value );
+					};
+
+				// Otherwise they run against the entire set
+				} else {
+					fn.call( elems, value );
+					fn = null;
+				}
+			}
+
+			if ( fn ) {
+				for (; i < length; i++ ) {
+					fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+				}
+			}
+
+			chainable = 1;
+		}
+
+		return chainable ?
+			elems :
+
+			// Gets
+			bulk ?
+				fn.call( elems ) :
+				length ? fn( elems[0], key ) : emptyGet;
+	},
+
+	now: function() {
+		return ( new Date() ).getTime();
+	}
+});
+
+jQuery.ready.promise = function( obj ) {
+	if ( !readyList ) {
+
+		readyList = jQuery.Deferred();
+
+		// Catch cases where $(document).ready() is called after the
+		// browser event has already occurred.
+		if ( document.readyState === "complete" || ( document.readyState !== "loading" && document.addEventListener ) ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			setTimeout( jQuery.ready, 1 );
+
+		// Standards-based browsers support DOMContentLoaded
+		} else if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", jQuery.ready, false );
+
+		// If IE event model is used
+		} else {
+			// Ensure firing before onload, maybe late but safe also for iframes
+			document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+			// A fallback to window.onload, that will always work
+			window.attachEvent( "onload", jQuery.ready );
+
+			// If IE and not a frame
+			// continually check to see if the document is ready
+			var top = false;
+
+			try {
+				top = window.frameElement == null && document.documentElement;
+			} catch(e) {}
+
+			if ( top && top.doScroll ) {
+				(function doScrollCheck() {
+					if ( !jQuery.isReady ) {
+
+						try {
+							// Use the trick by Diego Perini
+							// http://javascript.nwbox.com/IEContentLoaded/
+							top.doScroll("left");
+						} catch(e) {
+							return setTimeout( doScrollCheck, 50 );
+						}
+
+						// and execute any waiting functions
+						jQuery.ready();
+					}
+				})();
+			}
+		}
+	}
+	return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+	var object = optionsCache[ options ] = {};
+	jQuery.each( options.split( core_rspace ), function( _, flag ) {
+		object[ flag ] = true;
+	});
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	options: an optional list of space-separated options that will change how
+ *			the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+	// Convert options from String-formatted to Object-formatted if needed
+	// (we check in cache first)
+	options = typeof options === "string" ?
+		( optionsCache[ options ] || createOptions( options ) ) :
+		jQuery.extend( {}, options );
+
+	var // Last fire value (for non-forgettable lists)
+		memory,
+		// Flag to know if list was already fired
+		fired,
+		// Flag to know if list is currently firing
+		firing,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = !options.once && [],
+		// Fire callbacks
+		fire = function( data ) {
+			memory = options.memory && data;
+			fired = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			firing = true;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+					memory = false; // To prevent further calls using add
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( stack ) {
+					if ( stack.length ) {
+						fire( stack.shift() );
+					}
+				} else if ( memory ) {
+					list = [];
+				} else {
+					self.disable();
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					// First, we save the current length
+					var start = list.length;
+					(function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) {
+								list.push( arg );
+							} else if ( arg && arg.length ) {
+								// Inspect recursively
+								add( arg );
+							}
+						});
+					})( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away
+					} else if ( memory ) {
+						firingStart = start;
+						fire( memory );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+							// Handle firing indexes
+							if ( firing ) {
+								if ( index <= firingLength ) {
+									firingLength--;
+								}
+								if ( index <= firingIndex ) {
+									firingIndex--;
+								}
+							}
+						}
+					});
+				}
+				return this;
+			},
+			// Control if a given callback is in the list
+			has: function( fn ) {
+				return jQuery.inArray( fn, list ) > -1;
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				args = args || [];
+				args = [ context, args.slice ? args.slice() : args ];
+				if ( list && ( !fired || stack ) ) {
+					if ( firing ) {
+						stack.push( args );
+					} else {
+						fire( args );
+					}
+				}
+				return this;
+			},
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+jQuery.extend({
+
+	Deferred: function( func ) {
+		var tuples = [
+				// action, add listener, listener list, final state
+				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+				[ "notify", "progress", jQuery.Callbacks("memory") ]
+			],
+			state = "pending",
+			promise = {
+				state: function() {
+					return state;
+				},
+				always: function() {
+					deferred.done( arguments ).fail( arguments );
+					return this;
+				},
+				then: function( /* fnDone, fnFail, fnProgress */ ) {
+					var fns = arguments;
+					return jQuery.Deferred(function( newDefer ) {
+						jQuery.each( tuples, function( i, tuple ) {
+							var action = tuple[ 0 ],
+								fn = fns[ i ];
+							// deferred[ done | fail | progress ] for forwarding actions to newDefer
+							deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+								function() {
+									var returned = fn.apply( this, arguments );
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
+										returned.promise()
+											.done( newDefer.resolve )
+											.fail( newDefer.reject )
+											.progress( newDefer.notify );
+									} else {
+										newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+									}
+								} :
+								newDefer[ action ]
+							);
+						});
+						fns = null;
+					}).promise();
+				},
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
+				}
+			},
+			deferred = {};
+
+		// Keep pipe for back-compat
+		promise.pipe = promise.then;
+
+		// Add list-specific methods
+		jQuery.each( tuples, function( i, tuple ) {
+			var list = tuple[ 2 ],
+				stateString = tuple[ 3 ];
+
+			// promise[ done | fail | progress ] = list.add
+			promise[ tuple[1] ] = list.add;
+
+			// Handle state
+			if ( stateString ) {
+				list.add(function() {
+					// state = [ resolved | rejected ]
+					state = stateString;
+
+				// [ reject_list | resolve_list ].disable; progress_list.lock
+				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+			}
+
+			// deferred[ resolve | reject | notify ] = list.fire
+			deferred[ tuple[0] ] = list.fire;
+			deferred[ tuple[0] + "With" ] = list.fireWith;
+		});
+
+		// Make the deferred a promise
+		promise.promise( deferred );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( subordinate /* , ..., subordinateN */ ) {
+		var i = 0,
+			resolveValues = core_slice.call( arguments ),
+			length = resolveValues.length,
+
+			// the count of uncompleted subordinates
+			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+			// Update function for both resolve and progress values
+			updateFunc = function( i, contexts, values ) {
+				return function( value ) {
+					contexts[ i ] = this;
+					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+					if( values === progressValues ) {
+						deferred.notifyWith( contexts, values );
+					} else if ( !( --remaining ) ) {
+						deferred.resolveWith( contexts, values );
+					}
+				};
+			},
+
+			progressValues, progressContexts, resolveContexts;
+
+		// add listeners to Deferred subordinates; treat others as resolved
+		if ( length > 1 ) {
+			progressValues = new Array( length );
+			progressContexts = new Array( length );
+			resolveContexts = new Array( length );
+			for ( ; i < length; i++ ) {
+				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+					resolveValues[ i ].promise()
+						.done( updateFunc( i, resolveContexts, resolveValues ) )
+						.fail( deferred.reject )
+						.progress( updateFunc( i, progressContexts, progressValues ) );
+				} else {
+					--remaining;
+				}
+			}
+		}
+
+		// if we're not waiting on anything, resolve the master
+		if ( !remaining ) {
+			deferred.resolveWith( resolveContexts, resolveValues );
+		}
+
+		return deferred.promise();
+	}
+});
+jQuery.support = (function() {
+
+	var support,
+		all,
+		a,
+		select,
+		opt,
+		input,
+		fragment,
+		eventName,
+		i,
+		isSupported,
+		clickFn,
+		div = document.createElement("div");
+
+	// Preliminary tests
+	div.setAttribute( "className", "t" );
+	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+	all = div.getElementsByTagName("*");
+	a = div.getElementsByTagName("a")[ 0 ];
+	a.style.cssText = "top:1px;float:left;opacity:.5";
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return {};
+	}
+
+	// First batch of supports tests
+	select = document.createElement("select");
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName("input")[ 0 ];
+
+	support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName("tbody").length,
+
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName("link").length,
+
+		// Get the style information from getAttribute
+		// (IE uses .cssText instead)
+		style: /top/.test( a.getAttribute("style") ),
+
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		// Use a regex to work around a WebKit issue. See #5145
+		opacity: /^0.5/.test( a.style.opacity ),
+
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Make sure that if no value is specified for a checkbox
+		// that it defaults to "on".
+		// (WebKit defaults to "" instead)
+		checkOn: ( input.value === "on" ),
+
+		// Make sure that a selected-by-default option has a working selected property.
+		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+		optSelected: opt.selected,
+
+		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+		getSetAttribute: div.className !== "t",
+
+		// Tests for enctype support on a form(#6743)
+		enctype: !!document.createElement("form").enctype,
+
+		// Makes sure cloning an html5 element does not cause problems
+		// Where outerHTML is undefined, this still works
+		html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+		// jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+		boxModel: ( document.compatMode === "CSS1Compat" ),
+
+		// Will be defined later
+		submitBubbles: true,
+		changeBubbles: true,
+		focusinBubbles: false,
+		deleteExpando: true,
+		noCloneEvent: true,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableMarginRight: true,
+		boxSizingReliable: true,
+		pixelPosition: false
+	};
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+		div.attachEvent( "onclick", clickFn = function() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			support.noCloneEvent = false;
+		});
+		div.cloneNode( true ).fireEvent("onclick");
+		div.detachEvent( "onclick", clickFn );
+	}
+
+	// Check if a radio maintains its value
+	// after being appended to the DOM
+	input = document.createElement("input");
+	input.value = "t";
+	input.setAttribute( "type", "radio" );
+	support.radioValue = input.value === "t";
+
+	input.setAttribute( "checked", "checked" );
+
+	// #11217 - WebKit loses check when the name is after the checked attribute
+	input.setAttribute( "name", "t" );
+
+	div.appendChild( input );
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( div.lastChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	fragment.removeChild( input );
+	fragment.appendChild( div );
+
+	// Technique from Juriy Zaytsev
+	// http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+	// We only care about the case where non-standard event systems
+	// are used, namely in IE. Short-circuiting here helps us to
+	// avoid an eval call (in setAttribute) which can cause CSP
+	// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+	if ( div.attachEvent ) {
+		for ( i in {
+			submit: true,
+			change: true,
+			focusin: true
+		}) {
+			eventName = "on" + i;
+			isSupported = ( eventName in div );
+			if ( !isSupported ) {
+				div.setAttribute( eventName, "return;" );
+				isSupported = ( typeof div[ eventName ] === "function" );
+			}
+			support[ i + "Bubbles" ] = isSupported;
+		}
+	}
+
+	// Run tests that need a body at doc ready
+	jQuery(function() {
+		var container, div, tds, marginDiv,
+			divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+			body = document.getElementsByTagName("body")[0];
+
+		if ( !body ) {
+			// Return for frameset docs that don't have a body
+			return;
+		}
+
+		container = document.createElement("div");
+		container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+		body.insertBefore( container, body.firstChild );
+
+		// Construct the test element
+		div = document.createElement("div");
+		container.appendChild( div );
+
+		// Check if table cells still have offsetWidth/Height when they are set
+		// to display:none and there are still other visible table cells in a
+		// table row; if so, offsetWidth/Height are not reliable for use when
+		// determining if an element has been hidden directly using
+		// display:none (it is still safe to use offsets if a parent element is
+		// hidden; don safety goggles and see bug #4512 for more information).
+		// (only IE 8 fails this test)
+		div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+		tds = div.getElementsByTagName("td");
+		tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+		isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+		tds[ 0 ].style.display = "";
+		tds[ 1 ].style.display = "none";
+
+		// Check if empty table cells still have offsetWidth/Height
+		// (IE <= 8 fail this test)
+		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+		// Check box-sizing and margin behavior
+		div.innerHTML = "";
+		div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+		support.boxSizing = ( div.offsetWidth === 4 );
+		support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+		// NOTE: To any future maintainer, window.getComputedStyle was used here
+		// instead of getComputedStyle because it gave a better gzip size.
+		// The difference between window.getComputedStyle and getComputedStyle is
+		// 7 bytes
+		if ( window.getComputedStyle ) {
+			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+			// Check if div with explicit width and no margin-right incorrectly
+			// gets computed margin-right based on width of container. For more
+			// info see bug #3333
+			// Fails in WebKit before Feb 2011 nightlies
+			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+			marginDiv = document.createElement("div");
+			marginDiv.style.cssText = div.style.cssText = divReset;
+			marginDiv.style.marginRight = marginDiv.style.width = "0";
+			div.style.width = "1px";
+			div.appendChild( marginDiv );
+			support.reliableMarginRight =
+				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+		}
+
+		if ( typeof div.style.zoom !== "undefined" ) {
+			// Check if natively block-level elements act like inline-block
+			// elements when setting their display to 'inline' and giving
+			// them layout
+			// (IE < 8 does this)
+			div.innerHTML = "";
+			div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+			// Check if elements with layout shrink-wrap their children
+			// (IE 6 does this)
+			div.style.display = "block";
+			div.style.overflow = "visible";
+			div.innerHTML = "<div></div>";
+			div.firstChild.style.width = "5px";
+			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+			container.style.zoom = 1;
+		}
+
+		// Null elements to avoid leaks in IE
+		body.removeChild( container );
+		container = div = tds = marginDiv = null;
+	});
+
+	// Null elements to avoid leaks in IE
+	fragment.removeChild( div );
+	all = a = select = opt = input = fragment = div = null;
+
+	return support;
+})();
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+	cache: {},
+
+	deletedIds: [],
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"embed": true,
+		// Ban all objects except for Flash (which handle expandos)
+		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+		"applet": true
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+		return !!elem && !isEmptyDataObject( elem );
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache, ret,
+			internalKey = jQuery.expando,
+			getByName = typeof name === "string",
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ internalKey ] = id = jQuery.deletedIds.pop() || ++jQuery.uuid;
+			} else {
+				id = internalKey;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+
+			// Avoids exposing jQuery metadata on plain JS objects when the object
+			// is serialized using JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" || typeof name === "function" ) {
+			if ( pvt ) {
+				cache[ id ] = jQuery.extend( cache[ id ], name );
+			} else {
+				cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+			}
+		}
+
+		thisCache = cache[ id ];
+
+		// jQuery data() is stored in a separate object inside the object's internal data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data.
+		if ( !pvt ) {
+			if ( !thisCache.data ) {
+				thisCache.data = {};
+			}
+
+			thisCache = thisCache.data;
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ jQuery.camelCase( name ) ] = data;
+		}
+
+		// Check for both converted-to-camel and non-converted data property names
+		// If a data property was specified
+		if ( getByName ) {
+
+			// First Try to find as-is property data
+			ret = thisCache[ name ];
+
+			// Test for null|undefined property data
+			if ( ret == null ) {
+
+				// Try to find the camelCased property
+				ret = thisCache[ jQuery.camelCase( name ) ];
+			}
+		} else {
+			ret = thisCache;
+		}
+
+		return ret;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache, i, l,
+
+			isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+			id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+
+			thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+			if ( thisCache ) {
+
+				// Support array or space separated string names for data keys
+				if ( !jQuery.isArray( name ) ) {
+
+					// try the string as a key before any manipulation
+					if ( name in thisCache ) {
+						name = [ name ];
+					} else {
+
+						// split the camel cased version by spaces unless a key with the spaces exists
+						name = jQuery.camelCase( name );
+						if ( name in thisCache ) {
+							name = [ name ];
+						} else {
+							name = name.split(" ");
+						}
+					}
+				}
+
+				for ( i = 0, l = name.length; i < l; i++ ) {
+					delete thisCache[ name[i] ];
+				}
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( !pvt ) {
+			delete cache[ id ].data;
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !isEmptyDataObject( cache[ id ] ) ) {
+				return;
+			}
+		}
+
+		// Destroy the cache
+		if ( isNode ) {
+			jQuery.cleanData( [ elem ], true );
+
+		// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+		} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+			delete cache[ id ];
+
+		// When all else fails, null
+		} else {
+			cache[ id ] = null;
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+		// nodes accept data unless otherwise specified; rejection can be conditional
+		return !noData || noData !== true && elem.getAttribute("classid") === noData;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var parts, part, attr, name, l,
+			elem = this[0],
+			i = 0,
+			data = null;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = jQuery.data( elem );
+
+				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+					attr = elem.attributes;
+					for ( l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.substring(5) );
+
+							dataAttr( elem, name, data[ name ] );
+						}
+					}
+					jQuery._data( elem, "parsedAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each(function() {
+				jQuery.data( this, key );
+			});
+		}
+
+		parts = key.split( ".", 2 );
+		parts[1] = parts[1] ? "." + parts[1] : "";
+		part = parts[1] + "!";
+
+		return jQuery.access( this, function( value ) {
+
+			if ( value === undefined ) {
+				data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+				// Try to fetch any internally stored data first
+				if ( data === undefined && elem ) {
+					data = jQuery.data( elem, key );
+					data = dataAttr( elem, key, data );
+				}
+
+				return data === undefined && parts[1] ?
+					this.data( parts[0] ) :
+					data;
+			}
+
+			parts[1] = value;
+			this.each(function() {
+				var self = jQuery( this );
+
+				self.triggerHandler( "setData" + part, parts );
+				jQuery.data( this, key, value );
+				self.triggerHandler( "changeData" + part, parts );
+			});
+		}, null, value, arguments.length > 1, null, false );
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+
+		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+				data === "false" ? false :
+				data === "null" ? null :
+				// Only convert to a number if it doesn't change the string
+				+data + "" === data ? +data :
+				rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+	var name;
+	for ( name in obj ) {
+
+		// if the public data object is empty, the private is still empty
+		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+			continue;
+		}
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+jQuery.extend({
+	queue: function( elem, type, data ) {
+		var queue;
+
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			queue = jQuery._data( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !queue || jQuery.isArray(data) ) {
+					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+				} else {
+					queue.push( data );
+				}
+			}
+			return queue || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift(),
+			hooks = jQuery._queueHooks( elem, type ),
+			next = function() {
+				jQuery.dequeue( elem, type );
+			};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+		}
+
+		if ( fn ) {
+
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			// clear up the last queue stop function
+			delete hooks.stop;
+			fn.call( elem, next, hooks );
+		}
+		if ( !queue.length && hooks ) {
+			hooks.empty.fire();
+		}
+	},
+
+	// not intended for public consumption - generates a queueHooks object, or returns the current one
+	_queueHooks: function( elem, type ) {
+		var key = type + "queueHooks";
+		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+			empty: jQuery.Callbacks("once memory").add(function() {
+				jQuery.removeData( elem, type + "queue", true );
+				jQuery.removeData( elem, key, true );
+			})
+		});
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[0], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each(function() {
+				var queue = jQuery.queue( this, type, data );
+
+				// ensure a hooks for this queue
+				jQuery._queueHooks( this, type );
+
+				if ( type === "fx" && queue[0] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = setTimeout( next, time );
+			hooks.stop = function() {
+				clearTimeout( timeout );
+			};
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, obj ) {
+		var tmp,
+			count = 1,
+			defer = jQuery.Deferred(),
+			elements = this,
+			i = this.length,
+			resolve = function() {
+				if ( !( --count ) ) {
+					defer.resolveWith( elements, [ elements ] );
+				}
+			};
+
+		if ( typeof type !== "string" ) {
+			obj = type;
+			type = undefined;
+		}
+		type = type || "fx";
+
+		while( i-- ) {
+			if ( (tmp = jQuery._data( elements[ i ], type + "queueHooks" )) && tmp.empty ) {
+				count++;
+				tmp.empty.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( obj );
+	}
+});
+var nodeHook, boolHook, fixSpecified,
+	rclass = /[\t\r\n]/g,
+	rreturn = /\r/g,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea|)$/i,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+
+	prop: function( name, value ) {
+		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classNames, i, l, elem,
+			setClass, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			classNames = value.split( core_rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className && classNames.length === 1 ) {
+						elem.className = value;
+
+					} else {
+						setClass = " " + elem.className + " ";
+
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+								setClass += classNames[ c ] + " ";
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var removes, className, elem, c, cl, i, l;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call(this, j, this.className) );
+			});
+		}
+		if ( (value && typeof value === "string") || value === undefined ) {
+			removes = ( value || "" ).split( core_rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+				if ( elem.nodeType === 1 && elem.className ) {
+
+					className = (" " + elem.className + " ").replace( rclass, " " );
+
+					// loop over each item in the removal list
+					for ( c = 0, cl = removes.length; c < cl; c++ ) {
+						// Remove until there is nothing to remove,
+						while ( className.indexOf(" " + removes[ c ] + " ") > -1 ) {
+							className = className.replace( " " + removes[ c ] + " " , " " );
+						}
+					}
+					elem.className = value ? jQuery.trim( className ) : "";
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isBool = typeof stateVal === "boolean";
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					state = stateVal,
+					classNames = value.split( core_rspace );
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space separated list
+					state = isBool ? state : !self.hasClass( className );
+					self[ state ? "addClass" : "removeClass" ]( className );
+				}
+
+			} else if ( type === "undefined" || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// toggle whole className
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// handle most common string cases
+					ret.replace(rreturn, "") :
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var val,
+				self = jQuery(this);
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, self.val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// attributes.value is undefined in Blackberry 4.7 but
+				// uses .value. See #6932
+				var val = elem.attributes.value;
+				return !val || val.specified ? elem.value : elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, i, max, option,
+					index = elem.selectedIndex,
+					values = [],
+					options = elem.options,
+					one = elem.type === "select-one";
+
+				// Nothing was selected
+				if ( index < 0 ) {
+					return null;
+				}
+
+				// Loop through all the selected options
+				i = one ? index : 0;
+				max = one ? index + 1 : options.length;
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// Don't return options that are disabled or in a disabled optgroup
+					if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+							(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+				if ( one && !values.length && options.length ) {
+					return jQuery( options[ index ] ).val();
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var values = jQuery.makeArray( value );
+
+				jQuery(elem).find("option").each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	// Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+	attrFn: {},
+
+	attr: function( elem, name, value, pass ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+			return jQuery( elem )[ name ]( value );
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === "undefined" ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( notxml ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return;
+
+			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, "" + value );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+
+			ret = elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret === null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var propName, attrNames, name, isBool,
+			i = 0;
+
+		if ( value && elem.nodeType === 1 ) {
+
+			attrNames = value.split( core_rspace );
+
+			for ( ; i < attrNames.length; i++ ) {
+				name = attrNames[ i ];
+
+				if ( name ) {
+					propName = jQuery.propFix[ name ] || name;
+					isBool = rboolean.test( name );
+
+					// See #9699 for explanation of this approach (setting first, then removal)
+					// Do not do this for boolean attributes (see #10870)
+					if ( !isBool ) {
+						jQuery.attr( elem, name, "" );
+					}
+					elem.removeAttribute( getSetAttribute ? name : propName );
+
+					// Set corresponding property to false for boolean attributes
+					if ( isBool && propName in elem ) {
+						elem[ propName ] = false;
+					}
+				}
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+					jQuery.error( "type property can't be changed" );
+				} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to it's default in case type is set after value
+					// This is for element creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		},
+		// Use the value property for back compat
+		// Use the nodeHook for button elements in IE6/7 (#1954)
+		value: {
+			get: function( elem, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.get( elem, name );
+				}
+				return name in elem ?
+					elem.value :
+					null;
+			},
+			set: function( elem, value, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.set( elem, value, name );
+				}
+				// Does not return so that setAttribute is also used
+				elem.value = value;
+			}
+		}
+	},
+
+	propFix: {
+		tabindex: "tabIndex",
+		readonly: "readOnly",
+		"for": "htmlFor",
+		"class": "className",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		cellpadding: "cellPadding",
+		rowspan: "rowSpan",
+		colspan: "colSpan",
+		usemap: "useMap",
+		frameborder: "frameBorder",
+		contenteditable: "contentEditable"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return ( elem[ name ] = value );
+			}
+
+		} else {
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+				return ret;
+
+			} else {
+				return elem[ name ];
+			}
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				var attributeNode = elem.getAttributeNode("tabindex");
+
+				return attributeNode && attributeNode.specified ?
+					parseInt( attributeNode.value, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						undefined;
+			}
+		}
+	}
+});
+
+// Hook for boolean attributes
+boolHook = {
+	get: function( elem, name ) {
+		// Align boolean attributes with corresponding properties
+		// Fall back to attribute presence where some booleans are not supported
+		var attrNode,
+			property = jQuery.prop( elem, name );
+		return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+			name.toLowerCase() :
+			undefined;
+	},
+	set: function( elem, value, name ) {
+		var propName;
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			// value is true since we know at this point it's type boolean and not false
+			// Set boolean attributes to the same name and set the DOM property
+			propName = jQuery.propFix[ name ] || name;
+			if ( propName in elem ) {
+				// Only set the IDL specifically if it already exists on the element
+				elem[ propName ] = true;
+			}
+
+			elem.setAttribute( name, name.toLowerCase() );
+		}
+		return name;
+	}
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+	fixSpecified = {
+		name: true,
+		id: true,
+		coords: true
+	};
+
+	// Use this for any attribute in IE6/7
+	// This fixes almost every IE6/7 issue
+	nodeHook = jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret;
+			ret = elem.getAttributeNode( name );
+			return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+				ret.value :
+				undefined;
+		},
+		set: function( elem, value, name ) {
+			// Set the existing or create a new attribute node
+			var ret = elem.getAttributeNode( name );
+			if ( !ret ) {
+				ret = document.createAttribute( name );
+				elem.setAttributeNode( ret );
+			}
+			return ( ret.value = value + "" );
+		}
+	};
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		});
+	});
+
+	// Set contenteditable to false on removals(#10429)
+	// Setting to empty string throws an error as an invalid value
+	jQuery.attrHooks.contenteditable = {
+		get: nodeHook.get,
+		set: function( elem, value, name ) {
+			if ( value === "" ) {
+				value = "false";
+			}
+			nodeHook.set( elem, value, name );
+		}
+	};
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			get: function( elem ) {
+				var ret = elem.getAttribute( name, 2 );
+				return ret === null ? undefined : ret;
+			}
+		});
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Normalize to lowercase since IE uppercases css property names
+			return elem.style.cssText.toLowerCase() || undefined;
+		},
+		set: function( elem, value ) {
+			return ( elem.style.cssText = "" + value );
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+			return null;
+		}
+	});
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+	jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+	jQuery.each([ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			get: function( elem ) {
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				return elem.getAttribute("value") === null ? "on" : elem.value;
+			}
+		};
+	});
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	});
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+	rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+	rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	hoverHack = function( events ) {
+		return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+	};
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var elemData, eventHandle, events,
+			t, tns, type, namespaces, handleObj,
+			handleObjIn, handlers, special;
+
+		// Don't attach events to noData or text/comment nodes (allow plain objects tho)
+		if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		events = elemData.events;
+		if ( !events ) {
+			elemData.events = events = {};
+		}
+		eventHandle = elemData.handle;
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+			eventHandle.elem = elem;
+		}
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		types = jQuery.trim( hoverHack(types) ).split( " " );
+		for ( t = 0; t < types.length; t++ ) {
+
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = tns[1];
+			namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend({
+				type: type,
+				origType: tns[1],
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				namespace: namespaces.join(".")
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			handlers = events[ type ];
+			if ( !handlers ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener/attachEvent if the special events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					// Bind the global event handler to the element
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+
+					} else if ( elem.attachEvent ) {
+						elem.attachEvent( "on" + type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var t, tns, type, origType, namespaces, origCount,
+			j, events, special, eventType, handleObj,
+			elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+		if ( !elemData || !(events = elemData.events) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+		for ( t = 0; t < types.length; t++ ) {
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tns[1];
+			namespaces = tns[2];
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector? special.delegateType : special.bindType ) || type;
+			eventType = events[ type ] || [];
+			origCount = eventType.length;
+			namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+			// Remove matching events
+			for ( j = 0; j < eventType.length; j++ ) {
+				handleObj = eventType[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					 ( !handler || handler.guid === handleObj.guid ) &&
+					 ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+					 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+					eventType.splice( j--, 1 );
+
+					if ( handleObj.selector ) {
+						eventType.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( eventType.length === 0 && origCount !== eventType.length ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			delete elemData.handle;
+
+			// removeData also checks for emptiness and clears the expando if empty
+			// so use it instead of delete
+			jQuery.removeData( elem, "events", true );
+		}
+	},
+
+	// Events that are safe to short-circuit if no handlers are attached.
+	// Native DOM events should not be added, they may have inline handlers.
+	customEvent: {
+		"getData": true,
+		"setData": true,
+		"changeData": true
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+		// Don't do events on text and comment nodes
+		if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+			return;
+		}
+
+		// Event object or event type
+		var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+			type = event.type || event,
+			namespaces = [];
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf( "!" ) >= 0 ) {
+			// Exclusive events trigger only for the exact event (no namespaces)
+			type = type.slice(0, -1);
+			exclusive = true;
+		}
+
+		if ( type.indexOf( "." ) >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+
+		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+			// No jQuery handlers for this event type, and it can't have inline handlers
+			return;
+		}
+
+		// Caller can pass in an Event, Object, or just an event type string
+		event = typeof event === "object" ?
+			// jQuery.Event object
+			event[ jQuery.expando ] ? event :
+			// Object literal
+			new jQuery.Event( type, event ) :
+			// Just the event type (string)
+			new jQuery.Event( type );
+
+		event.type = type;
+		event.isTrigger = true;
+		event.exclusive = exclusive;
+		event.namespace = namespaces.join( "." );
+		event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+		ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+		// Handle a global trigger
+		if ( !elem ) {
+
+			// TODO: Stop taunting the data cache; remove global events and always attach to document
+			cache = jQuery.cache;
+			for ( i in cache ) {
+				if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+					jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+				}
+			}
+			return;
+		}
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data != null ? jQuery.makeArray( data ) : [];
+		data.unshift( event );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		eventPath = [[ elem, special.bindType || type ]];
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+			for ( old = elem; cur; cur = cur.parentNode ) {
+				eventPath.push([ cur, bubbleType ]);
+				old = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( old === (elem.ownerDocument || document) ) {
+				eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+			}
+		}
+
+		// Fire handlers on the event path
+		for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+			cur = eventPath[i][0];
+			event.type = eventPath[i][1];
+
+			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+			// Note that this is a bare JS function and not a jQuery handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+				event.preventDefault();
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Can't use an .isFunction() check here because IE6/7 fails that test.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				// IE<9 dies on focus/blur to hidden element (#1486)
+				if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					old = elem[ ontype ];
+
+					if ( old ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( old ) {
+						elem[ ontype ] = old;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event || window.event );
+
+		var i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related,
+			handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+			delegateCount = handlers.delegateCount,
+			args = [].slice.call( arguments ),
+			run_all = !event.exclusive && !event.namespace,
+			special = jQuery.event.special[ event.type ] || {},
+			handlerQueue = [];
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[0] = event;
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers that should run if there are delegated events
+		// Avoid non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && !(event.button && event.type === "click") ) {
+
+			// Pregenerate a single jQuery object for reuse with .is()
+			jqcur = jQuery(this);
+			jqcur.context = this;
+
+			for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+				// Don't process clicks (ONLY) on disabled elements (#6911, #8165, #xxxx)
+				if ( cur.disabled !== true || event.type !== "click" ) {
+					selMatch = {};
+					matches = [];
+					jqcur[0] = cur;
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+						sel = handleObj.selector;
+
+						if ( selMatch[ sel ] === undefined ) {
+							selMatch[ sel ] = jqcur.is( sel );
+						}
+						if ( selMatch[ sel ] ) {
+							matches.push( handleObj );
+						}
+					}
+					if ( matches.length ) {
+						handlerQueue.push({ elem: cur, matches: matches });
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( handlers.length > delegateCount ) {
+			handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+		}
+
+		// Run delegates first; they may want to stop propagation beneath us
+		for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+			matched = handlerQueue[ i ];
+			event.currentTarget = matched.elem;
+
+			for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+				handleObj = matched.matches[ j ];
+
+				// Triggered event must either 1) be non-exclusive and have no namespace, or
+				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+				if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+					event.data = handleObj.data;
+					event.handleObj = handleObj;
+
+					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+							.apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						event.result = ret;
+						if ( ret === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	// *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+	props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split(" "),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button,
+				fromElement = original.fromElement;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add relatedTarget, if necessary
+			if ( !event.relatedTarget && fromElement ) {
+				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop,
+			originalEvent = event,
+			fixHook = jQuery.event.fixHooks[ event.type ] || {},
+			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = jQuery.Event( originalEvent );
+
+		for ( i = copy.length; i; ) {
+			prop = copy[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+		if ( !event.target ) {
+			event.target = originalEvent.srcElement || document;
+		}
+
+		// Target should not be a text node (#504, Safari)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+		event.metaKey = !!event.metaKey;
+
+		return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: jQuery.bindReady
+		},
+
+		load: {
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+
+		focus: {
+			delegateType: "focusin"
+		},
+		blur: {
+			delegateType: "focusout"
+		},
+
+		beforeunload: {
+			setup: function( data, namespaces, eventHandle ) {
+				// We only want to do this special case on windows
+				if ( jQuery.isWindow( this ) ) {
+					this.onbeforeunload = eventHandle;
+				}
+			},
+
+			teardown: function( namespaces, eventHandle ) {
+				if ( this.onbeforeunload === eventHandle ) {
+					this.onbeforeunload = null;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{ type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	function( elem, type, handle ) {
+		var name = "on" + type;
+
+		if ( elem.detachEvent ) {
+
+			// #8545, #7054, preventing memory leaks for custom events in IE6-8 â
+			// detachEvent needed property on element, by name of that event, to properly expose it to GC
+			if ( typeof elem[ name ] === "undefined" ) {
+				elem[ name ] = null;
+			}
+
+			elem.detachEvent( name, handle );
+		}
+	};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !(this instanceof jQuery.Event) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+	return false;
+}
+function returnTrue() {
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+
+		// if preventDefault exists run it on the original event
+		if ( e.preventDefault ) {
+			e.preventDefault();
+
+		// otherwise set the returnValue property of the original event to false (IE)
+		} else {
+			e.returnValue = false;
+		}
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+		// if stopPropagation exists run it on the original event
+		if ( e.stopPropagation ) {
+			e.stopPropagation();
+		}
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var ret,
+				target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj,
+				selector = handleObj.selector;
+
+			// For mousenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+	jQuery.event.special.submit = {
+		setup: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Lazy-add a submit handler when a descendant form may potentially be submitted
+			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+				// Node name check avoids a VML-related crash in IE (#9807)
+				var elem = e.target,
+					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+				if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+					jQuery.event.add( form, "submit._submit", function( event ) {
+						event._submit_bubble = true;
+					});
+					jQuery._data( form, "_submit_attached", true );
+				}
+			});
+			// return undefined since we don't need an event listener
+		},
+
+		postDispatch: function( event ) {
+			// If form was submitted by the user, bubble the event up the tree
+			if ( event._submit_bubble ) {
+				delete event._submit_bubble;
+				if ( this.parentNode && !event.isTrigger ) {
+					jQuery.event.simulate( "submit", this.parentNode, event, true );
+				}
+			}
+		},
+
+		teardown: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+			jQuery.event.remove( this, "._submit" );
+		}
+	};
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+	jQuery.event.special.change = {
+
+		setup: function() {
+
+			if ( rformElems.test( this.nodeName ) ) {
+				// IE doesn't fire change on a check/radio until blur; trigger it on click
+				// after a propertychange. Eat the blur-change in special.change.handle.
+				// This still fires onchange a second time for check/radio after blur.
+				if ( this.type === "checkbox" || this.type === "radio" ) {
+					jQuery.event.add( this, "propertychange._change", function( event ) {
+						if ( event.originalEvent.propertyName === "checked" ) {
+							this._just_changed = true;
+						}
+					});
+					jQuery.event.add( this, "click._change", function( event ) {
+						if ( this._just_changed && !event.isTrigger ) {
+							this._just_changed = false;
+						}
+						// Allow triggered, simulated change events (#11500)
+						jQuery.event.simulate( "change", this, event, true );
+					});
+				}
+				return false;
+			}
+			// Delegated event; lazy-add a change handler on descendant inputs
+			jQuery.event.add( this, "beforeactivate._change", function( e ) {
+				var elem = e.target;
+
+				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+					jQuery.event.add( elem, "change._change", function( event ) {
+						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+							jQuery.event.simulate( "change", this.parentNode, event, true );
+						}
+					});
+					jQuery._data( elem, "_change_attached", true );
+				}
+			});
+		},
+
+		handle: function( event ) {
+			var elem = event.target;
+
+			// Swallow native change events from checkbox/radio, we already triggered them above
+			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+				return event.handleObj.handler.apply( this, arguments );
+			}
+		},
+
+		teardown: function() {
+			jQuery.event.remove( this, "._change" );
+
+			return rformElems.test( this.nodeName );
+		}
+	};
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0,
+			handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) { // && selector != null
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		if ( data == null && fn == null ) {
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return this;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on( types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		var handleObj, type;
+		if ( types && types.preventDefault && types.handleObj ) {
+			// ( event )  dispatched jQuery.Event
+			handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+			// ( types-object [, selector] )
+			for ( type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each(function() {
+			jQuery.event.remove( this, types, fn, selector );
+		});
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	live: function( types, data, fn ) {
+		jQuery( this.context ).on( types, this.selector, data, fn );
+		return this;
+	},
+	die: function( types, fn ) {
+		jQuery( this.context ).off( types, this.selector || "**", fn );
+		return this;
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	triggerHandler: function( type, data ) {
+		if ( this[0] ) {
+			return jQuery.event.trigger( type, data, this[0], true );
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			guid = fn.guid || jQuery.guid++,
+			i = 0,
+			toggler = function( event ) {
+				// Figure out which function to execute
+				var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+				jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+				// Make sure that clicks stop
+				event.preventDefault();
+
+				// and execute the function
+				return args[ lastToggle ].apply( this, arguments ) || false;
+			};
+
+		// link all the functions, so any of them can unbind this click handler
+		toggler.guid = guid;
+		while ( i < args.length ) {
+			args[ i++ ].guid = guid;
+		}
+
+		return this.click( toggler );
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		if ( fn == null ) {
+			fn = data;
+			data = null;
+		}
+
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+
+	if ( rkeyEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+	}
+
+	if ( rmouseEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+	}
+});
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2012 jQuery Foundation and other contributors
+ *  Released under the MIT license
+ *  http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+	dirruns,
+	sortOrder,
+	siblingCheck,
+	assertGetIdNotName,
+
+	document = window.document,
+	docElem = document.documentElement,
+
+	strundefined = "undefined",
+	hasDuplicate = false,
+	baseHasDuplicate = true,
+	done = 0,
+	slice = [].slice,
+	push = [].push,
+
+	expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+	// Regex
+
+	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+	// http://www.w3.org/TR/css3-syntax/#characters
+	characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+	// Loosely modeled on CSS identifier characters
+	// An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+	identifier = characterEncoding.replace( "w", "w#" ),
+
+	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+	operators = "([*^$|!~]?=)",
+	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+		"*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+	pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|((?:[^,]|\\\\,|(?:,(?=[^\\[]*\\]))|(?:,(?=[^\\(]*\\))))*))\\)|)",
+	pos = ":(nth|eq|gt|lt|first|last|even|odd)(?:\\((\\d*)\\)|)(?=[^-]|$)",
+	combinators = whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*",
+	groups = "(?=[^\\x20\\t\\r\\n\\f])(?:\\\\.|" + attributes + "|" + pseudos.replace( 2, 7 ) + "|[^\\\\(),])+",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+	rcombinators = new RegExp( "^" + combinators ),
+
+	// All simple (non-comma) selectors, excluding insignifant trailing whitespace
+	rgroups = new RegExp( groups + "?(?=" + whitespace + "*,|$)", "g" ),
+
+	// A selector, or everything after leading whitespace
+	// Optionally followed in either case by a ")" for terminating sub-selectors
+	rselector = new RegExp( "^(?:(?!,)(?:(?:^|,)" + whitespace + "*" + groups + ")*?|" + whitespace + "*(.*?))(\\)|$)" ),
+
+	// All combinators and selector components (attribute test, tag, pseudo, etc.), the latter appearing together when consecutive
+	rtokens = new RegExp( groups.slice( 19, -6 ) + "\\x20\\t\\r\\n\\f>+~])+|" + combinators, "g" ),
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+	rsibling = /[\x20\t\r\n\f]*[+~]/,
+	rendsWithNot = /:not\($/,
+
+	rheader = /h\d/i,
+	rinputs = /input|select|textarea|button/i,
+
+	rbackslash = /\\(?!\\)/g,
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
+		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+		"NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+		"TAG": new RegExp( "^(" + characterEncoding.replace( "[-", "[-\\*" ) + ")" ),
+		"ATTR": new RegExp( "^" + attributes ),
+		"PSEUDO": new RegExp( "^" + pseudos ),
+		"CHILD": new RegExp( "^:(only|nth|last|first)-child(?:\\(" + whitespace +
+			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+		"POS": new RegExp( pos, "ig" ),
+		// For use in libraries implementing .is()
+		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+	},
+
+	classCache = {},
+	cachedClasses = [],
+	compilerCache = {},
+	cachedSelectors = [],
+
+	// Mark a function for use in filtering
+	markFunction = function( fn ) {
+		fn.sizzleFilter = true;
+		return fn;
+	},
+
+	// Returns a function to use in pseudos for input types
+	createInputFunction = function( type ) {
+		return function( elem ) {
+			// Check the input's nodeName and type
+			return elem.nodeName.toLowerCase() === "input" && elem.type === type;
+		};
+	},
+
+	// Returns a function to use in pseudos for buttons
+	createButtonFunction = function( type ) {
+		return function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && elem.type === type;
+		};
+	},
+
+	// Used for testing something on an element
+	assert = function( fn ) {
+		var pass = false,
+			div = document.createElement("div");
+		try {
+			pass = fn( div );
+		} catch (e) {}
+		// release memory in IE
+		div = null;
+		return pass;
+	},
+
+	// Check if attributes should be retrieved by attribute nodes
+	assertAttributes = assert(function( div ) {
+		div.innerHTML = "<select></select>";
+		var type = typeof div.lastChild.getAttribute("multiple");
+		// IE8 returns a string for some attributes even when not present
+		return type !== "boolean" && type !== "string";
+	}),
+
+	// Check if getElementById returns elements by name
+	// Check if getElementsByName privileges form controls or returns elements by ID
+	assertUsableName = assert(function( div ) {
+		// Inject content
+		div.id = expando + 0;
+		div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
+		docElem.insertBefore( div, docElem.firstChild );
+
+		// Test
+		var pass = document.getElementsByName &&
+			// buggy browsers will return fewer than the correct 2
+			document.getElementsByName( expando ).length ===
+			// buggy browsers will return more than the correct 0
+			2 + document.getElementsByName( expando + 0 ).length;
+		assertGetIdNotName = !document.getElementById( expando );
+
+		// Cleanup
+		docElem.removeChild( div );
+
+		return pass;
+	}),
+
+	// Check if the browser returns only elements
+	// when doing getElementsByTagName("*")
+	assertTagNameNoComments = assert(function( div ) {
+		div.appendChild( document.createComment("") );
+		return div.getElementsByTagName("*").length === 0;
+	}),
+
+	// Check if getAttribute returns normalized href attributes
+	assertHrefNotNormalized = assert(function( div ) {
+		div.innerHTML = "<a href='#'></a>";
+		return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+			div.firstChild.getAttribute("href") === "#";
+	}),
+
+	// Check if getElementsByClassName can be trusted
+	assertUsableClassName = assert(function( div ) {
+		// Opera can't find a second classname (in 9.6)
+		div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
+		if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+			return false;
+		}
+
+		// Safari caches class attributes, doesn't catch changes (in 3.2)
+		div.lastChild.className = "e";
+		return div.getElementsByClassName("e").length !== 1;
+	});
+
+var Sizzle = function( selector, context, results, seed ) {
+	results = results || [];
+	context = context || document;
+	var match, elem, xml, m,
+		nodeType = context.nodeType;
+
+	if ( nodeType !== 1 && nodeType !== 9 ) {
+		return [];
+	}
+
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	xml = isXML( context );
+
+	if ( !xml && !seed ) {
+		if ( (match = rquickExpr.exec( selector )) ) {
+			// Speed-up: Sizzle("#ID")
+			if ( (m = match[1]) ) {
+				if ( nodeType === 9 ) {
+					elem = context.getElementById( m );
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE, Opera, and Webkit return items
+						// by name instead of ID
+						if ( elem.id === m ) {
+							results.push( elem );
+							return results;
+						}
+					} else {
+						return results;
+					}
+				} else {
+					// Context is not a document
+					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+						contains( context, elem ) && elem.id === m ) {
+						results.push( elem );
+						return results;
+					}
+				}
+
+			// Speed-up: Sizzle("TAG")
+			} else if ( match[2] ) {
+				push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+				return results;
+
+			// Speed-up: Sizzle(".CLASS")
+			} else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+				push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+				return results;
+			}
+		}
+	}
+
+	// All others
+	return select( selector, context, results, seed, xml );
+};
+
+var Expr = Sizzle.selectors = {
+
+	// Can be adjusted by the user
+	cacheLength: 50,
+
+	match: matchExpr,
+
+	order: [ "ID", "TAG" ],
+
+	attrHandle: {},
+
+	createPseudo: markFunction,
+
+	find: {
+		"ID": assertGetIdNotName ?
+			function( id, context, xml ) {
+				if ( typeof context.getElementById !== strundefined && !xml ) {
+					var m = context.getElementById( id );
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					return m && m.parentNode ? [m] : [];
+				}
+			} :
+			function( id, context, xml ) {
+				if ( typeof context.getElementById !== strundefined && !xml ) {
+					var m = context.getElementById( id );
+
+					return m ?
+						m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+							[m] :
+							undefined :
+						[];
+				}
+			},
+
+		"TAG": assertTagNameNoComments ?
+			function( tag, context ) {
+				if ( typeof context.getElementsByTagName !== strundefined ) {
+					return context.getElementsByTagName( tag );
+				}
+			} :
+			function( tag, context ) {
+				var results = context.getElementsByTagName( tag );
+
+				// Filter out possible comments
+				if ( tag === "*" ) {
+					var elem,
+						tmp = [],
+						i = 0;
+
+					for ( ; (elem = results[i]); i++ ) {
+						if ( elem.nodeType === 1 ) {
+							tmp.push( elem );
+						}
+					}
+
+					return tmp;
+				}
+				return results;
+			}
+	},
+
+	relative: {
+		">": { dir: "parentNode", first: true },
+		" ": { dir: "parentNode" },
+		"+": { dir: "previousSibling", first: true },
+		"~": { dir: "previousSibling" }
+	},
+
+	preFilter: {
+		"ATTR": function( match ) {
+			match[1] = match[1].replace( rbackslash, "" );
+
+			// Move the given value to match[3] whether quoted or unquoted
+			match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+			if ( match[2] === "~=" ) {
+				match[3] = " " + match[3] + " ";
+			}
+
+			return match.slice( 0, 4 );
+		},
+
+		"CHILD": function( match ) {
+			/* matches from matchExpr.CHILD
+				1 type (only|nth|...)
+				2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+				3 xn-component of xn+y argument ([+-]?\d*n|)
+				4 sign of xn-component
+				5 x of xn-component
+				6 sign of y-component
+				7 y of y-component
+			*/
+			match[1] = match[1].toLowerCase();
+
+			if ( match[1] === "nth" ) {
+				// nth-child requires argument
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				// numeric x and y parameters for Expr.filter.CHILD
+				// remember that false/true cast respectively to 0/1
+				match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+				match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+			// other types prohibit arguments
+			} else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			return match;
+		},
+
+		"PSEUDO": function( match ) {
+			var argument,
+				unquoted = match[4];
+
+			if ( matchExpr["CHILD"].test( match[0] ) ) {
+				return null;
+			}
+
+			// Relinquish our claim on characters in `unquoted` from a closing parenthesis on
+			if ( unquoted && (argument = rselector.exec( unquoted )) && argument.pop() ) {
+
+				match[0] = match[0].slice( 0, argument[0].length - unquoted.length - 1 );
+				unquoted = argument[0].slice( 0, -1 );
+			}
+
+			// Quoted or unquoted, we have the full argument
+			// Return only captures needed by the pseudo filter method (type and argument)
+			match.splice( 2, 3, unquoted || match[3] );
+			return match;
+		}
+	},
+
+	filter: {
+		"ID": assertGetIdNotName ?
+			function( id ) {
+				id = id.replace( rbackslash, "" );
+				return function( elem ) {
+					return elem.getAttribute("id") === id;
+				};
+			} :
+			function( id ) {
+				id = id.replace( rbackslash, "" );
+				return function( elem ) {
+					var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+					return node && node.value === id;
+				};
+			},
+
+		"TAG": function( nodeName ) {
+			if ( nodeName === "*" ) {
+				return function() { return true; };
+			}
+			nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+			return function( elem ) {
+				return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+			};
+		},
+
+		"CLASS": function( className ) {
+			var pattern = classCache[ className ];
+			if ( !pattern ) {
+				pattern = classCache[ className ] = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" );
+				cachedClasses.push( className );
+				// Avoid too large of a cache
+				if ( cachedClasses.length > Expr.cacheLength ) {
+					delete classCache[ cachedClasses.shift() ];
+				}
+			}
+			return function( elem ) {
+				return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+			};
+		},
+
+		"ATTR": function( name, operator, check ) {
+			if ( !operator ) {
+				return function( elem ) {
+					return Sizzle.attr( elem, name ) != null;
+				};
+			}
+
+			return function( elem ) {
+				var result = Sizzle.attr( elem, name ),
+					value = result + "";
+
+				if ( result == null ) {
+					return operator === "!=";
+				}
+
+				switch ( operator ) {
+					case "=":
+						return value === check;
+					case "!=":
+						return value !== check;
+					case "^=":
+						return check && value.indexOf( check ) === 0;
+					case "*=":
+						return check && value.indexOf( check ) > -1;
+					case "$=":
+						return check && value.substr( value.length - check.length ) === check;
+					case "~=":
+						return ( " " + value + " " ).indexOf( check ) > -1;
+					case "|=":
+						return value === check || value.substr( 0, check.length + 1 ) === check + "-";
+				}
+			};
+		},
+
+		"CHILD": function( type, argument, first, last ) {
+
+			if ( type === "nth" ) {
+				var doneName = done++;
+
+				return function( elem ) {
+					var parent, diff,
+						count = 0,
+						node = elem;
+
+					if ( first === 1 && last === 0 ) {
+						return true;
+					}
+
+					parent = elem.parentNode;
+
+					if ( parent && (parent[ expando ] !== doneName || !elem.sizset) ) {
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.sizset = ++count;
+								if ( node === elem ) {
+									break;
+								}
+							}
+						}
+
+						parent[ expando ] = doneName;
+					}
+
+					diff = elem.sizset - last;
+
+					if ( first === 0 ) {
+						return diff === 0;
+
+					} else {
+						return ( diff % first === 0 && diff / first >= 0 );
+					}
+				};
+			}
+
+			return function( elem ) {
+				var node = elem;
+
+				switch ( type ) {
+					case "only":
+					case "first":
+						while ( (node = node.previousSibling) ) {
+							if ( node.nodeType === 1 ) {
+								return false;
+							}
+						}
+
+						if ( type === "first" ) {
+							return true;
+						}
+
+						node = elem;
+
+						/* falls through */
+					case "last":
+						while ( (node = node.nextSibling) ) {
+							if ( node.nodeType === 1 ) {
+								return false;
+							}
+						}
+
+						return true;
+				}
+			};
+		},
+
+		"PSEUDO": function( pseudo, argument, context, xml ) {
+			// pseudo-class names are case-insensitive
+			// http://www.w3.org/TR/selectors/#pseudo-classes
+			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+			var fn = Expr.pseudos[ pseudo ] || Expr.pseudos[ pseudo.toLowerCase() ];
+
+			if ( !fn ) {
+				Sizzle.error( "unsupported pseudo: " + pseudo );
+			}
+
+			// The user may set fn.sizzleFilter to indicate
+			// that arguments are needed to create the filter function
+			// just as Sizzle does
+			if ( !fn.sizzleFilter ) {
+				return fn;
+			}
+
+			return fn( argument, context, xml );
+		}
+	},
+
+	pseudos: {
+		"not": markFunction(function( selector, context, xml ) {
+			// Trim the selector passed to compile
+			// to avoid treating leading and trailing
+			// spaces as combinators
+			var matcher = compile( selector.replace( rtrim, "$1" ), context, xml );
+			return function( elem ) {
+				return !matcher( elem );
+			};
+		}),
+
+		"enabled": function( elem ) {
+			return elem.disabled === false;
+		},
+
+		"disabled": function( elem ) {
+			return elem.disabled === true;
+		},
+
+		"checked": function( elem ) {
+			// In CSS3, :checked should return both checked and selected elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			var nodeName = elem.nodeName.toLowerCase();
+			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+		},
+
+		"selected": function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		"parent": function( elem ) {
+			return !Expr.pseudos["empty"]( elem );
+		},
+
+		"empty": function( elem ) {
+			// http://www.w3.org/TR/selectors/#empty-pseudo
+			// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+			//   not comment, processing instructions, or others
+			// Thanks to Diego Perini for the nodeName shortcut
+			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+			var nodeType;
+			elem = elem.firstChild;
+			while ( elem ) {
+				if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+					return false;
+				}
+				elem = elem.nextSibling;
+			}
+			return true;
+		},
+
+		"contains": markFunction(function( text ) {
+			return function( elem ) {
+				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+			};
+		}),
+
+		"has": markFunction(function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		}),
+
+		"header": function( elem ) {
+			return rheader.test( elem.nodeName );
+		},
+
+		"text": function( elem ) {
+			var type, attr;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" &&
+				(type = elem.type) === "text" &&
+				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+		},
+
+		// Input types
+		"radio": createInputFunction("radio"),
+		"checkbox": createInputFunction("checkbox"),
+		"file": createInputFunction("file"),
+		"password": createInputFunction("password"),
+		"image": createInputFunction("image"),
+
+		"submit": createButtonFunction("submit"),
+		"reset": createButtonFunction("reset"),
+
+		"button": function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === "button" || name === "button";
+		},
+
+		"input": function( elem ) {
+			return rinputs.test( elem.nodeName );
+		},
+
+		"focus": function( elem ) {
+			var doc = elem.ownerDocument;
+			return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
+		},
+
+		"active": function( elem ) {
+			return elem === elem.ownerDocument.activeElement;
+		}
+	},
+
+	setFilters: {
+		"first": function( elements, argument, not ) {
+			return not ? elements.slice( 1 ) : [ elements[0] ];
+		},
+
+		"last": function( elements, argument, not ) {
+			var elem = elements.pop();
+			return not ? elements : [ elem ];
+		},
+
+		"even": function( elements, argument, not ) {
+			var results = [],
+				i = not ? 1 : 0,
+				len = elements.length;
+			for ( ; i < len; i = i + 2 ) {
+				results.push( elements[i] );
+			}
+			return results;
+		},
+
+		"odd": function( elements, argument, not ) {
+			var results = [],
+				i = not ? 0 : 1,
+				len = elements.length;
+			for ( ; i < len; i = i + 2 ) {
+				results.push( elements[i] );
+			}
+			return results;
+		},
+
+		"lt": function( elements, argument, not ) {
+			return not ? elements.slice( +argument ) : elements.slice( 0, +argument );
+		},
+
+		"gt": function( elements, argument, not ) {
+			return not ? elements.slice( 0, +argument + 1 ) : elements.slice( +argument + 1 );
+		},
+
+		"eq": function( elements, argument, not ) {
+			var elem = elements.splice( +argument, 1 );
+			return not ? elements : elem;
+		}
+	}
+};
+
+// Deprecated
+Expr.setFilters["nth"] = Expr.setFilters["eq"];
+
+// Back-compat
+Expr.filters = Expr.pseudos;
+
+// IE6/7 return a modified href
+if ( !assertHrefNotNormalized ) {
+	Expr.attrHandle = {
+		"href": function( elem ) {
+			return elem.getAttribute( "href", 2 );
+		},
+		"type": function( elem ) {
+			return elem.getAttribute("type");
+		}
+	};
+}
+
+// Add getElementsByName if usable
+if ( assertUsableName ) {
+	Expr.order.push("NAME");
+	Expr.find["NAME"] = function( name, context ) {
+		if ( typeof context.getElementsByName !== strundefined ) {
+			return context.getElementsByName( name );
+		}
+	};
+}
+
+// Add getElementsByClassName if usable
+if ( assertUsableClassName ) {
+	Expr.order.splice( 1, 0, "CLASS" );
+	Expr.find["CLASS"] = function( className, context, xml ) {
+		if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+			return context.getElementsByClassName( className );
+		}
+	};
+}
+
+// If slice is not available, provide a backup
+try {
+	slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+	slice = function( i ) {
+		var elem, results = [];
+		for ( ; (elem = this[i]); i++ ) {
+			results.push( elem );
+		}
+		return results;
+	};
+}
+
+var isXML = Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833)
+	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+var contains = Sizzle.contains = docElem.compareDocumentPosition ?
+	function( a, b ) {
+		return !!( a.compareDocumentPosition( b ) & 16 );
+	} :
+	docElem.contains ?
+	function( a, b ) {
+		var adown = a.nodeType === 9 ? a.documentElement : a,
+			bup = b.parentNode;
+		return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+	} :
+	function( a, b ) {
+		while ( (b = b.parentNode) ) {
+			if ( b === a ) {
+				return true;
+			}
+		}
+		return false;
+	};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+	var node,
+		ret = "",
+		i = 0,
+		nodeType = elem.nodeType;
+
+	if ( nodeType ) {
+		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+			// Use textContent for elements
+			// innerText usage removed for consistency of new lines (see #11153)
+			if ( typeof elem.textContent === "string" ) {
+				return elem.textContent;
+			} else {
+				// Traverse its children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+					ret += getText( elem );
+				}
+			}
+		} else if ( nodeType === 3 || nodeType === 4 ) {
+			return elem.nodeValue;
+		}
+		// Do not include comment or processing instruction nodes
+	} else {
+
+		// If no nodeType, this is expected to be an array
+		for ( ; (node = elem[i]); i++ ) {
+			// Do not traverse comment nodes
+			ret += getText( node );
+		}
+	}
+	return ret;
+};
+
+Sizzle.attr = function( elem, name ) {
+	var attr,
+		xml = isXML( elem );
+
+	if ( !xml ) {
+		name = name.toLowerCase();
+	}
+	if ( Expr.attrHandle[ name ] ) {
+		return Expr.attrHandle[ name ]( elem );
+	}
+	if ( assertAttributes || xml ) {
+		return elem.getAttribute( name );
+	}
+	attr = elem.getAttributeNode( name );
+	return attr ?
+		typeof elem[ name ] === "boolean" ?
+			elem[ name ] ? name : null :
+			attr.specified ? attr.value : null :
+		null;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+// Check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+	return (baseHasDuplicate = 0);
+});
+
+
+if ( docElem.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+			a.compareDocumentPosition :
+			a.compareDocumentPosition(b) & 4
+		) ? -1 : 1;
+	};
+
+} else {
+	sortOrder = function( a, b ) {
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Fallback to using sourceIndex (in IE) if it's available on both nodes
+		} else if ( a.sourceIndex && b.sourceIndex ) {
+			return a.sourceIndex - b.sourceIndex;
+		}
+
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		if ( aup === bup ) {
+			return siblingCheck( a, b );
+
+		// If no parents were found then the nodes are disconnected
+		} else if ( !aup ) {
+			return -1;
+
+		} else if ( !bup ) {
+			return 1;
+		}
+
+		// Otherwise they're somewhere else in the tree so we need
+		// to build up a full list of the parentNodes for comparison
+		while ( cur ) {
+			ap.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		cur = bup;
+
+		while ( cur ) {
+			bp.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		al = ap.length;
+		bl = bp.length;
+
+		// Start walking down the tree looking for a discrepancy
+		for ( var i = 0; i < al && i < bl; i++ ) {
+			if ( ap[i] !== bp[i] ) {
+				return siblingCheck( ap[i], bp[i] );
+			}
+		}
+
+		// We ended someplace up the tree so do a sibling check
+		return i === al ?
+			siblingCheck( a, bp[i], -1 ) :
+			siblingCheck( ap[i], b, 1 );
+	};
+
+	siblingCheck = function( a, b, ret ) {
+		if ( a === b ) {
+			return ret;
+		}
+
+		var cur = a.nextSibling;
+
+		while ( cur ) {
+			if ( cur === b ) {
+				return -1;
+			}
+
+			cur = cur.nextSibling;
+		}
+
+		return 1;
+	};
+}
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+	var elem,
+		i = 1;
+
+	if ( sortOrder ) {
+		hasDuplicate = baseHasDuplicate;
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			for ( ; (elem = results[i]); i++ ) {
+				if ( elem === results[ i - 1 ] ) {
+					results.splice( i--, 1 );
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+function multipleContexts( selector, contexts, results, seed ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[i], results, seed );
+	}
+}
+
+function handlePOSGroup( selector, posfilter, argument, contexts, seed, not ) {
+	var results,
+		fn = Expr.setFilters[ posfilter.toLowerCase() ];
+
+	if ( !fn ) {
+		Sizzle.error( posfilter );
+	}
+
+	if ( selector || !(results = seed) ) {
+		multipleContexts( selector || "*", contexts, (results = []), seed );
+	}
+
+	return results.length > 0 ? fn( results, argument, not ) : [];
+}
+
+function handlePOS( selector, context, results, seed, groups ) {
+	var match, not, anchor, ret, elements, currentContexts, part, lastIndex,
+		i = 0,
+		len = groups.length,
+		rpos = matchExpr["POS"],
+		// This is generated here in case matchExpr["POS"] is extended
+		rposgroups = new RegExp( "^" + rpos.source + "(?!" + whitespace + ")", "i" ),
+		// This is for making sure non-participating
+		// matching groups are represented cross-browser (IE6-8)
+		setUndefined = function() {
+			var i = 1,
+				len = arguments.length - 2;
+			for ( ; i < len; i++ ) {
+				if ( arguments[i] === undefined ) {
+					match[i] = undefined;
+				}
+			}
+		};
+
+	for ( ; i < len; i++ ) {
+		// Reset regex index to 0
+		rpos.exec("");
+		selector = groups[i];
+		ret = [];
+		anchor = 0;
+		elements = seed;
+		while ( (match = rpos.exec( selector )) ) {
+			lastIndex = rpos.lastIndex = match.index + match[0].length;
+			if ( lastIndex > anchor ) {
+				part = selector.slice( anchor, match.index );
+				anchor = lastIndex;
+				currentContexts = [ context ];
+
+				if ( rcombinators.test(part) ) {
+					if ( elements ) {
+						currentContexts = elements;
+					}
+					elements = seed;
+				}
+
+				if ( (not = rendsWithNot.test( part )) ) {
+					part = part.slice( 0, -5 ).replace( rcombinators, "$&*" );
+				}
+
+				if ( match.length > 1 ) {
+					match[0].replace( rposgroups, setUndefined );
+				}
+				elements = handlePOSGroup( part, match[1], match[2], currentContexts, elements, not );
+			}
+		}
+
+		if ( elements ) {
+			ret = ret.concat( elements );
+
+			if ( (part = selector.slice( anchor )) && part !== ")" ) {
+				if ( rcombinators.test(part) ) {
+					multipleContexts( part, ret, results, seed );
+				} else {
+					Sizzle( part, context, results, seed ? seed.concat(elements) : elements );
+				}
+			} else {
+				push.apply( results, ret );
+			}
+		} else {
+			Sizzle( selector, context, results, seed );
+		}
+	}
+
+	// Do not sort if this is a single filter
+	return len === 1 ? results : Sizzle.uniqueSort( results );
+}
+
+function tokenize( selector, context, xml ) {
+	var tokens, soFar, type,
+		groups = [],
+		i = 0,
+
+		// Catch obvious selector issues: terminal ")"; nonempty fallback match
+		// rselector never fails to match *something*
+		match = rselector.exec( selector ),
+		matched = !match.pop() && !match.pop(),
+		selectorGroups = matched && selector.match( rgroups ) || [""],
+
+		preFilters = Expr.preFilter,
+		filters = Expr.filter,
+		checkContext = !xml && context !== document;
+
+	for ( ; (soFar = selectorGroups[i]) != null && matched; i++ ) {
+		groups.push( tokens = [] );
+
+		// Need to make sure we're within a narrower context if necessary
+		// Adding a descendant combinator will generate what is needed
+		if ( checkContext ) {
+			soFar = " " + soFar;
+		}
+
+		while ( soFar ) {
+			matched = false;
+
+			// Combinators
+			if ( (match = rcombinators.exec( soFar )) ) {
+				soFar = soFar.slice( match[0].length );
+
+				// Cast descendant combinators to space
+				matched = tokens.push({ part: match.pop().replace( rtrim, " " ), captures: match });
+			}
+
+			// Filters
+			for ( type in filters ) {
+				if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+					(match = preFilters[ type ]( match, context, xml )) ) ) {
+
+					soFar = soFar.slice( match.shift().length );
+					matched = tokens.push({ part: type, captures: match });
+				}
+			}
+
+			if ( !matched ) {
+				break;
+			}
+		}
+	}
+
+	if ( !matched ) {
+		Sizzle.error( selector );
+	}
+
+	return groups;
+}
+
+function addCombinator( matcher, combinator, context ) {
+	var dir = combinator.dir,
+		doneName = done++;
+
+	if ( !matcher ) {
+		// If there is no matcher to check, check against the context
+		matcher = function( elem ) {
+			return elem === context;
+		};
+	}
+	return combinator.first ?
+		function( elem, context ) {
+			while ( (elem = elem[ dir ]) ) {
+				if ( elem.nodeType === 1 ) {
+					return matcher( elem, context ) && elem;
+				}
+			}
+		} :
+		function( elem, context ) {
+			var cache,
+				dirkey = doneName + "." + dirruns,
+				cachedkey = dirkey + "." + cachedruns;
+			while ( (elem = elem[ dir ]) ) {
+				if ( elem.nodeType === 1 ) {
+					if ( (cache = elem[ expando ]) === cachedkey ) {
+						return elem.sizset;
+					} else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+						if ( elem.sizset ) {
+							return elem;
+						}
+					} else {
+						elem[ expando ] = cachedkey;
+						if ( matcher( elem, context ) ) {
+							elem.sizset = true;
+							return elem;
+						}
+						elem.sizset = false;
+					}
+				}
+			}
+		};
+}
+
+function addMatcher( higher, deeper ) {
+	return higher ?
+		function( elem, context ) {
+			var result = deeper( elem, context );
+			return result && higher( result === true ? elem : result, context );
+		} :
+		deeper;
+}
+
+// ["TAG", ">", "ID", " ", "CLASS"]
+function matcherFromTokens( tokens, context, xml ) {
+	var token, matcher,
+		i = 0;
+
+	for ( ; (token = tokens[i]); i++ ) {
+		if ( Expr.relative[ token.part ] ) {
+			matcher = addCombinator( matcher, Expr.relative[ token.part ], context );
+		} else {
+			token.captures.push( context, xml );
+			matcher = addMatcher( matcher, Expr.filter[ token.part ].apply( null, token.captures ) );
+		}
+	}
+
+	return matcher;
+}
+
+function matcherFromGroupMatchers( matchers ) {
+	return function( elem, context ) {
+		var matcher,
+			j = 0;
+		for ( ; (matcher = matchers[j]); j++ ) {
+			if ( matcher(elem, context) ) {
+				return true;
+			}
+		}
+		return false;
+	};
+}
+
+var compile = Sizzle.compile = function( selector, context, xml ) {
+	var tokens, group, i,
+		cached = compilerCache[ selector ];
+
+	// Return a cached group function if already generated (context dependent)
+	if ( cached && cached.context === context ) {
+		return cached;
+	}
+
+	// Generate a function of recursive functions that can be used to check each element
+	group = tokenize( selector, context, xml );
+	for ( i = 0; (tokens = group[i]); i++ ) {
+		group[i] = matcherFromTokens( tokens, context, xml );
+	}
+
+	// Cache the compiled function
+	cached = compilerCache[ selector ] = matcherFromGroupMatchers( group );
+	cached.context = context;
+	cached.runs = cached.dirruns = 0;
+	cachedSelectors.push( selector );
+	// Ensure only the most recent are cached
+	if ( cachedSelectors.length > Expr.cacheLength ) {
+		delete compilerCache[ cachedSelectors.shift() ];
+	}
+	return cached;
+};
+
+Sizzle.matches = function( expr, elements ) {
+	return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+	return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+var select = function( selector, context, results, seed, xml ) {
+	// Remove excessive whitespace
+	selector = selector.replace( rtrim, "$1" );
+	var elements, matcher, i, len, elem, token,
+		type, findContext, notTokens,
+		match = selector.match( rgroups ),
+		tokens = selector.match( rtokens ),
+		contextNodeType = context.nodeType;
+
+	// POS handling
+	if ( matchExpr["POS"].test(selector) ) {
+		return handlePOS( selector, context, results, seed, match );
+	}
+
+	if ( seed ) {
+		elements = slice.call( seed, 0 );
+
+	// To maintain document order, only narrow the
+	// set if there is one group
+	} else if ( match && match.length === 1 ) {
+
+		// Take a shortcut and set the context if the root selector is an ID
+		if ( tokens.length > 1 && contextNodeType === 9 && !xml &&
+				(match = matchExpr["ID"].exec( tokens[0] )) ) {
+
+			context = Expr.find["ID"]( match[1], context, xml )[0];
+			if ( !context ) {
+				return results;
+			}
+
+			selector = selector.slice( tokens.shift().length );
+		}
+
+		findContext = ( (match = rsibling.exec( tokens[0] )) && !match.index && context.parentNode ) || context;
+
+		// Get the last token, excluding :not
+		notTokens = tokens.pop();
+		token = notTokens.split(":not")[0];
+
+		for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+			type = Expr.order[i];
+
+			if ( (match = matchExpr[ type ].exec( token )) ) {
+				elements = Expr.find[ type ]( (match[1] || "").replace( rbackslash, "" ), findContext, xml );
+
+				if ( elements == null ) {
+					continue;
+				}
+
+				if ( token === notTokens ) {
+					selector = selector.slice( 0, selector.length - notTokens.length ) +
+						token.replace( matchExpr[ type ], "" );
+
+					if ( !selector ) {
+						push.apply( results, slice.call(elements, 0) );
+					}
+				}
+				break;
+			}
+		}
+	}
+
+	// Only loop over the given elements once
+	// If selector is empty, we're already done
+	if ( selector ) {
+		matcher = compile( selector, context, xml );
+		dirruns = matcher.dirruns++;
+
+		if ( elements == null ) {
+			elements = Expr.find["TAG"]( "*", (rsibling.test( selector ) && context.parentNode) || context );
+		}
+		for ( i = 0; (elem = elements[i]); i++ ) {
+			cachedruns = matcher.runs++;
+			if ( matcher(elem, context) ) {
+				results.push( elem );
+			}
+		}
+	}
+
+	return results;
+};
+
+if ( document.querySelectorAll ) {
+	(function() {
+		var disconnectedMatch,
+			oldSelect = select,
+			rescape = /'|\\/g,
+			rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+			rbuggyQSA = [],
+			// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+			// A support test would require too much code (would include document ready)
+			// just skip matchesSelector for :active
+			rbuggyMatches = [":active"],
+			matches = docElem.matchesSelector ||
+				docElem.mozMatchesSelector ||
+				docElem.webkitMatchesSelector ||
+				docElem.oMatchesSelector ||
+				docElem.msMatchesSelector;
+
+		// Build QSA regex
+		// Regex strategy adopted from Diego Perini
+		assert(function( div ) {
+			div.innerHTML = "<select><option selected></option></select>";
+
+			// IE8 - Some boolean attributes are not treated correctly
+			if ( !div.querySelectorAll("[selected]").length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+			}
+
+			// Webkit/Opera - :checked should return selected option elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			// IE8 throws error here (do not put tests after this one)
+			if ( !div.querySelectorAll(":checked").length ) {
+				rbuggyQSA.push(":checked");
+			}
+		});
+
+		assert(function( div ) {
+
+			// Opera 10-12/IE9 - ^= $= *= and empty values
+			// Should not select anything
+			div.innerHTML = "<p test=''></p>";
+			if ( div.querySelectorAll("[test^='']").length ) {
+				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+			}
+
+			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+			// IE8 throws error here (do not put tests after this one)
+			div.innerHTML = "<input type='hidden'>";
+			if ( !div.querySelectorAll(":enabled").length ) {
+				rbuggyQSA.push(":enabled", ":disabled");
+			}
+		});
+
+		rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+
+		select = function( selector, context, results, seed, xml ) {
+			// Only use querySelectorAll when not filtering,
+			// when this is not xml,
+			// and when no QSA bugs apply
+			if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+				if ( context.nodeType === 9 ) {
+					try {
+						push.apply( results, slice.call(context.querySelectorAll( selector ), 0) );
+						return results;
+					} catch(qsaError) {}
+				// qSA works strangely on Element-rooted queries
+				// We can work around this by specifying an extra ID on the root
+				// and working up from there (Thanks to Andrew Dupont for the technique)
+				// IE 8 doesn't work on object elements
+				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+					var old = context.getAttribute("id"),
+						nid = old || expando,
+						newContext = rsibling.test( selector ) && context.parentNode || context;
+
+					if ( old ) {
+						nid = nid.replace( rescape, "\\$&" );
+					} else {
+						context.setAttribute( "id", nid );
+					}
+
+					try {
+						push.apply( results, slice.call( newContext.querySelectorAll(
+							selector.replace( rgroups, "[id='" + nid + "'] $&" )
+						), 0 ) );
+						return results;
+					} catch(qsaError) {
+					} finally {
+						if ( !old ) {
+							context.removeAttribute("id");
+						}
+					}
+				}
+			}
+
+			return oldSelect( selector, context, results, seed, xml );
+		};
+
+		if ( matches ) {
+			assert(function( div ) {
+				// Check to see if it's possible to do matchesSelector
+				// on a disconnected node (IE 9)
+				disconnectedMatch = matches.call( div, "div" );
+
+				// This should fail with an exception
+				// Gecko does not error, returns false instead
+				try {
+					matches.call( div, "[test!='']:sizzle" );
+					rbuggyMatches.push( Expr.match.PSEUDO );
+				} catch ( e ) {}
+			});
+
+			// rbuggyMatches always contains :active, so no need for a length check
+			rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+			Sizzle.matchesSelector = function( elem, expr ) {
+				// Make sure that attribute selectors are quoted
+				expr = expr.replace( rattributeQuotes, "='$1']" );
+
+				// rbuggyMatches always contains :active, so no need for an existence check
+				if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {
+					try {
+						var ret = matches.call( elem, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9
+								elem.document && elem.document.nodeType !== 11 ) {
+							return ret;
+						}
+					} catch(e) {}
+				}
+
+				return Sizzle( expr, null, null, [ elem ] ).length > 0;
+			};
+		}
+	})();
+}
+
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+var runtil = /Until$/,
+	rparentsprev = /^(?:parents|prev(?:Until|All))/,
+	isSimple = /^.[^:#\[\.,]*$/,
+	rneedsContext = jQuery.expr.match.needsContext,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var i, l, length, n, r, ret,
+			self = this;
+
+		if ( typeof selector !== "string" ) {
+			return jQuery( selector ).filter(function() {
+				for ( i = 0, l = self.length; i < l; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			});
+		}
+
+		ret = this.pushStack( "", "find", selector );
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			length = ret.length;
+			jQuery.find( selector, this[i], ret );
+
+			if ( i > 0 ) {
+				// Make sure that the results are unique
+				for ( n = length; n < ret.length; n++ ) {
+					for ( r = 0; r < length; r++ ) {
+						if ( ret[r] === ret[n] ) {
+							ret.splice(n--, 1);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	has: function( target ) {
+		var i,
+			targets = jQuery( target, this ),
+			len = targets.length;
+
+		return this.filter(function() {
+			for ( i = 0; i < len; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector, false), "not", selector);
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector, true), "filter", selector );
+	},
+
+	is: function( selector ) {
+		return !!selector && (
+			typeof selector === "string" ?
+				// If this is a positional/relative selector, check membership in the returned set
+				// so $("p:first").is("p:last") won't return true for a doc with two "p".
+				rneedsContext.test( selector ) ?
+					jQuery( selector, this.context ).index( this[0] ) >= 0 :
+					jQuery.filter( selector, this ).length > 0 :
+				this.filter( selector ).length > 0 );
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			ret = [],
+			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( ; i < l; i++ ) {
+			cur = this[i];
+
+			while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+					ret.push( cur );
+					break;
+				}
+				cur = cur.parentNode;
+			}
+		}
+
+		ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+		return this.pushStack( ret, "closest", selectors );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+		}
+
+		// index in selector
+		if ( typeof elem === "string" ) {
+			return jQuery.inArray( this[0], jQuery( elem ) );
+		}
+
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[0] : elem, this );
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+			all :
+			jQuery.unique( all ) );
+	},
+
+	addBack: function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter(selector)
+		);
+	}
+});
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+	return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+function sibling( cur, dir ) {
+	do {
+		cur = cur[ dir ];
+	} while ( cur && cur.nodeType !== 1 );
+
+	return cur;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return jQuery.nodeName( elem, "iframe" ) ?
+			elem.contentDocument || elem.contentWindow.document :
+			jQuery.merge( [], elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var ret = jQuery.map( this, fn, until );
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( this.length > 1 && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 ?
+			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+			jQuery.find.matches(expr, elems);
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+	// Can't pass null or undefined to indexOf in Firefox 4
+	// Set to 0 to skip string check
+	qualifier = qualifier || 0;
+
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			var retVal = !!qualifier.call( elem, i, elem );
+			return retVal === keep;
+		});
+
+	} else if ( qualifier.nodeType ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			return ( elem === qualifier ) === keep;
+		});
+
+	} else if ( typeof qualifier === "string" ) {
+		var filtered = jQuery.grep(elements, function( elem ) {
+			return elem.nodeType === 1;
+		});
+
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter(qualifier, filtered, !keep);
+		} else {
+			qualifier = jQuery.filter( qualifier, filtered );
+		}
+	}
+
+	return jQuery.grep(elements, function( elem, i ) {
+		return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+	});
+}
+function createSafeFragment( document ) {
+	var list = nodeNames.split( "|" ),
+	safeFrag = document.createDocumentFragment();
+
+	if ( safeFrag.createElement ) {
+		while ( list.length ) {
+			safeFrag.createElement(
+				list.pop()
+			);
+		}
+	}
+	return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style|link)/i,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+	rcheckableType = /^(?:checkbox|radio)$/,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	},
+	safeFragment = createSafeFragment( document ),
+	fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "X<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( value ) {
+		return jQuery.access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+		}, null, value, arguments.length );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function(i) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( !isDisconnected( this[0] ) ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		}
+
+		if ( arguments.length ) {
+			var set = jQuery.clean( arguments );
+			return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+		}
+	},
+
+	after: function() {
+		if ( !isDisconnected( this[0] ) ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		}
+
+		if ( arguments.length ) {
+			var set = jQuery.clean( arguments );
+			return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		var elem,
+			i = 0;
+
+		for ( ; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		var elem,
+			i = 0;
+
+		for ( ; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		return jQuery.access( this, function( value ) {
+			var elem = this[0] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined ) {
+				return elem.nodeType === 1 ?
+					elem.innerHTML.replace( rinlinejQuery, "" ) :
+					undefined;
+			}
+
+			// See if we can take a shortcut and just use innerHTML
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
+				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+				value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+				try {
+					for (; i < l; i++ ) {
+						// Remove element nodes and prevent memory leaks
+						elem = this[i] || {};
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch(e) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function( value ) {
+		if ( !isDisconnected( this[0] ) ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		}
+
+		return this.length ?
+			this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+			this;
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+
+		// Flatten any nested arrays
+		args = [].concat.apply( [], args );
+
+		var results, first, fragment, iNoClone,
+			i = 0,
+			value = args[0],
+			scripts = [],
+			l = this.length;
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call( this, i, table ? self.html() : undefined );
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			results = jQuery.buildFragment( args, this, scripts );
+			fragment = results.fragment;
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				// Use the original fragment for the last item instead of the first because it can end up
+				// being emptied incorrectly in certain situations (#8070).
+				// Fragments from the fragment cache must always be cloned and never used in place.
+				for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+					callback.call(
+						table && jQuery.nodeName( this[i], "table" ) ?
+							findOrAppend( this[i], "tbody" ) :
+							this[i],
+						i === iNoClone ?
+							fragment :
+							jQuery.clone( fragment, true, true )
+					);
+				}
+			}
+
+			// Fix #11809: Avoid leaking memory
+			fragment = first = null;
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, function( i, elem ) {
+					if ( elem.src ) {
+						if ( jQuery.ajax ) {
+							jQuery.ajax({
+								url: elem.src,
+								type: "GET",
+								dataType: "script",
+								async: false,
+								global: false,
+								"throws": true
+							});
+						} else {
+							jQuery.error("no ajax");
+						}
+					} else {
+						jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+					}
+
+					if ( elem.parentNode ) {
+						elem.parentNode.removeChild( elem );
+					}
+				});
+			}
+		}
+
+		return this;
+	}
+});
+
+function findOrAppend( elem, tag ) {
+	return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var type, i, l,
+		oldData = jQuery._data( src ),
+		curData = jQuery._data( dest, oldData ),
+		events = oldData.events;
+
+	if ( events ) {
+		delete curData.handle;
+		curData.events = {};
+
+		for ( type in events ) {
+			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+				jQuery.event.add( dest, type, events[ type ][ i ] );
+			}
+		}
+	}
+
+	// make the cloned public data object a copy from the original
+	if ( curData.data ) {
+		curData.data = jQuery.extend( {}, curData.data );
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	if ( nodeName === "object" ) {
+		// IE6-10 improperly clones children of object elements using classid.
+		// IE10 throws NoModificationAllowedError if parent is null, #12132.
+		if ( dest.parentNode ) {
+			dest.outerHTML = src.outerHTML;
+		}
+
+		// This path appears unavoidable for IE9. When cloning an object
+		// element in IE9, the outerHTML strategy above is not sufficient.
+		// If the src has innerHTML and the destination does not,
+		// copy the src.innerHTML into the dest.innerHTML. #10324
+		if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+			dest.innerHTML = src.innerHTML;
+		}
+
+	} else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+
+		dest.defaultChecked = dest.checked = src.checked;
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+
+	// IE blanks contents when cloning scripts
+	} else if ( nodeName === "script" && dest.text !== src.text ) {
+		dest.text = src.text;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, context, scripts ) {
+	var fragment, cacheable, cachehit,
+		first = args[ 0 ];
+
+	// Set context from what may come in as undefined or a jQuery collection or a node
+	context = context || document;
+	context = (context[0] || context).ownerDocument || context[0] || context;
+
+	// Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( typeof context.createDocumentFragment === "undefined" ) {
+		context = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+	if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+		first.charAt(0) === "<" && !rnocache.test( first ) &&
+		(jQuery.support.checkClone || !rchecked.test( first )) &&
+		(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+		// Mark cacheable and look for a hit
+		cacheable = true;
+		fragment = jQuery.fragments[ first ];
+		cachehit = fragment !== undefined;
+	}
+
+	if ( !fragment ) {
+		fragment = context.createDocumentFragment();
+		jQuery.clean( args, context, fragment, scripts );
+
+		// Update the cache, but only store false
+		// unless this is a second parsing of the same content
+		if ( cacheable ) {
+			jQuery.fragments[ first ] = cachehit && fragment;
+		}
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var elems,
+			i = 0,
+			ret = [],
+			insert = jQuery( selector ),
+			l = insert.length,
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+		} else {
+			for ( ; i < l; i++ ) {
+				elems = ( i > 0 ? this.clone(true) : this ).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( typeof elem.getElementsByTagName !== "undefined" ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( typeof elem.querySelectorAll !== "undefined" ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( rcheckableType.test( elem.type ) ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var srcElements,
+			destElements,
+			i,
+			clone;
+
+		if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+			clone = elem.cloneNode( true );
+
+		// IE<=8 does not properly clone detached, unknown element nodes
+		} else {
+			fragmentDiv.innerHTML = elem.outerHTML;
+			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+		}
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				// Ensure that the destination node is not null; Fixes #9587
+				if ( destElements[i] ) {
+					cloneFixAttributes( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var j, safe, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+			i = 0,
+			ret = [];
+
+		// Ensure that context is a document
+		if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+			context = document;
+		}
+
+		// Use the already-created safe fragment if context permits
+		for ( safe = context === document && safeFragment; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// Ensure a safe container in which to render the html
+					safe = safe || createSafeFragment( context );
+					div = div || safe.appendChild( context.createElement("div") );
+
+					// Fix "XHTML"-style tags in all browsers
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+					// Go to html and back, then peel off extra wrappers
+					tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+					depth = wrap[0];
+					div.innerHTML = wrap[1] + elem + wrap[2];
+
+					// Move to the right depth
+					while ( depth-- ) {
+						div = div.lastChild;
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						hasBody = rtbody.test(elem);
+							tbody = tag === "table" && !hasBody ?
+								div.firstChild && div.firstChild.childNodes :
+
+								// String was a bare <thead> or <tfoot>
+								wrap[1] === "<table>" && !hasBody ?
+									div.childNodes :
+									[];
+
+						for ( j = tbody.length - 1; j >= 0 ; --j ) {
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
+							}
+						}
+					}
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+					}
+
+					elem = div.childNodes;
+
+					// Remember the top-level container for proper cleanup
+					div = safe.lastChild;
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		// Fix #11356: Clear elements from safeFragment
+		if ( div ) {
+			safe.removeChild( div );
+			elem = div = safe = null;
+		}
+
+		// Reset defaultChecked for any radios and checkboxes
+		// about to be appended to the DOM in IE 6/7 (#8060)
+		if ( !jQuery.support.appendChecked ) {
+			for ( i = 0; (elem = ret[i]) != null; i++ ) {
+				if ( jQuery.nodeName( elem, "input" ) ) {
+					fixDefaultChecked( elem );
+				} else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+					jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+				}
+			}
+		}
+
+		// Append elements to a provided document fragment
+		if ( fragment ) {
+			// Special handling of each script element
+			handleScript = function( elem ) {
+				// Check if we consider it executable
+				if ( !elem.type || rscriptType.test( elem.type ) ) {
+					// Detach the script and store it in the scripts array (if provided) or the fragment
+					// Return truthy to indicate that it has been handled
+					return scripts ?
+						scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+						fragment.appendChild( elem );
+				}
+			};
+
+			for ( i = 0; (elem = ret[i]) != null; i++ ) {
+				// Check if we're done after handling an executable script
+				if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+					// Append to fragment and handle embedded scripts
+					fragment.appendChild( elem );
+					if ( typeof elem.getElementsByTagName !== "undefined" ) {
+						// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+						jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+						// Splice the scripts into ret after their former ancestor and advance our index beyond them
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+						i += jsTags.length;
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems, /* internal */ acceptData ) {
+		var data, id, elem, type,
+			i = 0,
+			internalKey = jQuery.expando,
+			cache = jQuery.cache,
+			deleteExpando = jQuery.support.deleteExpando,
+			special = jQuery.event.special;
+
+		for ( ; (elem = elems[i]) != null; i++ ) {
+
+			if ( acceptData || jQuery.acceptData( elem ) ) {
+
+				id = elem[ internalKey ];
+				data = id && cache[ id ];
+
+				if ( data ) {
+					if ( data.events ) {
+						for ( type in data.events ) {
+							if ( special[ type ] ) {
+								jQuery.event.remove( elem, type );
+
+							// This is a shortcut to avoid jQuery.event.remove's overhead
+							} else {
+								jQuery.removeEvent( elem, type, data.handle );
+							}
+						}
+					}
+
+					// Remove cache only if it was not already removed by jQuery.event.remove
+					if ( cache[ id ] ) {
+
+						delete cache[ id ];
+
+						// IE does not allow us to delete expando properties from nodes,
+						// nor does it have a removeAttribute function on Document nodes;
+						// we must handle all of these cases
+						if ( deleteExpando ) {
+							delete elem[ internalKey ];
+
+						} else if ( elem.removeAttribute ) {
+							elem.removeAttribute( internalKey );
+
+						} else {
+							elem[ internalKey ] = null;
+						}
+
+						jQuery.deletedIds.push( id );
+					}
+				}
+			}
+		}
+	}
+});
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+	ua = ua.toLowerCase();
+
+	var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+		/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+		/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+		/(msie) ([\w.]+)/.exec( ua ) ||
+		ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+		[];
+
+	return {
+		browser: match[ 1 ] || "",
+		version: match[ 2 ] || "0"
+	};
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+	browser[ matched.browser ] = true;
+	browser.version = matched.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+// Maintained for back-compat only
+if ( browser.webkit ) {
+	browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+jQuery.sub = function() {
+	function jQuerySub( selector, context ) {
+		return new jQuerySub.fn.init( selector, context );
+	}
+	jQuery.extend( true, jQuerySub, this );
+	jQuerySub.superclass = this;
+	jQuerySub.fn = jQuerySub.prototype = this();
+	jQuerySub.fn.constructor = jQuerySub;
+	jQuerySub.sub = this.sub;
+	jQuerySub.fn.init = function init( selector, context ) {
+		if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+			context = jQuerySub( context );
+		}
+
+		return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+	};
+	jQuerySub.fn.init.prototype = jQuerySub.fn;
+	var rootjQuerySub = jQuerySub(document);
+	return jQuerySub;
+};
+	
+})();
+var curCSS, iframe, iframeDoc,
+	ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	rposition = /^(top|right|bottom|left)$/,
+	rmargin = /^margin/,
+	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+	rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+	elemdisplay = {},
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: 0,
+		fontWeight: 400,
+		lineHeight: 1
+	},
+
+	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+	eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+	// shortcut for names that are not vendor prefixed
+	if ( name in style ) {
+		return name;
+	}
+
+	// check for vendor prefixed names
+	var capName = name.charAt(0).toUpperCase() + name.slice(1),
+		origName = name,
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in style ) {
+			return name;
+		}
+	}
+
+	return origName;
+}
+
+function isHidden( elem, el ) {
+	elem = el || elem;
+	return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+	var elem, display,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		values[ index ] = jQuery._data( elem, "olddisplay" );
+		if ( show ) {
+			// Reset the inline display of this element to learn if it is
+			// being hidden by cascaded rules or not
+			if ( !values[ index ] && elem.style.display === "none" ) {
+				elem.style.display = "";
+			}
+
+			// Set elements which have been overridden with display: none
+			// in a stylesheet to whatever the default browser style is
+			// for such an element
+			if ( elem.style.display === "" && isHidden( elem ) ) {
+				values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+			}
+		} else {
+			display = curCSS( elem, "display" );
+
+			if ( !values[ index ] && display !== "none" ) {
+				jQuery._data( elem, "olddisplay", display );
+			}
+		}
+	}
+
+	// Set the display of most of the elements in a second loop
+	// to avoid the constant reflow
+	for ( index = 0; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+			elem.style.display = show ? values[ index ] || "" : "none";
+		}
+	}
+
+	return elements;
+}
+
+jQuery.fn.extend({
+	css: function( name, value ) {
+		return jQuery.access( this, function( elem, name, value ) {
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	},
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state, fn2 ) {
+		var bool = typeof state === "boolean";
+
+		if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+			return eventsToggle.apply( this, arguments );
+		}
+
+		return this.each(function() {
+			if ( bool ? state : isHidden( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = jQuery.camelCase( name ),
+			style = elem.style;
+
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, numeric, extra ) {
+		var val, num, hooks,
+			origName = jQuery.camelCase( name );
+
+		// Make sure that we're working with the right name
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+		// gets hook for the prefixed version
+		// followed by the unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name );
+		}
+
+		//convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Return, converting to number if forced or a qualifier was provided and val looks numeric
+		if ( numeric || extra !== undefined ) {
+			num = parseFloat( val );
+			return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+		}
+		return val;
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var ret, name,
+			old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	}
+});
+
+// NOTE: To any future maintainer, we've used both window.getComputedStyle
+// and getComputedStyle here to produce a better gzip size
+if ( window.getComputedStyle ) {
+	curCSS = function( elem, name ) {
+		var ret, width, minWidth, maxWidth,
+			computed = getComputedStyle( elem, null ),
+			style = elem.style;
+
+		if ( computed ) {
+
+			ret = computed[ name ];
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+
+			// A tribute to the "awesome hack by Dean Edwards"
+			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+				width = style.width;
+				minWidth = style.minWidth;
+				maxWidth = style.maxWidth;
+
+				style.minWidth = style.maxWidth = style.width = ret;
+				ret = computed.width;
+
+				style.width = width;
+				style.minWidth = minWidth;
+				style.maxWidth = maxWidth;
+			}
+		}
+
+		return ret;
+	};
+} else if ( document.documentElement.currentStyle ) {
+	curCSS = function( elem, name ) {
+		var left, rsLeft,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			style = elem.style;
+
+		// Avoid setting ret to empty string here
+		// so we don't default to auto
+		if ( ret == null && style && style[ name ] ) {
+			ret = style[ name ];
+		}
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		// but not position css attributes, as those are proportional to the parent element instead
+		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+			// Remember the original values
+			left = style.left;
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : ret;
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+	var matches = rnumsplit.exec( value );
+	return matches ?
+			Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+			value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+	var i = extra === ( isBorderBox ? "border" : "content" ) ?
+		// If we already have the right measurement, avoid augmentation
+		4 :
+		// Otherwise initialize for horizontal or vertical properties
+		name === "width" ? 1 : 0,
+
+		val = 0;
+
+	for ( ; i < 4; i += 2 ) {
+		// both box models exclude margin, so add it if we want it
+		if ( extra === "margin" ) {
+			// we use jQuery.css instead of curCSS here
+			// because of the reliableMarginRight CSS hook!
+			val += jQuery.css( elem, extra + cssExpand[ i ], true );
+		}
+
+		// From this point on we use curCSS for maximum performance (relevant in animations)
+		if ( isBorderBox ) {
+			// border-box includes padding, so remove it if we want content
+			if ( extra === "content" ) {
+				val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+			}
+
+			// at this point, extra isn't border nor margin, so remove border
+			if ( extra !== "margin" ) {
+				val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+			}
+		} else {
+			// at this point, extra isn't content, so add padding
+			val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+			// at this point, extra isn't content nor padding, so add border
+			if ( extra !== "padding" ) {
+				val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+			}
+		}
+	}
+
+	return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property, which is equivalent to the border-box value
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		valueIsBorderBox = true,
+		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+	if ( val <= 0 ) {
+		// Fall back to computed then uncomputed css if necessary
+		val = curCSS( elem, name );
+		if ( val < 0 || val == null ) {
+			val = elem.style[ name ];
+		}
+
+		// Computed unit is not pixels. Stop here and return.
+		if ( rnumnonpx.test(val) ) {
+			return val;
+		}
+
+		// we need the check for style in case a browser which returns unreliable values
+		// for getComputedStyle silently falls back to the reliable elem.style
+		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+		// Normalize "", auto, and prepare for extra
+		val = parseFloat( val ) || 0;
+	}
+
+	// use the active box-sizing model to add/subtract irrelevant styles
+	return ( val +
+		augmentWidthOrHeight(
+			elem,
+			name,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox
+		)
+	) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+	if ( elemdisplay[ nodeName ] ) {
+		return elemdisplay[ nodeName ];
+	}
+
+	var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+		display = elem.css("display");
+	elem.remove();
+
+	// If the simple way fails,
+	// get element's real default display by attaching it to a temp iframe
+	if ( display === "none" || display === "" ) {
+		// Use the already-created iframe if possible
+		iframe = document.body.appendChild(
+			iframe || jQuery.extend( document.createElement("iframe"), {
+				frameBorder: 0,
+				width: 0,
+				height: 0
+			})
+		);
+
+		// Create a cacheable copy of the iframe document on first call.
+		// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+		// document to it; WebKit & Firefox won't allow reusing the iframe document.
+		if ( !iframeDoc || !iframe.createElement ) {
+			iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+			iframeDoc.write("<!doctype html><html><body>");
+			iframeDoc.close();
+		}
+
+		elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+		display = curCSS( elem, "display" );
+		document.body.removeChild( iframe );
+	}
+
+	// Store the correct default display
+	elemdisplay[ nodeName ] = display;
+
+	return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 || curCSS( elem, "display" ) !== "none" ) {
+					return getWidthOrHeight( elem, name, extra );
+				} else {
+					return jQuery.swap( elem, cssShow, function() {
+						return getWidthOrHeight( elem, name, extra );
+					});
+				}
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			return setPositiveNumber( elem, value, extra ?
+				augmentWidthOrHeight(
+					elem,
+					name,
+					extra,
+					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+				) : 0
+			);
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+				style.removeAttribute ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there there is no filter style applied in a css rule, we are done
+				if ( currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				return jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						return curCSS( elem, "marginRight" );
+					}
+				});
+			}
+		};
+	}
+
+	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+	// getComputedStyle returns percent when specified for top/left/bottom/right
+	// rather than make the css module depend on the offset module, we just check for it here
+	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+		jQuery.each( [ "top", "left" ], function( i, prop ) {
+			jQuery.cssHooks[ prop ] = {
+				get: function( elem, computed ) {
+					if ( computed ) {
+						var ret = curCSS( elem, prop );
+						// if curCSS returns percentage, fallback to offset
+						return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+					}
+				}
+			};
+		});
+	}
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i,
+
+				// assumes a single number if not a string
+				parts = typeof value === "string" ? value.split(" ") : [ value ],
+				expanded = {};
+
+			for ( i = 0; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( !rmargin.test( prefix ) ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+});
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, value ) {
+			// If value is a function, invoke it and return its value
+			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+		};
+
+	// Set traditional to true for jQuery <= 1.3.2 behavior.
+	if ( traditional === undefined ) {
+		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		});
+
+	} else {
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+var // Document location
+	ajaxLocation,
+	// Document location segments
+	ajaxLocParts,
+
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType, list, placeBefore,
+			dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
+			i = 0,
+			length = dataTypes.length;
+
+		if ( jQuery.isFunction( func ) ) {
+			// For each dataType in the dataTypeExpression
+			for ( ; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var selection,
+		list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters );
+
+	for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+	if ( typeof url !== "string" && _load ) {
+		return _load.apply( this, arguments );
+	}
+
+	// Don't do a request if no elements are being requested
+	if ( !this.length ) {
+		return this;
+	}
+
+	var selector, type, response,
+		self = this,
+		off = url.indexOf(" ");
+
+	if ( off >= 0 ) {
+		selector = url.slice( off, url.length );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( jQuery.isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// Request the remote document
+	jQuery.ajax({
+		url: url,
+
+		// if "type" variable is undefined, then "GET" method will be used
+		type: type,
+		dataType: "html",
+		data: params,
+		complete: function( jqXHR, status ) {
+			if ( callback ) {
+				self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+			}
+		}
+	}).done(function( responseText ) {
+
+		// Save response for use in complete callback
+		response = arguments;
+
+		// See if a selector was specified
+		self.html( selector ?
+
+			// Create a dummy div to hold the results
+			jQuery("<div>")
+
+				// inject the contents of the document in, removing the scripts
+				// to avoid any 'Permission Denied' errors in IE
+				.append( responseText.replace( rscript, "" ) )
+
+				// Locate the specified elements
+				.find( selector ) :
+
+			// If not, just inject the full result
+			responseText );
+
+	});
+
+	return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.on( o, f );
+	};
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		if ( settings ) {
+			// Building a settings object
+			ajaxExtend( target, jQuery.ajaxSettings );
+		} else {
+			// Extending ajaxSettings
+			settings = target;
+			target = jQuery.ajaxSettings;
+		}
+		ajaxExtend( target, settings );
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": allTypes
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			context: true,
+			url: true
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // ifModified key
+			ifModifiedKey,
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// The jqXHR state
+			state = 0,
+			// Default abort message
+			strAbort = "canceled",
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					modified = jqXHR.getResponseHeader("Last-Modified");
+					if ( modified ) {
+						jQuery.lastModified[ ifModifiedKey ] = modified;
+					}
+					modified = jqXHR.getResponseHeader("Etag");
+					if ( modified ) {
+						jQuery.etag[ ifModifiedKey ] = modified;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					isSuccess = ajaxConvert( s, response );
+					statusText = isSuccess.state;
+					success = isSuccess.data;
+					error = isSuccess.error;
+					isSuccess = !error;
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.add;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for ( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.always( tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already and return
+				return jqXHR.abort();
+
+		}
+
+		// aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var ct, type, finalDataType, firstDataType,
+		contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields;
+
+	// Fill responseXXX fields
+	for ( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	var conv, conv2, current, tmp,
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice(),
+		prev = dataTypes[ 0 ],
+		converters = {},
+		i = 0;
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	// Convert to each sequential dataType, tolerating list modification
+	for ( ; (current = dataTypes[++i]); ) {
+
+		// There's only work to do if current dataType is non-auto
+		if ( current !== "*" ) {
+
+			// Convert response if prev dataType is non-auto and differs from current
+			if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split(" ");
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.splice( i--, 0, current );
+								}
+
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s["throws"] ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+						}
+					}
+				}
+			}
+
+			// Update prev for next iteration
+			prev = current;
+		}
+	}
+
+	return { state: "success", data: response };
+}
+var oldCallbacks = [],
+	rquestion = /\?/,
+	rjsonp = /(=)\?(?=&|$)|\?\?/,
+	nonce = jQuery.now();
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		data = s.data,
+		url = s.url,
+		hasCallback = s.jsonp !== false,
+		replaceInUrl = hasCallback && rjsonp.test( url ),
+		replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
+			!( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
+			rjsonp.test( data );
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+		overwritten = window[ callbackName ];
+
+		// Insert callback into url or form data
+		if ( replaceInUrl ) {
+			s.url = url.replace( rjsonp, "$1" + callbackName );
+		} else if ( replaceInData ) {
+			s.data = data.replace( rjsonp, "$1" + callbackName );
+		} else if ( hasCallback ) {
+			s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always(function() {
+			// Restore preexisting value
+			window[ callbackName ] = overwritten;
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+				// make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		});
+
+		// Delegate to script
+		return "script";
+	}
+});
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+var xhrCallbacks,
+	// #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var handle, i,
+						xhr = s.xhr();
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occurred
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+
+									// When requesting binary data, IE6-9 will throw an exception
+									// on any attempt to access responseText (#11426)
+									try {
+										responses.text = xhr.responseText;
+									} catch( _ ) {
+									}
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					if ( !s.async ) {
+						// if we're in sync mode we fire the callback
+						callback();
+					} else if ( xhr.readyState === 4 ) {
+						// (IE6 & IE7) if it's in cache and has been
+						// retrieved directly we need to fire the callback
+						setTimeout( callback, 0 );
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+var fxNow, timerId,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+	rrun = /queueHooks$/,
+	animationPrefilters = [ defaultPrefilter ],
+	tweeners = {
+		"*": [function( prop, value ) {
+			var end, unit, prevScale,
+				tween = this.createTween( prop, value ),
+				parts = rfxnum.exec( value ),
+				target = tween.cur(),
+				start = +target || 0,
+				scale = 1;
+
+			if ( parts ) {
+				end = +parts[2];
+				unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+				// We need to compute starting value
+				if ( unit !== "px" && start ) {
+					// Iteratively approximate from a nonzero starting point
+					// Prefer the current property, because this process will be trivial if it uses the same units
+					// Fallback to end or a simple constant
+					start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+					do {
+						// If previous iteration zeroed out, double until we get *something*
+						// Use a string for doubling factor so we don't accidentally see scale as unchanged below
+						prevScale = scale = scale || ".5";
+
+						// Adjust and apply
+						start = start / scale;
+						jQuery.style( tween.elem, prop, start + unit );
+
+						// Update scale, tolerating zeroes from tween.cur()
+						scale = tween.cur() / target;
+
+					// Stop looping if we've hit the mark or scale is unchanged
+					} while ( scale !== 1 && scale !== prevScale );
+				}
+
+				tween.unit = unit;
+				tween.start = start;
+				// If a +=/-= token was provided, we're doing a relative animation
+				tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+			}
+			return tween;
+		}]
+	};
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout(function() {
+		fxNow = undefined;
+	}, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+	jQuery.each( props, function( prop, value ) {
+		var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+			index = 0,
+			length = collection.length;
+		for ( ; index < length; index++ ) {
+			if ( collection[ index ].call( animation, prop, value ) ) {
+
+				// we're done with this property
+				return;
+			}
+		}
+	});
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		index = 0,
+		tweenerIndex = 0,
+		length = animationPrefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+			// don't match elem in the :animated selector
+			delete tick.elem;
+		}),
+		tick = function() {
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+				percent = 1 - ( remaining / animation.duration || 0 ),
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length ; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+			if ( percent < 1 && length ) {
+				return remaining;
+			} else {
+				deferred.resolveWith( elem, [ animation ] );
+				return false;
+			}
+		},
+		animation = deferred.promise({
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, { specialEasing: {} }, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end, easing ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+					// if we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// resolve when we played the last frame
+				// otherwise, reject
+				if ( gotoEnd ) {
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		}),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length ; index++ ) {
+		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			return result;
+		}
+	}
+
+	createTweens( animation, props );
+
+	if ( jQuery.isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			anim: animation,
+			queue: animation.opts.queue,
+			elem: elem
+		})
+	);
+
+	// attach callbacks from options
+	return animation.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = jQuery.camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( jQuery.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// not quite $.extend, this wont overwrite keys already present.
+			// also - reusing 'index' from above because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+	tweener: function( props, callback ) {
+		if ( jQuery.isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.split(" ");
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length ; index++ ) {
+			prop = props[ index ];
+			tweeners[ prop ] = tweeners[ prop ] || [];
+			tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			animationPrefilters.unshift( callback );
+		} else {
+			animationPrefilters.push( callback );
+		}
+	}
+});
+
+function defaultPrefilter( elem, props, opts ) {
+	var index, prop, value, length, dataShow, tween, hooks, oldfire,
+		anim = this,
+		style = elem.style,
+		orig = {},
+		handled = [],
+		hidden = elem.nodeType && isHidden( elem );
+
+	// handle queue: false promises
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always(function() {
+			// doing this makes sure that the complete handler will be called
+			// before this completes
+			anim.always(function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			});
+		});
+	}
+
+	// height/width overflow pass
+	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+		// Make sure that nothing sneaks out
+		// Record all 3 overflow attributes because IE does not
+		// change the overflow attribute when overflowX and
+		// overflowY are set to the same value
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Set display property to inline-block for height/width
+		// animations on inline elements that are having width/height animated
+		if ( jQuery.css( elem, "display" ) === "inline" &&
+				jQuery.css( elem, "float" ) === "none" ) {
+
+			// inline-level elements accept inline-block;
+			// block-level elements need to be inline with layout
+			if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+				style.display = "inline-block";
+
+			} else {
+				style.zoom = 1;
+			}
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		if ( !jQuery.support.shrinkWrapBlocks ) {
+			anim.done(function() {
+				style.overflow = opts.overflow[ 0 ];
+				style.overflowX = opts.overflow[ 1 ];
+				style.overflowY = opts.overflow[ 2 ];
+			});
+		}
+	}
+
+
+	// show/hide pass
+	for ( index in props ) {
+		value = props[ index ];
+		if ( rfxtypes.exec( value ) ) {
+			delete props[ index ];
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+				continue;
+			}
+			handled.push( index );
+		}
+	}
+
+	length = handled.length;
+	if ( length ) {
+		dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+		if ( hidden ) {
+			jQuery( elem ).show();
+		} else {
+			anim.done(function() {
+				jQuery( elem ).hide();
+			});
+		}
+		anim.done(function() {
+			var prop;
+			jQuery.removeData( elem, "fxshow", true );
+			for ( prop in orig ) {
+				jQuery.style( elem, prop, orig[ prop ] );
+			}
+		});
+		for ( index = 0 ; index < length ; index++ ) {
+			prop = handled[ index ];
+			tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+			orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = tween.start;
+				if ( hidden ) {
+					tween.end = tween.start;
+					tween.start = prop === "width" || prop === "height" ? 1 : 0;
+				}
+			}
+		}
+	}
+}
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || "swing";
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration );
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			if ( tween.elem[ tween.prop ] != null &&
+				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// passing any value as a 4th parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails
+			// so, simple values such as "10px" are parsed to Float.
+			// complex values such as "rotate(1rad)" are returned as is.
+			result = jQuery.css( tween.elem, tween.prop, false, "" );
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+			// use step hook for back compat - use cssHook if its there - use .style if its
+			// available and use plain properties where available
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ||
+			// special check for .toggle( handler, handler, ... )
+			( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+});
+
+jQuery.fn.extend({
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// show any hidden elements after setting opacity to 0
+		return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+			// animate to the value specified
+			.end().animate({ opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations resolve immediately
+				if ( empty ) {
+					anim.stop( true );
+				}
+			};
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = jQuery._data( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	}
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		attrs = { height: type },
+		i = 0;
+
+	// if we include width, step value is 1 to do all cssExpand values,
+	// if we don't include width, step value is 2 to skip over Left and Right
+	for( ; i < 4 ; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show"),
+	slideUp: genFx("hide"),
+	slideToggle: genFx("toggle"),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			jQuery.isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+	};
+
+	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+	// normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( jQuery.isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p*Math.PI ) / 2;
+	}
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+	var timer,
+		timers = jQuery.timers,
+		i = 0;
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+		// Checks the timer has not already been removed
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+};
+
+jQuery.fx.timer = function( timer ) {
+	if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+	}
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.stop = function() {
+	clearInterval( timerId );
+	timerId = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+	// Default speed
+	_default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+var rroot = /^(?:body|html)$/i;
+
+jQuery.fn.offset = function( options ) {
+	if ( arguments.length ) {
+		return options === undefined ?
+			this :
+			this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+	}
+
+	var box, docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft, top, left,
+		elem = this[ 0 ],
+		doc = elem && elem.ownerDocument;
+
+	if ( !doc ) {
+		return;
+	}
+
+	if ( (body = doc.body) === elem ) {
+		return jQuery.offset.bodyOffset( elem );
+	}
+
+	docElem = doc.documentElement;
+
+	// Make sure we're not dealing with a disconnected DOM node
+	if ( !jQuery.contains( docElem, elem ) ) {
+		return { top: 0, left: 0 };
+	}
+
+	box = elem.getBoundingClientRect();
+	win = getWindow( doc );
+	clientTop  = docElem.clientTop  || body.clientTop  || 0;
+	clientLeft = docElem.clientLeft || body.clientLeft || 0;
+	scrollTop  = win.pageYOffset || docElem.scrollTop;
+	scrollLeft = win.pageXOffset || docElem.scrollLeft;
+	top  = box.top  + scrollTop  - clientTop;
+	left = box.left + scrollLeft - clientLeft;
+
+	return { top: top, left: left };
+};
+
+jQuery.offset = {
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[0] ) {
+			return;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent || document.body;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+	var top = /Y/.test( prop );
+
+	jQuery.fn[ method ] = function( val ) {
+		return jQuery.access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? (prop in win) ? win[ prop ] :
+					win.document.documentElement[ method ] :
+					elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : jQuery( win ).scrollLeft(),
+					 top ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length, null );
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+		// margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return jQuery.access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( jQuery.isWindow( elem ) ) {
+					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+					// isn't a whole lot we can do. See pull request at this URL for discussion:
+					// https://github.com/jquery/jquery/pull/764
+					return elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, value, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable );
+		};
+	});
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+	define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window );
Index: /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/tester.css
===================================================================
--- /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/tester.css	(revision 7116)
+++ /FCSEndpointTester/branches/non-vaadin-old/src/main/webapp/tester.css	(revision 7116)
@@ -0,0 +1,56 @@
+@CHARSET "ISO-8859-1";
+
+body {
+	font-family: Arial, Helvetica, Sans-Serif;
+	font-size: 12pt;
+}
+
+h1 {
+	font-size: 120%;
+}
+
+h2 {
+	font-size: 110%;
+}
+
+p,pre {
+	margin: 2px 0 2px 0;
+}
+
+ul.log {
+	list-style: none;
+	margin: 0;
+	padding-left: 0;
+}
+
+.success {
+	color: #00aa00;
+	background-color: inherit;
+}
+
+.warning {
+	color: #aa0000;
+	background-color: inherit;
+}
+
+.error {
+	color: #aa0000;
+	background-color: inherit;
+}
+
+pre,code {
+	font-size: 90%;
+}
+
+a.togglelink {
+	font-size: 85%;
+	color: #0000a0;
+	background-color: inherit;
+	vertical-align: middle;
+	line-height: 85%;
+}
+
+a.togglelink:VISITED {
+    color: #0000a0;
+    background-color: inherit;
+}
Index: /FCSEndpointTester/trunk/LICENSE.txt
===================================================================
--- /FCSEndpointTester/trunk/LICENSE.txt	(revision 7116)
+++ /FCSEndpointTester/trunk/LICENSE.txt	(revision 7116)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
Index: /FCSEndpointTester/trunk/pom.xml
===================================================================
--- /FCSEndpointTester/trunk/pom.xml	(revision 7116)
+++ /FCSEndpointTester/trunk/pom.xml	(revision 7116)
@@ -0,0 +1,280 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>eu.clarin.sru</groupId>
+    <artifactId>FCSEndpointTester</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>war</packaging>
+    <name>Tester for CLARIN-FCS</name>
+    <description>A test-service to test CLARIN-FCS endpoints for protocol conformance</description>
+    <organization>
+        <name>IDS Mannheim</name>
+        <url>http://www.ids-mannheim.de/</url>
+    </organization>
+
+    <properties>
+        <!-- project settings -->
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!-- plug-in settings -->
+        <maven.compiler.source>1.6</maven.compiler.source>
+        <maven.compiler.target>1.6</maven.compiler.target>
+        <!-- dependencies -->
+        <slf4j.version>1.7.21</slf4j.version>
+        <httpClient.version>4.5.2</httpClient.version>
+        <!-- Vaadin dependencies -->
+        <vaadin.version>7.7.3</vaadin.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>joda-time</groupId>
+            <artifactId>joda-time</artifactId>
+            <version>2.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.9.10</version>
+        </dependency>
+
+        <dependency>
+            <groupId>eu.clarin.sru.fcs</groupId>
+            <artifactId>fcs-simple-client</artifactId>
+            <version>1.4.0-SNAPSHOT</version>
+        </dependency>
+
+        <!-- 
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+        -->
+
+        <!-- Vaadin -->
+        <dependency>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-server</artifactId>
+            <version>${vaadin.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-themes</artifactId>
+            <version>${vaadin.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-client</artifactId>
+            <version>${vaadin.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-client-compiler</artifactId>
+            <version>${vaadin.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <!-- 
+        <dependency>
+            <groupId>com.vaadin</groupId>
+            <artifactId>vaadin-push</artifactId>
+            <version>${vaadin.version}</version>
+        </dependency>
+        -->
+
+        <!-- Vaadin add-ons
+            
+             Make sure to include all widgets in 'Widgetset.gwt.xml'!
+        -->
+        <dependency>
+            <groupId>org.vaadin.addons</groupId>
+            <artifactId>popupbutton</artifactId>
+            <version>2.6.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.vaadin.addons</groupId>
+            <artifactId>animator</artifactId>
+            <version>1.7.3</version>
+        </dependency>
+    </dependencies>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-jdk14</artifactId>
+                <version>${slf4j.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+                <version>${httpClient.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.6.0</version>
+                <configuration>
+                    <encoding>${project.source.encoding}</encoding>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>3.0.1</version>
+                <configuration>
+                    <encoding>${project.source.encoding}</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>2.6</version>
+                <configuration>
+                    <webResources>
+                        <resource>
+                            <directory>${project.build.directory}/vaadin-webapp/</directory>
+                            <filtering>false</filtering>
+                            <excludes>
+                                <exclude>**/deploy/**</exclude>
+                                <exclude>**/gwt-unitCache/**</exclude>
+                            </excludes>
+                        </resource>
+                    </webResources>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>com.vaadin</groupId>
+                <artifactId>vaadin-maven-plugin</artifactId>
+                <version>${vaadin.version}</version>
+                <configuration>
+                    <!-- <runTarget>mobilemail</runTarget> -->
+                    <!--
+                    <noServer>true</noServer>
+                    <!- - Remove draftCompile when project is ready - ->
+                    <draftCompile>false</draftCompile>
+                    <compileReport>true</compileReport>
+                    <style>OBF</style>
+                    <strict>true</strict>
+                    <runTarget>http://localhost:8080/</runTarget>
+                     -->
+                    <extraJvmArgs>-Xmx512M -Xss1024k</extraJvmArgs>
+                    <webappDirectory>${project.build.directory}/vaadin-webapp/VAADIN/widgetsets</webappDirectory>
+                    <noServer>true</noServer>
+                    <draftCompile>false</draftCompile>
+                    <compileReport>false</compileReport>
+                    <style>OBF</style>
+                    <strict>true</strict>
+                    <soyc>false</soyc>
+                </configuration>
+                <executions>
+                    <execution>
+                        <configuration>
+                            <!-- if you don't specify any modules, the plugin 
+                                will find them -->
+                            <!--
+                            <modules>
+                                <module>com.vaadin.demo.mobilemail.gwt.ColorPickerWidgetSet</module> 
+                            </modules>
+                            -->
+                        </configuration>
+                        <goals>
+                            <goal>resources</goal>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.eclipse.m2e</groupId>
+                    <artifactId>lifecycle-mapping</artifactId>
+                    <version>1.0.0</version>
+                    <configuration>
+                        <lifecycleMappingMetadata>
+                            <pluginExecutions>
+                                <pluginExecution>
+                                    <pluginExecutionFilter>
+                                        <groupId>com.vaadin</groupId>
+                                        <artifactId>vaadin-maven-plugin</artifactId>
+                                        <versionRange>
+                                            [${vaadin.version},)
+                                        </versionRange>
+                                        <goals>
+                                            <goal>resources</goal>
+                                            <goal>compile</goal>
+                                        </goals>
+                                    </pluginExecutionFilter>
+                                    <action>
+                                        <execute />
+                                    </action>
+                                </pluginExecution>
+                            </pluginExecutions>
+                        </lifecycleMappingMetadata>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <repositories>
+        <repository>
+            <id>clarin</id>
+            <url>http://catalog.clarin.eu/ds/nexus/content/repositories/Clarin/</url>
+        </repository>
+
+        <repository>
+            <id>vaadin-addons</id>
+            <url>http://maven.vaadin.com/vaadin-addons</url>
+        </repository>
+    </repositories>
+</project>
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSEndpointTester.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSEndpointTester.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSEndpointTester.java	(revision 7116)
@@ -0,0 +1,444 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.utils.HttpClientUtils;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.impl.NoConnectionReuseStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.reflections.Reflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import eu.clarin.sru.client.SRUClient;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUExplainResponse;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUVersion;
+import eu.clarin.sru.client.fcs.ClarinFCSClientBuilder;
+import eu.clarin.sru.client.fcs.ClarinFCSConstants;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescription;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescriptionParser;
+
+
+public class FCSEndpointTester implements ServletContextListener {
+    public interface ProgressListener {
+        public void updateProgress(String message);
+
+        public void updateMaximum(int maximum);
+
+        public void onDone(FCSTestContext context, List<FCSTestResult> results);
+
+        public void onError(String message, Throwable e);
+    }
+    private static final String TESTCASE_PACKAGE =
+            "eu.clarin.fcs.tester.tests";
+    private static final String USER_AGENT = "FCSEndpointTester/1.0.0";
+    private static final int DEFAULT_CONNECT_TIMEOUT = -1;
+    private static final int DEFAULT_SOCKET_TIMEOUT  = -1;
+    private static final List<FCSTest> TESTS;
+    private static final Logger logger =
+            LoggerFactory.getLogger(FCSEndpointTester.class);
+    private final FCSLoggingHandler logcapturehandler =
+            new FCSLoggingHandler();
+    private final ExecutorService executor = Executors.newCachedThreadPool();
+    private static final FCSEndpointTester INSTANCE = new FCSEndpointTester();
+
+
+    public static FCSEndpointTester getInstance() {
+        return INSTANCE;
+    }
+
+
+    private void destroy() {
+        executor.shutdownNow();
+        try {
+            executor.awaitTermination(5000, TimeUnit.MILLISECONDS);
+            logger.debug("thread pool terminated");
+        } catch (InterruptedException e) {
+            /* IGNORE */
+        }
+    }
+
+
+    private static CloseableHttpClient createHttpClient(int connectTimeout,
+            int socketTimeout) {
+        final PoolingHttpClientConnectionManager manager =
+                new PoolingHttpClientConnectionManager();
+        manager.setDefaultMaxPerRoute(8);
+        manager.setMaxTotal(128);
+
+        final SocketConfig socketConfig = SocketConfig.custom()
+                .setSoReuseAddress(true)
+                .setSoLinger(0)
+                .build();
+
+        final RequestConfig requestConfig = RequestConfig.custom()
+                .setAuthenticationEnabled(false)
+                .setRedirectsEnabled(true)
+                .setMaxRedirects(4)
+                .setCircularRedirectsAllowed(false)
+                .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
+                .setConnectTimeout(connectTimeout)
+                .setSocketTimeout(socketTimeout)
+                .setConnectionRequestTimeout(0) /* infinite */
+                .build();
+
+        return HttpClients.custom()
+                .setUserAgent(USER_AGENT)
+                .setConnectionManager(manager)
+                .setDefaultSocketConfig(socketConfig)
+                .setDefaultRequestConfig(requestConfig)
+                .setConnectionReuseStrategy(new NoConnectionReuseStrategy())
+                .build();
+    }
+
+
+    public void performTests(final ProgressListener listener,
+            final FCSTestProfile profile,
+            final String endpointURI,
+            final String searchTerm,
+            final boolean strictMode,
+            final boolean performProbeQuest,
+            final int connectTimeout,
+            final int socketTimeout) {
+        if (listener == null) {
+            throw new NullPointerException("listener == null");
+        }
+        try {
+            executor.submit(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        FCSTestContext context = null;
+                        if (performProbeQuest) {
+                            doPerformProbeRequest(listener, endpointURI);
+                        }
+                        // auto-detect FCS version?
+                        if (profile == null) {
+                            context = doPerformAutodetect(listener,
+                                    endpointURI,
+                                    searchTerm,
+                                    strictMode,
+                                    connectTimeout,
+                                    socketTimeout);
+                        } else {
+                            context = new FCSTestContext(profile,
+                                    endpointURI,
+                                    searchTerm,
+                                    strictMode,
+                                    connectTimeout,
+                                    socketTimeout);
+                        }
+                        context.init();
+                        doPerformTests(listener, context);
+                    } catch (IOException e) {
+                        listener.onError("An error occurred", e);
+                    } catch (SRUClientException e) {
+                        listener.onError(e.getMessage(), e.getCause());
+                    } catch (Throwable t) {
+                        logger.error("Internal error!", t);
+                        listener.onError("Internal error!", t);
+                    }
+                }
+            });
+        } catch (RejectedExecutionException e) {
+            listener.onError("Error starting tests", null);
+        }
+    }
+
+
+    private FCSTestContext doPerformAutodetect(final ProgressListener listener,
+            final String endpointURI,
+            final String searchTerm,
+            final boolean strictMode,
+            final int connectTimeout,
+            final int socketTimeout) throws SRUClientException {
+        listener.updateProgress("Detecting CLARIN-FCS profile ...");
+
+        FCSTestProfile profile = null;
+
+        SRUClient client = new ClarinFCSClientBuilder()
+                .addDefaultDataViewParsers()
+                .setDefaultSRUVersion(SRUVersion.VERSION_2_0)
+                .unknownDataViewAsString()
+                .enableLegacySupport()
+                .registerExtraResponseDataParser(
+                        new ClarinFCSEndpointDescriptionParser())
+                .buildClient();
+
+        try {
+            SRUExplainRequest request = new SRUExplainRequest(endpointURI);
+            request.setStrictMode(false);
+            request.setVersion(SRUVersion.VERSION_1_2);
+            request.setExtraRequestData(ClarinFCSConstants.X_FCS_ENDPOINT_DESCRIPTION,
+                    ClarinFCSConstants.TRUE);
+            request.setParseRecordDataEnabled(true);
+            SRUExplainResponse response = client.explain(request);
+
+            ClarinFCSEndpointDescription ed =
+                    response.getFirstExtraResponseData(ClarinFCSEndpointDescription.class);
+            if (ed != null) {
+                if (ed.getVersion() == 1) {
+                    profile = FCSTestProfile.CLARIN_FCS_1_0;
+                } else if (ed.getVersion() == 2) {
+                    throw new SRUClientException("CLARIN-FCS version 2.0 " +
+                            "MUST use SRU 2.0. Endpoint used SRU 1.2 " +
+                            "but claimed to support FCS 2.0 in it's " +
+                            "endpoint description!");
+                }
+            } else {
+                profile = FCSTestProfile.CLARIN_FCS_LEGACY;
+            }
+
+            if (profile == null) {
+                request = new SRUExplainRequest(endpointURI);
+                request.setStrictMode(false);
+                request.setVersion(SRUVersion.VERSION_2_0);
+                request.setExtraRequestData(
+                        ClarinFCSConstants.X_FCS_ENDPOINT_DESCRIPTION,
+                        ClarinFCSConstants.TRUE);
+                request.setParseRecordDataEnabled(true);
+                response = client.explain(request);
+
+                ed = response.getFirstExtraResponseData(
+                                ClarinFCSEndpointDescription.class);
+                if (ed != null) {
+                    if (ed.getVersion() == 2) {
+                        profile = FCSTestProfile.CLARIN_FCS_2_0;
+                    }
+                }
+            }
+            if (profile != null) {
+                final FCSTestContext context =
+                        new FCSTestContext(profile,
+                                endpointURI,
+                                searchTerm,
+                                strictMode,
+                                connectTimeout,
+                                socketTimeout);
+                return context;
+            }
+        } catch (SRUClientException e) {
+            logger.error("error", e);
+            throw new SRUClientException("An error occured while " +
+                    "auto-detecting CLARIN-FCS version", e);
+        }
+        throw new SRUClientException("Unable to auto-detect CLARIN-FCS version!");
+    }
+
+
+    private void doPerformTests(final ProgressListener listener,
+            final FCSTestContext context) {
+        /* make sure that we'll capture the logging records */
+        java.util.logging.Logger l =
+                java.util.logging.Logger.getLogger("eu.clarin.sru");
+        l.setLevel(Level.FINEST);
+        boolean found = false;
+        Handler[] handlers = l.getHandlers();
+        if (handlers != null) {
+            for (Handler handler : handlers) {
+                if (handler == logcapturehandler) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+        if (!found) {
+            l.addHandler(logcapturehandler);
+        }
+
+
+        List<FCSTest> tests = new ArrayList<FCSTest>();
+        for (FCSTest test : TESTS) {
+            final FCSTestCase tc =
+                    test.getClass().getAnnotation(FCSTestCase.class);
+            if (Arrays.binarySearch(tc.profiles(), context.getProfile()) >= 0) {
+                tests.add(test);
+            }
+        }
+
+        List<FCSTestResult> results = null;
+
+        final SRUSimpleClient client = context.getClient();
+        final FCSTestHandler handler = new FCSTestHandler();
+        final int totalCount = tests.size();
+        int num = 1;
+        listener.updateMaximum(totalCount);
+        for (FCSTest test : tests) {
+            if (results == null) {
+                results = new LinkedList<FCSTestResult>();
+            }
+
+            logger.debug("running test {}:{}", num, test.getName());
+            final String message = String.format(
+                    "Performing test \"%s:%s\" (%d/%d) ...",
+                    context.getProfile().toDisplayString(),
+                    test.getName(), num, totalCount);
+            listener.updateProgress(message);
+            num++;
+
+            FCSTestResult result = null;
+            try {
+                handler.reset();
+                result = test.perform(context, client, handler);
+                result.setLogRecords(logcapturehandler.getLogRecords());
+            } catch (SRUClientException e) {
+                String msg = e.getMessage();
+                if (msg == null) {
+                    msg = "unclassified exception from SRUClient";
+                }
+                final LogRecord record = new LogRecord(Level.SEVERE, msg);
+                record.setThrown(e);
+                logcapturehandler.publish(record);
+                result = new FCSTestResult(test,
+                        FCSTestResult.Code.ERROR,
+                        msg,
+                        logcapturehandler.getLogRecords());
+            } catch (Throwable t) {
+                final LogRecord record =
+                        new LogRecord(Level.SEVERE, "Internal Error!");
+                record.setThrown(t);
+                logcapturehandler.publish(record);
+                result = new FCSTestResult(test,
+                        FCSTestResult.Code.ERROR,
+                        record.getMessage(),
+                        logcapturehandler.getLogRecords());
+            }
+            results.add(result);
+        } // for
+        listener.onDone(context, results);
+    }
+
+
+    private void doPerformProbeRequest(final ProgressListener listener,
+            final String baseURI) throws IOException {
+        try {
+            logger.debug("performing initial probe request to {}",
+                    baseURI);
+            listener.updateProgress(
+                    "Performing HTTP HEAD probe request ...");
+            final CloseableHttpClient client =
+                    createHttpClient(DEFAULT_CONNECT_TIMEOUT,
+                            DEFAULT_SOCKET_TIMEOUT);
+            final HttpHead request = new HttpHead(baseURI);
+            HttpResponse response = null;
+            try {
+                response = client.execute(request);
+                StatusLine status = response.getStatusLine();
+                if (status.getStatusCode() != HttpStatus.SC_OK) {
+                    throw new IOException("Probe request to endpoint " +
+                            "returned unexpected HTTP status " +
+                            status.getStatusCode());
+                }
+            } finally {
+                HttpClientUtils.closeQuietly(response);
+
+                HttpClientUtils.closeQuietly(client);
+            }
+        } catch (ClientProtocolException e) {
+            throw new IOException(e);
+        }
+    }
+
+
+    @Override
+    public void contextInitialized(ServletContextEvent event) {
+        logger.info("initialized");
+    }
+
+
+    @Override
+    public void contextDestroyed(ServletContextEvent event) {
+        logger.info("shutting down ...");
+        INSTANCE.destroy();
+    }
+
+
+    static {
+        List<FCSTest> tests = null;
+        try {
+            Reflections reflections = new Reflections(TESTCASE_PACKAGE);
+            Set<Class<?>> annotations =
+                    reflections.getTypesAnnotatedWith(FCSTestCase.class);
+            if ((annotations != null) && !annotations.isEmpty()) {
+                List<Class<?>> classes = new ArrayList<Class<?>>(
+                        annotations.size());
+                for (Class<?> clazz : annotations) {
+                    FCSTestCase tc = clazz.getAnnotation(FCSTestCase.class);
+                    if ((tc != null) && (tc.priority() >= 0)) {
+                        classes.add(clazz);
+                    }
+                }
+                Collections.sort(classes, new Comparator<Class<?>>() {
+                    @Override
+                    public int compare(Class<?> o1, Class<?> o2) {
+                        FCSTestCase a1 = o1.getAnnotation(FCSTestCase.class);
+                        FCSTestCase a2 = o2.getAnnotation(FCSTestCase.class);
+                        return a1.priority() - a2.priority();
+                    }
+                });
+                for (Class<?> clazz : classes) {
+                    if (tests == null) {
+                        tests = new ArrayList<FCSTest>(classes.size());
+                    }
+                    tests.add((FCSTest) clazz.newInstance());
+                }
+            }
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+
+        if (tests != null) {
+            TESTS = Collections.unmodifiableList(tests);
+        } else {
+            TESTS = Collections.emptyList();
+        }
+    }
+
+} // class SRUEndpointTester
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSLoggingHandler.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSLoggingHandler.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSLoggingHandler.java	(revision 7116)
@@ -0,0 +1,64 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+
+class FCSLoggingHandler extends Handler {
+    public final Map<Long, List<LogRecord>> messages =
+            new HashMap<Long, List<LogRecord>>();
+
+
+    @Override
+    public void publish(LogRecord record) {
+        final Long id = Thread.currentThread().getId();
+
+        List<LogRecord> records = null;
+        synchronized (messages) {
+            records = messages.get(id);
+            if (records == null) {
+                records = new LinkedList<LogRecord>();
+                messages.put(id, records);
+            }
+        } // synchronized (messages)
+        records.add(record);
+    }
+
+
+    @Override
+    public void flush() {
+    }
+
+
+    @Override
+    public void close() throws SecurityException {
+    }
+
+
+    List<LogRecord> getLogRecords() {
+        synchronized (messages) {
+            return messages.remove(Thread.currentThread().getId());
+        } // synchronized (messages)
+    }
+
+} // class SRULoggingHandler
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTest.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTest.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTest.java	(revision 7116)
@@ -0,0 +1,81 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.util.List;
+import java.util.logging.LogRecord;
+
+
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+public abstract class FCSTest {
+    public abstract String getName();
+
+
+    public abstract String getDescription();
+
+
+    public abstract String getExpected();
+
+
+    public abstract FCSTestResult perform(FCSTestContext context,
+            SRUSimpleClient client, FCSTestHandler handler)
+            throws SRUClientException;
+
+
+    protected FCSTestResult makeSuccess() {
+        return new FCSTestResult(this, FCSTestResult.Code.SUCCESS,
+                "The test case was processed successfully");
+    }
+
+
+    protected FCSTestResult makeWarning(String message) {
+        return new FCSTestResult(this, FCSTestResult.Code.WARNING, message);
+    }
+
+
+    protected FCSTestResult makeWarningUnexpectedDiagnostics() {
+        return new FCSTestResult(this, FCSTestResult.Code.WARNING,
+                "One or more unexpected diagnostic reported by endpoint");
+    }
+
+
+    protected FCSTestResult makeGenericError() {
+        return new FCSTestResult(this, FCSTestResult.Code.ERROR,
+                "Something went wrong");
+    }
+
+
+    protected FCSTestResult makeError(String message) {
+        return new FCSTestResult(this, FCSTestResult.Code.ERROR, message);
+    }
+
+
+    protected FCSTestResult makeError(List<LogRecord> records) {
+        return new FCSTestResult(this, FCSTestResult.Code.ERROR,
+                "An error occurred while processing the test case", records);
+    }
+
+
+    protected FCSTestResult makeErrorNoDiagnostic(String diagnostic) {
+        return new FCSTestResult(this, FCSTestResult.Code.ERROR, String.format(
+                "Endpoint did not report expected diagnostic: %s", diagnostic));
+    }
+
+} // class FCSTest
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestCase.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestCase.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestCase.java	(revision 7116)
@@ -0,0 +1,30 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FCSTestCase {
+    public int priority();
+    public FCSTestProfile[] profiles();
+} // annotation FCSTestCase
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestContext.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestContext.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestContext.java	(revision 7116)
@@ -0,0 +1,172 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import org.apache.commons.lang.RandomStringUtils;
+
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUVersion;
+import eu.clarin.sru.client.fcs.ClarinFCSClientBuilder;
+import eu.clarin.sru.client.fcs.ClarinFCSEndpointDescriptionParser;
+
+
+public class FCSTestContext {
+    private final FCSTestProfile profile;
+    private final String baseURI;
+    private final String userSearchTerm;
+    private final boolean strictMode;
+    private final int connectTimeout;
+    private final int socketTimeout;
+    private final String randomSearchTerm =
+            RandomStringUtils.randomAlphanumeric(16);
+    private final String unicodeSearchTerm = "Ã¶Ã€ÃŒÃÃÃÃâ¬";
+    private SRUSimpleClient client;
+
+
+    public FCSTestContext(FCSTestProfile profile, String baseURI,
+            String userSearchTerm, boolean strictMode, int connectTimeout,
+            int socketTimeout) {
+        if (profile == null) {
+            throw new NullPointerException("profile == null");
+        }
+        this.profile = profile;
+        if (baseURI == null) {
+            throw new NullPointerException("baseURI == null");
+        }
+        this.baseURI = baseURI;
+        this.connectTimeout = connectTimeout;
+        this.socketTimeout = socketTimeout;
+        this.strictMode = strictMode;
+        if (userSearchTerm.indexOf(' ') != -1) {
+            this.userSearchTerm = "\"" + userSearchTerm + "\"";
+        } else {
+            this.userSearchTerm = userSearchTerm;
+        }
+    }
+
+
+    public void init() {
+        if (client != null) {
+            throw new IllegalStateException("already initialized!");
+        }
+
+        ClarinFCSClientBuilder builder = null;
+        switch (profile) {
+        case CLARIN_FCS_1_0:
+            builder = new ClarinFCSClientBuilder()
+                .addDefaultDataViewParsers()
+                .setDefaultSRUVersion(SRUVersion.VERSION_1_2)
+                .unknownDataViewAsString()
+                .registerExtraResponseDataParser(
+                        new ClarinFCSEndpointDescriptionParser());
+            if (!strictMode) {
+                builder.enableLegacySupport();
+            }
+            break;
+        case CLARIN_FCS_2_0:
+            builder = new ClarinFCSClientBuilder()
+                .addDefaultDataViewParsers()
+                .setDefaultSRUVersion(SRUVersion.VERSION_2_0)
+                .unknownDataViewAsString()
+                .registerExtraResponseDataParser(
+                        new ClarinFCSEndpointDescriptionParser());
+            break;
+        case CLARIN_FCS_LEGACY:
+            builder = new ClarinFCSClientBuilder()
+                .addDefaultDataViewParsers()
+                .setDefaultSRUVersion(SRUVersion.VERSION_1_2)
+                .unknownDataViewAsString()
+                .enableLegacySupport()
+                .enableFullLegacyCompatMode();
+            break;
+        }
+        client = builder.buildSimpleClient();
+    }
+
+
+    public FCSTestProfile getProfile() {
+        return profile;
+    }
+
+
+    public String getBaseURI() {
+        return baseURI;
+    }
+
+
+    public boolean isStrictMode() {
+        return strictMode;
+    }
+
+
+    public int getConnectTimeout() {
+        return connectTimeout;
+    }
+
+
+    public int getSocketTimeout() {
+        return socketTimeout;
+    }
+
+
+    public String getUserSearchTerm() {
+        return userSearchTerm;
+    }
+
+
+    public String getRandomSearchTerm() {
+        return randomSearchTerm;
+    }
+
+
+    public String getUnicodeSearchTerm() {
+        return unicodeSearchTerm;
+    }
+
+
+    public SRUSimpleClient getClient() {
+        return client;
+    }
+
+
+    public SRUExplainRequest createExplainRequest() {
+        SRUExplainRequest request =
+                new SRUExplainRequest(baseURI.toString());
+        request.setStrictMode(strictMode);
+        return request;
+    }
+
+
+    public SRUScanRequest createScanRequest() {
+        SRUScanRequest request =
+                new SRUScanRequest(baseURI.toString());
+        request.setStrictMode(strictMode);
+        return request;
+    }
+
+
+    public SRUSearchRetrieveRequest createSearchRetrieveRequest() {
+        SRUSearchRetrieveRequest request =
+                new SRUSearchRetrieveRequest(baseURI.toString());
+        request.setStrictMode(strictMode);
+        return request;
+    }
+
+} // class FCSTestContext
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestHandler.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestHandler.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestHandler.java	(revision 7116)
@@ -0,0 +1,172 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUDefaultHandlerAdapter;
+import eu.clarin.sru.client.SRUDiagnostic;
+import eu.clarin.sru.client.SRURecordData;
+import eu.clarin.sru.client.SRUSurrogateRecordData;
+import eu.clarin.sru.client.SRUWhereInList;
+
+
+public class FCSTestHandler extends SRUDefaultHandlerAdapter {
+    public static class Record {
+        private final String identifier;
+        private final int position;
+        private final SRURecordData data;
+
+
+        public Record(String identifier, int position, SRURecordData data) {
+            this.identifier = identifier;
+            this.position = position;
+            this.data = data;
+        }
+
+
+        public String getIdentifier() {
+            return identifier;
+        }
+
+
+        public int getPosition() {
+            return position;
+        }
+
+
+        public SRURecordData getData() {
+            return data;
+        }
+    }
+
+    public static class SurrogateDiagnostic implements SRURecordData {
+        private final SRUDiagnostic diagnostic;
+
+
+        public SurrogateDiagnostic(SRUDiagnostic diagnostic) {
+            this.diagnostic = diagnostic;
+        }
+
+
+        @Override
+        public boolean isTransient() {
+            return false;
+        }
+
+
+        @Override
+        public String getRecordSchema() {
+            return SRUSurrogateRecordData.RECORD_SCHEMA;
+        }
+
+
+        public SRUDiagnostic getDiagnostic() {
+            return diagnostic;
+        }
+
+
+        public boolean isDiagnostic(String uri) {
+            if (uri == null) {
+                throw new NullPointerException("uri == null");
+            }
+            return uri.equals(diagnostic.getURI());
+        }
+    }
+
+    private List<String> termList = new ArrayList<String>();
+    private List<Record> recordList = new ArrayList<Record>(512);
+    private List<SRUDiagnostic> diagnostics;
+
+
+    @Override
+    public void onDiagnostics(List<SRUDiagnostic> diagnostics)
+            throws SRUClientException {
+        if (this.diagnostics == null) {
+            this.diagnostics = diagnostics;
+        } else {
+            this.diagnostics.addAll(diagnostics);
+        }
+    }
+
+
+    @Override
+    public void onTerm(String value, int numberOfRecords, String displayTerm,
+            SRUWhereInList whereInList) throws SRUClientException {
+        termList.add(value);
+    }
+
+
+    @Override
+    public void onRecord(String identifier, int position, SRURecordData data)
+            throws SRUClientException {
+        recordList.add(new Record(identifier, position, data));
+    }
+
+
+    @Override
+    public void onSurrogateRecord(String identifier, int position,
+            SRUDiagnostic data) throws SRUClientException {
+        recordList.add(new Record(identifier, position,
+                new SurrogateDiagnostic(data)));
+    }
+
+
+    void reset() {
+        termList.clear();
+        recordList.clear();
+        this.diagnostics = null;
+    }
+
+
+    public boolean findDiagnostic(String uri) {
+        if (uri == null) {
+            throw new NullPointerException("uri == null");
+        }
+        if (diagnostics != null) {
+            for (SRUDiagnostic diagnostic : diagnostics) {
+                if (uri.equals(diagnostic.getURI())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    public int getDiagnosticCount() {
+        return diagnostics != null ? diagnostics.size() : 0;
+    }
+
+
+    public List<String> getTerms() {
+        return termList;
+    }
+
+
+    public int getRecordCount() {
+        return recordList.size();
+    }
+
+
+    public List<Record> getRecords() {
+        return recordList;
+    }
+
+} // class SRUTestHandler
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestProfile.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestProfile.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestProfile.java	(revision 7116)
@@ -0,0 +1,43 @@
+/**
+ * This software is copyright (c) 2013-2016 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+public enum FCSTestProfile {
+    CLARIN_FCS_1_0(0), CLARIN_FCS_2_0(1), CLARIN_FCS_LEGACY(2);
+
+    final int id;
+
+
+    private FCSTestProfile(final int id) {
+        this.id = id;
+    }
+
+
+    public String toDisplayString() {
+        switch (this) {
+        case CLARIN_FCS_1_0:
+            return "CLARIN FCS 1.0";
+        case CLARIN_FCS_2_0:
+            return "CLARIN FCS 2.0";
+        case CLARIN_FCS_LEGACY:
+            return "Legacy FCS";
+        default:
+            return "";
+        }
+    }
+
+} // enum FCSTestProfile
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestResult.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestResult.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/FCSTestResult.java	(revision 7116)
@@ -0,0 +1,87 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester;
+
+import java.util.List;
+import java.util.logging.LogRecord;
+
+
+public class FCSTestResult {
+    public static enum Code {
+        SUCCESS, WARNING, ERROR
+    }
+
+    private final FCSTest testCase;
+    private final Code code;
+    private final String message;
+    private List<LogRecord> records;
+
+
+    public FCSTestResult(FCSTest testCase, Code code, String message,
+            List<LogRecord> records) {
+        this.testCase = testCase;
+        this.code = code;
+        this.message = message;
+        this.records = records;
+    }
+
+
+    public FCSTestResult(FCSTest testCase, Code code, String message) {
+        this(testCase, code, message, null);
+    }
+
+
+    public FCSTestResult(FCSTest testCase, Code code) {
+        this(testCase, code, null, null);
+    }
+
+
+    public void setLogRecords(List<LogRecord> records) {
+        this.records = records;
+    }
+
+
+    public Code getCode() {
+        return code;
+    }
+
+
+    public List<LogRecord> getLogRecords() {
+        return records;
+    }
+
+
+    public String getTestCaseName() {
+        return testCase.getName();
+    }
+
+
+    public String getTestCaseDescription() {
+        return testCase.getDescription();
+    }
+
+
+    public String getActualResultMessage() {
+        return message;
+    }
+
+
+    public String getExpectedResultMessage() {
+        return testCase.getExpected();
+    }
+
+} // class SRUTestResult
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain1.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain1.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain1.java	(revision 7116)
@@ -0,0 +1,63 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 1000, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestExplain1 extends FCSTest {
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Regular explain request using default version";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "No errors or diagnostics";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUExplainRequest req = context.createExplainRequest();
+        client.explain(req, handler);
+        return handler.getDiagnosticCount() == 0 ? makeSuccess()
+                : makeWarningUnexpectedDiagnostics();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain2.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain2.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain2.java	(revision 7116)
@@ -0,0 +1,66 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 1010, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestExplain2 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'operation' and 'version' arguments";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "An plain explain response with SRU version choosen by server";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUExplainRequest req = context.createExplainRequest();
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_OPERATION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return makeSuccess();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain3.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain3.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain3.java	(revision 7116)
@@ -0,0 +1,63 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=1020, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestExplain3 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'version' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "An plain explain response with SRU version choosen by server";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUExplainRequest req = context.createExplainRequest();
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return makeSuccess();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain4.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain4.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain4.java	(revision 7116)
@@ -0,0 +1,63 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=1040, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestExplain4 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain without 'operation' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "An plain explain response with SRU version argument";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUExplainRequest req = context.createExplainRequest();
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_OPERATION,
+                SRUExplainRequest.MALFORMED_OMIT);
+        client.explain(req, handler);
+        return makeSuccess();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain5.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain5.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestExplain5.java	(revision 7116)
@@ -0,0 +1,65 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUExplainRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=1050, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestExplain5 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Explain";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Explain with invalid value for 'version' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/5\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUExplainRequest req = context.createExplainRequest();
+        req.setExtraRequestData(SRUExplainRequest.X_MALFORMED_VERSION,
+                "9.9");
+        client.explain(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/5")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/5");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan1.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan1.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan1.java	(revision 7116)
@@ -0,0 +1,66 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=2000, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestScan1 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan with missing 'scanClause' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/7\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUScanRequest req = context.createScanRequest();
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_SCAN_CLAUSE,
+                SRUScanRequest.MALFORMED_OMIT);
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/7")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/7");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan2.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan2.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan2.java	(revision 7116)
@@ -0,0 +1,77 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import java.util.List;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=2010, profiles = {
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestScan2 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan on 'fcs.resource = root'";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "One or more terms returned within scan response";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUScanRequest req = context.createScanRequest();
+        req.setScanClause("fcs.resource=root");
+        client.scan(req, handler);
+
+        if (handler.findDiagnostic("info:srw/diagnostic/1/4")) {
+            return makeWarning("Endpoint does not support 'scan' operation");
+        } else {
+            if (handler.getDiagnosticCount() == 0) {
+                List<String> terms = handler.getTerms();
+                if (terms.size() >= 1) {
+                    return makeSuccess();
+                } else {
+                    return makeWarning("Scan on 'fcs.resource = root' should yield at least one collection");
+                }
+            } else {
+                return makeWarningUnexpectedDiagnostics();
+            }
+        }
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan3.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan3.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan3.java	(revision 7116)
@@ -0,0 +1,79 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import java.util.List;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=2030, profiles = {
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestScan3 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan on 'fcs.resource = root' with 'maximumTerms' with value 1";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Exactly one term returned within scan response";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUScanRequest req = context.createScanRequest();
+        req.setScanClause("fcs.resource=root");
+        req.setMaximumTerms(1);
+        client.scan(req, handler);
+
+        if (handler.getDiagnosticCount() > 0) {
+            if (handler.findDiagnostic("info:srw/diagnostic/1/4")) {
+                return makeWarning("Endpoint does not support 'scan' operation");
+            } else {
+                return makeWarningUnexpectedDiagnostics();
+            }
+        } else {
+            List<String> terms = handler.getTerms();
+            if (terms.size() == 1) {
+                return makeSuccess();
+            } else if (terms.size() > 1) {
+                return makeWarning("Endpoint did not honor 'maximumTerms' argument");
+            }
+        }
+        return makeGenericError();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan4.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan4.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan4.java	(revision 7116)
@@ -0,0 +1,67 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=2040, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestScan4 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan on 'fcs.resource = root' with bad 'maximumTerms' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUScanRequest req = context.createScanRequest();
+        req.setScanClause("fcs.resource=root");
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_MAXIMUM_TERMS,
+                "invalid");
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan5.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan5.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestScan5.java	(revision 7116)
@@ -0,0 +1,67 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUScanRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=2050, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestScan5 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "Scan";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Scan on 'fcs.resource = root' with bad 'responsePosition' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUScanRequest req = context.createScanRequest();
+        req.setScanClause("fcs.resource=root");
+        req.setExtraRequestData(SRUScanRequest.X_MALFORMED_RESPONSE_POSITION,
+                "invalid");
+        client.scan(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch1.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch1.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch1.java	(revision 7116)
@@ -0,0 +1,67 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=3000, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch1 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search for random string";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "No errors or diagnostics (and zero or more records)";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        client.searchRetrieve(req, handler);
+        return handler.getDiagnosticCount() == 0
+                ? makeSuccess()
+                : makeWarningUnexpectedDiagnostics();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch10.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch10.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch10.java	(revision 7116)
@@ -0,0 +1,68 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=3100, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch10 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search to provoke first record position out of range diagnostic";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/61\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setStartRecord(Integer.MAX_VALUE);
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/61")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/61");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch11.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch11.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch11.java	(revision 7116)
@@ -0,0 +1,67 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+@FCSTestCase(priority=3200, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch11 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search for string with non-ASCII characters encoded as UTF-8";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "No errors or diagnostics (and zero or more records)";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getUnicodeSearchTerm());
+        client.searchRetrieve(req, handler);
+        return handler.getDiagnosticCount() == 0
+                ? makeSuccess()
+                : makeWarningUnexpectedDiagnostics();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch12.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch12.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch12.java	(revision 7116)
@@ -0,0 +1,158 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestHandler.SurrogateDiagnostic;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUDiagnostic;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.SRUSurrogateRecordData;
+import eu.clarin.sru.client.fcs.DataView;
+import eu.clarin.sru.client.fcs.LegacyClarinFCSRecordData;
+import eu.clarin.sru.client.fcs.Resource;
+import eu.clarin.sru.client.fcs.Resource.ResourceFragment;
+
+@FCSTestCase(priority=3600, profiles = {
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+@SuppressWarnings("deprecation")
+public class TestSearch12 extends FCSTest {
+    private static final String FCS_RECORD_SCHEMA = "http://clarin.eu/fcs/1.0";
+    private static final String MIME_TYPE_KWIC =
+            "application/x-clarin-fcs-kwic+xml";
+
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search for user specified search term and " +
+                "requesting endpoint to return results in CLARIN-FCS " +
+                "record schema '" + FCS_RECORD_SCHEMA + "'";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting at least one record in CLARIN-FCS legacy record " +
+                "schema (without any surrogate diagnostics)";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getUserSearchTerm());
+        req.setRecordSchema(FCS_RECORD_SCHEMA);
+        req.setMaximumRecords(5);
+        client.searchRetrieve(req, handler);
+        if (handler.findDiagnostic("info:srw/diagnostic/1/66")) {
+            return makeError("Endpoint claims to not " +
+                    "support FCS record schema (" + FCS_RECORD_SCHEMA + ")");
+        } else if (handler.getRecordCount() == 0) {
+            return makeWarning("Endpoint has no results " +"" +
+                    "for search term \"" + context.getUserSearchTerm() +
+                    "\". Please supply a different search term.");
+        } else {
+            for (FCSTestHandler.Record record : handler.getRecords()) {
+                String recordSchema = record.getData().getRecordSchema();
+                if (SRUSurrogateRecordData.RECORD_SCHEMA.equals(recordSchema)) {
+                    final SurrogateDiagnostic data =
+                            (SurrogateDiagnostic) record.getData();
+                    final SRUDiagnostic d = data.getDiagnostic();
+
+                    if (data.isDiagnostic("info:srw/diagnostic/1/67")) {
+                        return makeError("Endpoint cannot render record in " +
+                                "CLARIN-FCS record format and returned " +
+                                "surrogate diagnostic \"info:srw/diagnostic/1/67\" " +
+                                "instead.");
+                    } else if (data.isDiagnostic("info:clarin/sru/diagnostic/2")) {
+                        return makeError("Endpoint sent one or more records with record " +
+                                        "schema of '" + d.getDetails() +
+                                        "' instead of '" + FCS_RECORD_SCHEMA +
+                                        "'.");
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        sb.append("Endpoint returned unexpected surrgogate ")
+                                .append("diagnostic \"")
+                                .append(d.getURI()).append("\"");
+                        if (d.getDetails() != null) {
+                            sb.append("; details = \"")
+                                .append(d.getDetails()).append("\"");
+                        }
+                        if (d.getMessage() != null) {
+                            sb.append("; message = \"")
+                                .append(d.getMessage()).append("\"");
+                        }
+                        return makeError(sb.toString());
+                    }
+                } else if (FCS_RECORD_SCHEMA.equals(recordSchema)) {
+                    final LegacyClarinFCSRecordData data =
+                            (LegacyClarinFCSRecordData) record.getData();
+                    final Resource resource = data.getResource();
+                    boolean foundKwic = false;
+                    if (resource.hasDataViews()) {
+                        for (DataView dataView : resource.getDataViews()) {
+                            if (dataView.isMimeType(MIME_TYPE_KWIC)) {
+                                foundKwic = true;
+                            }
+                        }
+                    }
+
+                    if (resource.hasResourceFragments()) {
+                        for (ResourceFragment fragment : resource.getResourceFragments()) {
+                            for (DataView dataView : fragment.getDataViews()) {
+                                if (dataView.isMimeType(MIME_TYPE_KWIC)) {
+                                    foundKwic = true;
+                                }
+                            }
+                        }
+                    }
+                    if (!foundKwic) {
+                        return makeError("Endpoint did not provide mandatory KWIC dataview in results");
+                    }
+
+                } else {
+                    return makeError("Endpoint does not supply results in FCS record schema (" + FCS_RECORD_SCHEMA + "\"");
+                }
+            }
+            if (handler.getRecordCount() > req.getMaximumRecords()) {
+                return makeError("Endpoint did not honor upper requested limit for " +
+                                "\"maximumRecords\" parameter (up to " +
+                                req.getMaximumRecords() +
+                                " records where requested and endpoint delivered " +
+                                handler.getRecordCount() + " results)");
+            }
+            return makeSuccess();
+        }
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch2.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch2.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch2.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+import eu.clarin.sru.client.fcs.ClarinFCSRecordData;
+
+@FCSTestCase(priority=Integer.MIN_VALUE, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch2 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Searching for random string with CLARIN FCS " +
+                "record schema '" + ClarinFCSRecordData.RECORD_SCHEMA + "'";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "No errors or diagnostics";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA);
+        client.searchRetrieve(req, handler);
+        return handler.getDiagnosticCount() == 0
+                ? makeSuccess()
+                : makeWarningUnexpectedDiagnostics();
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch3.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch3.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch3.java	(revision 7116)
@@ -0,0 +1,66 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3030, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch3 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with missing 'query' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/7\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setExtraRequestData(SRUSearchRetrieveRequest.X_MALFORMED_QUERY,
+                SRUSearchRetrieveRequest.MALFORMED_OMIT);
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/7")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/7");
+    }
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch4.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch4.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch4.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3040, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch4 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid 'recordPacking' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/71\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setExtraRequestData(
+                SRUSearchRetrieveRequest.X_MALFORMED_RECORD_XML_ESCAPING, "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/71")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/71");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch5.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch5.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch5.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3050, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch5 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value for 'startRecord' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setExtraRequestData(
+                SRUSearchRetrieveRequest.X_MALFORMED_START_RECORD, "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch6.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch6.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch6.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3060, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch6 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value of 0 for 'startRecord' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setExtraRequestData(
+                SRUSearchRetrieveRequest.X_MALFORMED_START_RECORD, "0");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch7.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch7.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch7.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3070, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch7 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value for 'maximumRecords' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setExtraRequestData(
+                SRUSearchRetrieveRequest.X_MALFORMED_MAXIMUM_RECORDS, "invalid");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch8.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch8.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch8.java	(revision 7116)
@@ -0,0 +1,70 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3080, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch8 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid value of -1 for 'maximumRecords' argument";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/6\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                context.getRandomSearchTerm());
+        req.setExtraRequestData(
+                SRUSearchRetrieveRequest.X_MALFORMED_MAXIMUM_RECORDS, "-1");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/6")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/6");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch9.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch9.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/tests/TestSearch9.java	(revision 7116)
@@ -0,0 +1,68 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.tests;
+
+import eu.clarin.fcs.tester.FCSTest;
+import eu.clarin.fcs.tester.FCSTestCase;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestHandler;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.sru.client.SRUClientConstants;
+import eu.clarin.sru.client.SRUClientException;
+import eu.clarin.sru.client.SRUSearchRetrieveRequest;
+import eu.clarin.sru.client.SRUSimpleClient;
+
+
+@FCSTestCase(priority = 3090, profiles = {
+        FCSTestProfile.CLARIN_FCS_1_0,
+        FCSTestProfile.CLARIN_FCS_2_0,
+        FCSTestProfile.CLARIN_FCS_LEGACY
+})
+public class TestSearch9 extends FCSTest {
+
+    @Override
+    public String getName() {
+        return "SearchRetrieve";
+    }
+
+
+    @Override
+    public String getDescription() {
+        return "Search with invalid query";
+    }
+
+
+    @Override
+    public String getExpected() {
+        return "Expecting diagnostic \"info:srw/diagnostic/1/10\"";
+    }
+
+
+    @Override
+    public FCSTestResult perform(FCSTestContext context, SRUSimpleClient client,
+            FCSTestHandler handler) throws SRUClientException {
+        SRUSearchRetrieveRequest req = context.createSearchRetrieveRequest();
+        req.setQuery(SRUClientConstants.QUERY_TYPE_CQL,
+                "\"" + context.getRandomSearchTerm() + "\" =");
+        client.searchRetrieve(req, handler);
+        return handler.findDiagnostic("info:srw/diagnostic/1/10")
+                ? makeSuccess()
+                : makeErrorNoDiagnostic("info:srw/diagnostic/1/10");
+    }
+
+}
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/NoResultsView.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/NoResultsView.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/NoResultsView.java	(revision 7116)
@@ -0,0 +1,39 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.ui;
+
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.VerticalLayout;
+
+@SuppressWarnings("serial")
+public class NoResultsView extends VerticalLayout {
+
+    public NoResultsView() {
+        setMargin(true);
+        setSizeFull();
+
+        final Label message =
+                new Label("<h2>No results available.</h2>", ContentMode.HTML);
+        message.setSizeUndefined();
+        message.setStyleName("no-results-label");
+        addComponent(message);
+        setComponentAlignment(message, Alignment.MIDDLE_CENTER);
+    }
+
+} // class NoResultsView
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ProgressWindow.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ProgressWindow.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ProgressWindow.java	(revision 7116)
@@ -0,0 +1,117 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.ui;
+
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.ProgressBar;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.themes.Reindeer;
+
+
+@SuppressWarnings("serial")
+public class ProgressWindow extends Window {
+    private final Label messageLabel;
+    private final ProgressBar progress;
+    private float maximum = -1;
+
+
+    public ProgressWindow(String caption, String message) {
+        super(caption);
+        setModal(true);
+        setReadOnly(true);
+        setResizable(false);
+        setStyleName(Reindeer.WINDOW_LIGHT);
+
+        VerticalLayout layout = new VerticalLayout();
+        layout.setMargin(true);
+        layout.setSpacing(true);
+        layout.setSizeUndefined();
+
+        progress = new ProgressBar();
+        progress.setIndeterminate(false);
+        progress.setWidth("350px");
+        layout.addComponent(progress);
+        layout.setComponentAlignment(progress, Alignment.MIDDLE_CENTER);
+
+        messageLabel = new Label(message);
+        messageLabel.setSizeUndefined();
+        layout.addComponent(messageLabel);
+        layout.setComponentAlignment(messageLabel, Alignment.MIDDLE_CENTER);
+        setContent(layout);
+    }
+
+
+    @Override
+    public void attach() {
+        super.attach();
+        ((TesterUI) UI.getCurrent()).enablePolling();
+    }
+
+
+    @Override
+    public void detach() {
+        ((TesterUI) UI.getCurrent()).disablePolling();
+        super.detach();
+    }
+
+
+    @Override
+    public void close() {
+        UI.getCurrent().removeWindow(ProgressWindow.this);
+    }
+
+
+//    public void updateStatus(String message) {
+//      if (message != null) {
+//          messageLabel.setValue(message);
+//      }
+//      forceRepaint();
+//    }
+
+
+    public void updateMaximum(int maximum) {
+        if ((maximum > 0) && (this.maximum < 0)) {
+            this.maximum = maximum;
+        }
+        forceRepaint();
+    }
+
+
+    public void updateStatus(String message, float done) {
+        if ((maximum > 0) && (done >= 0)) {
+            progress.setValue(Math.min(done / maximum, 1.0f));
+        }
+        if (message != null) {
+            messageLabel.setValue(message);
+        }
+        forceRepaint();
+    }
+
+
+    private void forceRepaint() {
+        markAsDirtyRecursive();
+        try {
+            Thread.sleep(125);
+        } catch (InterruptedException e) {
+            /* IGNORE */
+        }
+    }
+
+} // class ProgressWindow
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ResultsView.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ResultsView.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/ResultsView.java	(revision 7116)
@@ -0,0 +1,228 @@
+/**
+ * This software is copyright (c) 2013 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.ui;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.vaadin.jouni.animator.AnimatorProxy;
+import org.vaadin.jouni.animator.shared.AnimType;
+
+import com.vaadin.event.MouseEvents;
+import com.vaadin.server.Resource;
+import com.vaadin.server.ThemeResource;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Panel;
+import com.vaadin.ui.VerticalLayout;
+
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestResult;
+import eu.clarin.fcs.tester.FCSTestResult.Code;
+
+@SuppressWarnings("serial")
+public class ResultsView extends VerticalLayout {
+    private static final Logger logger =
+            LoggerFactory.getLogger(ResultsView.class);
+    private static final Resource ICON_SUCCESS =
+            new ThemeResource("icons/result_success.png");
+    private static final Resource ICON_WARNING =
+            new ThemeResource("icons/result_warning.png");
+    private static final Resource ICON_ERROR   =
+            new ThemeResource("icons/result_error.png");
+    private static final DateTimeFormatter dateFmt =
+            DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
+
+
+    public ResultsView(FCSTestContext context, List<FCSTestResult> results) {
+        setMargin(true);
+
+        logger.debug("render results");
+
+        int count_success = 0;
+        int count_warning = 0;
+        int count_error   = 0;
+        for (FCSTestResult result : results) {
+            switch (result.getCode()) {
+            case SUCCESS:
+                count_success++;
+                break;
+            case WARNING:
+                count_warning++;
+                break;
+            case ERROR:
+                count_error++;
+                break;
+            } // switch
+        } // for
+
+        final String profileDisplayString =
+                context.getProfile().toDisplayString();
+
+        final Label resultsHeadline = new Label(String.format(
+                "<h2>Result for %s (using test profile %s):</h2>",
+                StringEscapeUtils.escapeXml(context.getBaseURI()),
+                profileDisplayString), ContentMode.HTML);
+        resultsHeadline.setStyleName("result-overall-headline");
+        addComponent(resultsHeadline);
+
+        Label resultOverall;
+        if (count_error > 0) {
+            String msg;
+            if (count_error == 1) {
+                msg = "The endpoint fails to pass in one test.";
+            } else {
+                msg = String.format("The endpoint fails to pass in %d tests.", count_error);
+            }
+            resultOverall = new Label(msg);
+            resultOverall.setIcon(ICON_ERROR);
+        } else if (count_warning > 0) {
+            String msg;
+            if (count_warning == 1) {
+                msg = "The endpoint has minor problems to pass one test.";
+            } else {
+                msg = String.format("The endpoint has minor problems to pass %d tests.", count_warning);
+            }
+            resultOverall = new Label(msg);
+            resultOverall.setIcon(getIconForCode(Code.WARNING));
+        } else {
+            resultOverall = new Label("The endpoint passed all tests successfully.");
+            resultOverall.setIcon(getIconForCode(Code.SUCCESS));
+        }
+        resultOverall.setStyleName("result-overall-label");
+        addComponent(resultOverall);
+
+        final Label resultsOverview = new Label(
+                String.format("Success: %d,  Warnings: %d, Errors: %d",
+                        count_success, count_warning, count_error));
+        addComponent(resultsOverview);
+
+        final Label resultListHeadline =
+                new Label("<h2>Results for individual test cases:</h2>", ContentMode.HTML);
+        resultListHeadline.setStyleName("result-individual-headline");
+        addComponent(resultListHeadline);
+
+        for (FCSTestResult result : results) {
+            final CssLayout content = new CssLayout();
+            content.addStyleName("result-panel-content");
+
+            final Label expectedResultLabel = new Label(
+                    String.format("<span class=\"result-label-caption\">Expected result:</span> %s",
+                            StringEscapeUtils.escapeXml(result.getExpectedResultMessage())),
+                    ContentMode.HTML);
+            expectedResultLabel.setStyleName("result-label");
+            content.addComponent(expectedResultLabel);
+
+            final Label actualResultLabel = new Label(
+                    String.format("<span class=\"result-label-caption\">Actual result:</span> <em>%s</em>",
+                            StringEscapeUtils.escapeXml(result.getActualResultMessage())),
+                    ContentMode.HTML);
+            actualResultLabel.setStyleName("result-label");
+            content.addComponent(actualResultLabel);
+
+            if (result.getLogRecords() != null) {
+                final StringBuilder sb = new StringBuilder();
+                for (LogRecord record : result.getLogRecords()) {
+                    String clazz = null;
+                    if (record.getLevel() == Level.SEVERE) {
+                        clazz = "error";
+                    } else if (record.getLevel() == Level.WARNING) {
+                        clazz = "warning";
+                    }
+                    if (clazz != null) {
+                        sb.append("<span class=\"").append(clazz).append("\">");
+                    }
+                    sb.append("<span class= \"timestamp\">")
+                        .append("[")
+                        .append(StringEscapeUtils.escapeXml(dateFmt.print(record.getMillis())))
+                        .append("] ")
+                        .append("<span>")
+                        .append(StringEscapeUtils.escapeXml(record.getMessage()));
+                    if (clazz != null) {
+                        sb.append("</span>");
+                    }
+                    sb.append("<br />");
+                }
+
+                final Label logMessages =
+                        new Label(sb.toString(), ContentMode.HTML);
+                logMessages.setCaption("Debug messages:");
+                logMessages.setStyleName("result-panel-content-messages");
+                content.addComponent(logMessages);
+            }
+
+            final String caption = String.format("[%s] %s: %s",
+                    profileDisplayString,
+                    result.getTestCaseName(),
+                    result.getTestCaseDescription());
+
+            final AnimatorProxy proxy = new AnimatorProxy();
+            proxy.setStyleName("animator");
+            final CssLayout c = new CssLayout();
+            c.setWidth("100%");
+            c.addComponent(proxy);
+
+            final Panel panel =
+                    new Panel(StringEscapeUtils.escapeXml(caption), c);
+            panel.addStyleName("result-panel");
+            panel.setIcon(getIconForCode(result.getCode()));
+            panel.addClickListener(new MouseEvents.ClickListener() {
+                boolean open = false;
+
+                @Override
+                public void click(MouseEvents.ClickEvent event) {
+                    if (open) {
+                        proxy.animate(content, AnimType.ROLL_UP_CLOSE_REMOVE);
+                        panel.removeStyleName("result-panel-opened");
+                        open = false;
+                    } else {
+                        if ((content.getParent() == null) || (content.getParent() != c)) {
+                            c.addComponent(content);
+                        }
+                        proxy.animate(content, AnimType.ROLL_DOWN_OPEN);
+                        panel.addStyleName("result-panel-opened");
+                        open = true;
+                    }
+                }
+            });
+            addComponent(panel);
+        } // for
+    }
+
+
+    private static Resource getIconForCode(FCSTestResult.Code code) {
+        if (code != null) {
+            switch (code) {
+            case SUCCESS:
+                return ICON_SUCCESS;
+            case WARNING:
+                return ICON_WARNING;
+            case ERROR:
+                return ICON_ERROR;
+            }
+        }
+        return null;
+    }
+
+} // class TestResultView
Index: /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/TesterUI.java
===================================================================
--- /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/TesterUI.java	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/java/eu/clarin/fcs/tester/ui/TesterUI.java	(revision 7116)
@@ -0,0 +1,537 @@
+/**
+ * This software is copyright (c) 2013-2016 by
+ *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ * This is free software. You can redistribute it
+ * and/or modify it under the terms described in
+ * the GNU General Public License v3 of which you
+ * should have received a copy. Otherwise you can download
+ * it from
+ *
+ *   http://www.gnu.org/licenses/gpl-3.0.txt
+ *
+ * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
+ *
+ * @license http://www.gnu.org/licenses/gpl-3.0.txt
+ *  GNU General Public License v3
+ */
+package eu.clarin.fcs.tester.ui;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.vaadin.hene.popupbutton.PopupButton;
+
+import com.vaadin.annotations.PreserveOnRefresh;
+import com.vaadin.annotations.Theme;
+import com.vaadin.annotations.Title;
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.data.Validator;
+import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.UserError;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinServlet;
+import com.vaadin.shared.ui.MarginInfo;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Notification;
+import com.vaadin.ui.Panel;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.VerticalLayout;
+
+import eu.clarin.fcs.tester.FCSEndpointTester;
+import eu.clarin.fcs.tester.FCSTestContext;
+import eu.clarin.fcs.tester.FCSTestProfile;
+import eu.clarin.fcs.tester.FCSTestResult;
+
+
+@SuppressWarnings("serial")
+@PreserveOnRefresh
+@Theme("clarin")
+@Title("CLARIN-FCS endpoint conformance tester")
+@Widgetset("eu.clarin.fcs.tester.ui.Widgetset")
+public class TesterUI extends UI {
+    private static final String PARAM_DEFAULT_ENDPOINT_URI =
+            "eu.clarin.fcs.tester.defaultEndpointURI";
+    private static final String PARAM_DEFAULT_SEARCH_TERM =
+            "eu.clarin.fcs.tester.defaultSearchTerm";
+    private static final String COOKIE_NAME_ENDPOINT_URI =
+            "lastEndpointURI";
+    private static final String COOKIE_NAME_SEARCH_TERM =
+            "lastSearchTerm";
+    private static final Logger logger =
+            LoggerFactory.getLogger(TesterUI.class);
+    private static final ErrorMessage ERROR_INVALID_INPUT =
+            new UserError("Invalid input:");
+    private TextField uriField;
+    private TextField queryField;
+    private ComboBox profileCombo;
+    private CheckBox strictModeCheckbox;
+    private CheckBox performProbeCheckbox;
+    private ComboBox connectTimeoutCombo;
+    private ComboBox socketTimeoutCombo;
+    private Panel content;
+    private int pollingRequests = 0;
+
+
+    @Override
+    protected void init(VaadinRequest request) {
+        logger.debug("init(): remoteAddr={}, session={}",
+                request.getRemoteAddr(), request.getWrappedSession().getId());
+
+        final ServletContext ctx =
+                VaadinServlet.getCurrent().getServletContext();
+
+        final VerticalLayout layout = new VerticalLayout();
+        layout.setSizeFull();
+        layout.setMargin(true);
+        layout.setSpacing(false);
+        layout.setStyleName("small-margin");
+        setContent(layout);
+
+        final Label title = new Label(
+                "<h1>CLARIN-FCS endpoint conformance tester</h1>",
+                ContentMode.HTML);
+        title.setStyleName("header");
+        layout.addComponent(title);
+
+        /*
+         * input panel
+         */
+        final HorizontalLayout top = new HorizontalLayout();
+        top.setSpacing(true);
+        top.setMargin(new MarginInfo(false, true, false, true));
+        top.setWidth("100%");
+        top.setStyleName("small-margin");
+
+        final FormLayout inputForm = new FormLayout();
+        top.addComponent(inputForm);
+        top.setComponentAlignment(inputForm, Alignment.MIDDLE_LEFT);
+        top.setExpandRatio(inputForm, 1.0f);
+
+        uriField = new TextField("Endpoint BaseURI:");
+        uriField.setWidth("100%");
+        uriField.setInputPrompt("Please enter BaseURI of endpoint.");
+        uriField.setRequiredError("An endpoint BaseURI is required!");
+        uriField.setMaxLength(255);
+        uriField.setNullRepresentation("");
+        uriField.setRequired(true);
+        uriField.addValidator(new Validator() {
+            @Override
+            public void validate(Object value) throws InvalidValueException {
+                if (value != null) {
+                    String s = (String) value;
+                    s = s.trim();
+                    if (!s.isEmpty()) {
+                        try {
+                            new URL(s);
+                        } catch (MalformedURLException e) {
+                            throw new InvalidValueException("Invalid URI syntax!");
+                        }
+                    }
+                }
+            }
+        });
+        String defaultEndpointURI = getCookie(COOKIE_NAME_ENDPOINT_URI);
+        if (defaultEndpointURI == null) {
+            defaultEndpointURI =
+                    ctx.getInitParameter(PARAM_DEFAULT_ENDPOINT_URI);
+            if (defaultEndpointURI != null) {
+                defaultEndpointURI = defaultEndpointURI.trim();
+                if (defaultEndpointURI.isEmpty()) {
+                    defaultEndpointURI = null;
+                }
+            }
+        }
+
+        uriField.setValue(defaultEndpointURI);
+        inputForm.addComponent(uriField);
+
+        queryField = new TextField("Search Term:");
+        queryField.setWidth("100%");
+        queryField.setInputPrompt("Please enter a word that occurs at least once in you data.");
+        queryField.setMaxLength(64);
+        queryField.setNullRepresentation("");
+        queryField.setRequired(true);
+        queryField.setRequiredError("A search term is required!");
+
+        String defaultSearchTerm = getCookie(COOKIE_NAME_SEARCH_TERM);
+        if (defaultSearchTerm == null) {
+            defaultSearchTerm = ctx.getInitParameter(PARAM_DEFAULT_SEARCH_TERM);
+            if (defaultSearchTerm != null) {
+                defaultSearchTerm = defaultSearchTerm.trim();
+            }
+        }
+        queryField.setValue(defaultSearchTerm);
+        inputForm.addComponent(queryField);
+
+        final Button goButton = new Button("Go");
+        top.addComponent(goButton);
+        top.setComponentAlignment(goButton, Alignment.MIDDLE_LEFT);
+
+        /* options panel */
+        final VerticalLayout optionsContent = new VerticalLayout();
+        optionsContent.setMargin(true);
+        optionsContent.setSpacing(true);
+        optionsContent.setStyleName("small-margin");
+
+        profileCombo = new ComboBox("CLARIN-FCS test profile:");
+        profileCombo.addItem(PROFILE_AUTODETECT);
+        profileCombo.setItemCaption(PROFILE_AUTODETECT, "Auto detect");
+        profileCombo.addItem(PROFILE_FCS_2_0);
+        profileCombo.setItemCaption(PROFILE_FCS_2_0, "CLARIN-FCS 2.0");
+        profileCombo.addItem(PROFILE_FCS_1_0);
+        profileCombo.setItemCaption(PROFILE_FCS_1_0, "CLARIN-FCS 1.0");
+        profileCombo.addItem(PROFILE_FCS_LEGACY);
+        profileCombo.setItemCaption(PROFILE_FCS_LEGACY, "Legacy FCS");
+        profileCombo.select(PROFILE_AUTODETECT);
+        profileCombo.setNullSelectionAllowed(false);
+        profileCombo.setTextInputAllowed(false);
+        profileCombo.setWidth("100%");
+        optionsContent.addComponent(profileCombo);
+
+        strictModeCheckbox = new CheckBox("Perform checks in strict mode");
+        strictModeCheckbox.setWidth("100%");
+        strictModeCheckbox.setValue(Boolean.TRUE);
+        optionsContent.addComponent(strictModeCheckbox);
+
+        performProbeCheckbox = new CheckBox("Perform HTTP HEAD probe request");
+        performProbeCheckbox.setWidth("100%");
+        performProbeCheckbox.setValue(Boolean.TRUE);
+        optionsContent.addComponent(performProbeCheckbox);
+
+        connectTimeoutCombo = new ComboBox("Connect timeout:");
+        fillTimeoutCombo(connectTimeoutCombo);
+        connectTimeoutCombo.select(TIMEOUT_15_SECONDS);
+        connectTimeoutCombo.setNullSelectionAllowed(false);
+        connectTimeoutCombo.setTextInputAllowed(false);
+        connectTimeoutCombo.setWidth("100%");
+        optionsContent.addComponent(connectTimeoutCombo);
+
+        socketTimeoutCombo = new ComboBox("Socket timeout:");
+        fillTimeoutCombo(socketTimeoutCombo);
+        socketTimeoutCombo.select(TIMEOUT_30_SECONDS);
+        socketTimeoutCombo.setNullSelectionAllowed(false);
+        socketTimeoutCombo.setTextInputAllowed(false);
+        socketTimeoutCombo.setWidth("100%");
+        optionsContent.addComponent(socketTimeoutCombo);
+
+//        final Table x = new Table("Custom Request Parameters");
+//        x.setWidth("100%");
+//        x.setHeight("200px");
+//        // x.setIt
+//        IndexedContainer data = new IndexedContainer();
+//        data.addContainerProperty("KEY", String.class, "");
+//        data.addContainerProperty("VALUE", String.class, "");
+//
+//        Object id1 = data.addItem();
+//        data.getContainerProperty(id1, "KEY").setValue("key1");
+//        data.getContainerProperty(id1, "VALUE").setValue("value1");
+//
+//        x.setContainerDataSource(data);
+//        optionsContent.addComponent(x);
+//
+//        HorizontalLayout addLayout = new HorizontalLayout();
+//        addLayout.setSpacing(true);
+//        final TextField keyField = new TextField();
+//        addLayout.addComponent(keyField);
+//        final TextField valueField = new TextField();
+//        addLayout.addComponent(valueField);
+//        final Button addButton = new Button("Add");
+//        addLayout.addComponent(addButton);
+//        optionsContent.addComponent(addLayout);
+
+        final PopupButton optionsButton = new PopupButton("Options");
+        optionsButton.setContent(optionsContent);
+        top.addComponent(optionsButton);
+        top.setComponentAlignment(optionsButton, Alignment.MIDDLE_LEFT);
+        layout.addComponent(top);
+
+        /*
+         * main content
+         */
+        content = new Panel();
+        content.setSizeFull();
+        content.setStyleName("main-content");
+        layout.addComponent(content);
+        layout.setExpandRatio(content, 1.0f);
+
+        setNoResultsView();
+
+        /* setup up some behaviors */
+        goButton.addClickListener(new Button.ClickListener() {
+            @Override
+            public void buttonClick(ClickEvent event) {
+                boolean valid = true;
+                try {
+                    uriField.validate();
+                    uriField.setComponentError(null);
+                } catch (InvalidValueException e) {
+                    valid = false;
+                    uriField.setComponentError(ERROR_INVALID_INPUT);
+                }
+                try {
+                    queryField.validate();
+                    queryField.setComponentError(null);
+                } catch (InvalidValueException e) {
+                    valid = false;
+                    queryField.setComponentError(ERROR_INVALID_INPUT);
+                }
+
+                if (valid) {
+                    performAction();
+                }
+
+            }
+        });
+
+        uriField.focus();
+
+//        final Notification info =
+//                new Notification("The CLARIN-FCS endpoint conformance tester does" +
+//                        " not yet support the new FCS specification!",
+//                        Notification.Type.ERROR_MESSAGE);
+//        info.setDelayMsec(5000);
+//        info.setDescription("(Dismiss this message by clicking it)");
+//        info.show(Page.getCurrent());
+    }
+
+
+    @Override
+    public void detach() {
+        setPollInterval(-1);
+        super.detach();
+    }
+
+
+    void enablePolling() {
+        if (++pollingRequests > 0) {
+            setPollInterval(250);
+        }
+    }
+
+
+    void disablePolling() {
+        if (--pollingRequests <= 0) {
+            setPollInterval(-1);
+            pollingRequests = 0;
+        }
+    }
+
+
+    private void setNoResultsView() {
+        Component component = content.getContent();
+        if ((component == null) ||
+                !component.getClass().equals(NoResultsView.class)) {
+            content.setContent(new NoResultsView());
+        }
+    }
+
+
+    private void performAction() {
+        FCSTestProfile profile = null;
+        switch (((Integer) profileCombo.getValue()).intValue()) {
+        case PROFILE_FCS_1_0:
+            profile = FCSTestProfile.CLARIN_FCS_1_0;
+            break;
+        case PROFILE_FCS_2_0:
+            profile = FCSTestProfile.CLARIN_FCS_2_0;
+            break;
+        case PROFILE_FCS_LEGACY:
+            profile = FCSTestProfile.CLARIN_FCS_LEGACY;
+            break;
+        default:
+            /* DO NOTHING */
+        }
+
+        final String endpointURI = uriField.getValue();
+        final String searchTerm = queryField.getValue();
+        final boolean strictMode =
+                strictModeCheckbox.getValue().booleanValue();
+        final boolean performProbeRequest =
+                performProbeCheckbox.getValue().booleanValue();
+        final int connectTimeout =
+                ((Integer) connectTimeoutCombo.getValue()).intValue();
+        final int socketTimeout =
+                ((Integer) socketTimeoutCombo.getValue()).intValue();
+
+        setCookie(COOKIE_NAME_ENDPOINT_URI, endpointURI);
+        setCookie(COOKIE_NAME_SEARCH_TERM, searchTerm);
+
+        logger.info("perform check for: profile={}, endpointURI={} query={}, strict={}",
+                profile, endpointURI, searchTerm, (strictMode ? "yes" : "no"));
+
+        final FCSEndpointTester tester = FCSEndpointTester.getInstance();
+
+        final ProgressWindow progressWindow = new ProgressWindow(
+                "Checking SRU/CQL endpoint ....", "Initializing ...");
+        setNoResultsView();
+        addWindow(progressWindow);
+
+        final FCSEndpointTester.ProgressListener listener =
+                new FCSEndpointTester.ProgressListener() {
+            private int test = -1;
+            @Override
+            public void updateProgress(final String message) {
+                accessSynchronously(new Runnable() {
+                    @Override
+                    public void run() {
+                        progressWindow.updateStatus(message, test);
+                        if (test != -1) {
+                            test++;
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void updateMaximum(final int maximum) {
+                accessSynchronously(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (maximum > 0) {
+                            progressWindow.updateMaximum(maximum);
+                            test = 0;
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void onDone(final FCSTestContext context,
+                    final List<FCSTestResult> results) {
+                // force update of status bar
+                accessSynchronously(new Runnable() {
+                    @Override
+                    public void run() {
+                        progressWindow.updateStatus("Rendering results ...",
+                                Integer.MAX_VALUE);
+                    }
+                });
+
+                // render results
+                accessSynchronously(new Runnable() {
+                    @Override
+                    public void run() {
+                        content.setContent(
+                                new ResultsView(context, results));
+                        progressWindow.close();
+                    }
+                });
+            }
+
+
+            @Override
+            public void onError(final String message, Throwable t) {
+                final String desc = (t != null) ? t.getMessage() : null;
+                accessSynchronously(new Runnable() {
+                    @Override
+                    public void run() {
+                        setNoResultsView();
+                        progressWindow.close();
+                        Notification.show(message, desc,
+                                Notification.Type.ERROR_MESSAGE);
+                    }
+                });
+            }
+        };
+
+        /*
+         * now finally submit to tester for running ...
+         */
+        tester.performTests(listener,
+                    profile,
+                    endpointURI,
+                    searchTerm,
+                    strictMode,
+                    performProbeRequest,
+                    connectTimeout,
+                    socketTimeout);
+    }
+
+
+    private void readObject(java.io.ObjectInputStream in) throws IOException,
+            ClassNotFoundException {
+        in.defaultReadObject();
+        setNoResultsView();
+    }
+
+
+    private String getCookie(String name) {
+        Cookie[] cookies = VaadinService.getCurrentRequest().getCookies();
+        if (cookies != null) {
+            for (Cookie cookie : cookies) {
+                if (name.equals(cookie.getName())) {
+                    String s = cookie.getValue();
+                    if (s != null) {
+                        s = s.trim();
+                        if (s.isEmpty()) {
+                            s = null;
+                        }
+                        return s;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+
+    private void setCookie(String name, String value) {
+        Cookie cookie = new Cookie(name, value);
+        cookie.setMaxAge(Integer.MAX_VALUE);
+        cookie.setPath(VaadinService.getCurrentRequest().getContextPath());
+        VaadinService.getCurrentResponse().addCookie(cookie);
+    }
+
+    private static final int PROFILE_AUTODETECT  = 1;
+    private static final int PROFILE_FCS_1_0     = 2;
+    private static final int PROFILE_FCS_2_0     = 3;
+    private static final int PROFILE_FCS_LEGACY = 4;
+
+    private static final int TIMEOUT_5_SECONDS   =   5 * 1000;
+    private static final int TIMEOUT_10_SECONDS  =  10 * 1000;
+    private static final int TIMEOUT_15_SECONDS  =  15 * 1000;
+    private static final int TIMEOUT_30_SECONDS  =  30 * 1000;
+    private static final int TIMEOUT_60_SECONDS  =  60 * 1000;
+    private static final int TIMEOUT_120_SECONDS = 120 * 1000;
+    private static final int TIMEOUT_180_SECONDS = 180 * 1000;
+    private static final int TIMEOUT_300_SECONDS = 300 * 1000;
+
+    private void fillTimeoutCombo(ComboBox combo) {
+        combo.addItem(TIMEOUT_5_SECONDS);
+        combo.setItemCaption(TIMEOUT_5_SECONDS, "5 seconds");
+        combo.addItem(TIMEOUT_10_SECONDS);
+        combo.setItemCaption(TIMEOUT_10_SECONDS, "10 seconds");
+        combo.addItem(TIMEOUT_15_SECONDS);
+        combo.setItemCaption(TIMEOUT_15_SECONDS, "15 seconds");
+        combo.addItem(TIMEOUT_30_SECONDS);
+        combo.setItemCaption(TIMEOUT_30_SECONDS, "30 seconds");
+        combo.addItem(TIMEOUT_60_SECONDS);
+        combo.setItemCaption(TIMEOUT_60_SECONDS, "1 minute");
+        combo.addItem(TIMEOUT_120_SECONDS);
+        combo.setItemCaption(TIMEOUT_120_SECONDS, "2 minutes");
+        combo.addItem(TIMEOUT_180_SECONDS);
+        combo.setItemCaption(TIMEOUT_180_SECONDS, "3 minutes");
+        combo.addItem(TIMEOUT_300_SECONDS);
+        combo.setItemCaption(TIMEOUT_300_SECONDS, "5 minutes");
+    }
+
+} // class TesterUI
Index: /FCSEndpointTester/trunk/src/main/resources/eu/clarin/fcs/tester/ui/Widgetset.gwt.xml
===================================================================
--- /FCSEndpointTester/trunk/src/main/resources/eu/clarin/fcs/tester/ui/Widgetset.gwt.xml	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/resources/eu/clarin/fcs/tester/ui/Widgetset.gwt.xml	(revision 7116)
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module>
+    <!--
+        This file is automatically updated based on new dependencies by the
+        goal "vaadin:update-widgetset".
+    -->
+
+    <!-- Inherit DefaultWidgetSet -->
+    <inherits name="com.vaadin.DefaultWidgetSet" /> 
+    
+    <!--
+     Uncomment the following to compile the widgetset for one browser only.
+     This can reduce the GWT compilation time significantly when debugging.
+     The line should be commented out before deployment to production
+     environments.
+      
+     Multiple browsers can be specified for GWT 1.7 as a comma separated
+     list. The supported user agents at the moment of writing were:
+     ie6,ie8,gecko,gecko1_8,safari,opera
+     
+     The value gecko1_8 is used for Firefox 3 and later and safari is used for
+     webkit based browsers including Google Chrome.
+    -->
+    <!-- <set-property name="user.agent" value="gecko1_8"/> -->
+   
+    <!--
+     To enable SuperDevMode, uncomment this line.
+     
+     SuperDevMode enables debugging of the client side of a Vaadin
+     application using the JavaScript debugger of a browser. Java code is
+     shown and debugging can take place on the Java level when using a browser
+     that support source maps (currently Chrome, implementation under work
+     for Firefox).
+     
+     After uncommenting this property, compile the widgetset once and then
+     start the SuperDevMode server process in parallel with your normal
+     server. Then add the command line parameter ?superdevmode to your
+     application URL. Vaadin Eclipse plug-in can create a launch for the
+     SuperDevMode server (Project Properties... -> Vaadin). 
+     
+     See https://vaadin.com/wiki/-/wiki/Main/Using%20SuperDevMode for more
+     information and instructions.
+    -->
+    <!-- <set-configuration-property name="devModeRedirectEnabled" value="true" /> -->
+    
+
+    <!--  add-ons -->
+    <inherits name="org.vaadin.hene.popupbutton.widgetset.PopupbuttonWidgetset" />
+    <inherits name="org.vaadin.jouni.animator.AnimatorWidgetset" />
+</module>
+
Index: /FCSEndpointTester/trunk/src/main/resources/logging.properties
===================================================================
--- /FCSEndpointTester/trunk/src/main/resources/logging.properties	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/resources/logging.properties	(revision 7116)
@@ -0,0 +1,3 @@
+#
+# Do not delete, otherwise Vaadin might disable debug logging!
+#
Index: /FCSEndpointTester/trunk/src/main/webapp/VAADIN/themes/clarin/styles.css
===================================================================
--- /FCSEndpointTester/trunk/src/main/webapp/VAADIN/themes/clarin/styles.css	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/webapp/VAADIN/themes/clarin/styles.css	(revision 7116)
@@ -0,0 +1,109 @@
+@import "../reindeer/legacy-styles.css";
+
+.header {
+	color: inherit;
+	padding: 0 0 4px 0;
+	border-bottom: 1px solid rgba(0, 0, 0, 0.25);
+}
+
+.header h1 {
+	margin: 0;
+	padding: 0 76px 0 0;
+	height: 42px;
+	line-height: 42px;
+	vertical-align: middle;
+	text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15);;
+	background-color: transparent;
+	background-image: url("icons/clarin-d-logo.png");
+	background-attachment: scroll;
+	background-repeat: no-repeat;
+	background-position: top right;
+}
+
+.small-margin {
+	padding: 4px !important;
+}
+
+.main-content {
+	color: inherit;
+	background-color: #ffffff;
+	border: 1px solid rgba(0, 0, 0, 0.08) !important;
+}
+
+.no-results-label {
+	opacity: 0.15;
+}
+
+.result-overall-headline h2 {
+	margin: 0 0 2px 0;
+}
+
+.v-caption-result-overall-label {
+	display: inline !important;
+}
+
+.v-caption-result-overall-label img {
+	margin-top: -4px;
+}
+
+.result-overall-label {
+	font-size: 16px;
+	display: inline !important;
+}
+
+.v-slot-result-overall-label {
+	margin-bottom: 2px;
+}
+
+.result-individual-headline h2 {
+	margin: 4px 0 2px 0;
+}
+
+.result-panel .v-panel-caption img {
+	padding-right: 16px;
+	margin-top: -1px;
+}
+
+.result-panel .v-panel-caption {
+	background: transparent url("../base/common/img/sprites.png") no-repeat
+		22px -37px;
+}
+
+.result-panel-opened .v-panel-caption {
+	background-position: 11px -9px;
+}
+
+.result-panel .v-panel-content {
+	border: none;
+}
+
+.result-panel .v-panel-caption {
+	padding-bottom: 2px;
+	font-size: 16px;
+	line-height: 16px;
+	font-weight: normal;
+}
+
+.result-panel .v-panel-deco,
+.result-panel .animator {
+	display: none;
+}
+
+.result-panel-content {
+	padding: 2px;
+	margin-bottom: 4px;
+	border-left: 1px solid rgba(0, 0, 0, 0.08) !important;
+	border-right: 1px solid rgba(0, 0, 0, 0.08) !important;
+	border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
+}
+
+.result-panel .result-label {
+	padding: 1px 0 1px 0;
+	font-size: 14px;
+	line-height: 14px;
+	vertical-align: bottom;
+}
+
+.result-panel .v-caption-result-panel-content-messages {
+	margin-top: 4px;
+}
Index: /FCSEndpointTester/trunk/src/main/webapp/WEB-INF/web.xml
===================================================================
--- /FCSEndpointTester/trunk/src/main/webapp/WEB-INF/web.xml	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/webapp/WEB-INF/web.xml	(revision 7116)
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://java.sun.com/xml/ns/javaee"
+    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+    version="2.5">
+    <display-name>CLARIN-FCS conformance test tool</display-name>
+
+    <listener>
+        <listener-class>eu.clarin.fcs.tester.FCSEndpointTester</listener-class>
+    </listener>
+
+    <servlet>
+        <servlet-name>vaadin</servlet-name>
+        <servlet-class>com.vaadin.server.VaadinServlet</servlet-class>
+        <init-param>
+            <description>Vaadin UI to display</description>
+            <param-name>UI</param-name>
+            <param-value>eu.clarin.fcs.tester.ui.TesterUI</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>vaadin</servlet-name>
+        <url-pattern>/app/*</url-pattern>
+    </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>vaadin</servlet-name>
+        <url-pattern>/VAADIN/*</url-pattern>
+    </servlet-mapping>
+
+    <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+</web-app>
Index: /FCSEndpointTester/trunk/src/main/webapp/index.jsp
===================================================================
--- /FCSEndpointTester/trunk/src/main/webapp/index.jsp	(revision 7116)
+++ /FCSEndpointTester/trunk/src/main/webapp/index.jsp	(revision 7116)
@@ -0,0 +1,4 @@
+<%@ page language="java" %>
+<%
+    response.sendRedirect("app/");
+%>
