Cleanup deployment
diff --git a/dev/js/demo/about.html b/dev/js/demo/about.html
new file mode 100644
index 0000000..39afb46
--- /dev/null
+++ b/dev/js/demo/about.html
@@ -0,0 +1,108 @@
+ <!DOCTYPE html>
+<html>
+ <head>
+ <title>Tutorial demo</title>
+ <link type="text/css" rel="stylesheet" href="../../css/build/kalamar.css" />
+ <script src="../src/session.js"></script>
+ <script src="../src/tutorial.js"></script>
+ </head>
+ <body class="embedded">
+ <div id="sidebar" tabindex="0" class="active">
+ <div>
+<!-- Not embedded:
+ <h2>Tutorial</h2>
+-->
+ <nav>
+ <ul>
+ <li><a href="/doc/korap">KorAP</a>
+ <ul>
+ <li><a href="/doc/kalamar" class="active">Kalamar</a></li>
+ <li><a href="/doc/koral">Koral</a></li>
+ <li><a href="/doc/kustvakt">Kustvakt</a></li>
+ <li><a href="/doc/krill">Krill</a></li>
+ <!-- <li>Karang</li> -->
+ </ul>
+ </li>
+ <li><a href="/doc/ql">Query Languages</a>
+ <ul>
+ <li><a href="/doc/ql/cosmas-2">Cosmas II</a></li>
+ <li><a href="/doc/ql/poliqarp-plus">Poliqarp+</a></li>
+ <li><a href="/doc/ql/annis">Annis QL</a></li>
+ <li><a href="/doc/ql/cql">CQL</a></li>
+ <li><a href="/doc/ql/regexp">RegExp</a></li>
+ </ul>
+ </li>
+ <li><a href="/doc/data">Data</a>
+ <ul>
+ <li><a href="/doc/data/corpus">Corpora</a>
+ <ul>
+ <li><a href="/doc/data/corpus/dereko">DeReKo</a></li>
+ </ul>
+ </li>
+ <li><a href="/doc/data/annotation">Annotations</a>
+ <ul>
+ <li><a href="/doc/data/annotation/connexor">Connexor</a></li>
+ <li><a href="/doc/data/annotation/mate">Mate</a></li>
+ <li><a href="/doc/data/annotation/treetagger">TreeTagger</a></li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li><a href="/doc/api">API</a>
+<!--
+ <ul>
+ <li><a href="/doc/api/koralquery">KoralQuery</a></li>
+ <li><a href="/doc/api/search">Search API</a></li>
+ <li><a href="/doc/api/match">Match Information API</a></li>
+ <li><a href="/doc/api/user">User API</a></li>
+ </ul>
+-->
+ </li>
+ </ul>
+ </nav>
+ </div>
+ </div>
+ <main class="tutorial">
+ <h2>KorAP-Tutorial</h2>
+ <section id="tut-intro">
+ <h3>Frontend Features</h3>
+ <p>This frontend ...</p>
+ </section>
+
+ <section id="tut-examples">
+ <h3>Example Queries</h3>
+ <p><strong>Poliqarp</strong>: Find all occurrences of the lemma "baum" as annotated by the <a href="#">default foundry</a>.</p>
+ <pre class="query tutorial" data-query="[base=Baum]" data-query-language="poliqarp"><code>[base=Baum]</code></pre>
+ </section>
+
+ <section id="tut-caveats">
+ <h3>Caveats</h3>
+ <p>Memcheck is not perfect; it occasionally produces false positives, and there are mechanisms for suppressing these (see Suppressing errors in the Valgrind User Manual). However, it is typically right 99% of the time, so you should be wary of ignoring its error messages. After all, you wouldn’t ignore warning messages produced by a compiler, right?</p>
+ <p>The suppression mechanism is also useful if Memcheck is reporting errors in library code that you cannot change. The default suppression set hides a lot of these, but you may come across more. Memcheck cannot detect every memory error your program has. For example, it can’t detect out-of-range reads or writes to arrays that are allocated statically or on the stack. But it should detect many errors that could crash your program (eg. cause a segmentation fault).</p>
+ <p>Try to make your program so clean that Memcheck reports no errors. Once you achieve this state, it is much easier to see when changes to the program cause Memcheck to report new errors. Experience from several years of Memcheck use shows that it is possible to make even huge programs run Memcheck-clean. For example, large parts of KDE, OpenOffice.org and Firefox are Memcheck-clean, or very close to it.</p>
+ </section>
+ <section id="tut-more-information">
+ <h3>More information</h3>
+ <p>Please consult the Valgrind FAQ and the Valgrind User Manual, which have much more information. Note that the other tools in the Valgrind distribution can be invoked with the <code>--tool</code> option.</p>
+ </section>
+ <section id="tut-overview">
+ <h3>An Overview of Valgrind</h3>
+ <p>Valgrind is an instrumentation framework for building dynamic analysis tools. It comes with a set of tools each of which performs some kind of debugging, profiling, or similar task that helps you improve your programs. Valgrind’s architecture is modular, so new tools can be created easily and without disturbing the existing structure.</p>
+ <p>A number of useful tools are supplied as standard.</p>
+ <ol>
+ <li><strong>Memcheck</strong> is a memory error detector. It helps you make your programs, particularly those written in C and C++, more correct.</li>
+ <li><strong>Cachegrind</strong> is a cache and branch-prediction profiler. It helps you make your programs run faster.</li>
+ <li><strong>Callgrind</strong> is a call-graph generating cache profiler. It has some overlap with Cachegrind, but also gathers some information that Cachegrind does not.</li>
+ <li><strong>Helgrind</strong> is a thread error detector. It helps you make your multi-threaded programs more correct.</li>
+ <li><strong>DRD</strong> is also a thread error detector. It is similar to Helgrind but uses different analysis techniques and so may find different problems.</li>
+ <li><strong>Massif</strong> is a heap profiler. It helps you make your programs use less memory.</li>
+ <li><strong>DHAT</strong> is a different kind of heap profiler. It helps you understand issues of block lifetimes, block utilisation, and layout inefficiencies.</li>
+ <li><strong>SGcheck</strong> is an experimental tool that can detect overruns of stack and global arrays. Its functionality is complementary to that of Memcheck: SGcheck finds problems that Memcheck can’t, and vice versa.</li>
+ <li><strong>BBV</strong> is an experimental SimPoint basic block vector generator. It is useful to people doing computer architecture research and development.</li>
+ </ol>
+ <p>There are also a couple of minor tools that aren’t useful to most users: Lackey is an example tool that illustrates some instrumentation basics; and Nulgrind is the minimal Valgrind tool that does no analysis or instrumentation, and is only useful for testing purposes. Valgrind is closely tied to details of the CPU and operating system, and to a lesser extent, the compiler and basic C libraries. Nonetheless, it supports a number of widely-used platforms, listed in full at <a href="http://www.valgrind.org/">valgrind.org</a>.</p>
+ <p>Valgrind is built via the standard Unix ./configure, make, make install process; full details are given in the README file in the distribution.</p>
+ </section>
+ </main>
+ </body>
+</html>
diff --git a/dev/js/demo/all.html b/dev/js/demo/all.html
new file mode 100644
index 0000000..ec6d017
--- /dev/null
+++ b/dev/js/demo/all.html
@@ -0,0 +1,156 @@
+ <!DOCTYPE html>
+<html>
+ <head>
+ <title>CSS demo</title>
+ <meta charset="utf-8" />
+ <link type="text/css" rel="stylesheet" href="../../css/build/kalamar.css" />
+ </head>
+ <body>
+ <div id="kalamar-bg"></div>
+ <header>
+ <a href="/" class="logo"><h1><span>KorAP - Corpus Analysis Platform</span></h1></a>
+ <form autocomplete="off" action="/kalamar">
+ <div id="searchbar">
+ <input type="search"
+ placeholder="Find ..."
+ name="q"
+ id="q-field"
+ autofocus="autofocus" />
+ <button type="submit"><span>Go</span></button>
+ <!-- <i class="fa fa-arrow-circle-down show-hint" onclick="hint.popUp()"></i> -->
+ </div>
+ <div id="vc-view"></div>
+ in <input type="hidden" id="vc-name" name="vc-name" value="Wikipedia" />
+ <input type="text" name="vc" id="vc" value="corpusID = Wikipedia" />
+ with <span class="select">
+ <!-- Change this to js-menu -->
+ <select name="ql" id="ql-field">
+ <option value="poliqarp">Poliqarp</option>
+ <option value="cosmas2">Cosmas II</option>
+ <option value="annis">Annis</option>
+ <option value="cql">CQL v1.2</option>
+ </select>
+ </span>
+ <div id="button-right">
+ <input type="checkbox"
+ value="1"
+ name="cutoff"
+ class="checkbox"
+ id="q-cutoff-field" />
+ <label for="q-cutoff-field"><span></span>Glimpse</label>
+
+ <!-- Todo: open tutorial - like openTutorial() -->
+ <a href="about.html" title="Tutorial" class="tutorial" id="view-tutorial"><span>Tutorial</span></a>
+ </div>
+ </form>
+ </header>
+
+ <div id="sidebar" tabindex="0">
+ <h2>Tutorial</h2>
+ <p>Hui</p>
+ </div>
+
+ <main>
+ <div class="resultinfo">
+ <div id="pagination">
+ <a rel="prev"><span><span><</span></span></a>
+ <a rel="self"><span>1</span></a>
+ <a href="#2"><span>2</span></a>
+ <a href="#3"><span>3</span></a>
+ <a class="ellipsis"><span><span>...</span></span></a>
+ <a href="#52230"><span>52230</span></a>
+ <a rel="next" href="#2"><span><span>></span></span></a>
+ </div>
+ <p class="found">Found <span id="total-results">unknown amount of matches</span> in 59.93ms</p>
+ </div>
+
+ <div id="search">
+ <ol class="align-left">
+ <li data-corpus-id="WPD"
+ data-doc-id="WWW"
+ data-text-id="03313"
+ data-match-id="p102-103"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans"
+ id="WPD-WWW.03313-p102-103">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">In diesem Beispiel ist zu sehen, dass die beiden Variablen a und b lediglich ihre Werte an die Funktion </span><mark><mark class="class-2 level-1">Dies </mark><mark class="class-1 level-0"><mark class="class-2 level-1"><mark class="class-3 level-2">ist</mark> ein</mark> Test</mark></mark><span class="context-right"> übergeben, aber im Gegensatz zu einem Referenzparamter dabei unverändert bleiben.</span></div>
+ <!-- only inject via javascript! -->
+ </div>
+ <p class="ref"><strong>Wertparameter</strong> by Hubi,Zwobot,4; published on 2005-03-28 as WWW.03313 (WPD)</p>
+ <!-- only inject via javascript! -->
+ <!--
+ <ul class="action right">
+ <li class="close" title="Close"><a href="#">Close hit<i class="fa fa-toggle-up"></i></a></li>
+ <li class="open" title="Open in new tab"><a href="#WPD-WWW.03313-p102-103" target="_blank"><i class="fa fa-external-link-square"></i></a></li>
+ <li onclick="showTable(this)" title="Annotations"><i class="fa fa-info-circle"></i></li>
+ </ul>
+-->
+ </li>
+ <li data-corpus-id="WPD"
+ data-doc-id="FFF"
+ data-text-id="01460"
+ data-match-id="p119-120"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans"
+ id="WPD-FFF.01460-p119-120">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">Der Fehler unterläuft häufig bei der direkten Übersetzung aus dem Englischen, wenn im originalen Ausdruck die beiden Wortteile verschiedene Wörter sind und sich das Adjektiv wahlweise auf das erste oder zweite Wort bezieht. Ein Beispiel ist multiples Testproblem für multiple </span><span class="match">test</span><span class="context-right"> problem.</span></div>
+ <div class="tokenInfo"></div>
+ </div>
+ <p class="ref"><strong>Fehlbezogenes Adjektiv</strong> by Joni2,Peterlustig,BWBot; published on 2005-03-28 as FFF.01460 (WPD)</p>
+ <ul class="action right">
+ <li class="close" title="Close"><a href="#"><i class="fa fa-toggle-up"></i></a></li>
+ <li class="open" title="Open in new tab"><a href="#WPD-FFF.01460-p119-120" target="_blank"><i class="fa fa-external-link-square"></i></a></li>
+ <li onclick="showTable(this)" title="Annotations"><i class="fa fa-info-circle"></i></li>
+ </ul>
+ </li>
+
+ <li data-corpus-id="WPD"
+ data-doc-id="HHH"
+ data-text-id="06056"
+ data-match-id="p2564-2565"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans xip/c=spans"
+ id="WPD-HHH.06056-p2564-2565">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">HDTV Samples from European (and other) broadcasters and </span><span class="match">test</span><span class="context-right"> transmissions in Europe</span></div>
+ <div class="tokenInfo"></div>
+ </div>
+ <p class="ref"><strong>High Definition Television</strong> by ArtMechanic,TheK,Andreas -horn- Hornig; published on 2005-03-28 as HHH.06056 (WPD)</p>
+ <ul class="action right">
+ <li class="close" title="Close"><a href="#"><i class="fa fa-toggle-up"></i></a></li>
+ <li class="open" title="Open in new tab"><a href="#WPD-HHH.06056-p2564-2565" target="_blank"><i class="fa fa-external-link-square"></i></a></li>
+ <li onclick="showTable(this)" title="Annotations"><i class="fa fa-info-circle"></i></li>
+ </ul>
+ </li>
+ </div>
+ <div id="menu"></div>
+
+ <div class="intro">
+ <p><strong>KorAP</strong> is a new Corpus Analysis Platform, suited for large, multiple annotated corpora and complex search queries independent of particular research questions.</p>
+ <p>This is the new frontend for KorAP, with currently no access to restricted corpora. For full access, please visit the <a href="http://korap.ids-mannheim.de/app">first frontend</a>.</p>
+
+ <p><strong>New to KorAP?</strong> Please check out our <a href="#">tutorial</a>!</p>
+
+ <p>KorAP is developed at the <a href="http://www.ids-mannheim.de">Institute for the German Language</a> and funded by the <a href="http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/">Leibniz Association</a>. The separated modules are being published as open source at <a href="http://github.com/KorAP">GitHub</a>.</p>
+ </div>
+ <div id="logos">
+ <div>
+ <a href="http://www.ids-mannheim.de/" class="logo"><p id="ids-logo"><span>Developed at the Institute for the German Language (IDS)</span></p></a>
+ <a href="http://www.leibniz-gemeinschaft.de/" class="logo"><p id="leibniz-logo"><span>Member of the Leibniz Association</span></p></a>
+ </div>
+ </div>
+ </main>
+ <script src="../lib/dagre/dagre.min.js"></script>
+ <script src="../src/api.js"></script>
+ <script src="../src/hint.js"></script>
+ <script src="../src/match.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../src/vc.js"></script>
+ <script src="../src/session.js"></script>
+ <script src="../src/tutorial.js"></script>
+ <script src="../src/util.js"></script>
+ <script>
+ KorAP.URL = 'http://localhost:3000';
+ </script>
+ <script src="./all.js"></script>
+ </body>
+</html>
diff --git a/dev/js/demo/all.js b/dev/js/demo/all.js
new file mode 100644
index 0000000..b29b0b5
--- /dev/null
+++ b/dev/js/demo/all.js
@@ -0,0 +1,455 @@
+var snippet = "<span title=\"cnx/l:meist\">" +
+ " <span title=\"cnx/p:ADV\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:meist\">" +
+ " <span title=\"mate/p:ADV\">" +
+ " <span title=\"opennlp/p:ADV\">meist</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:deutlich\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:deutlich\">" +
+ " <span title=\"mate/m:degree:pos\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">deutlich</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:fähig\">" +
+ " <span title=\"cnx/l:leistung\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@NH\">" +
+ " <span title=\"mate/l:leistungsfähig\">" +
+ " <span title=\"mate/m:degree:comp\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">leistungsfähiger</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>";
+
+var treeSnippet =
+ "<span class=\"context-left\"></span>" +
+ "<span class=\"match\">" +
+ " <span title=\"xip/c:MC\">" +
+ " <span title=\"xip/c:TOP\">" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">Mit</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:DET\">dieser</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NOUN\">Methode</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:VERB\">ist</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">es</span>" +
+ " </span>" +
+ " <span title=\"xip/c:AP\">" +
+ " <span title=\"xip/c:ADV\">nun</span>" +
+ " <span title=\"xip/c:ADJ\">möglich</span>" +
+ " </span>" +
+ " <span title=\"xip/c:ADV\">z. B.</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Voice</span>" +
+ " </span>" +
+ " </span>" + "(" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Sprache</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" + ")" +
+ " <span title=\"xip/c:VERB\">bevorzugt</span>" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">in</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">der</span>" +
+ " </span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Bridge</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:INFC\">" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:VERB\">weiterzugeben</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span class=\"context-right\"></span>";
+
+var treeSnippet2 =
+ "<span class=\"context-left\"><\/span>"+
+ "<span class=\"match\">"+
+ "<span title=\"xip\/c:NPA\">"+
+ "<span title=\"xip\/c:NP\">"+
+ "<span title=\"xip\/c:NOUN\">HDTV<\/span>"+
+ "<\/span>"+
+ "<\/span> "+
+ "<span title=\"xip\/c:NPA\">" +
+ "<span title=\"xip\/c:NP\">"+
+ "<span title=\"xip\/c:NOUN\">Samples<\/span>"+
+ "<\/span>"+
+ "<\/span> "+
+ "<span title=\"xip\/c:ADV\">from<\/span> "+
+ "<span title=\"xip\/c:NPA\">"+
+ "<span title=\"xip\/c:NP\">"+
+ "<span title=\"xip\/c:NOUN\">European<\/span>"+
+ "<\/span>"+
+ "<\/span> ("+
+ "<span title=\"xip\/c:INS\">"+
+ "<span title=\"xip\/c:NPA\">"+
+ "<span title=\"xip\/c:NP\">"+
+ "<span title=\"xip\/c:NOUN\">and<\/span>"+
+ "<\/span>"+
+ "<\/span> "+
+ "<span title=\"xip\/c:ADV\">other<\/span>"+
+ "<\/span>) "+
+ "<span title=\"xip\/c:ADV\">broadcasters<\/span> "+
+ "<span title=\"xip\/c:NPA\">"+
+ "<span title=\"xip\/c:NP\">"+
+ "<span title=\"xip\/c:NOUN\">and<\/span>"+
+ "<\/span>"+
+ "<\/span> "+
+ "<span title=\"xip\/c:VERB\">test<\/span> "+
+ "<span title=\"xip\/c:ADV\">transmissions<\/span> "+
+ "<span title=\"xip\/c:PREP\">in<\/span> "+
+ "<span title=\"xip\/c:NOUN\">Europe<\/span>"+
+ "<\/span>"+
+ "<span class=\"context-right\"><\/span>";
+
+var menuContent = [
+ ['cnx/c', 'cnx', 'c'],
+ ['mate/c', 'mate', 'c'],
+ ['base/c', 'base', 'c'],
+ ['xip/c', 'xip', 'c'],
+ ['tt/c', 'tt', 'c']
+];
+
+var namedEntities = [
+ ["I-LOC", "I-LOC ", "Location"],
+ ["I-MISC", "I-MISC ", "Miscellaneous"],
+ ["I-ORG", "I-ORG ", "Organization"],
+ ["I-PER", "I-PER ", "Person"]
+];
+
+// http://www.ids-mannheim.de/cosmas2/projekt/referenz/stts/morph.html
+// http://nachhalt.sfb632.uni-potsdam.de/owl-docu/stts.html
+var sttsArray = [
+ // "$.", "$(", "$,"
+ ["ADJA","ADJA ", "Attributive Adjective"],
+ ["ADJD","ADJD ", "Predicative Adjective"],
+ ["ADV","ADV ", "Adverb"],
+ ["APPO","APPO ", "Postposition"],
+ ["APPR","APPR ", "Preposition"],
+ ["APPRART","APPRART ", "Preposition with Determiner"],
+ ["APZR","APZR ","Right Circumposition"],
+ ["ART","ART ", "Determiner"],
+ ["CARD","CARD ", "Cardinal Number"],
+ ["FM","FM ", "Foreign Material"],
+ ["ITJ","ITJ ", "Interjection"],
+ ["KOKOM","KOKOM ", "Comparison Particle"],
+ ["KON","KON ", "Coordinating Conjuncion"],
+ ["KOUI","KOUI ", "Subordinating Conjunction with 'zu'"],
+ ["KOUS","KOUS ", "Subordinating Conjunction with Sentence"],
+ ["NE","NE ", "Named Entity"],
+ ["NN","NN ", "Normal Nomina"],
+ ["PAV", "PAV ", "Pronominal Adverb"],
+ ["PDAT","PDAT ","Attributive Demonstrative Pronoun"],
+ ["PDS","PDS ", "Substitutive Demonstrative Pronoun"],
+ ["PIAT","PIAT ", "Attributive Indefinite Pronoun without Determiner"],
+ ["PIDAT","PIDAT ", "Attributive Indefinite Pronoun with Determiner"],
+ ["PIS","PIS ", "Substitutive Indefinite Pronoun"],
+ ["PPER","PPER ", "Personal Pronoun"],
+ ["PPOSAT","PPOSAT ", "Attributive Possessive Pronoun"],
+ ["PPOSS","PPOSS ", "Substitutive Possessive Pronoun"],
+ ["PRELAT","PRELAT ", "Attributive Relative Pronoun"],
+ ["PRELS","PRELS ", "Substitutive Relative Pronoun"],
+ ["PRF","PRF ", "Reflexive Pronoun"],
+ ["PROAV","PROAV ", "Pronominal Adverb"],
+ ["PTKA","PTKA ","Particle with Adjective"],
+ ["PTKANT","PTKANT ", "Answering Particle"],
+ ["PTKNEG","PTKNEG ", "Negation Particle"],
+ ["PTKVZ","PTKVZ ", "Separated Verbal Particle"],
+ ["PTKZU","PTKZU ", "'zu' Particle"],
+ ["PWAT","PWAT ", "Attributive Interrogative Pronoun"],
+ ["PWAV","PWAV ", "Adverbial Interrogative Pronoun"],
+ ["PWS","PWS ", "Substitutive Interrogative Pronoun"],
+ ["TRUNC","TRUNC ","Truncated"],
+ ["VAFIN","VAFIN ", "Auxiliary Finite Verb"],
+ ["VAINF","VAINF ", "Auxiliary Infinite Verb"],
+ ["VAIMP","VAIMP ", "Auxiliary Finite Imperative Verb"],
+ ["VAPP","VAPP ", "Auxiliary Perfect Participle"],
+ ["VMFIN","VMFIN ", "Modal Finite Verb"],
+ ["VMINF","VMINF ", "Modal Infinite Verb"],
+ ["VMPP","VMPP ", "Modal Perfect Participle"],
+ ["VVFIN","VVFIN ","Finite Verb"],
+ ["VVIMP","VVIMP ", "Finite Imperative Verb"],
+ ["VVINF","VVINF ", "Infinite Verb"],
+ ["VVIZU","VVIZU ", "Infinite Verb with 'zu'"],
+ ["VVPP","VVPP ", "Perfect Participle"],
+ ["XY", "XY ", "Non-Word"]
+];
+
+var mateSttsArray = sttsArray.slice(0);
+mateSttsArray.push(
+ ["<root-POS>","<root-POS>","Root Part of Speech"]
+);
+
+
+var vcExample = {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:and",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Der Birnbaum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"Mannheim",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"Aufzucht und Pflege",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"Gedichte",
+ "match":"match:eq",
+ "rewrites" : [
+ {
+ "@type": "koral:rewrite",
+ "src" : "policy",
+ "operation" : "operation:injection",
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsdatum",
+ "type":"type:date",
+ "value":"2015-03-05",
+ "match":"match:geq"
+ }
+ ]
+};
+
+
+KorAP.hintArray = {
+ "-" : [
+ ["Connexor", "cnx/", "Constituency, Lemma, Morphology, Part-of-Speech, Syntax"],
+ ["CoreNLP", "corenlp/", "Named Entities"],
+ ["Mate", "mate/", "Lemma, Morphology, Part-of-Speech"],
+ ["OpenNLP", "opennlp/", "Part-of-Speech"],
+ ["TreeTagger", "tt/", "Lemma, Part-of-Speech"],
+ ["Xerox Parser", "xip/", "Constituency, Lemma, Part-of-Speech"]
+ ],
+ "corenlp/" : [
+ ["Named Entity", "ne=" , "Combined"],
+ ["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
+ ["Named Entity", "ne_hgc_175m_600=", "ne_hgc_175m_600"]
+ ],
+ "corenlp/ne=" : namedEntities,
+ "corenlp/ne_dewac_175m_600=" : namedEntities,
+ "corenlp/ne_hgc_175m_600=" : namedEntities,
+ "cnx/" : [
+ ["Constituency", "c="],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ],
+ "cnx/c=" : [
+ ["np", "np ", "Nominal Phrase"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/m=" : [
+ ["Abbr","Abbr ", "Nouns: Abbreviation"],
+ ["CMP","CMP ", "Adjective: Comparative"],
+ ["IMP", "IMP ", "Mood: Imperative"],
+ ["IND", "IND ", "Mood: Indicative"],
+ ["INF", "INF ", "Infinitive"],
+ ["ORD","ORD ", "Numeral: Ordinal"],
+ ["PAST", "PAST ", "Tense: past"],
+ ["PCP", "PCP ", "Participle"],
+ ["PERF", "PERF ", "Perfective Participle"],
+ ["PL","PL ", "Nouns: Plural"],
+ ["PRES", "PRES ", "Tense: present"],
+ ["PROG", "PROG ", "Progressive Participle"],
+ ["Prop","Prop ", "Nouns: Proper Noun"],
+ ["SUB", "SUB ", "Mood: Subjunctive"],
+ ["SUP","SUP ", "Adjective: Superlative"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/p=" : [
+ ["A", "A ", "Adjective"],
+ ["ADV", "ADV ", "Adverb"],
+ ["CC", "CC ", "Coordination Marker"],
+ ["CS", "CS ", "Clause Marker"],
+ ["DET", "DET ", "Determiner"],
+ ["INTERJ", "INTERJ ", "Interjection"],
+ ["N", "N ", "Noun"],
+ ["NUM", "NUM ", "Numeral"],
+ ["PREP", "PREP ", "Preposition"],
+ ["PRON", "PRON ", "Pro-Nominal"],
+ ["V", "V ", "Verb"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/syntax.html
+ "cnx/syn=" : [
+ ["@ADVL", "@ADVL ", "Adverbial Head"],
+ ["@AUX", "@AUX ", "Auxiliary Verb"],
+ ["@CC", "@CC ", "Coordination"]
+ ["@MAIN", "@MAIN ", "Main Verb"],
+ ["@NH", "@NH ", "Nominal Head"],
+ ["@POSTMOD", "@POSTMOD ", "Postmodifier"],
+ ["@PREMARK", "@PREMARK ", "Preposed Marker"],
+ ["@PREMOD", "@POSTMOD ", "Premodifier"]
+ ],
+ "opennlp/" : [
+ ["Part-of-Speech", "p="]
+ ],
+ "opennlp/p=" : sttsArray,
+ "xip/" : [
+ ["Constituency", "c="],
+ // Inactive: ["Dependency", "d="],
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="],
+ ],
+ // "xip/c=" : [],
+ // Inactive: "xip/d=" : [],
+ // "xip/p=" : [],
+ "tt/" : [
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="]
+ ],
+ "tt/p=" : sttsArray,
+ "mate/" : [
+ // Inactive: "d" : ["d=", "Dependency"],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="]
+ ],
+ // Inactive: mate/d=
+ "mate/p=" : mateSttsArray,
+ "mate/m=" : [
+ ["Case", "case:"],
+ ["Degree", "degree:"],
+ ["Gender", "gender:"],
+ ["Mood", "mood:"],
+ ["Number", "number:"],
+ ["Person", "person:"],
+ ["Tense","tense:"],
+ ["No type", "<no-type> "]
+ ],
+ "mate/m=case:" : [
+ ["acc", "acc ", "Accusative"],
+ ["dat","dat ", "Dative"],
+ ["gen", "gen ","Genitive"],
+ ["nom","nom ", "Nominative"],
+ ["*","* ", "Undefined"]
+ ],
+ "mate/m=degree:" : [
+ ["comp","comp ", "Comparative"],
+ ["pos","pos ", "Positive"],
+ ["sup","sup ", "Superative"]
+ ],
+ "mate/m=gender:" : [
+ ["fem", "fem ", "Feminium"],
+ ["masc", "masc ", "Masculinum"],
+ ["neut","neut ", "Neuter"],
+ ["*","* ","Undefined"]
+ ],
+ "mate/m=mood:" : [
+ ["imp","imp ", "Imperative"],
+ ["ind","ind ", "Indicative"],
+ ["subj","subj ", "Subjunctive"]
+ ],
+ "mate/m=number:" : [
+ ["pl","pl ","Plural"],
+ ["sg","sg ","Singular"],
+ ["*","* ","Undefined"]
+ ],
+ "mate/m=person:" : [
+ ["1","1 ", "First Person"],
+ ["2","2 ", "Second Person"],
+ ["3","3 ", "Third Person"]
+ ],
+ "mate/m=tense:" : [
+ ["past","past ", "Past"],
+ ["pres","pres ", "Present"]
+ ]
+};
+
+// Parse and show the table
+// Override getMatchInfo API call
+KorAP.API.getMatchInfo = function(match, callObj, cb) {
+ if (callObj["spans"] !== undefined && callObj["spans"] === true) {
+ cb({ "snippet": treeSnippet2 });
+ }
+ else {
+ cb({ "snippet": snippet });
+ }
+};
+
+
+/**
+ * Do some things at the beginning.
+ */
+document.addEventListener('DOMContentLoaded', function () {
+
+ // Decorate actions
+ var init = KorAP.init();
+
+ var menu = KorAP.MatchTreeMenu.create(
+ undefined,
+ menuContent
+ );
+
+ // document.getElementById('vc-choose').click();
+
+ // init.tutorial.show();
+
+ // Don't hide!!!
+ menu.hide = function () {};
+ document.getElementById('menu').appendChild(menu.element());
+ menu.limit(3);
+ menu.show();
+ menu.focus();
+});
diff --git a/dev/js/demo/hint.html b/dev/js/demo/hint.html
new file mode 100644
index 0000000..154bcaf
--- /dev/null
+++ b/dev/js/demo/hint.html
@@ -0,0 +1,248 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Hint demo</title>
+ <meta charset="utf-8" />
+ <script src="../src/menu.js"></script>
+ <script src="../src/hint.js"></script>
+ <link type="text/css" rel="stylesheet" href="../../css/build/kalamar.css" />
+ <script>
+// http://www.nlpado.de/~sebastian/software/ner_german.shtml
+// http://www.cnts.ua.ac.be/conll2003/ner/
+var namedEntities = [
+ ["I-LOC", "I-LOC ", "Location"],
+ ["I-MISC", "I-MISC ", "Miscellaneous"],
+ ["I-ORG", "I-ORG ", "Organization"],
+ ["I-PER", "I-PER ", "Person"]
+];
+
+// http://www.ids-mannheim.de/cosmas2/projekt/referenz/stts/morph.html
+// http://nachhalt.sfb632.uni-potsdam.de/owl-docu/stts.html
+var sttsArray = [
+ // "$.", "$(", "$,"
+ ["ADJA","ADJA ", "Attributive Adjective"],
+ ["ADJD","ADJD ", "Predicative Adjective"],
+ ["ADV","ADV ", "Adverb"],
+ ["APPO","APPO ", "Postposition"],
+ ["APPR","APPR ", "Preposition"],
+ ["APPRART","APPRART ", "Preposition with Determiner"],
+ ["APZR","APZR ","Right Circumposition"],
+ ["ART","ART ", "Determiner"],
+ ["CARD","CARD ", "Cardinal Number"],
+ ["FM","FM ", "Foreign Material"],
+ ["ITJ","ITJ ", "Interjection"],
+ ["KOKOM","KOKOM ", "Comparison Particle"],
+ ["KON","KON ", "Coordinating Conjuncion"],
+ ["KOUI","KOUI ", "Subordinating Conjunction with 'zu'"],
+ ["KOUS","KOUS ", "Subordinating Conjunction with Sentence"],
+ ["NE","NE ", "Named Entity"],
+ ["NN","NN ", "Normal Nomina"],
+ ["PAV", "PAV ", "Pronominal Adverb"],
+ ["PDAT","PDAT ","Attributive Demonstrative Pronoun"],
+ ["PDS","PDS ", "Substitutive Demonstrative Pronoun"],
+ ["PIAT","PIAT ", "Attributive Indefinite Pronoun without Determiner"],
+ ["PIDAT","PIDAT ", "Attributive Indefinite Pronoun with Determiner"],
+ ["PIS","PIS ", "Substitutive Indefinite Pronoun"],
+ ["PPER","PPER ", "Personal Pronoun"],
+ ["PPOSAT","PPOSAT ", "Attributive Possessive Pronoun"],
+ ["PPOSS","PPOSS ", "Substitutive Possessive Pronoun"],
+ ["PRELAT","PRELAT ", "Attributive Relative Pronoun"],
+ ["PRELS","PRELS ", "Substitutive Relative Pronoun"],
+ ["PRF","PRF ", "Reflexive Pronoun"],
+ ["PROAV","PROAV ", "Pronominal Adverb"],
+ ["PTKA","PTKA ","Particle with Adjective"],
+ ["PTKANT","PTKANT ", "Answering Particle"],
+ ["PTKNEG","PTKNEG ", "Negation Particle"],
+ ["PTKVZ","PTKVZ ", "Separated Verbal Particle"],
+ ["PTKZU","PTKZU ", "'zu' Particle"],
+ ["PWAT","PWAT ", "Attributive Interrogative Pronoun"],
+ ["PWAV","PWAV ", "Adverbial Interrogative Pronoun"],
+ ["PWS","PWS ", "Substitutive Interrogative Pronoun"],
+ ["TRUNC","TRUNC ","Truncated"],
+ ["VAFIN","VAFIN ", "Auxiliary Finite Verb"],
+ ["VAINF","VAINF ", "Auxiliary Infinite Verb"],
+ ["VAIMP","VAIMP ", "Auxiliary Finite Imperative Verb"],
+ ["VAPP","VAPP ", "Auxiliary Perfect Participle"],
+ ["VMFIN","VMFIN ", "Modal Finite Verb"],
+ ["VMINF","VMINF ", "Modal Infinite Verb"],
+ ["VMPP","VMPP ", "Modal Perfect Participle"],
+ ["VVFIN","VVFIN ","Finite Verb"],
+ ["VVIMP","VVIMP ", "Finite Imperative Verb"],
+ ["VVINF","VVINF ", "Infinite Verb"],
+ ["VVIZU","VVIZU ", "Infinite Verb with 'zu'"],
+ ["VVPP","VVPP ", "Perfect Participle"],
+ ["XY", "XY ", "Non-Word"]
+];
+
+var mateSttsArray = sttsArray.slice(0);
+mateSttsArray.push(
+ ["<root-POS>","<root-POS>","Root Part of Speech"]
+);
+
+
+var hintArray = {
+ "-" : [
+ ["Connexor", "cnx/", "Constituency, Lemma, Morphology, Part-of-Speech, Syntax"],
+ ["CoreNLP", "corenlp/", "Named Entities"],
+ ["Mate", "mate/", "Lemma, Morphology, Part-of-Speech"],
+ ["OpenNLP", "opennlp/", "Part-of-Speech"],
+ ["TreeTagger", "tt/", "Lemma, Part-of-Speech"],
+ ["Xerox Parser", "xip/", "Constituency, Lemma, Part-of-Speech"]
+ ],
+ "corenlp/" : [
+ ["Named Entity", "ne=" , "Combined"],
+ ["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
+ ["Named Entity", "ne_hgc_175m_600=", "ne_hgc_175m_600"]
+ ],
+ "corenlp/ne=" : namedEntities,
+ "corenlp/ne_dewac_175m_600=" : namedEntities,
+ "corenlp/ne_hgc_175m_600=" : namedEntities,
+ "cnx/" : [
+ ["Constituency", "c="],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ],
+ "cnx/c=" : [
+ ["np", "np ", "Nominal Phrase"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/m=" : [
+ ["Abbr","Abbr ", "Nouns: Abbreviation"],
+ ["CMP","CMP ", "Adjective: Comparative"],
+ ["IMP", "IMP ", "Mood: Imperative"],
+ ["IND", "IND ", "Mood: Indicative"],
+ ["INF", "INF ", "Infinitive"],
+ ["ORD","ORD ", "Numeral: Ordinal"],
+ ["PAST", "PAST ", "Tense: past"],
+ ["PCP", "PCP ", "Participle"],
+ ["PERF", "PERF ", "Perfective Participle"],
+ ["PL","PL ", "Nouns: Plural"],
+ ["PRES", "PRES ", "Tense: present"],
+ ["PROG", "PROG ", "Progressive Participle"],
+ ["Prop","Prop ", "Nouns: Proper Noun"],
+ ["SUB", "SUB ", "Mood: Subjunctive"],
+ ["SUP","SUP ", "Adjective: Superlative"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/p=" : [
+ ["A", "A ", "Adjective"],
+ ["ADV", "ADV ", "Adverb"],
+ ["CC", "CC ", "Coordination Marker"],
+ ["CS", "CS ", "Clause Marker"],
+ ["DET", "DET ", "Determiner"],
+ ["INTERJ", "INTERJ ", "Interjection"],
+ ["N", "N ", "Noun"],
+ ["NUM", "NUM ", "Numeral"],
+ ["PREP", "PREP ", "Preposition"],
+ ["PRON", "PRON ", "Pro-Nominal"],
+ ["V", "V ", "Verb"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/syntax.html
+ "cnx/syn=" : [
+ ["@ADVL", "@ADVL ", "Adverbial Head"],
+ ["@AUX", "@AUX ", "Auxiliary Verb"],
+ ["@CC", "@CC ", "Coordination"]
+ ["@MAIN", "@MAIN ", "Main Verb"],
+ ["@NH", "@NH ", "Nominal Head"],
+ ["@POSTMOD", "@POSTMOD ", "Postmodifier"],
+ ["@PREMARK", "@PREMARK ", "Preposed Marker"],
+ ["@PREMOD", "@POSTMOD ", "Premodifier"]
+ ],
+ "opennlp/" : [
+ ["Part-of-Speech", "p="]
+ ],
+ "opennlp/p=" : sttsArray,
+ "xip/" : [
+ ["Constituency", "c="],
+ // Inactive: ["Dependency", "d="],
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="],
+ ],
+ // "xip/c=" : [],
+ // Inactive: "xip/d=" : [],
+ // "xip/p=" : [],
+ "tt/" : [
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="]
+ ],
+ "tt/p=" : sttsArray,
+ "mate/" : [
+ // Inactive: "d" : ["d=", "Dependency"],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="]
+ ],
+ // Inactive: mate/d=
+ "mate/p=" : mateSttsArray,
+ "mate/m=" : [
+ ["Case", "case:"],
+ ["Degree", "degree:"],
+ ["Gender", "gender:"],
+ ["Mood", "mood:"],
+ ["Number", "number:"],
+ ["Person", "person:"],
+ ["Tense","tense:"],
+ ["No type", "<no-type> "]
+ ],
+ "mate/m=case:" : [
+ ["acc", "acc ", "Accusative"],
+ ["dat","dat ", "Dative"],
+ ["gen", "gen ","Genitive"],
+ ["nom","nom ", "Nominative"],
+ ["*","* ", "Undefined"]
+ ],
+ "mate/m=degree:" : [
+ ["comp","comp ", "Comparative"],
+ ["pos","pos ", "Positive"],
+ ["sup","sup ", "Superative"]
+ ],
+ "mate/m=gender:" : [
+ ["fem", "fem ", "Feminium"],
+ ["masc", "masc ", "Masculinum"],
+ ["neut","neut ", "Neuter"],
+ ["*","* ","Undefined"]
+ ],
+ "mate/m=mood:" : [
+ ["imp","imp ", "Imperative"],
+ ["ind","ind ", "Indicative"],
+ ["subj","subj ", "Subjunctive"]
+ ],
+ "mate/m=number:" : [
+ ["pl","pl ","Plural"],
+ ["sg","sg ","Singular"],
+ ["*","* ","Undefined"]
+ ],
+ "mate/m=person:" : [
+ ["1","1 ", "First Person"],
+ ["2","2 ", "Second Person"],
+ ["3","3 ", "Third Person"]
+ ],
+ "mate/m=tense:" : [
+ ["past","past ", "Past"],
+ ["pres","pres ", "Present"]
+ ]
+};
+
+ </script>
+ </head>
+ <body>
+ <header>
+ <form autocomplete="off" action="/kalamar">
+ <div id="searchbar">
+ <input type="search"
+ placeholder="Find ..."
+ name="q"
+ id="q-field"
+ autofocus="autofocus" />
+ <button type="submit"><span>Go</span></button>
+ </div>
+ </form>
+ </header>
+ <script>
+KorAP.hintArray = hintArray;
+var input = KorAP.Hint.create();
+ </script>
+ </body>
+</html>
diff --git a/dev/js/demo/match.html b/dev/js/demo/match.html
new file mode 100644
index 0000000..7b51996
--- /dev/null
+++ b/dev/js/demo/match.html
@@ -0,0 +1,205 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Match demo</title>
+ <meta charset="utf-8" />
+ <script src="../lib/dagre/dagre.min.js"></script>
+ <script src="../src/util.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../src/match.js"></script>
+ <link type="text/css"
+ rel="stylesheet"
+ href="../../css/font-awesome.min.css"></link>
+ <link type="text/css"
+ rel="stylesheet"
+ href="../../css/build/kalamar.css"></link>
+ <style type="text/css"
+ rel="stylesheet">
+
+body {
+ background-color: #ffa500;
+}
+
+body, text {
+ font-family: tahoma, verdana, arial;
+ color: #444;
+}
+
+text {
+ fill: #444;
+}
+
+ </style>
+ </head>
+ <body>
+ <div id="search">
+ <ol class="align-left">
+ <li data-corpus-id="WPD"
+ data-doc-id="WWW"
+ data-text-id="03313"
+ data-match-id="p102-103"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans"
+ id="WPD-WWW.03313-p102-103">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">In diesem Beispiel ist zu sehen, dass die beiden Variablen a und b lediglich ihre Werte an die Funktion </span><span class="match">test</span><span class="context-right"> übergeben, aber im Gegensatz zu einem Referenzparamter dabei unverändert bleiben.</span></div>
+ </div>
+ <p class="ref"><strong>Wertparameter</strong> by Hubi,Zwobot,4; published on 2005-03-28 as WWW.03313 (WPD)</p>
+ </li>
+ </ol>
+ </div>
+
+ <script>
+var snippet = "<span title=\"cnx/l:meist\">" +
+ " <span title=\"cnx/p:ADV\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:meist\">" +
+ " <span title=\"mate/p:ADV\">" +
+ " <span title=\"opennlp/p:ADV\">meist</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:deutlich\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:deutlich\">" +
+ " <span title=\"mate/m:degree:pos\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">deutlich</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:fähig\">" +
+ " <span title=\"cnx/l:leistung\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@NH\">" +
+ " <span title=\"mate/l:leistungsfähig\">" +
+ " <span title=\"mate/m:degree:comp\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">leistungsfähiger</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>";
+
+var treeSnippet =
+ "<span class=\"context-left\"></span>" +
+ "<span class=\"match\">" +
+ " <span title=\"xip/c:MC\">" +
+ " <span title=\"xip/c:TOP\">" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">Mit</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:DET\">dieser</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NOUN\">Methode</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:VERB\">ist</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">es</span>" +
+ " </span>" +
+ " <span title=\"xip/c:AP\">" +
+ " <span title=\"xip/c:ADV\">nun</span>" +
+ " <span title=\"xip/c:ADJ\">möglich</span>" +
+ " </span>" +
+ " <span title=\"xip/c:ADV\">z. B.</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Voice</span>" +
+ " </span>" +
+ " </span>" + "(" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Sprache</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" + ")" +
+ " <span title=\"xip/c:VERB\">bevorzugt</span>" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">in</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">der</span>" +
+ " </span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Bridge</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:INFC\">" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:VERB\">weiterzugeben</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span class=\"context-right\"></span>";
+
+// Parse and show the table
+// Override getMatchInfo API call
+KorAP.API.getMatchInfo = function(match, callObj) {
+ if (callObj["spans"] !== undefined && callObj["spans"] === true) {
+ return { "snippet": treeSnippet };
+ }
+ else {
+ return { "snippet": snippet };
+ }
+};
+
+KorAP.init();
+ </script>
+
+ <!--
+ <div class="matchinfo">
+ <div class="matchtable">
+ <table>
+ <thead>
+ <tr>
+ <th>Foundry</th>
+ <th>Layer</th>
+ <th>Ich</th>
+ <th>Du</th>
+ <th>Er</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr tabindex="0">
+ <th>Hallo</th>
+ <th>X</th>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ<br />ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ </tr>
+ <tr tabindex="0">
+ <th>geht</th>
+ <th>y</th>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ<br />ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ<br />ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ </tr>
+ <tr tabindex="0">
+ <th>es</th>
+ <th>z</th>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ <td>ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ<br />ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ<br />ABC DEF GHI JKL MNO PQR STU VWX YZ ABC DEF GHI JKL MNO PQR STU VWX YZ</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ -->
+
+ </body>
+</html>
diff --git a/dev/js/demo/matchSort.html b/dev/js/demo/matchSort.html
new file mode 100644
index 0000000..860560e
--- /dev/null
+++ b/dev/js/demo/matchSort.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Match demo</title>
+ <meta charset="utf-8" />
+ <link type="text/css"
+ rel="stylesheet"
+ href="../../css/build/kalamar.css"></link>
+ <style type="text/css"
+ rel="stylesheet">
+body {
+ background-color: #ffa500;
+}
+ </style>
+ </head>
+ <body>
+ <div id="search">
+ <ol class="align-left">
+ <li data-corpus-id="WPD"
+ data-doc-id="WWW"
+ data-text-id="03313"
+ data-match-id="p102-103"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans"
+ id="WPD-WWW.03313-p102-103">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">In diesem Beispiel ist zu sehen, dass die beiden Variablen a und b lediglich ihre Werte an die Funktion </span><span class="match">test</span><span class="context-right"> übergeben, aber im Gegensatz zu einem Referenzparamter dabei unverändert bleiben.</span></div>
+ </div>
+ <p class="ref"><strong>Wertparameter</strong> by Hubi,Zwobot,4; published on 2005-03-28 as WWW.03313 (WPD)</p>
+ </li>
+ <li data-corpus-id="WPD"
+ data-doc-id="WWW"
+ data-text-id="03313"
+ data-available-info="base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens tt/p=tokens tt/s=spans"
+ id="WPD-WWW.03313-p102-103">
+ <ol>
+ <li data-match-id="p102-103">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">In diesem Beispiel ist zu sehen, dass die beiden Variablen a und b lediglich ihre Werte an die Funktion </span><span class="match">test</span><span class="context-right"> übergeben, aber im Gegensatz zu einem Referenzparamter dabei unverändert bleiben.</span></div>
+ </div>
+ <p class="ref"><strong>Wertparameter</strong> by Hubi,Zwobot,4; published on 2005-03-28 as WWW.03313 (WPD)</p>
+ </li>
+ <li data-match-id="p108-109">
+ <div>
+ <div class="snippet startMore endMore"><span class="context-left">In diesem Beispiel ist zu sehen, dass die beiden Variablen a und b lediglich ihre Werte an die Funktion </span><span class="match">test</span><span class="context-right"> übergeben, aber im Gegensatz zu einem Referenzparamter dabei unverändert bleiben.</span></div>
+ </div>
+ <p class="ref"><strong>Wertparameter</strong> by Hubi,Zwobot,4; published on 2005-03-28 as WWW.03313 (WPD)</p>
+ </li>
+ </ol>
+ <span class="matchMore">+5</span>
+ </li>
+ </ol>
+ </div>
+
+
+ <p>Sort by</p>
+ <p>click auf ein Item gibt ihm höchste Priorität, Klick auf asc/desc ändert die Sortierung, Klick auf ein aktives Feld deaktiviert. Sortiert wird nur nach Klick auf (okay) if prefix feld</p>
+ <ul class="menu sort">
+ <li class="active">CorpusID (asc|desc)</li>
+ <li class="active">Author (asc|desc)</li>
+ <li>Date</li>
+ <span class="pref right" style="right: 0; left: auto;">Sortieren</span>
+ </ul>
+
+ </body>
+</html>
diff --git a/dev/js/demo/menu.html b/dev/js/demo/menu.html
new file mode 100644
index 0000000..985be65
--- /dev/null
+++ b/dev/js/demo/menu.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Menu demo</title>
+ <meta charset="utf-8" />
+ <script src="../src/menu.js"></script>
+<!--
+ <link href="../../css/vc.css" rel="stylesheet" type="text/css"></link>
+-->
+ <link href="../../css/build/kalamar.css" rel="stylesheet" type="text/css"></link>
+ <style type="text/css" rel="stylesheet">
+.info {
+ background-color: #ddd;
+ color: black;
+ padding: 1em;
+ font-family: mono;
+}
+body {
+ margin: 0;
+ background-color: #ddd;
+}
+ </style>
+ </head>
+ <body>
+ <div style="position: absolute; background-color: #7ba400; height: 50px; width: 100%;"></div>
+
+ <div id="menu" class="vc"></div>
+
+ <main>
+ <div>
+ <div id="pagination"><a rel="prev"><span><i class="fa fa-caret-left"></i></span></a><a rel="self"><span>1</span></a><a href="/kalamar?q=der+%5Bmate/m%3Dnumber:sg%5D&ql=poliqarp&p=2"><span>2</span></a><a href="/kalamar?q=der+%5Bmate/m%3Dnumber:sg%5D&ql=poliqarp&p=3"><span>3</span></a><span><i class="fa fa-ellipsis-h"></i></span><a href="/kalamar?q=der+%5Bmate/m%3Dnumber:sg%5D&ql=poliqarp&p=52230"><span>52230</span></a><a rel="next" href="/kalamar?q=der+%5Bmate/m%3Dnumber:sg%5D&ql=poliqarp&p=2"><span><i class="fa fa-caret-right"></i></span></a></div>
+ </div>
+ </main>
+
+ <script>
+KorAP.OwnMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem).upgradeTo(KorAP.OwnMenuItem)._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+
+ // enter or click
+ onclick : function () {
+ console.log(this._name);
+ },
+
+ // right arrow
+ further : function () {
+ console.log("Further: " + this._name);
+ },
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._content = document.createTextNode(this._name);
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ }
+};
+
+KorAP.OwnPrefix = {
+ create : function () {
+ return Object.create(KorAP.MenuPrefix)
+ .upgradeTo(KorAP.OwnPrefix)
+ ._init();
+ },
+ onclick : function () {
+ console.log('Prefix: ' + this.value());
+ }
+}
+
+KorAP.OwnMenu = {
+ create : function (params) {
+ return Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.OwnMenu)
+ ._init(KorAP.OwnMenuItem, KorAP.OwnPrefix, params);
+ }
+};
+
+var menu = KorAP.OwnMenu.create([
+ ['Titel', 'title', 'string'],
+ ['Untertitel', 'subTitle', 'string'],
+ ['Veröffentlichungsdatum', 'pubDate', 'date'],
+ ['Länge', 'length', 'integer'],
+ ['Autor', 'author', 'string'],
+ ['Genre', 'genre', 'string'],
+ ['corpusID', 'corpusID', 'string'],
+ ['docID', 'docID', 'string'],
+ ['textID', 'textID', 'string']
+]);
+
+ document.getElementById('menu').appendChild(menu.element());
+
+ menu.limit(3);
+ menu.show('');
+ menu.focus();
+
+ </script>
+ </body>
+</html>
diff --git a/dev/js/demo/session.html b/dev/js/demo/session.html
new file mode 100644
index 0000000..59cfe4f
--- /dev/null
+++ b/dev/js/demo/session.html
@@ -0,0 +1,44 @@
+ <!DOCTYPE html>
+<html>
+ <head>
+ <title>Cookie demo</title>
+ <script src="../src/session.js"></script>
+ <script>
+
+var cookey;
+
+function add (val) {
+ var list = document.getElementById('number');
+ list.textContent += '-' + val;
+ cookey.set('n', list.textContent);
+};
+
+function removeCookie () {
+ cookey.clear();
+};
+
+document.addEventListener('DOMContentLoaded', function () {
+ cookey = KorAP.Session.create('peter');
+ document.getElementById('number').textContent = cookey.get('n') || '';
+
+ var elements = document.getElementsByClassName('num');
+ for (var i = 0; i < elements.length; i++) {
+ elements[i].addEventListener(
+ 'click',
+ function (e) {
+ add(this.textContent);
+ }
+ );
+ };
+});
+
+ </script>
+ </head>
+ <body>
+ <div id="number"></div>
+ <span class="num">1</span>
+ <span class="num">2</span>
+ <span class="num">3</span>
+ <div onclick="removeCookie()">remove</div>
+ </body>
+</html>
diff --git a/dev/js/demo/vc.html b/dev/js/demo/vc.html
new file mode 100644
index 0000000..ae6aef1
--- /dev/null
+++ b/dev/js/demo/vc.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Virtual Collection demo</title>
+ <meta charset="utf-8" />
+ <script src="../src/menu.js"></script>
+ <script src="../src/vc.js"></script>
+ <link href="../../css/build/kalamar.css" rel="stylesheet" type="text/css"></link>
+ <style type="text/css" rel="stylesheet">
+
+body {
+ background-color: #7ba400;
+ color: white;
+ font-family: tahoma, verdana, arial;
+ font-size: 10pt;
+ margin: 20px;
+}
+
+.info {
+ background-color:white;
+ color: black;
+ padding: 1em;
+ font-family: mono;
+}
+ </style>
+ </head>
+ <body>
+ <div id="vc"></div>
+ <div id="menu" class="vc"></div>
+
+ <script>
+ var json = {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:and",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Der Birnbaum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"Mannheim",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"Aufzucht und Pflege",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"Gedichte",
+ "match":"match:eq",
+ "rewrites" : [
+ {
+ "@type": "koral:rewrite",
+ "src" : "policy",
+ "operation" : "operation:injection",
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsdatum",
+ "type":"type:date",
+ "value":"2015-03-05",
+ "match":"match:geq"
+ }
+ ]
+ };
+ KorAP.Locale.AND = 'und';
+ KorAP.Locale.OR = 'oder';
+
+ var vc = KorAP.VirtualCollection.render(json);
+ document.getElementById('vc').appendChild(vc.element());
+
+ function showJSON() {
+ document.getElementById("json").innerHTML = JSON.stringify(vc.root().toJson());
+ };
+
+ function showQuery() {
+ document.getElementById("query").innerHTML = vc.root().toQuery();
+ };
+
+ </script>
+
+ <hr />
+
+ <p><a onclick="showJSON()" style="cursor:pointer">show JSON!</a></p>
+ <div id="json" class="info"></div>
+
+ <hr />
+
+ <p><a onclick="showQuery()" style="cursor:pointer">show Query!</a></p>
+ <div id="query" class="info"></div>
+
+ </body>
+</html>
diff --git a/dev/js/lib/dagre/dagre.js b/dev/js/lib/dagre/dagre.js
new file mode 100644
index 0000000..b62dde6
--- /dev/null
+++ b/dev/js/lib/dagre/dagre.js
@@ -0,0 +1,10825 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.dagre=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+Copyright (c) 2012-2014 Chris Pettitt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+module.exports = {
+ graphlib: require("./lib/graphlib"),
+
+ layout: require("./lib/layout"),
+ debug: require("./lib/debug"),
+ util: {
+ time: require("./lib/util").time,
+ notime: require("./lib/util").notime
+ },
+ version: require("./lib/version")
+};
+
+},{"./lib/debug":6,"./lib/graphlib":7,"./lib/layout":9,"./lib/util":29,"./lib/version":30}],2:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ greedyFAS = require("./greedy-fas");
+
+module.exports = {
+ run: run,
+ undo: undo
+};
+
+function run(g) {
+ var fas = (g.graph().acyclicer === "greedy"
+ ? greedyFAS(g, weightFn(g))
+ : dfsFAS(g));
+ _.each(fas, function(e) {
+ var label = g.edge(e);
+ g.removeEdge(e);
+ label.forwardName = e.name;
+ label.reversed = true;
+ g.setEdge(e.w, e.v, label, _.uniqueId("rev"));
+ });
+
+ function weightFn(g) {
+ return function(e) {
+ return g.edge(e).weight;
+ };
+ }
+}
+
+function dfsFAS(g) {
+ var fas = [],
+ stack = {},
+ visited = {};
+
+ function dfs(v) {
+ if (_.has(visited, v)) {
+ return;
+ }
+ visited[v] = true;
+ stack[v] = true;
+ _.each(g.outEdges(v), function(e) {
+ if (_.has(stack, e.w)) {
+ fas.push(e);
+ } else {
+ dfs(e.w);
+ }
+ });
+ delete stack[v];
+ }
+
+ _.each(g.nodes(), dfs);
+ return fas;
+}
+
+function undo(g) {
+ _.each(g.edges(), function(e) {
+ var label = g.edge(e);
+ if (label.reversed) {
+ g.removeEdge(e);
+
+ var forwardName = label.forwardName;
+ delete label.reversed;
+ delete label.forwardName;
+ g.setEdge(e.w, e.v, label, forwardName);
+ }
+ });
+}
+
+},{"./greedy-fas":8,"./lodash":10}],3:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = addBorderSegments;
+
+function addBorderSegments(g) {
+ function dfs(v) {
+ var children = g.children(v),
+ node = g.node(v);
+ if (children.length) {
+ _.each(children, dfs);
+ }
+
+ if (_.has(node, "minRank")) {
+ node.borderLeft = [];
+ node.borderRight = [];
+ for (var rank = node.minRank, maxRank = node.maxRank + 1;
+ rank < maxRank;
+ ++rank) {
+ addBorderNode(g, "borderLeft", "_bl", v, node, rank);
+ addBorderNode(g, "borderRight", "_br", v, node, rank);
+ }
+ }
+ }
+
+ _.each(g.children(), dfs);
+}
+
+function addBorderNode(g, prop, prefix, sg, sgNode, rank) {
+ var label = { width: 0, height: 0, rank: rank },
+ prev = sgNode[prop][rank - 1],
+ curr = util.addDummyNode(g, "border", label, prefix);
+ sgNode[prop][rank] = curr;
+ g.setParent(curr, sg);
+ if (prev) {
+ g.setEdge(prev, curr, { weight: 1 });
+ }
+}
+
+},{"./lodash":10,"./util":29}],4:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = {
+ adjust: adjust,
+ undo: undo
+};
+
+function adjust(g) {
+ var rankDir = g.graph().rankdir.toLowerCase();
+ if (rankDir === "lr" || rankDir === "rl") {
+ swapWidthHeight(g);
+ }
+}
+
+function undo(g) {
+ var rankDir = g.graph().rankdir.toLowerCase();
+ if (rankDir === "bt" || rankDir === "rl") {
+ reverseY(g);
+ }
+
+ if (rankDir === "lr" || rankDir === "rl") {
+ swapXY(g);
+ swapWidthHeight(g);
+ }
+}
+
+function swapWidthHeight(g) {
+ _.each(g.nodes(), function(v) { swapWidthHeightOne(g.node(v)); });
+ _.each(g.edges(), function(e) { swapWidthHeightOne(g.edge(e)); });
+}
+
+function swapWidthHeightOne(attrs) {
+ var w = attrs.width;
+ attrs.width = attrs.height;
+ attrs.height = w;
+}
+
+function reverseY(g) {
+ _.each(g.nodes(), function(v) { reverseYOne(g.node(v)); });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, reverseYOne);
+ if (_.has(edge, "y")) {
+ reverseYOne(edge);
+ }
+ });
+}
+
+function reverseYOne(attrs) {
+ attrs.y = -attrs.y;
+}
+
+function swapXY(g) {
+ _.each(g.nodes(), function(v) { swapXYOne(g.node(v)); });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, swapXYOne);
+ if (_.has(edge, "x")) {
+ swapXYOne(edge);
+ }
+ });
+}
+
+function swapXYOne(attrs) {
+ var x = attrs.x;
+ attrs.x = attrs.y;
+ attrs.y = x;
+}
+
+},{"./lodash":10}],5:[function(require,module,exports){
+/*
+ * Simple doubly linked list implementation derived from Cormen, et al.,
+ * "Introduction to Algorithms".
+ */
+
+module.exports = List;
+
+function List() {
+ var sentinel = {};
+ sentinel._next = sentinel._prev = sentinel;
+ this._sentinel = sentinel;
+}
+
+List.prototype.dequeue = function() {
+ var sentinel = this._sentinel,
+ entry = sentinel._prev;
+ if (entry !== sentinel) {
+ unlink(entry);
+ return entry;
+ }
+};
+
+List.prototype.enqueue = function(entry) {
+ var sentinel = this._sentinel;
+ if (entry._prev && entry._next) {
+ unlink(entry);
+ }
+ entry._next = sentinel._next;
+ sentinel._next._prev = entry;
+ sentinel._next = entry;
+ entry._prev = sentinel;
+};
+
+List.prototype.toString = function() {
+ var strs = [],
+ sentinel = this._sentinel,
+ curr = sentinel._prev;
+ while (curr !== sentinel) {
+ strs.push(JSON.stringify(curr, filterOutLinks));
+ curr = curr._prev;
+ }
+ return "[" + strs.join(", ") + "]";
+};
+
+function unlink(entry) {
+ entry._prev._next = entry._next;
+ entry._next._prev = entry._prev;
+ delete entry._next;
+ delete entry._prev;
+}
+
+function filterOutLinks(k, v) {
+ if (k !== "_next" && k !== "_prev") {
+ return v;
+ }
+}
+
+},{}],6:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = {
+ debugOrdering: debugOrdering
+};
+
+/* istanbul ignore next */
+function debugOrdering(g) {
+ var layerMatrix = util.buildLayerMatrix(g);
+
+ var h = new Graph({ compound: true, multigraph: true }).setGraph({});
+
+ _.each(g.nodes(), function(v) {
+ h.setNode(v, { label: v });
+ h.setParent(v, "layer" + g.node(v).rank);
+ });
+
+ _.each(g.edges(), function(e) {
+ h.setEdge(e.v, e.w, {}, e.name);
+ });
+
+ _.each(layerMatrix, function(layer, i) {
+ var layerV = "layer" + i;
+ h.setNode(layerV, { rank: "same" });
+ _.reduce(layer, function(u, v) {
+ h.setEdge(u, v, { style: "invis" });
+ return v;
+ });
+ });
+
+ return h;
+}
+
+},{"./graphlib":7,"./lodash":10,"./util":29}],7:[function(require,module,exports){
+/* global window */
+
+var graphlib;
+
+if (require) {
+ try {
+ graphlib = require("graphlib");
+ } catch (e) {}
+}
+
+if (!graphlib) {
+ graphlib = window.graphlib;
+}
+
+module.exports = graphlib;
+
+},{"graphlib":31}],8:[function(require,module,exports){
+var _ = require("./lodash"),
+ Graph = require("./graphlib").Graph,
+ List = require("./data/list");
+
+/*
+ * A greedy heuristic for finding a feedback arc set for a graph. A feedback
+ * arc set is a set of edges that can be removed to make a graph acyclic.
+ * The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and
+ * effective heuristic for the feedback arc set problem." This implementation
+ * adjusts that from the paper to allow for weighted edges.
+ */
+module.exports = greedyFAS;
+
+var DEFAULT_WEIGHT_FN = _.constant(1);
+
+function greedyFAS(g, weightFn) {
+ if (g.nodeCount() <= 1) {
+ return [];
+ }
+ var state = buildState(g, weightFn || DEFAULT_WEIGHT_FN);
+ var results = doGreedyFAS(state.graph, state.buckets, state.zeroIdx);
+
+ // Expand multi-edges
+ return _.flatten(_.map(results, function(e) {
+ return g.outEdges(e.v, e.w);
+ }), true);
+}
+
+function doGreedyFAS(g, buckets, zeroIdx) {
+ var results = [],
+ sources = buckets[buckets.length - 1],
+ sinks = buckets[0];
+
+ var entry;
+ while (g.nodeCount()) {
+ while ((entry = sinks.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+ while ((entry = sources.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+ if (g.nodeCount()) {
+ for (var i = buckets.length - 2; i > 0; --i) {
+ entry = buckets[i].dequeue();
+ if (entry) {
+ results = results.concat(removeNode(g, buckets, zeroIdx, entry, true));
+ break;
+ }
+ }
+ }
+ }
+
+ return results;
+}
+
+function removeNode(g, buckets, zeroIdx, entry, collectPredecessors) {
+ var results = collectPredecessors ? [] : undefined;
+
+ _.each(g.inEdges(entry.v), function(edge) {
+ var weight = g.edge(edge),
+ uEntry = g.node(edge.v);
+
+ if (collectPredecessors) {
+ results.push({ v: edge.v, w: edge.w });
+ }
+
+ uEntry.out -= weight;
+ assignBucket(buckets, zeroIdx, uEntry);
+ });
+
+ _.each(g.outEdges(entry.v), function(edge) {
+ var weight = g.edge(edge),
+ w = edge.w,
+ wEntry = g.node(w);
+ wEntry["in"] -= weight;
+ assignBucket(buckets, zeroIdx, wEntry);
+ });
+
+ g.removeNode(entry.v);
+
+ return results;
+}
+
+function buildState(g, weightFn) {
+ var fasGraph = new Graph(),
+ maxIn = 0,
+ maxOut = 0;
+
+ _.each(g.nodes(), function(v) {
+ fasGraph.setNode(v, { v: v, "in": 0, out: 0 });
+ });
+
+ // Aggregate weights on nodes, but also sum the weights across multi-edges
+ // into a single edge for the fasGraph.
+ _.each(g.edges(), function(e) {
+ var prevWeight = fasGraph.edge(e.v, e.w) || 0,
+ weight = weightFn(e),
+ edgeWeight = prevWeight + weight;
+ fasGraph.setEdge(e.v, e.w, edgeWeight);
+ maxOut = Math.max(maxOut, fasGraph.node(e.v).out += weight);
+ maxIn = Math.max(maxIn, fasGraph.node(e.w)["in"] += weight);
+ });
+
+ var buckets = _.range(maxOut + maxIn + 3).map(function() { return new List(); });
+ var zeroIdx = maxIn + 1;
+
+ _.each(fasGraph.nodes(), function(v) {
+ assignBucket(buckets, zeroIdx, fasGraph.node(v));
+ });
+
+ return { graph: fasGraph, buckets: buckets, zeroIdx: zeroIdx };
+}
+
+function assignBucket(buckets, zeroIdx, entry) {
+ if (!entry.out) {
+ buckets[0].enqueue(entry);
+ } else if (!entry["in"]) {
+ buckets[buckets.length - 1].enqueue(entry);
+ } else {
+ buckets[entry.out - entry["in"] + zeroIdx].enqueue(entry);
+ }
+}
+
+},{"./data/list":5,"./graphlib":7,"./lodash":10}],9:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ acyclic = require("./acyclic"),
+ normalize = require("./normalize"),
+ rank = require("./rank"),
+ normalizeRanks = require("./util").normalizeRanks,
+ parentDummyChains = require("./parent-dummy-chains"),
+ removeEmptyRanks = require("./util").removeEmptyRanks,
+ nestingGraph = require("./nesting-graph"),
+ addBorderSegments = require("./add-border-segments"),
+ coordinateSystem = require("./coordinate-system"),
+ order = require("./order"),
+ position = require("./position"),
+ util = require("./util"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = layout;
+
+function layout(g, opts) {
+ var time = opts && opts.debugTiming ? util.time : util.notime;
+ time("layout", function() {
+ var layoutGraph = time(" buildLayoutGraph",
+ function() { return buildLayoutGraph(g); });
+ time(" runLayout", function() { runLayout(layoutGraph, time); });
+ time(" updateInputGraph", function() { updateInputGraph(g, layoutGraph); });
+ });
+}
+
+function runLayout(g, time) {
+ time(" makeSpaceForEdgeLabels", function() { makeSpaceForEdgeLabels(g); });
+ time(" removeSelfEdges", function() { removeSelfEdges(g); });
+ time(" acyclic", function() { acyclic.run(g); });
+ time(" nestingGraph.run", function() { nestingGraph.run(g); });
+ time(" rank", function() { rank(util.asNonCompoundGraph(g)); });
+ time(" injectEdgeLabelProxies", function() { injectEdgeLabelProxies(g); });
+ time(" removeEmptyRanks", function() { removeEmptyRanks(g); });
+ time(" nestingGraph.cleanup", function() { nestingGraph.cleanup(g); });
+ time(" normalizeRanks", function() { normalizeRanks(g); });
+ time(" assignRankMinMax", function() { assignRankMinMax(g); });
+ time(" removeEdgeLabelProxies", function() { removeEdgeLabelProxies(g); });
+ time(" normalize.run", function() { normalize.run(g); });
+ time(" parentDummyChains", function() { parentDummyChains(g); });
+ time(" addBorderSegments", function() { addBorderSegments(g); });
+ time(" order", function() { order(g); });
+ time(" insertSelfEdges", function() { insertSelfEdges(g); });
+ time(" adjustCoordinateSystem", function() { coordinateSystem.adjust(g); });
+ time(" position", function() { position(g); });
+ time(" positionSelfEdges", function() { positionSelfEdges(g); });
+ time(" removeBorderNodes", function() { removeBorderNodes(g); });
+ time(" normalize.undo", function() { normalize.undo(g); });
+ time(" fixupEdgeLabelCoords", function() { fixupEdgeLabelCoords(g); });
+ time(" undoCoordinateSystem", function() { coordinateSystem.undo(g); });
+ time(" translateGraph", function() { translateGraph(g); });
+ time(" assignNodeIntersects", function() { assignNodeIntersects(g); });
+ time(" reversePoints", function() { reversePointsForReversedEdges(g); });
+ time(" acyclic.undo", function() { acyclic.undo(g); });
+}
+
+/*
+ * Copies final layout information from the layout graph back to the input
+ * graph. This process only copies whitelisted attributes from the layout graph
+ * to the input graph, so it serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function updateInputGraph(inputGraph, layoutGraph) {
+ _.each(inputGraph.nodes(), function(v) {
+ var inputLabel = inputGraph.node(v),
+ layoutLabel = layoutGraph.node(v);
+
+ if (inputLabel) {
+ inputLabel.x = layoutLabel.x;
+ inputLabel.y = layoutLabel.y;
+
+ if (layoutGraph.children(v).length) {
+ inputLabel.width = layoutLabel.width;
+ inputLabel.height = layoutLabel.height;
+ }
+ }
+ });
+
+ _.each(inputGraph.edges(), function(e) {
+ var inputLabel = inputGraph.edge(e),
+ layoutLabel = layoutGraph.edge(e);
+
+ inputLabel.points = layoutLabel.points;
+ if (_.has(layoutLabel, "x")) {
+ inputLabel.x = layoutLabel.x;
+ inputLabel.y = layoutLabel.y;
+ }
+ });
+
+ inputGraph.graph().width = layoutGraph.graph().width;
+ inputGraph.graph().height = layoutGraph.graph().height;
+}
+
+var graphNumAttrs = ["nodesep", "edgesep", "ranksep", "marginx", "marginy"],
+ graphDefaults = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: "tb" },
+ graphAttrs = ["acyclicer", "ranker", "rankdir", "align"],
+ nodeNumAttrs = ["width", "height"],
+ nodeDefaults = { width: 0, height: 0 },
+ edgeNumAttrs = ["minlen", "weight", "width", "height", "labeloffset"],
+ edgeDefaults = {
+ minlen: 1, weight: 1, width: 0, height: 0,
+ labeloffset: 10, labelpos: "r"
+ },
+ edgeAttrs = ["labelpos"];
+
+/*
+ * Constructs a new graph from the input graph, which can be used for layout.
+ * This process copies only whitelisted attributes from the input graph to the
+ * layout graph. Thus this function serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function buildLayoutGraph(inputGraph) {
+ var g = new Graph({ multigraph: true, compound: true }),
+ graph = canonicalize(inputGraph.graph());
+
+ g.setGraph(_.merge({},
+ graphDefaults,
+ selectNumberAttrs(graph, graphNumAttrs),
+ _.pick(graph, graphAttrs)));
+
+ _.each(inputGraph.nodes(), function(v) {
+ var node = canonicalize(inputGraph.node(v));
+ g.setNode(v, _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults));
+ g.setParent(v, inputGraph.parent(v));
+ });
+
+ _.each(inputGraph.edges(), function(e) {
+ var edge = canonicalize(inputGraph.edge(e));
+ g.setEdge(e, _.merge({},
+ edgeDefaults,
+ selectNumberAttrs(edge, edgeNumAttrs),
+ _.pick(edge, edgeAttrs)));
+ });
+
+ return g;
+}
+
+/*
+ * This idea comes from the Gansner paper: to account for edge labels in our
+ * layout we split each rank in half by doubling minlen and halving ranksep.
+ * Then we can place labels at these mid-points between nodes.
+ *
+ * We also add some minimal padding to the width to push the label for the edge
+ * away from the edge itself a bit.
+ */
+function makeSpaceForEdgeLabels(g) {
+ var graph = g.graph();
+ graph.ranksep /= 2;
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ edge.minlen *= 2;
+ if (edge.labelpos.toLowerCase() !== "c") {
+ if (graph.rankdir === "TB" || graph.rankdir === "BT") {
+ edge.width += edge.labeloffset;
+ } else {
+ edge.height += edge.labeloffset;
+ }
+ }
+ });
+}
+
+/*
+ * Creates temporary dummy nodes that capture the rank in which each edge's
+ * label is going to, if it has one of non-zero width and height. We do this
+ * so that we can safely remove empty ranks while preserving balance for the
+ * label's position.
+ */
+function injectEdgeLabelProxies(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.width && edge.height) {
+ var v = g.node(e.v),
+ w = g.node(e.w),
+ label = { rank: (w.rank - v.rank) / 2 + v.rank, e: e };
+ util.addDummyNode(g, "edge-proxy", label, "_ep");
+ }
+ });
+}
+
+function assignRankMinMax(g) {
+ var maxRank = 0;
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.borderTop) {
+ node.minRank = g.node(node.borderTop).rank;
+ node.maxRank = g.node(node.borderBottom).rank;
+ maxRank = _.max(maxRank, node.maxRank);
+ }
+ });
+ g.graph().maxRank = maxRank;
+}
+
+function removeEdgeLabelProxies(g) {
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.dummy === "edge-proxy") {
+ g.edge(node.e).labelRank = node.rank;
+ g.removeNode(v);
+ }
+ });
+}
+
+function translateGraph(g) {
+ var minX = Number.POSITIVE_INFINITY,
+ maxX = 0,
+ minY = Number.POSITIVE_INFINITY,
+ maxY = 0,
+ graphLabel = g.graph(),
+ marginX = graphLabel.marginx || 0,
+ marginY = graphLabel.marginy || 0;
+
+ function getExtremes(attrs) {
+ var x = attrs.x,
+ y = attrs.y,
+ w = attrs.width,
+ h = attrs.height;
+ minX = Math.min(minX, x - w / 2);
+ maxX = Math.max(maxX, x + w / 2);
+ minY = Math.min(minY, y - h / 2);
+ maxY = Math.max(maxY, y + h / 2);
+ }
+
+ _.each(g.nodes(), function(v) { getExtremes(g.node(v)); });
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (_.has(edge, "x")) {
+ getExtremes(edge);
+ }
+ });
+
+ minX -= marginX;
+ minY -= marginY;
+
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ node.x -= minX;
+ node.y -= minY;
+ });
+
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ _.each(edge.points, function(p) {
+ p.x -= minX;
+ p.y -= minY;
+ });
+ if (_.has(edge, "x")) { edge.x -= minX; }
+ if (_.has(edge, "y")) { edge.y -= minY; }
+ });
+
+ graphLabel.width = maxX - minX + marginX;
+ graphLabel.height = maxY - minY + marginY;
+}
+
+function assignNodeIntersects(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e),
+ nodeV = g.node(e.v),
+ nodeW = g.node(e.w),
+ p1, p2;
+ if (!edge.points) {
+ edge.points = [];
+ p1 = nodeW;
+ p2 = nodeV;
+ } else {
+ p1 = edge.points[0];
+ p2 = edge.points[edge.points.length - 1];
+ }
+ edge.points.unshift(util.intersectRect(nodeV, p1));
+ edge.points.push(util.intersectRect(nodeW, p2));
+ });
+}
+
+function fixupEdgeLabelCoords(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (_.has(edge, "x")) {
+ if (edge.labelpos === "l" || edge.labelpos === "r") {
+ edge.width -= edge.labeloffset;
+ }
+ switch (edge.labelpos) {
+ case "l": edge.x -= edge.width / 2 + edge.labeloffset; break;
+ case "r": edge.x += edge.width / 2 + edge.labeloffset; break;
+ }
+ }
+ });
+}
+
+function reversePointsForReversedEdges(g) {
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.reversed) {
+ edge.points.reverse();
+ }
+ });
+}
+
+function removeBorderNodes(g) {
+ _.each(g.nodes(), function(v) {
+ if (g.children(v).length) {
+ var node = g.node(v),
+ t = g.node(node.borderTop),
+ b = g.node(node.borderBottom),
+ l = g.node(_.last(node.borderLeft)),
+ r = g.node(_.last(node.borderRight));
+
+ node.width = Math.abs(r.x - l.x);
+ node.height = Math.abs(b.y - t.y);
+ node.x = l.x + node.width / 2;
+ node.y = t.y + node.height / 2;
+ }
+ });
+
+ _.each(g.nodes(), function(v) {
+ if (g.node(v).dummy === "border") {
+ g.removeNode(v);
+ }
+ });
+}
+
+function removeSelfEdges(g) {
+ _.each(g.edges(), function(e) {
+ if (e.v === e.w) {
+ var node = g.node(e.v);
+ if (!node.selfEdges) {
+ node.selfEdges = [];
+ }
+ node.selfEdges.push({ e: e, label: g.edge(e) });
+ g.removeEdge(e);
+ }
+ });
+}
+
+function insertSelfEdges(g) {
+ var layers = util.buildLayerMatrix(g);
+ _.each(layers, function(layer) {
+ var orderShift = 0;
+ _.each(layer, function(v, i) {
+ var node = g.node(v);
+ node.order = i + orderShift;
+ _.each(node.selfEdges, function(selfEdge) {
+ util.addDummyNode(g, "selfedge", {
+ width: selfEdge.label.width,
+ height: selfEdge.label.height,
+ rank: node.rank,
+ order: i + (++orderShift),
+ e: selfEdge.e,
+ label: selfEdge.label
+ }, "_se");
+ });
+ delete node.selfEdges;
+ });
+ });
+}
+
+function positionSelfEdges(g) {
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (node.dummy === "selfedge") {
+ var selfNode = g.node(node.e.v),
+ x = selfNode.x + selfNode.width / 2,
+ y = selfNode.y,
+ dx = node.x - x,
+ dy = selfNode.height / 2;
+ g.setEdge(node.e, node.label);
+ g.removeNode(v);
+ node.label.points = [
+ { x: x + 2 * dx / 3, y: y - dy },
+ { x: x + 5 * dx / 6, y: y - dy },
+ { x: x + dx , y: y },
+ { x: x + 5 * dx / 6, y: y + dy },
+ { x: x + 2 * dx / 3, y: y + dy },
+ ];
+ node.label.x = node.x;
+ node.label.y = node.y;
+ }
+ });
+}
+
+function selectNumberAttrs(obj, attrs) {
+ return _.mapValues(_.pick(obj, attrs), Number);
+}
+
+function canonicalize(attrs) {
+ var newAttrs = {};
+ _.each(attrs, function(v, k) {
+ newAttrs[k.toLowerCase()] = v;
+ });
+ return newAttrs;
+}
+
+},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./graphlib":7,"./lodash":10,"./nesting-graph":11,"./normalize":12,"./order":17,"./parent-dummy-chains":22,"./position":24,"./rank":26,"./util":29}],10:[function(require,module,exports){
+/* global window */
+
+var lodash;
+
+if (require) {
+ try {
+ lodash = require("lodash");
+ } catch (e) {}
+}
+
+if (!lodash) {
+ lodash = window._;
+}
+
+module.exports = lodash;
+
+},{"lodash":51}],11:[function(require,module,exports){
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = {
+ run: run,
+ cleanup: cleanup
+};
+
+/*
+ * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
+ * adds appropriate edges to ensure that all cluster nodes are placed between
+ * these boundries, and ensures that the graph is connected.
+ *
+ * In addition we ensure, through the use of the minlen property, that nodes
+ * and subgraph border nodes to not end up on the same rank.
+ *
+ * Preconditions:
+ *
+ * 1. Input graph is a DAG
+ * 2. Nodes in the input graph has a minlen attribute
+ *
+ * Postconditions:
+ *
+ * 1. Input graph is connected.
+ * 2. Dummy nodes are added for the tops and bottoms of subgraphs.
+ * 3. The minlen attribute for nodes is adjusted to ensure nodes do not
+ * get placed on the same rank as subgraph border nodes.
+ *
+ * The nesting graph idea comes from Sander, "Layout of Compound Directed
+ * Graphs."
+ */
+function run(g) {
+ var root = util.addDummyNode(g, "root", {}, "_root"),
+ depths = treeDepths(g),
+ height = _.max(depths) - 1,
+ nodeSep = 2 * height + 1;
+
+ g.graph().nestingRoot = root;
+
+ // Multiply minlen by nodeSep to align nodes on non-border ranks.
+ _.each(g.edges(), function(e) { g.edge(e).minlen *= nodeSep; });
+
+ // Calculate a weight that is sufficient to keep subgraphs vertically compact
+ var weight = sumWeights(g) + 1;
+
+ // Create border nodes and link them up
+ _.each(g.children(), function(child) {
+ dfs(g, root, nodeSep, weight, height, depths, child);
+ });
+
+ // Save the multiplier for node layers for later removal of empty border
+ // layers.
+ g.graph().nodeRankFactor = nodeSep;
+}
+
+function dfs(g, root, nodeSep, weight, height, depths, v) {
+ var children = g.children(v);
+ if (!children.length) {
+ if (v !== root) {
+ g.setEdge(root, v, { weight: 0, minlen: nodeSep });
+ }
+ return;
+ }
+
+ var top = util.addBorderNode(g, "_bt"),
+ bottom = util.addBorderNode(g, "_bb"),
+ label = g.node(v);
+
+ g.setParent(top, v);
+ label.borderTop = top;
+ g.setParent(bottom, v);
+ label.borderBottom = bottom;
+
+ _.each(children, function(child) {
+ dfs(g, root, nodeSep, weight, height, depths, child);
+
+ var childNode = g.node(child),
+ childTop = childNode.borderTop ? childNode.borderTop : child,
+ childBottom = childNode.borderBottom ? childNode.borderBottom : child,
+ thisWeight = childNode.borderTop ? weight : 2 * weight,
+ minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
+
+ g.setEdge(top, childTop, {
+ weight: thisWeight,
+ minlen: minlen,
+ nestingEdge: true
+ });
+
+ g.setEdge(childBottom, bottom, {
+ weight: thisWeight,
+ minlen: minlen,
+ nestingEdge: true
+ });
+ });
+
+ if (!g.parent(v)) {
+ g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
+ }
+}
+
+function treeDepths(g) {
+ var depths = {};
+ function dfs(v, depth) {
+ var children = g.children(v);
+ if (children && children.length) {
+ _.each(children, function(child) {
+ dfs(child, depth + 1);
+ });
+ }
+ depths[v] = depth;
+ }
+ _.each(g.children(), function(v) { dfs(v, 1); });
+ return depths;
+}
+
+function sumWeights(g) {
+ return _.reduce(g.edges(), function(acc, e) {
+ return acc + g.edge(e).weight;
+ }, 0);
+}
+
+function cleanup(g) {
+ var graphLabel = g.graph();
+ g.removeNode(graphLabel.nestingRoot);
+ delete graphLabel.nestingRoot;
+ _.each(g.edges(), function(e) {
+ var edge = g.edge(e);
+ if (edge.nestingEdge) {
+ g.removeEdge(e);
+ }
+ });
+}
+
+},{"./lodash":10,"./util":29}],12:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ util = require("./util");
+
+module.exports = {
+ run: run,
+ undo: undo
+};
+
+/*
+ * Breaks any long edges in the graph into short segments that span 1 layer
+ * each. This operation is undoable with the denormalize function.
+ *
+ * Pre-conditions:
+ *
+ * 1. The input graph is a DAG.
+ * 2. Each node in the graph has a "rank" property.
+ *
+ * Post-condition:
+ *
+ * 1. All edges in the graph have a length of 1.
+ * 2. Dummy nodes are added where edges have been split into segments.
+ * 3. The graph is augmented with a "dummyChains" attribute which contains
+ * the first dummy in each chain of dummy nodes produced.
+ */
+function run(g) {
+ g.graph().dummyChains = [];
+ _.each(g.edges(), function(edge) { normalizeEdge(g, edge); });
+}
+
+function normalizeEdge(g, e) {
+ var v = e.v,
+ vRank = g.node(v).rank,
+ w = e.w,
+ wRank = g.node(w).rank,
+ name = e.name,
+ edgeLabel = g.edge(e),
+ labelRank = edgeLabel.labelRank;
+
+ if (wRank === vRank + 1) return;
+
+ g.removeEdge(e);
+
+ var dummy, attrs, i;
+ for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
+ edgeLabel.points = [];
+ attrs = {
+ width: 0, height: 0,
+ edgeLabel: edgeLabel, edgeObj: e,
+ rank: vRank
+ };
+ dummy = util.addDummyNode(g, "edge", attrs, "_d");
+ if (vRank === labelRank) {
+ attrs.width = edgeLabel.width;
+ attrs.height = edgeLabel.height;
+ attrs.dummy = "edge-label";
+ attrs.labelpos = edgeLabel.labelpos;
+ }
+ g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
+ if (i === 0) {
+ g.graph().dummyChains.push(dummy);
+ }
+ v = dummy;
+ }
+
+ g.setEdge(v, w, { weight: edgeLabel.weight }, name);
+}
+
+function undo(g) {
+ _.each(g.graph().dummyChains, function(v) {
+ var node = g.node(v),
+ origLabel = node.edgeLabel,
+ w;
+ g.setEdge(node.edgeObj, origLabel);
+ while (node.dummy) {
+ w = g.successors(v)[0];
+ g.removeNode(v);
+ origLabel.points.push({ x: node.x, y: node.y });
+ if (node.dummy === "edge-label") {
+ origLabel.x = node.x;
+ origLabel.y = node.y;
+ origLabel.width = node.width;
+ origLabel.height = node.height;
+ }
+ v = w;
+ node = g.node(v);
+ }
+ });
+}
+
+},{"./lodash":10,"./util":29}],13:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = addSubgraphConstraints;
+
+function addSubgraphConstraints(g, cg, vs) {
+ var prev = {},
+ rootPrev;
+
+ _.each(vs, function(v) {
+ var child = g.parent(v),
+ parent,
+ prevChild;
+ while (child) {
+ parent = g.parent(child);
+ if (parent) {
+ prevChild = prev[parent];
+ prev[parent] = child;
+ } else {
+ prevChild = rootPrev;
+ rootPrev = child;
+ }
+ if (prevChild && prevChild !== child) {
+ cg.setEdge(prevChild, child);
+ return;
+ }
+ child = parent;
+ }
+ });
+
+ /*
+ function dfs(v) {
+ var children = v ? g.children(v) : g.children();
+ if (children.length) {
+ var min = Number.POSITIVE_INFINITY,
+ subgraphs = [];
+ _.each(children, function(child) {
+ var childMin = dfs(child);
+ if (g.children(child).length) {
+ subgraphs.push({ v: child, order: childMin });
+ }
+ min = Math.min(min, childMin);
+ });
+ _.reduce(_.sortBy(subgraphs, "order"), function(prev, curr) {
+ cg.setEdge(prev.v, curr.v);
+ return curr;
+ });
+ return min;
+ }
+ return g.node(v).order;
+ }
+ dfs(undefined);
+ */
+}
+
+},{"../lodash":10}],14:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = barycenter;
+
+function barycenter(g, movable) {
+ return _.map(movable, function(v) {
+ var inV = g.inEdges(v);
+ if (!inV.length) {
+ return { v: v };
+ } else {
+ var result = _.reduce(inV, function(acc, e) {
+ var edge = g.edge(e),
+ nodeU = g.node(e.v);
+ return {
+ sum: acc.sum + (edge.weight * nodeU.order),
+ weight: acc.weight + edge.weight
+ };
+ }, { sum: 0, weight: 0 });
+
+ return {
+ v: v,
+ barycenter: result.sum / result.weight,
+ weight: result.weight
+ };
+ }
+ });
+}
+
+
+},{"../lodash":10}],15:[function(require,module,exports){
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph;
+
+module.exports = buildLayerGraph;
+
+/*
+ * Constructs a graph that can be used to sort a layer of nodes. The graph will
+ * contain all base and subgraph nodes from the request layer in their original
+ * hierarchy and any edges that are incident on these nodes and are of the type
+ * requested by the "relationship" parameter.
+ *
+ * Nodes from the requested rank that do not have parents are assigned a root
+ * node in the output graph, which is set in the root graph attribute. This
+ * makes it easy to walk the hierarchy of movable nodes during ordering.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph is a DAG
+ * 2. Base nodes in the input graph have a rank attribute
+ * 3. Subgraph nodes in the input graph has minRank and maxRank attributes
+ * 4. Edges have an assigned weight
+ *
+ * Post-conditions:
+ *
+ * 1. Output graph has all nodes in the movable rank with preserved
+ * hierarchy.
+ * 2. Root nodes in the movable layer are made children of the node
+ * indicated by the root attribute of the graph.
+ * 3. Non-movable nodes incident on movable nodes, selected by the
+ * relationship parameter, are included in the graph (without hierarchy).
+ * 4. Edges incident on movable nodes, selected by the relationship
+ * parameter, are added to the output graph.
+ * 5. The weights for copied edges are aggregated as need, since the output
+ * graph is not a multi-graph.
+ */
+function buildLayerGraph(g, rank, relationship) {
+ var root = createRootNode(g),
+ result = new Graph({ compound: true }).setGraph({ root: root })
+ .setDefaultNodeLabel(function(v) { return g.node(v); });
+
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v),
+ parent = g.parent(v);
+
+ if (node.rank === rank || node.minRank <= rank && rank <= node.maxRank) {
+ result.setNode(v);
+ result.setParent(v, parent || root);
+
+ // This assumes we have only short edges!
+ _.each(g[relationship](v), function(e) {
+ var u = e.v === v ? e.w : e.v,
+ edge = result.edge(u, v),
+ weight = !_.isUndefined(edge) ? edge.weight : 0;
+ result.setEdge(u, v, { weight: g.edge(e).weight + weight });
+ });
+
+ if (_.has(node, "minRank")) {
+ result.setNode(v, {
+ borderLeft: node.borderLeft[rank],
+ borderRight: node.borderRight[rank]
+ });
+ }
+ }
+ });
+
+ return result;
+}
+
+function createRootNode(g) {
+ var v;
+ while (g.hasNode((v = _.uniqueId("_root"))));
+ return v;
+}
+
+},{"../graphlib":7,"../lodash":10}],16:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = crossCount;
+
+/*
+ * A function that takes a layering (an array of layers, each with an array of
+ * ordererd nodes) and a graph and returns a weighted crossing count.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph must be simple (not a multigraph), directed, and include
+ * only simple edges.
+ * 2. Edges in the input graph must have assigned weights.
+ *
+ * Post-conditions:
+ *
+ * 1. The graph and layering matrix are left unchanged.
+ *
+ * This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
+ */
+function crossCount(g, layering) {
+ var cc = 0;
+ for (var i = 1; i < layering.length; ++i) {
+ cc += twoLayerCrossCount(g, layering[i-1], layering[i]);
+ }
+ return cc;
+}
+
+function twoLayerCrossCount(g, northLayer, southLayer) {
+ // Sort all of the edges between the north and south layers by their position
+ // in the north layer and then the south. Map these edges to the position of
+ // their head in the south layer.
+ var southPos = _.zipObject(southLayer,
+ _.map(southLayer, function (v, i) { return i; }));
+ var southEntries = _.flatten(_.map(northLayer, function(v) {
+ return _.chain(g.outEdges(v))
+ .map(function(e) {
+ return { pos: southPos[e.w], weight: g.edge(e).weight };
+ })
+ .sortBy("pos")
+ .value();
+ }), true);
+
+ // Build the accumulator tree
+ var firstIndex = 1;
+ while (firstIndex < southLayer.length) firstIndex <<= 1;
+ var treeSize = 2 * firstIndex - 1;
+ firstIndex -= 1;
+ var tree = _.map(new Array(treeSize), function() { return 0; });
+
+ // Calculate the weighted crossings
+ var cc = 0;
+ _.each(southEntries.forEach(function(entry) {
+ var index = entry.pos + firstIndex;
+ tree[index] += entry.weight;
+ var weightSum = 0;
+ while (index > 0) {
+ if (index % 2) {
+ weightSum += tree[index + 1];
+ }
+ index = (index - 1) >> 1;
+ tree[index] += entry.weight;
+ }
+ cc += entry.weight * weightSum;
+ }));
+
+ return cc;
+}
+
+},{"../lodash":10}],17:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ initOrder = require("./init-order"),
+ crossCount = require("./cross-count"),
+ sortSubgraph = require("./sort-subgraph"),
+ buildLayerGraph = require("./build-layer-graph"),
+ addSubgraphConstraints = require("./add-subgraph-constraints"),
+ Graph = require("../graphlib").Graph,
+ util = require("../util");
+
+module.exports = order;
+
+/*
+ * Applies heuristics to minimize edge crossings in the graph and sets the best
+ * order solution as an order attribute on each node.
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be DAG
+ * 2. Graph nodes must be objects with a "rank" attribute
+ * 3. Graph edges must have the "weight" attribute
+ *
+ * Post-conditions:
+ *
+ * 1. Graph nodes will have an "order" attribute based on the results of the
+ * algorithm.
+ */
+function order(g) {
+ var maxRank = util.maxRank(g),
+ downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), "inEdges"),
+ upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), "outEdges");
+
+ var layering = initOrder(g);
+ assignOrder(g, layering);
+
+ var bestCC = Number.POSITIVE_INFINITY,
+ best;
+
+ for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
+ sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
+
+ layering = util.buildLayerMatrix(g);
+ var cc = crossCount(g, layering);
+ if (cc < bestCC) {
+ lastBest = 0;
+ best = _.cloneDeep(layering);
+ bestCC = cc;
+ }
+ }
+
+ assignOrder(g, best);
+}
+
+function buildLayerGraphs(g, ranks, relationship) {
+ return _.map(ranks, function(rank) {
+ return buildLayerGraph(g, rank, relationship);
+ });
+}
+
+function sweepLayerGraphs(layerGraphs, biasRight) {
+ var cg = new Graph();
+ _.each(layerGraphs, function(lg) {
+ var root = lg.graph().root;
+ var sorted = sortSubgraph(lg, root, cg, biasRight);
+ _.each(sorted.vs, function(v, i) {
+ lg.node(v).order = i;
+ });
+ addSubgraphConstraints(lg, cg, sorted.vs);
+ });
+}
+
+function assignOrder(g, layering) {
+ _.each(layering, function(layer) {
+ _.each(layer, function(v, i) {
+ g.node(v).order = i;
+ });
+ });
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./add-subgraph-constraints":13,"./build-layer-graph":15,"./cross-count":16,"./init-order":18,"./sort-subgraph":20}],18:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = initOrder;
+
+/*
+ * Assigns an initial order value for each node by performing a DFS search
+ * starting from nodes in the first rank. Nodes are assigned an order in their
+ * rank as they are first visited.
+ *
+ * This approach comes from Gansner, et al., "A Technique for Drawing Directed
+ * Graphs."
+ *
+ * Returns a layering matrix with an array per layer and each layer sorted by
+ * the order of its nodes.
+ */
+function initOrder(g) {
+ var visited = {},
+ simpleNodes = _.filter(g.nodes(), function(v) {
+ return !g.children(v).length;
+ }),
+ maxRank = _.max(_.map(simpleNodes, function(v) { return g.node(v).rank; })),
+ layers = _.map(_.range(maxRank + 1), function() { return []; });
+
+ function dfs(v) {
+ if (_.has(visited, v)) return;
+ visited[v] = true;
+ var node = g.node(v);
+ layers[node.rank].push(v);
+ _.each(g.successors(v), dfs);
+ }
+
+ var orderedVs = _.sortBy(simpleNodes, function(v) { return g.node(v).rank; });
+ _.each(orderedVs, dfs);
+
+ return layers;
+}
+
+},{"../lodash":10}],19:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = resolveConflicts;
+
+/*
+ * Given a list of entries of the form {v, barycenter, weight} and a
+ * constraint graph this function will resolve any conflicts between the
+ * constraint graph and the barycenters for the entries. If the barycenters for
+ * an entry would violate a constraint in the constraint graph then we coalesce
+ * the nodes in the conflict into a new node that respects the contraint and
+ * aggregates barycenter and weight information.
+ *
+ * This implementation is based on the description in Forster, "A Fast and
+ * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
+ * differs in some specific details.
+ *
+ * Pre-conditions:
+ *
+ * 1. Each entry has the form {v, barycenter, weight}, or if the node has
+ * no barycenter, then {v}.
+ *
+ * Returns:
+ *
+ * A new list of entries of the form {vs, i, barycenter, weight}. The list
+ * `vs` may either be a singleton or it may be an aggregation of nodes
+ * ordered such that they do not violate constraints from the constraint
+ * graph. The property `i` is the lowest original index of any of the
+ * elements in `vs`.
+ */
+function resolveConflicts(entries, cg) {
+ var mappedEntries = {};
+ _.each(entries, function(entry, i) {
+ var tmp = mappedEntries[entry.v] = {
+ indegree: 0,
+ "in": [],
+ out: [],
+ vs: [entry.v],
+ i: i
+ };
+ if (!_.isUndefined(entry.barycenter)) {
+ tmp.barycenter = entry.barycenter;
+ tmp.weight = entry.weight;
+ }
+ });
+
+ _.each(cg.edges(), function(e) {
+ var entryV = mappedEntries[e.v],
+ entryW = mappedEntries[e.w];
+ if (!_.isUndefined(entryV) && !_.isUndefined(entryW)) {
+ entryW.indegree++;
+ entryV.out.push(mappedEntries[e.w]);
+ }
+ });
+
+ var sourceSet = _.filter(mappedEntries, function(entry) {
+ return !entry.indegree;
+ });
+
+ return doResolveConflicts(sourceSet);
+}
+
+function doResolveConflicts(sourceSet) {
+ var entries = [];
+
+ function handleIn(vEntry) {
+ return function(uEntry) {
+ if (uEntry.merged) {
+ return;
+ }
+ if (_.isUndefined(uEntry.barycenter) ||
+ _.isUndefined(vEntry.barycenter) ||
+ uEntry.barycenter >= vEntry.barycenter) {
+ mergeEntries(vEntry, uEntry);
+ }
+ };
+ }
+
+ function handleOut(vEntry) {
+ return function(wEntry) {
+ wEntry["in"].push(vEntry);
+ if (--wEntry.indegree === 0) {
+ sourceSet.push(wEntry);
+ }
+ };
+ }
+
+ while (sourceSet.length) {
+ var entry = sourceSet.pop();
+ entries.push(entry);
+ _.each(entry["in"].reverse(), handleIn(entry));
+ _.each(entry.out, handleOut(entry));
+ }
+
+ return _.chain(entries)
+ .filter(function(entry) { return !entry.merged; })
+ .map(function(entry) {
+ return _.pick(entry, ["vs", "i", "barycenter", "weight"]);
+ })
+ .value();
+}
+
+function mergeEntries(target, source) {
+ var sum = 0,
+ weight = 0;
+
+ if (target.weight) {
+ sum += target.barycenter * target.weight;
+ weight += target.weight;
+ }
+
+ if (source.weight) {
+ sum += source.barycenter * source.weight;
+ weight += source.weight;
+ }
+
+ target.vs = source.vs.concat(target.vs);
+ target.barycenter = sum / weight;
+ target.weight = weight;
+ target.i = Math.min(source.i, target.i);
+ source.merged = true;
+}
+
+},{"../lodash":10}],20:[function(require,module,exports){
+var _ = require("../lodash"),
+ barycenter = require("./barycenter"),
+ resolveConflicts = require("./resolve-conflicts"),
+ sort = require("./sort");
+
+module.exports = sortSubgraph;
+
+function sortSubgraph(g, v, cg, biasRight) {
+ var movable = g.children(v),
+ node = g.node(v),
+ bl = node ? node.borderLeft : undefined,
+ br = node ? node.borderRight: undefined,
+ subgraphs = {};
+
+ if (bl) {
+ movable = _.filter(movable, function(w) {
+ return w !== bl && w !== br;
+ });
+ }
+
+ var barycenters = barycenter(g, movable);
+ _.each(barycenters, function(entry) {
+ if (g.children(entry.v).length) {
+ var subgraphResult = sortSubgraph(g, entry.v, cg, biasRight);
+ subgraphs[entry.v] = subgraphResult;
+ if (_.has(subgraphResult, "barycenter")) {
+ mergeBarycenters(entry, subgraphResult);
+ }
+ }
+ });
+
+ var entries = resolveConflicts(barycenters, cg);
+ expandSubgraphs(entries, subgraphs);
+
+ var result = sort(entries, biasRight);
+
+ if (bl) {
+ result.vs = _.flatten([bl, result.vs, br], true);
+ if (g.predecessors(bl).length) {
+ var blPred = g.node(g.predecessors(bl)[0]),
+ brPred = g.node(g.predecessors(br)[0]);
+ if (!_.has(result, "barycenter")) {
+ result.barycenter = 0;
+ result.weight = 0;
+ }
+ result.barycenter = (result.barycenter * result.weight +
+ blPred.order + brPred.order) / (result.weight + 2);
+ result.weight += 2;
+ }
+ }
+
+ return result;
+}
+
+function expandSubgraphs(entries, subgraphs) {
+ _.each(entries, function(entry) {
+ entry.vs = _.flatten(entry.vs.map(function(v) {
+ if (subgraphs[v]) {
+ return subgraphs[v].vs;
+ }
+ return v;
+ }), true);
+ });
+}
+
+function mergeBarycenters(target, other) {
+ if (!_.isUndefined(target.barycenter)) {
+ target.barycenter = (target.barycenter * target.weight +
+ other.barycenter * other.weight) /
+ (target.weight + other.weight);
+ target.weight += other.weight;
+ } else {
+ target.barycenter = other.barycenter;
+ target.weight = other.weight;
+ }
+}
+
+},{"../lodash":10,"./barycenter":14,"./resolve-conflicts":19,"./sort":21}],21:[function(require,module,exports){
+var _ = require("../lodash"),
+ util = require("../util");
+
+module.exports = sort;
+
+function sort(entries, biasRight) {
+ var parts = util.partition(entries, function(entry) {
+ return _.has(entry, "barycenter");
+ });
+ var sortable = parts.lhs,
+ unsortable = _.sortBy(parts.rhs, function(entry) { return -entry.i; }),
+ vs = [],
+ sum = 0,
+ weight = 0,
+ vsIndex = 0;
+
+ sortable.sort(compareWithBias(!!biasRight));
+
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+
+ _.each(sortable, function (entry) {
+ vsIndex += entry.vs.length;
+ vs.push(entry.vs);
+ sum += entry.barycenter * entry.weight;
+ weight += entry.weight;
+ vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+ });
+
+ var result = { vs: _.flatten(vs, true) };
+ if (weight) {
+ result.barycenter = sum / weight;
+ result.weight = weight;
+ }
+ return result;
+}
+
+function consumeUnsortable(vs, unsortable, index) {
+ var last;
+ while (unsortable.length && (last = _.last(unsortable)).i <= index) {
+ unsortable.pop();
+ vs.push(last.vs);
+ index++;
+ }
+ return index;
+}
+
+function compareWithBias(bias) {
+ return function(entryV, entryW) {
+ if (entryV.barycenter < entryW.barycenter) {
+ return -1;
+ } else if (entryV.barycenter > entryW.barycenter) {
+ return 1;
+ }
+
+ return !bias ? entryV.i - entryW.i : entryW.i - entryV.i;
+ };
+}
+
+},{"../lodash":10,"../util":29}],22:[function(require,module,exports){
+var _ = require("./lodash");
+
+module.exports = parentDummyChains;
+
+function parentDummyChains(g) {
+ var postorderNums = postorder(g);
+
+ _.each(g.graph().dummyChains, function(v) {
+ var node = g.node(v),
+ edgeObj = node.edgeObj,
+ pathData = findPath(g, postorderNums, edgeObj.v, edgeObj.w),
+ path = pathData.path,
+ lca = pathData.lca,
+ pathIdx = 0,
+ pathV = path[pathIdx],
+ ascending = true;
+
+ while (v !== edgeObj.w) {
+ node = g.node(v);
+
+ if (ascending) {
+ while ((pathV = path[pathIdx]) !== lca &&
+ g.node(pathV).maxRank < node.rank) {
+ pathIdx++;
+ }
+
+ if (pathV === lca) {
+ ascending = false;
+ }
+ }
+
+ if (!ascending) {
+ while (pathIdx < path.length - 1 &&
+ g.node(pathV = path[pathIdx + 1]).minRank <= node.rank) {
+ pathIdx++;
+ }
+ pathV = path[pathIdx];
+ }
+
+ g.setParent(v, pathV);
+ v = g.successors(v)[0];
+ }
+ });
+}
+
+// Find a path from v to w through the lowest common ancestor (LCA). Return the
+// full path and the LCA.
+function findPath(g, postorderNums, v, w) {
+ var vPath = [],
+ wPath = [],
+ low = Math.min(postorderNums[v].low, postorderNums[w].low),
+ lim = Math.max(postorderNums[v].lim, postorderNums[w].lim),
+ parent,
+ lca;
+
+ // Traverse up from v to find the LCA
+ parent = v;
+ do {
+ parent = g.parent(parent);
+ vPath.push(parent);
+ } while (parent &&
+ (postorderNums[parent].low > low || lim > postorderNums[parent].lim));
+ lca = parent;
+
+ // Traverse from w to LCA
+ parent = w;
+ while ((parent = g.parent(parent)) !== lca) {
+ wPath.push(parent);
+ }
+
+ return { path: vPath.concat(wPath.reverse()), lca: lca };
+}
+
+function postorder(g) {
+ var result = {},
+ lim = 0;
+
+ function dfs(v) {
+ var low = lim;
+ _.each(g.children(v), dfs);
+ result[v] = { low: low, lim: lim++ };
+ }
+ _.each(g.children(), dfs);
+
+ return result;
+}
+
+},{"./lodash":10}],23:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph,
+ util = require("../util");
+
+/*
+ * This module provides coordinate assignment based on Brandes and Köpf, "Fast
+ * and Simple Horizontal Coordinate Assignment."
+ */
+
+module.exports = {
+ positionX: positionX,
+ findType1Conflicts: findType1Conflicts,
+ findType2Conflicts: findType2Conflicts,
+ addConflict: addConflict,
+ hasConflict: hasConflict,
+ verticalAlignment: verticalAlignment,
+ horizontalCompaction: horizontalCompaction,
+ alignCoordinates: alignCoordinates,
+ findSmallestWidthAlignment: findSmallestWidthAlignment,
+ balance: balance
+};
+
+/*
+ * Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
+ * property. A type-1 conflict is one where a non-inner segment crosses an
+ * inner segment. An inner segment is an edge with both incident nodes marked
+ * with the "dummy" property.
+ *
+ * This algorithm scans layer by layer, starting with the second, for type-1
+ * conflicts between the current layer and the previous layer. For each layer
+ * it scans the nodes from left to right until it reaches one that is incident
+ * on an inner segment. It then scans predecessors to determine if they have
+ * edges that cross that inner segment. At the end a final scan is done for all
+ * nodes on the current rank to see if they cross the last visited inner
+ * segment.
+ *
+ * This algorithm (safely) assumes that a dummy node will only be incident on a
+ * single node in the layers being scanned.
+ */
+function findType1Conflicts(g, layering) {
+ var conflicts = {};
+
+ function visitLayer(prevLayer, layer) {
+ var
+ // last visited node in the previous layer that is incident on an inner
+ // segment.
+ k0 = 0,
+ // Tracks the last node in this layer scanned for crossings with a type-1
+ // segment.
+ scanPos = 0,
+ prevLayerLength = prevLayer.length,
+ lastNode = _.last(layer);
+
+ _.each(layer, function(v, i) {
+ var w = findOtherInnerSegmentNode(g, v),
+ k1 = w ? g.node(w).order : prevLayerLength;
+
+ if (w || v === lastNode) {
+ _.each(layer.slice(scanPos, i +1), function(scanNode) {
+ _.each(g.predecessors(scanNode), function(u) {
+ var uLabel = g.node(u),
+ uPos = uLabel.order;
+ if ((uPos < k0 || k1 < uPos) &&
+ !(uLabel.dummy && g.node(scanNode).dummy)) {
+ addConflict(conflicts, u, scanNode);
+ }
+ });
+ });
+ scanPos = i + 1;
+ k0 = k1;
+ }
+ });
+
+ return layer;
+ }
+
+ _.reduce(layering, visitLayer);
+ return conflicts;
+}
+
+function findType2Conflicts(g, layering) {
+ var conflicts = {};
+
+ function scan(south, southPos, southEnd, prevNorthBorder, nextNorthBorder) {
+ var v;
+ _.each(_.range(southPos, southEnd), function(i) {
+ v = south[i];
+ if (g.node(v).dummy) {
+ _.each(g.predecessors(v), function(u) {
+ var uNode = g.node(u);
+ if (uNode.dummy &&
+ (uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
+ addConflict(conflicts, u, v);
+ }
+ });
+ }
+ });
+ }
+
+
+ function visitLayer(north, south) {
+ var prevNorthPos = -1,
+ nextNorthPos,
+ southPos = 0;
+
+ _.each(south, function(v, southLookahead) {
+ if (g.node(v).dummy === "border") {
+ var predecessors = g.predecessors(v);
+ if (predecessors.length) {
+ nextNorthPos = g.node(predecessors[0]).order;
+ scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
+ southPos = southLookahead;
+ prevNorthPos = nextNorthPos;
+ }
+ }
+ scan(south, southPos, south.length, nextNorthPos, north.length);
+ });
+
+ return south;
+ }
+
+ _.reduce(layering, visitLayer);
+ return conflicts;
+}
+
+function findOtherInnerSegmentNode(g, v) {
+ if (g.node(v).dummy) {
+ return _.find(g.predecessors(v), function(u) {
+ return g.node(u).dummy;
+ });
+ }
+}
+
+function addConflict(conflicts, v, w) {
+ if (v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+
+ var conflictsV = conflicts[v];
+ if (!conflictsV) {
+ conflicts[v] = conflictsV = {};
+ }
+ conflictsV[w] = true;
+}
+
+function hasConflict(conflicts, v, w) {
+ if (v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ return _.has(conflicts[v], w);
+}
+
+/*
+ * Try to align nodes into vertical "blocks" where possible. This algorithm
+ * attempts to align a node with one of its median neighbors. If the edge
+ * connecting a neighbor is a type-1 conflict then we ignore that possibility.
+ * If a previous node has already formed a block with a node after the node
+ * we're trying to form a block with, we also ignore that possibility - our
+ * blocks would be split in that scenario.
+ */
+function verticalAlignment(g, layering, conflicts, neighborFn) {
+ var root = {},
+ align = {},
+ pos = {};
+
+ // We cache the position here based on the layering because the graph and
+ // layering may be out of sync. The layering matrix is manipulated to
+ // generate different extreme alignments.
+ _.each(layering, function(layer) {
+ _.each(layer, function(v, order) {
+ root[v] = v;
+ align[v] = v;
+ pos[v] = order;
+ });
+ });
+
+ _.each(layering, function(layer) {
+ var prevIdx = -1;
+ _.each(layer, function(v) {
+ var ws = neighborFn(v);
+ if (ws.length) {
+ ws = _.sortBy(ws, function(w) { return pos[w]; });
+ var mp = (ws.length - 1) / 2;
+ for (var i = Math.floor(mp), il = Math.ceil(mp); i <= il; ++i) {
+ var w = ws[i];
+ if (align[v] === v &&
+ prevIdx < pos[w] &&
+ !hasConflict(conflicts, v, w)) {
+ align[w] = v;
+ align[v] = root[v] = root[w];
+ prevIdx = pos[w];
+ }
+ }
+ }
+ });
+ });
+
+ return { root: root, align: align };
+}
+
+function horizontalCompaction(g, layering, root, align, reverseSep) {
+ // This portion of the algorithm differs from BK due to a number of problems.
+ // Instead of their algorithm we construct a new block graph and do two
+ // sweeps. The first sweep places blocks with the smallest possible
+ // coordinates. The second sweep removes unused space by moving blocks to the
+ // greatest coordinates without violating separation.
+ var xs = {},
+ blockG = buildBlockGraph(g, layering, root, reverseSep);
+
+ // First pass, assign smallest coordinates via DFS
+ var visited = {};
+ function pass1(v) {
+ if (!_.has(visited, v)) {
+ visited[v] = true;
+ xs[v] = _.reduce(blockG.inEdges(v), function(max, e) {
+ pass1(e.v);
+ return Math.max(max, xs[e.v] + blockG.edge(e));
+ }, 0);
+ }
+ }
+ _.each(blockG.nodes(), pass1);
+
+ function pass2(v) {
+ if (visited[v] !== 2) {
+ visited[v]++;
+ var min = _.reduce(blockG.outEdges(v), function(min, e) {
+ pass2(e.w);
+ return Math.min(min, xs[e.w] - blockG.edge(e));
+ }, Number.POSITIVE_INFINITY);
+ if (min !== Number.POSITIVE_INFINITY) {
+ xs[v] = Math.max(xs[v], min);
+ }
+ }
+ }
+ _.each(blockG.nodes(), pass2);
+
+
+ // Assign x coordinates to all nodes
+ _.each(align, function(v) {
+ xs[v] = xs[root[v]];
+ });
+
+ return xs;
+}
+
+
+function buildBlockGraph(g, layering, root, reverseSep) {
+ var blockGraph = new Graph(),
+ graphLabel = g.graph(),
+ sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
+
+ _.each(layering, function(layer) {
+ var u;
+ _.each(layer, function(v) {
+ var vRoot = root[v];
+ blockGraph.setNode(vRoot);
+ if (u) {
+ var uRoot = root[u],
+ prevMax = blockGraph.edge(uRoot, vRoot);
+ blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0));
+ }
+ u = v;
+ });
+ });
+
+ return blockGraph;
+}
+
+/*
+ * Returns the alignment that has the smallest width of the given alignments.
+ */
+function findSmallestWidthAlignment(g, xss) {
+ return _.min(xss, function(xs) {
+ var min = _.min(xs, function(x, v) { return x - width(g, v) / 2; }),
+ max = _.max(xs, function(x, v) { return x + width(g, v) / 2; });
+ return max - min;
+ });
+}
+
+/*
+ * Align the coordinates of each of the layout alignments such that
+ * left-biased alignments have their minimum coordinate at the same point as
+ * the minimum coordinate of the smallest width alignment and right-biased
+ * alignments have their maximum coordinate at the same point as the maximum
+ * coordinate of the smallest width alignment.
+ */
+function alignCoordinates(xss, alignTo) {
+ var alignToMin = _.min(alignTo),
+ alignToMax = _.max(alignTo);
+
+ _.each(["u", "d"], function(vert) {
+ _.each(["l", "r"], function(horiz) {
+ var alignment = vert + horiz,
+ xs = xss[alignment],
+ delta;
+ if (xs === alignTo) return;
+
+ delta = horiz === "l" ? alignToMin - _.min(xs) : alignToMax - _.max(xs);
+
+ if (delta) {
+ xss[alignment] = _.mapValues(xs, function(x) { return x + delta; });
+ }
+ });
+ });
+}
+
+function balance(xss, align) {
+ return _.mapValues(xss.ul, function(ignore, v) {
+ if (align) {
+ return xss[align.toLowerCase()][v];
+ } else {
+ var xs = _.sortBy(_.pluck(xss, v));
+ return (xs[1] + xs[2]) / 2;
+ }
+ });
+}
+
+function positionX(g) {
+ var layering = util.buildLayerMatrix(g),
+ conflicts = _.merge(findType1Conflicts(g, layering),
+ findType2Conflicts(g, layering));
+
+ var xss = {},
+ adjustedLayering;
+ _.each(["u", "d"], function(vert) {
+ adjustedLayering = vert === "u" ? layering : _.values(layering).reverse();
+ _.each(["l", "r"], function(horiz) {
+ if (horiz === "r") {
+ adjustedLayering = _.map(adjustedLayering, function(inner) {
+ return _.values(inner).reverse();
+ });
+ }
+
+ var neighborFn = _.bind(vert === "u" ? g.predecessors : g.successors, g);
+ var align = verticalAlignment(g, adjustedLayering, conflicts, neighborFn);
+ var xs = horizontalCompaction(g, adjustedLayering,
+ align.root, align.align,
+ horiz === "r");
+ if (horiz === "r") {
+ xs = _.mapValues(xs, function(x) { return -x; });
+ }
+ xss[vert + horiz] = xs;
+ });
+ });
+
+ var smallestWidth = findSmallestWidthAlignment(g, xss);
+ alignCoordinates(xss, smallestWidth);
+ return balance(xss, g.graph().align);
+}
+
+function sep(nodeSep, edgeSep, reverseSep) {
+ return function(g, v, w) {
+ var vLabel = g.node(v),
+ wLabel = g.node(w),
+ sum = 0,
+ delta;
+
+ sum += vLabel.width / 2;
+ if (_.has(vLabel, "labelpos")) {
+ switch (vLabel.labelpos.toLowerCase()) {
+ case "l": delta = -vLabel.width / 2; break;
+ case "r": delta = vLabel.width / 2; break;
+ }
+ }
+ if (delta) {
+ sum += reverseSep ? delta : -delta;
+ }
+ delta = 0;
+
+ sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
+ sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
+
+ sum += wLabel.width / 2;
+ if (_.has(wLabel, "labelpos")) {
+ switch (wLabel.labelpos.toLowerCase()) {
+ case "l": delta = wLabel.width / 2; break;
+ case "r": delta = -wLabel.width / 2; break;
+ }
+ }
+ if (delta) {
+ sum += reverseSep ? delta : -delta;
+ }
+ delta = 0;
+
+ return sum;
+ };
+}
+
+function width(g, v) {
+ return g.node(v).width;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29}],24:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ util = require("../util"),
+ positionX = require("./bk").positionX;
+
+module.exports = position;
+
+function position(g) {
+ g = util.asNonCompoundGraph(g);
+
+ positionY(g);
+ _.each(positionX(g), function(x, v) {
+ g.node(v).x = x;
+ });
+}
+
+function positionY(g) {
+ var layering = util.buildLayerMatrix(g),
+ rankSep = g.graph().ranksep,
+ prevY = 0;
+ _.each(layering, function(layer) {
+ var maxHeight = _.max(_.map(layer, function(v) { return g.node(v).height; }));
+ _.each(layer, function(v) {
+ g.node(v).y = prevY + maxHeight / 2;
+ });
+ prevY += maxHeight + rankSep;
+ });
+}
+
+
+},{"../lodash":10,"../util":29,"./bk":23}],25:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ Graph = require("../graphlib").Graph,
+ slack = require("./util").slack;
+
+module.exports = feasibleTree;
+
+/*
+ * Constructs a spanning tree with tight edges and adjusted the input node's
+ * ranks to achieve this. A tight edge is one that is has a length that matches
+ * its "minlen" attribute.
+ *
+ * The basic structure for this function is derived from Gansner, et al., "A
+ * Technique for Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be a DAG.
+ * 2. Graph must be connected.
+ * 3. Graph must have at least one node.
+ * 5. Graph nodes must have been previously assigned a "rank" property that
+ * respects the "minlen" property of incident edges.
+ * 6. Graph edges must have a "minlen" property.
+ *
+ * Post-conditions:
+ *
+ * - Graph nodes will have their rank adjusted to ensure that all edges are
+ * tight.
+ *
+ * Returns a tree (undirected graph) that is constructed using only "tight"
+ * edges.
+ */
+function feasibleTree(g) {
+ var t = new Graph({ directed: false });
+
+ // Choose arbitrary node from which to start our tree
+ var start = g.nodes()[0],
+ size = g.nodeCount();
+ t.setNode(start, {});
+
+ var edge, delta;
+ while (tightTree(t, g) < size) {
+ edge = findMinSlackEdge(t, g);
+ delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
+ shiftRanks(t, g, delta);
+ }
+
+ return t;
+}
+
+/*
+ * Finds a maximal tree of tight edges and returns the number of nodes in the
+ * tree.
+ */
+function tightTree(t, g) {
+ function dfs(v) {
+ _.each(g.nodeEdges(v), function(e) {
+ var edgeV = e.v,
+ w = (v === edgeV) ? e.w : edgeV;
+ if (!t.hasNode(w) && !slack(g, e)) {
+ t.setNode(w, {});
+ t.setEdge(v, w, {});
+ dfs(w);
+ }
+ });
+ }
+
+ _.each(t.nodes(), dfs);
+ return t.nodeCount();
+}
+
+/*
+ * Finds the edge with the smallest slack that is incident on tree and returns
+ * it.
+ */
+function findMinSlackEdge(t, g) {
+ return _.min(g.edges(), function(e) {
+ if (t.hasNode(e.v) !== t.hasNode(e.w)) {
+ return slack(g, e);
+ }
+ });
+}
+
+function shiftRanks(t, g, delta) {
+ _.each(t.nodes(), function(v) {
+ g.node(v).rank += delta;
+ });
+}
+
+},{"../graphlib":7,"../lodash":10,"./util":28}],26:[function(require,module,exports){
+"use strict";
+
+var rankUtil = require("./util"),
+ longestPath = rankUtil.longestPath,
+ feasibleTree = require("./feasible-tree"),
+ networkSimplex = require("./network-simplex");
+
+module.exports = rank;
+
+/*
+ * Assigns a rank to each node in the input graph that respects the "minlen"
+ * constraint specified on edges between nodes.
+ *
+ * This basic structure is derived from Gansner, et al., "A Technique for
+ * Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ * 1. Graph must be a connected DAG
+ * 2. Graph nodes must be objects
+ * 3. Graph edges must have "weight" and "minlen" attributes
+ *
+ * Post-conditions:
+ *
+ * 1. Graph nodes will have a "rank" attribute based on the results of the
+ * algorithm. Ranks can start at any index (including negative), we'll
+ * fix them up later.
+ */
+function rank(g) {
+ switch(g.graph().ranker) {
+ case "network-simplex": networkSimplexRanker(g); break;
+ case "tight-tree": tightTreeRanker(g); break;
+ case "longest-path": longestPathRanker(g); break;
+ default: networkSimplexRanker(g);
+ }
+}
+
+// A fast and simple ranker, but results are far from optimal.
+var longestPathRanker = longestPath;
+
+function tightTreeRanker(g) {
+ longestPath(g);
+ feasibleTree(g);
+}
+
+function networkSimplexRanker(g) {
+ networkSimplex(g);
+}
+
+},{"./feasible-tree":25,"./network-simplex":27,"./util":28}],27:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+ feasibleTree = require("./feasible-tree"),
+ slack = require("./util").slack,
+ initRank = require("./util").longestPath,
+ preorder = require("../graphlib").alg.preorder,
+ postorder = require("../graphlib").alg.postorder,
+ simplify = require("../util").simplify;
+
+module.exports = networkSimplex;
+
+// Expose some internals for testing purposes
+networkSimplex.initLowLimValues = initLowLimValues;
+networkSimplex.initCutValues = initCutValues;
+networkSimplex.calcCutValue = calcCutValue;
+networkSimplex.leaveEdge = leaveEdge;
+networkSimplex.enterEdge = enterEdge;
+networkSimplex.exchangeEdges = exchangeEdges;
+
+/*
+ * The network simplex algorithm assigns ranks to each node in the input graph
+ * and iteratively improves the ranking to reduce the length of edges.
+ *
+ * Preconditions:
+ *
+ * 1. The input graph must be a DAG.
+ * 2. All nodes in the graph must have an object value.
+ * 3. All edges in the graph must have "minlen" and "weight" attributes.
+ *
+ * Postconditions:
+ *
+ * 1. All nodes in the graph will have an assigned "rank" attribute that has
+ * been optimized by the network simplex algorithm. Ranks start at 0.
+ *
+ *
+ * A rough sketch of the algorithm is as follows:
+ *
+ * 1. Assign initial ranks to each node. We use the longest path algorithm,
+ * which assigns ranks to the lowest position possible. In general this
+ * leads to very wide bottom ranks and unnecessarily long edges.
+ * 2. Construct a feasible tight tree. A tight tree is one such that all
+ * edges in the tree have no slack (difference between length of edge
+ * and minlen for the edge). This by itself greatly improves the assigned
+ * rankings by shorting edges.
+ * 3. Iteratively find edges that have negative cut values. Generally a
+ * negative cut value indicates that the edge could be removed and a new
+ * tree edge could be added to produce a more compact graph.
+ *
+ * Much of the algorithms here are derived from Gansner, et al., "A Technique
+ * for Drawing Directed Graphs." The structure of the file roughly follows the
+ * structure of the overall algorithm.
+ */
+function networkSimplex(g) {
+ g = simplify(g);
+ initRank(g);
+ var t = feasibleTree(g);
+ initLowLimValues(t);
+ initCutValues(t, g);
+
+ var e, f;
+ while ((e = leaveEdge(t))) {
+ f = enterEdge(t, g, e);
+ exchangeEdges(t, g, e, f);
+ }
+}
+
+/*
+ * Initializes cut values for all edges in the tree.
+ */
+function initCutValues(t, g) {
+ var vs = postorder(t, t.nodes());
+ vs = vs.slice(0, vs.length - 1);
+ _.each(vs, function(v) {
+ assignCutValue(t, g, v);
+ });
+}
+
+function assignCutValue(t, g, child) {
+ var childLab = t.node(child),
+ parent = childLab.parent;
+ t.edge(child, parent).cutvalue = calcCutValue(t, g, child);
+}
+
+/*
+ * Given the tight tree, its graph, and a child in the graph calculate and
+ * return the cut value for the edge between the child and its parent.
+ */
+function calcCutValue(t, g, child) {
+ var childLab = t.node(child),
+ parent = childLab.parent,
+ // True if the child is on the tail end of the edge in the directed graph
+ childIsTail = true,
+ // The graph's view of the tree edge we're inspecting
+ graphEdge = g.edge(child, parent),
+ // The accumulated cut value for the edge between this node and its parent
+ cutValue = 0;
+
+ if (!graphEdge) {
+ childIsTail = false;
+ graphEdge = g.edge(parent, child);
+ }
+
+ cutValue = graphEdge.weight;
+
+ _.each(g.nodeEdges(child), function(e) {
+ var isOutEdge = e.v === child,
+ other = isOutEdge ? e.w : e.v;
+
+ if (other !== parent) {
+ var pointsToHead = isOutEdge === childIsTail,
+ otherWeight = g.edge(e).weight;
+
+ cutValue += pointsToHead ? otherWeight : -otherWeight;
+ if (isTreeEdge(t, child, other)) {
+ var otherCutValue = t.edge(child, other).cutvalue;
+ cutValue += pointsToHead ? -otherCutValue : otherCutValue;
+ }
+ }
+ });
+
+ return cutValue;
+}
+
+function initLowLimValues(tree, root) {
+ if (arguments.length < 2) {
+ root = tree.nodes()[0];
+ }
+ dfsAssignLowLim(tree, {}, 1, root);
+}
+
+function dfsAssignLowLim(tree, visited, nextLim, v, parent) {
+ var low = nextLim,
+ label = tree.node(v);
+
+ visited[v] = true;
+ _.each(tree.neighbors(v), function(w) {
+ if (!_.has(visited, w)) {
+ nextLim = dfsAssignLowLim(tree, visited, nextLim, w, v);
+ }
+ });
+
+ label.low = low;
+ label.lim = nextLim++;
+ if (parent) {
+ label.parent = parent;
+ } else {
+ // TODO should be able to remove this when we incrementally update low lim
+ delete label.parent;
+ }
+
+ return nextLim;
+}
+
+function leaveEdge(tree) {
+ return _.find(tree.edges(), function(e) {
+ return tree.edge(e).cutvalue < 0;
+ });
+}
+
+function enterEdge(t, g, edge) {
+ var v = edge.v,
+ w = edge.w;
+
+ // For the rest of this function we assume that v is the tail and w is the
+ // head, so if we don't have this edge in the graph we should flip it to
+ // match the correct orientation.
+ if (!g.hasEdge(v, w)) {
+ v = edge.w;
+ w = edge.v;
+ }
+
+ var vLabel = t.node(v),
+ wLabel = t.node(w),
+ tailLabel = vLabel,
+ flip = false;
+
+ // If the root is in the tail of the edge then we need to flip the logic that
+ // checks for the head and tail nodes in the candidates function below.
+ if (vLabel.lim > wLabel.lim) {
+ tailLabel = wLabel;
+ flip = true;
+ }
+
+ var candidates = _.filter(g.edges(), function(edge) {
+ return flip === isDescendant(t, t.node(edge.v), tailLabel) &&
+ flip !== isDescendant(t, t.node(edge.w), tailLabel);
+ });
+
+ return _.min(candidates, function(edge) { return slack(g, edge); });
+}
+
+function exchangeEdges(t, g, e, f) {
+ var v = e.v,
+ w = e.w;
+ t.removeEdge(v, w);
+ t.setEdge(f.v, f.w, {});
+ initLowLimValues(t);
+ initCutValues(t, g);
+ updateRanks(t, g);
+}
+
+function updateRanks(t, g) {
+ var root = _.find(t.nodes(), function(v) { return !g.node(v).parent; }),
+ vs = preorder(t, root);
+ vs = vs.slice(1);
+ _.each(vs, function(v) {
+ var parent = t.node(v).parent,
+ edge = g.edge(v, parent),
+ flipped = false;
+
+ if (!edge) {
+ edge = g.edge(parent, v);
+ flipped = true;
+ }
+
+ g.node(v).rank = g.node(parent).rank + (flipped ? edge.minlen : -edge.minlen);
+ });
+}
+
+/*
+ * Returns true if the edge is in the tree.
+ */
+function isTreeEdge(tree, u, v) {
+ return tree.hasEdge(u, v);
+}
+
+/*
+ * Returns true if the specified node is descendant of the root node per the
+ * assigned low and lim attributes in the tree.
+ */
+function isDescendant(tree, vLabel, rootLabel) {
+ return rootLabel.low <= vLabel.lim && vLabel.lim <= rootLabel.lim;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./feasible-tree":25,"./util":28}],28:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = {
+ longestPath: longestPath,
+ slack: slack
+};
+
+/*
+ * Initializes ranks for the input graph using the longest path algorithm. This
+ * algorithm scales well and is fast in practice, it yields rather poor
+ * solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
+ * ranks wide and leaving edges longer than necessary. However, due to its
+ * speed, this algorithm is good for getting an initial ranking that can be fed
+ * into other algorithms.
+ *
+ * This algorithm does not normalize layers because it will be used by other
+ * algorithms in most cases. If using this algorithm directly, be sure to
+ * run normalize at the end.
+ *
+ * Pre-conditions:
+ *
+ * 1. Input graph is a DAG.
+ * 2. Input graph node labels can be assigned properties.
+ *
+ * Post-conditions:
+ *
+ * 1. Each node will be assign an (unnormalized) "rank" property.
+ */
+function longestPath(g) {
+ var visited = {};
+
+ function dfs(v) {
+ var label = g.node(v);
+ if (_.has(visited, v)) {
+ return label.rank;
+ }
+ visited[v] = true;
+
+ var rank = _.min(_.map(g.outEdges(v), function(e) {
+ return dfs(e.w) - g.edge(e).minlen;
+ }));
+
+ if (rank === Number.POSITIVE_INFINITY) {
+ rank = 0;
+ }
+
+ return (label.rank = rank);
+ }
+
+ _.each(g.sources(), dfs);
+}
+
+/*
+ * Returns the amount of slack for the given edge. The slack is defined as the
+ * difference between the length of the edge and its minimum length.
+ */
+function slack(g, e) {
+ return g.node(e.w).rank - g.node(e.v).rank - g.edge(e).minlen;
+}
+
+},{"../lodash":10}],29:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+ Graph = require("./graphlib").Graph;
+
+module.exports = {
+ addDummyNode: addDummyNode,
+ simplify: simplify,
+ asNonCompoundGraph: asNonCompoundGraph,
+ successorWeights: successorWeights,
+ predecessorWeights: predecessorWeights,
+ intersectRect: intersectRect,
+ buildLayerMatrix: buildLayerMatrix,
+ normalizeRanks: normalizeRanks,
+ removeEmptyRanks: removeEmptyRanks,
+ addBorderNode: addBorderNode,
+ maxRank: maxRank,
+ partition: partition,
+ time: time,
+ notime: notime
+};
+
+/*
+ * Adds a dummy node to the graph and return v.
+ */
+function addDummyNode(g, type, attrs, name) {
+ var v;
+ do {
+ v = _.uniqueId(name);
+ } while (g.hasNode(v));
+
+ attrs.dummy = type;
+ g.setNode(v, attrs);
+ return v;
+}
+
+/*
+ * Returns a new graph with only simple edges. Handles aggregation of data
+ * associated with multi-edges.
+ */
+function simplify(g) {
+ var simplified = new Graph().setGraph(g.graph());
+ _.each(g.nodes(), function(v) { simplified.setNode(v, g.node(v)); });
+ _.each(g.edges(), function(e) {
+ var simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 },
+ label = g.edge(e);
+ simplified.setEdge(e.v, e.w, {
+ weight: simpleLabel.weight + label.weight,
+ minlen: Math.max(simpleLabel.minlen, label.minlen)
+ });
+ });
+ return simplified;
+}
+
+function asNonCompoundGraph(g) {
+ var simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph());
+ _.each(g.nodes(), function(v) {
+ if (!g.children(v).length) {
+ simplified.setNode(v, g.node(v));
+ }
+ });
+ _.each(g.edges(), function(e) {
+ simplified.setEdge(e, g.edge(e));
+ });
+ return simplified;
+}
+
+function successorWeights(g) {
+ var weightMap = _.map(g.nodes(), function(v) {
+ var sucs = {};
+ _.each(g.outEdges(v), function(e) {
+ sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight;
+ });
+ return sucs;
+ });
+ return _.zipObject(g.nodes(), weightMap);
+}
+
+function predecessorWeights(g) {
+ var weightMap = _.map(g.nodes(), function(v) {
+ var preds = {};
+ _.each(g.inEdges(v), function(e) {
+ preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight;
+ });
+ return preds;
+ });
+ return _.zipObject(g.nodes(), weightMap);
+}
+
+/*
+ * Finds where a line starting at point ({x, y}) would intersect a rectangle
+ * ({x, y, width, height}) if it were pointing at the rectangle's center.
+ */
+function intersectRect(rect, point) {
+ var x = rect.x;
+ var y = rect.y;
+
+ // Rectangle intersection algorithm from:
+ // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
+ var dx = point.x - x;
+ var dy = point.y - y;
+ var w = rect.width / 2;
+ var h = rect.height / 2;
+
+ if (!dx && !dy) {
+ throw new Error("Not possible to find intersection inside of the rectangle");
+ }
+
+ var sx, sy;
+ if (Math.abs(dy) * w > Math.abs(dx) * h) {
+ // Intersection is top or bottom of rect.
+ if (dy < 0) {
+ h = -h;
+ }
+ sx = h * dx / dy;
+ sy = h;
+ } else {
+ // Intersection is left or right of rect.
+ if (dx < 0) {
+ w = -w;
+ }
+ sx = w;
+ sy = w * dy / dx;
+ }
+
+ return { x: x + sx, y: y + sy };
+}
+
+/*
+ * Given a DAG with each node assigned "rank" and "order" properties, this
+ * function will produce a matrix with the ids of each node.
+ */
+function buildLayerMatrix(g) {
+ var layering = _.map(_.range(maxRank(g) + 1), function() { return []; });
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v),
+ rank = node.rank;
+ if (!_.isUndefined(rank)) {
+ layering[rank][node.order] = v;
+ }
+ });
+ return layering;
+}
+
+/*
+ * Adjusts the ranks for all nodes in the graph such that all nodes v have
+ * rank(v) >= 0 and at least one node w has rank(w) = 0.
+ */
+function normalizeRanks(g) {
+ var min = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+ _.each(g.nodes(), function(v) {
+ var node = g.node(v);
+ if (_.has(node, "rank")) {
+ node.rank -= min;
+ }
+ });
+}
+
+function removeEmptyRanks(g) {
+ // Ranks may not start at 0, so we need to offset them
+ var offset = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+
+ var layers = [];
+ _.each(g.nodes(), function(v) {
+ var rank = g.node(v).rank - offset;
+ if (!_.has(layers, rank)) {
+ layers[rank] = [];
+ }
+ layers[rank].push(v);
+ });
+
+ var delta = 0,
+ nodeRankFactor = g.graph().nodeRankFactor;
+ _.each(layers, function(vs, i) {
+ if (_.isUndefined(vs) && i % nodeRankFactor !== 0) {
+ --delta;
+ } else if (delta) {
+ _.each(vs, function(v) { g.node(v).rank += delta; });
+ }
+ });
+}
+
+function addBorderNode(g, prefix, rank, order) {
+ var node = {
+ width: 0,
+ height: 0
+ };
+ if (arguments.length >= 4) {
+ node.rank = rank;
+ node.order = order;
+ }
+ return addDummyNode(g, "border", node, prefix);
+}
+
+function maxRank(g) {
+ return _.max(_.map(g.nodes(), function(v) {
+ var rank = g.node(v).rank;
+ if (!_.isUndefined(rank)) {
+ return rank;
+ }
+ }));
+}
+
+/*
+ * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
+ * function returns true for an entry it goes into `lhs`. Otherwise it goes
+ * into `rhs.
+ */
+function partition(collection, fn) {
+ var result = { lhs: [], rhs: [] };
+ _.each(collection, function(value) {
+ if (fn(value)) {
+ result.lhs.push(value);
+ } else {
+ result.rhs.push(value);
+ }
+ });
+ return result;
+}
+
+/*
+ * Returns a new function that wraps `fn` with a timer. The wrapper logs the
+ * time it takes to execute the function.
+ */
+function time(name, fn) {
+ var start = _.now();
+ try {
+ return fn();
+ } finally {
+ console.log(name + " time: " + (_.now() - start) + "ms");
+ }
+}
+
+function notime(name, fn) {
+ return fn();
+}
+
+},{"./graphlib":7,"./lodash":10}],30:[function(require,module,exports){
+module.exports = "0.7.1";
+
+},{}],31:[function(require,module,exports){
+/**
+ * Copyright (c) 2014, Chris Pettitt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var lib = require("./lib");
+
+module.exports = {
+ Graph: lib.Graph,
+ json: require("./lib/json"),
+ alg: require("./lib/alg"),
+ version: lib.version
+};
+
+},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = components;
+
+function components(g) {
+ var visited = {},
+ cmpts = [],
+ cmpt;
+
+ function dfs(v) {
+ if (_.has(visited, v)) return;
+ visited[v] = true;
+ cmpt.push(v);
+ _.each(g.successors(v), dfs);
+ _.each(g.predecessors(v), dfs);
+ }
+
+ _.each(g.nodes(), function(v) {
+ cmpt = [];
+ dfs(v);
+ if (cmpt.length) {
+ cmpts.push(cmpt);
+ }
+ });
+
+ return cmpts;
+}
+
+},{"../lodash":49}],33:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = dfs;
+
+/*
+ * A helper that preforms a pre- or post-order traversal on the input graph
+ * and returns the nodes in the order they were visited. This algorithm treats
+ * the input as undirected.
+ *
+ * Order must be one of "pre" or "post".
+ */
+function dfs(g, vs, order) {
+ if (!_.isArray(vs)) {
+ vs = [vs];
+ }
+
+ var acc = [],
+ visited = {};
+ _.each(vs, function(v) {
+ if (!g.hasNode(v)) {
+ throw new Error("Graph does not have node: " + v);
+ }
+
+ doDfs(g, v, order === "post", visited, acc);
+ });
+ return acc;
+}
+
+function doDfs(g, v, postorder, visited, acc) {
+ if (!_.has(visited, v)) {
+ visited[v] = true;
+
+ if (!postorder) { acc.push(v); }
+ _.each(g.neighbors(v), function(w) {
+ doDfs(g, w, postorder, visited, acc);
+ });
+ if (postorder) { acc.push(v); }
+ }
+}
+
+},{"../lodash":49}],34:[function(require,module,exports){
+var dijkstra = require("./dijkstra"),
+ _ = require("../lodash");
+
+module.exports = dijkstraAll;
+
+function dijkstraAll(g, weightFunc, edgeFunc) {
+ return _.transform(g.nodes(), function(acc, v) {
+ acc[v] = dijkstra(g, v, weightFunc, edgeFunc);
+ }, {});
+}
+
+},{"../lodash":49,"./dijkstra":35}],35:[function(require,module,exports){
+var _ = require("../lodash"),
+ PriorityQueue = require("../data/priority-queue");
+
+module.exports = dijkstra;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function dijkstra(g, source, weightFn, edgeFn) {
+ return runDijkstra(g, String(source),
+ weightFn || DEFAULT_WEIGHT_FUNC,
+ edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runDijkstra(g, source, weightFn, edgeFn) {
+ var results = {},
+ pq = new PriorityQueue(),
+ v, vEntry;
+
+ var updateNeighbors = function(edge) {
+ var w = edge.v !== v ? edge.v : edge.w,
+ wEntry = results[w],
+ weight = weightFn(edge),
+ distance = vEntry.distance + weight;
+
+ if (weight < 0) {
+ throw new Error("dijkstra does not allow negative edge weights. " +
+ "Bad edge: " + edge + " Weight: " + weight);
+ }
+
+ if (distance < wEntry.distance) {
+ wEntry.distance = distance;
+ wEntry.predecessor = v;
+ pq.decrease(w, distance);
+ }
+ };
+
+ g.nodes().forEach(function(v) {
+ var distance = v === source ? 0 : Number.POSITIVE_INFINITY;
+ results[v] = { distance: distance };
+ pq.add(v, distance);
+ });
+
+ while (pq.size() > 0) {
+ v = pq.removeMin();
+ vEntry = results[v];
+ if (vEntry.distance === Number.POSITIVE_INFINITY) {
+ break;
+ }
+
+ edgeFn(v).forEach(updateNeighbors);
+ }
+
+ return results;
+}
+
+},{"../data/priority-queue":45,"../lodash":49}],36:[function(require,module,exports){
+var _ = require("../lodash"),
+ tarjan = require("./tarjan");
+
+module.exports = findCycles;
+
+function findCycles(g) {
+ return _.filter(tarjan(g), function(cmpt) { return cmpt.length > 1; });
+}
+
+},{"../lodash":49,"./tarjan":43}],37:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = floydWarshall;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function floydWarshall(g, weightFn, edgeFn) {
+ return runFloydWarshall(g,
+ weightFn || DEFAULT_WEIGHT_FUNC,
+ edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runFloydWarshall(g, weightFn, edgeFn) {
+ var results = {},
+ nodes = g.nodes();
+
+ nodes.forEach(function(v) {
+ results[v] = {};
+ results[v][v] = { distance: 0 };
+ nodes.forEach(function(w) {
+ if (v !== w) {
+ results[v][w] = { distance: Number.POSITIVE_INFINITY };
+ }
+ });
+ edgeFn(v).forEach(function(edge) {
+ var w = edge.v === v ? edge.w : edge.v,
+ d = weightFn(edge);
+ results[v][w] = { distance: d, predecessor: v };
+ });
+ });
+
+ nodes.forEach(function(k) {
+ var rowK = results[k];
+ nodes.forEach(function(i) {
+ var rowI = results[i];
+ nodes.forEach(function(j) {
+ var ik = rowI[k];
+ var kj = rowK[j];
+ var ij = rowI[j];
+ var altDistance = ik.distance + kj.distance;
+ if (altDistance < ij.distance) {
+ ij.distance = altDistance;
+ ij.predecessor = kj.predecessor;
+ }
+ });
+ });
+ });
+
+ return results;
+}
+
+},{"../lodash":49}],38:[function(require,module,exports){
+module.exports = {
+ components: require("./components"),
+ dijkstra: require("./dijkstra"),
+ dijkstraAll: require("./dijkstra-all"),
+ findCycles: require("./find-cycles"),
+ floydWarshall: require("./floyd-warshall"),
+ isAcyclic: require("./is-acyclic"),
+ postorder: require("./postorder"),
+ preorder: require("./preorder"),
+ prim: require("./prim"),
+ tarjan: require("./tarjan"),
+ topsort: require("./topsort")
+};
+
+},{"./components":32,"./dijkstra":35,"./dijkstra-all":34,"./find-cycles":36,"./floyd-warshall":37,"./is-acyclic":39,"./postorder":40,"./preorder":41,"./prim":42,"./tarjan":43,"./topsort":44}],39:[function(require,module,exports){
+var topsort = require("./topsort");
+
+module.exports = isAcyclic;
+
+function isAcyclic(g) {
+ try {
+ topsort(g);
+ } catch (e) {
+ if (e instanceof topsort.CycleException) {
+ return false;
+ }
+ throw e;
+ }
+ return true;
+}
+
+},{"./topsort":44}],40:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = postorder;
+
+function postorder(g, vs) {
+ return dfs(g, vs, "post");
+}
+
+},{"./dfs":33}],41:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = preorder;
+
+function preorder(g, vs) {
+ return dfs(g, vs, "pre");
+}
+
+},{"./dfs":33}],42:[function(require,module,exports){
+var _ = require("../lodash"),
+ Graph = require("../graph"),
+ PriorityQueue = require("../data/priority-queue");
+
+module.exports = prim;
+
+function prim(g, weightFunc) {
+ var result = new Graph(),
+ parents = {},
+ pq = new PriorityQueue(),
+ v;
+
+ function updateNeighbors(edge) {
+ var w = edge.v === v ? edge.w : edge.v,
+ pri = pq.priority(w);
+ if (pri !== undefined) {
+ var edgeWeight = weightFunc(edge);
+ if (edgeWeight < pri) {
+ parents[w] = v;
+ pq.decrease(w, edgeWeight);
+ }
+ }
+ }
+
+ if (g.nodeCount() === 0) {
+ return result;
+ }
+
+ _.each(g.nodes(), function(v) {
+ pq.add(v, Number.POSITIVE_INFINITY);
+ result.setNode(v);
+ });
+
+ // Start from an arbitrary node
+ pq.decrease(g.nodes()[0], 0);
+
+ var init = false;
+ while (pq.size() > 0) {
+ v = pq.removeMin();
+ if (_.has(parents, v)) {
+ result.setEdge(v, parents[v]);
+ } else if (init) {
+ throw new Error("Input graph is not connected: " + g);
+ } else {
+ init = true;
+ }
+
+ g.nodeEdges(v).forEach(updateNeighbors);
+ }
+
+ return result;
+}
+
+},{"../data/priority-queue":45,"../graph":46,"../lodash":49}],43:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = tarjan;
+
+function tarjan(g) {
+ var index = 0,
+ stack = [],
+ visited = {}, // node id -> { onStack, lowlink, index }
+ results = [];
+
+ function dfs(v) {
+ var entry = visited[v] = {
+ onStack: true,
+ lowlink: index,
+ index: index++
+ };
+ stack.push(v);
+
+ g.successors(v).forEach(function(w) {
+ if (!_.has(visited, w)) {
+ dfs(w);
+ entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink);
+ } else if (visited[w].onStack) {
+ entry.lowlink = Math.min(entry.lowlink, visited[w].index);
+ }
+ });
+
+ if (entry.lowlink === entry.index) {
+ var cmpt = [],
+ w;
+ do {
+ w = stack.pop();
+ visited[w].onStack = false;
+ cmpt.push(w);
+ } while (v !== w);
+ results.push(cmpt);
+ }
+ }
+
+ g.nodes().forEach(function(v) {
+ if (!_.has(visited, v)) {
+ dfs(v);
+ }
+ });
+
+ return results;
+}
+
+},{"../lodash":49}],44:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = topsort;
+topsort.CycleException = CycleException;
+
+function topsort(g) {
+ var visited = {},
+ stack = {},
+ results = [];
+
+ function visit(node) {
+ if (_.has(stack, node)) {
+ throw new CycleException();
+ }
+
+ if (!_.has(visited, node)) {
+ stack[node] = true;
+ visited[node] = true;
+ _.each(g.predecessors(node), visit);
+ delete stack[node];
+ results.push(node);
+ }
+ }
+
+ _.each(g.sinks(), visit);
+
+ if (_.size(visited) !== g.nodeCount()) {
+ throw new CycleException();
+ }
+
+ return results;
+}
+
+function CycleException() {}
+
+},{"../lodash":49}],45:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = PriorityQueue;
+
+/**
+ * A min-priority queue data structure. This algorithm is derived from Cormen,
+ * et al., "Introduction to Algorithms". The basic idea of a min-priority
+ * queue is that you can efficiently (in O(1) time) get the smallest key in
+ * the queue. Adding and removing elements takes O(log n) time. A key can
+ * have its priority decreased in O(log n) time.
+ */
+function PriorityQueue() {
+ this._arr = [];
+ this._keyIndices = {};
+}
+
+/**
+ * Returns the number of elements in the queue. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.size = function() {
+ return this._arr.length;
+};
+
+/**
+ * Returns the keys that are in the queue. Takes `O(n)` time.
+ */
+PriorityQueue.prototype.keys = function() {
+ return this._arr.map(function(x) { return x.key; });
+};
+
+/**
+ * Returns `true` if **key** is in the queue and `false` if not.
+ */
+PriorityQueue.prototype.has = function(key) {
+ return _.has(this._keyIndices, key);
+};
+
+/**
+ * Returns the priority for **key**. If **key** is not present in the queue
+ * then this function returns `undefined`. Takes `O(1)` time.
+ *
+ * @param {Object} key
+ */
+PriorityQueue.prototype.priority = function(key) {
+ var index = this._keyIndices[key];
+ if (index !== undefined) {
+ return this._arr[index].priority;
+ }
+};
+
+/**
+ * Returns the key for the minimum element in this queue. If the queue is
+ * empty this function throws an Error. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.min = function() {
+ if (this.size() === 0) {
+ throw new Error("Queue underflow");
+ }
+ return this._arr[0].key;
+};
+
+/**
+ * Inserts a new key into the priority queue. If the key already exists in
+ * the queue this function returns `false`; otherwise it will return `true`.
+ * Takes `O(n)` time.
+ *
+ * @param {Object} key the key to add
+ * @param {Number} priority the initial priority for the key
+ */
+PriorityQueue.prototype.add = function(key, priority) {
+ var keyIndices = this._keyIndices;
+ key = String(key);
+ if (!_.has(keyIndices, key)) {
+ var arr = this._arr;
+ var index = arr.length;
+ keyIndices[key] = index;
+ arr.push({key: key, priority: priority});
+ this._decrease(index);
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
+ */
+PriorityQueue.prototype.removeMin = function() {
+ this._swap(0, this._arr.length - 1);
+ var min = this._arr.pop();
+ delete this._keyIndices[min.key];
+ this._heapify(0);
+ return min.key;
+};
+
+/**
+ * Decreases the priority for **key** to **priority**. If the new priority is
+ * greater than the previous priority, this function will throw an Error.
+ *
+ * @param {Object} key the key for which to raise priority
+ * @param {Number} priority the new priority for the key
+ */
+PriorityQueue.prototype.decrease = function(key, priority) {
+ var index = this._keyIndices[key];
+ if (priority > this._arr[index].priority) {
+ throw new Error("New priority is greater than current priority. " +
+ "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
+ }
+ this._arr[index].priority = priority;
+ this._decrease(index);
+};
+
+PriorityQueue.prototype._heapify = function(i) {
+ var arr = this._arr;
+ var l = 2 * i,
+ r = l + 1,
+ largest = i;
+ if (l < arr.length) {
+ largest = arr[l].priority < arr[largest].priority ? l : largest;
+ if (r < arr.length) {
+ largest = arr[r].priority < arr[largest].priority ? r : largest;
+ }
+ if (largest !== i) {
+ this._swap(i, largest);
+ this._heapify(largest);
+ }
+ }
+};
+
+PriorityQueue.prototype._decrease = function(index) {
+ var arr = this._arr;
+ var priority = arr[index].priority;
+ var parent;
+ while (index !== 0) {
+ parent = index >> 1;
+ if (arr[parent].priority < priority) {
+ break;
+ }
+ this._swap(index, parent);
+ index = parent;
+ }
+};
+
+PriorityQueue.prototype._swap = function(i, j) {
+ var arr = this._arr;
+ var keyIndices = this._keyIndices;
+ var origArrI = arr[i];
+ var origArrJ = arr[j];
+ arr[i] = origArrJ;
+ arr[j] = origArrI;
+ keyIndices[origArrJ.key] = i;
+ keyIndices[origArrI.key] = j;
+};
+
+},{"../lodash":49}],46:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = Graph;
+
+var DEFAULT_EDGE_NAME = "\x00",
+ GRAPH_NODE = "\x00",
+ EDGE_KEY_DELIM = "\x01";
+
+// Implementation notes:
+//
+// * Node id query functions should return string ids for the nodes
+// * Edge id query functions should return an "edgeObj", edge object, that is
+// composed of enough information to uniquely identify an edge: {v, w, name}.
+// * Internally we use an "edgeId", a stringified form of the edgeObj, to
+// reference edges. This is because we need a performant way to look these
+// edges up and, object properties, which have string keys, are the closest
+// we're going to get to a performant hashtable in JavaScript.
+
+function Graph(opts) {
+ this._isDirected = _.has(opts, "directed") ? opts.directed : true;
+ this._isMultigraph = _.has(opts, "multigraph") ? opts.multigraph : false;
+ this._isCompound = _.has(opts, "compound") ? opts.compound : false;
+
+ // Label for the graph itself
+ this._label = undefined;
+
+ // Defaults to be set when creating a new node
+ this._defaultNodeLabelFn = _.constant(undefined);
+
+ // Defaults to be set when creating a new edge
+ this._defaultEdgeLabelFn = _.constant(undefined);
+
+ // v -> label
+ this._nodes = {};
+
+ if (this._isCompound) {
+ // v -> parent
+ this._parent = {};
+
+ // v -> children
+ this._children = {};
+ this._children[GRAPH_NODE] = {};
+ }
+
+ // v -> edgeObj
+ this._in = {};
+
+ // u -> v -> Number
+ this._preds = {};
+
+ // v -> edgeObj
+ this._out = {};
+
+ // v -> w -> Number
+ this._sucs = {};
+
+ // e -> edgeObj
+ this._edgeObjs = {};
+
+ // e -> label
+ this._edgeLabels = {};
+}
+
+/* Number of nodes in the graph. Should only be changed by the implementation. */
+Graph.prototype._nodeCount = 0;
+
+/* Number of edges in the graph. Should only be changed by the implementation. */
+Graph.prototype._edgeCount = 0;
+
+
+/* === Graph functions ========= */
+
+Graph.prototype.isDirected = function() {
+ return this._isDirected;
+};
+
+Graph.prototype.isMultigraph = function() {
+ return this._isMultigraph;
+};
+
+Graph.prototype.isCompound = function() {
+ return this._isCompound;
+};
+
+Graph.prototype.setGraph = function(label) {
+ this._label = label;
+ return this;
+};
+
+Graph.prototype.graph = function() {
+ return this._label;
+};
+
+
+/* === Node functions ========== */
+
+Graph.prototype.setDefaultNodeLabel = function(newDefault) {
+ if (!_.isFunction(newDefault)) {
+ newDefault = _.constant(newDefault);
+ }
+ this._defaultNodeLabelFn = newDefault;
+ return this;
+};
+
+Graph.prototype.nodeCount = function() {
+ return this._nodeCount;
+};
+
+Graph.prototype.nodes = function() {
+ return _.keys(this._nodes);
+};
+
+Graph.prototype.sources = function() {
+ return _.filter(this.nodes(), function(v) {
+ return _.isEmpty(this._in[v]);
+ }, this);
+};
+
+Graph.prototype.sinks = function() {
+ return _.filter(this.nodes(), function(v) {
+ return _.isEmpty(this._out[v]);
+ }, this);
+};
+
+Graph.prototype.setNodes = function(vs, value) {
+ var args = arguments;
+ _.each(vs, function(v) {
+ if (args.length > 1) {
+ this.setNode(v, value);
+ } else {
+ this.setNode(v);
+ }
+ }, this);
+ return this;
+};
+
+Graph.prototype.setNode = function(v, value) {
+ if (_.has(this._nodes, v)) {
+ if (arguments.length > 1) {
+ this._nodes[v] = value;
+ }
+ return this;
+ }
+
+ this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
+ if (this._isCompound) {
+ this._parent[v] = GRAPH_NODE;
+ this._children[v] = {};
+ this._children[GRAPH_NODE][v] = true;
+ }
+ this._in[v] = {};
+ this._preds[v] = {};
+ this._out[v] = {};
+ this._sucs[v] = {};
+ ++this._nodeCount;
+ return this;
+};
+
+Graph.prototype.node = function(v) {
+ return this._nodes[v];
+};
+
+Graph.prototype.hasNode = function(v) {
+ return _.has(this._nodes, v);
+};
+
+Graph.prototype.removeNode = function(v) {
+ var self = this;
+ if (_.has(this._nodes, v)) {
+ var removeEdge = function(e) { self.removeEdge(self._edgeObjs[e]); };
+ delete this._nodes[v];
+ if (this._isCompound) {
+ this._removeFromParentsChildList(v);
+ delete this._parent[v];
+ _.each(this.children(v), function(child) {
+ this.setParent(child);
+ }, this);
+ delete this._children[v];
+ }
+ _.each(_.keys(this._in[v]), removeEdge);
+ delete this._in[v];
+ delete this._preds[v];
+ _.each(_.keys(this._out[v]), removeEdge);
+ delete this._out[v];
+ delete this._sucs[v];
+ --this._nodeCount;
+ }
+ return this;
+};
+
+Graph.prototype.setParent = function(v, parent) {
+ if (!this._isCompound) {
+ throw new Error("Cannot set parent in a non-compound graph");
+ }
+
+ if (_.isUndefined(parent)) {
+ parent = GRAPH_NODE;
+ } else {
+ for (var ancestor = parent;
+ !_.isUndefined(ancestor);
+ ancestor = this.parent(ancestor)) {
+ if (ancestor === v) {
+ throw new Error("Setting " + parent+ " as parent of " + v +
+ " would create create a cycle");
+ }
+ }
+
+ this.setNode(parent);
+ }
+
+ this.setNode(v);
+ this._removeFromParentsChildList(v);
+ this._parent[v] = parent;
+ this._children[parent][v] = true;
+ return this;
+};
+
+Graph.prototype._removeFromParentsChildList = function(v) {
+ delete this._children[this._parent[v]][v];
+};
+
+Graph.prototype.parent = function(v) {
+ if (this._isCompound) {
+ var parent = this._parent[v];
+ if (parent !== GRAPH_NODE) {
+ return parent;
+ }
+ }
+};
+
+Graph.prototype.children = function(v) {
+ if (_.isUndefined(v)) {
+ v = GRAPH_NODE;
+ }
+
+ if (this._isCompound) {
+ var children = this._children[v];
+ if (children) {
+ return _.keys(children);
+ }
+ } else if (v === GRAPH_NODE) {
+ return this.nodes();
+ } else if (this.hasNode(v)) {
+ return [];
+ }
+};
+
+Graph.prototype.predecessors = function(v) {
+ var predsV = this._preds[v];
+ if (predsV) {
+ return _.keys(predsV);
+ }
+};
+
+Graph.prototype.successors = function(v) {
+ var sucsV = this._sucs[v];
+ if (sucsV) {
+ return _.keys(sucsV);
+ }
+};
+
+Graph.prototype.neighbors = function(v) {
+ var preds = this.predecessors(v);
+ if (preds) {
+ return _.union(preds, this.successors(v));
+ }
+};
+
+/* === Edge functions ========== */
+
+Graph.prototype.setDefaultEdgeLabel = function(newDefault) {
+ if (!_.isFunction(newDefault)) {
+ newDefault = _.constant(newDefault);
+ }
+ this._defaultEdgeLabelFn = newDefault;
+ return this;
+};
+
+Graph.prototype.edgeCount = function() {
+ return this._edgeCount;
+};
+
+Graph.prototype.edges = function() {
+ return _.values(this._edgeObjs);
+};
+
+Graph.prototype.setPath = function(vs, value) {
+ var self = this,
+ args = arguments;
+ _.reduce(vs, function(v, w) {
+ if (args.length > 1) {
+ self.setEdge(v, w, value);
+ } else {
+ self.setEdge(v, w);
+ }
+ return w;
+ });
+ return this;
+};
+
+/*
+ * setEdge(v, w, [value, [name]])
+ * setEdge({ v, w, [name] }, [value])
+ */
+Graph.prototype.setEdge = function() {
+ var v, w, name, value,
+ valueSpecified = false;
+
+ if (_.isPlainObject(arguments[0])) {
+ v = arguments[0].v;
+ w = arguments[0].w;
+ name = arguments[0].name;
+ if (arguments.length === 2) {
+ value = arguments[1];
+ valueSpecified = true;
+ }
+ } else {
+ v = arguments[0];
+ w = arguments[1];
+ name = arguments[3];
+ if (arguments.length > 2) {
+ value = arguments[2];
+ valueSpecified = true;
+ }
+ }
+
+ v = "" + v;
+ w = "" + w;
+ if (!_.isUndefined(name)) {
+ name = "" + name;
+ }
+
+ var e = edgeArgsToId(this._isDirected, v, w, name);
+ if (_.has(this._edgeLabels, e)) {
+ if (valueSpecified) {
+ this._edgeLabels[e] = value;
+ }
+ return this;
+ }
+
+ if (!_.isUndefined(name) && !this._isMultigraph) {
+ throw new Error("Cannot set a named edge when isMultigraph = false");
+ }
+
+ // It didn't exist, so we need to create it.
+ // First ensure the nodes exist.
+ this.setNode(v);
+ this.setNode(w);
+
+ this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name);
+
+ var edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
+ // Ensure we add undirected edges in a consistent way.
+ v = edgeObj.v;
+ w = edgeObj.w;
+
+ Object.freeze(edgeObj);
+ this._edgeObjs[e] = edgeObj;
+ incrementOrInitEntry(this._preds[w], v);
+ incrementOrInitEntry(this._sucs[v], w);
+ this._in[w][e] = edgeObj;
+ this._out[v][e] = edgeObj;
+ this._edgeCount++;
+ return this;
+};
+
+Graph.prototype.edge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name));
+ return this._edgeLabels[e];
+};
+
+Graph.prototype.hasEdge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name));
+ return _.has(this._edgeLabels, e);
+};
+
+Graph.prototype.removeEdge = function(v, w, name) {
+ var e = (arguments.length === 1
+ ? edgeObjToId(this._isDirected, arguments[0])
+ : edgeArgsToId(this._isDirected, v, w, name)),
+ edge = this._edgeObjs[e];
+ if (edge) {
+ v = edge.v;
+ w = edge.w;
+ delete this._edgeLabels[e];
+ delete this._edgeObjs[e];
+ decrementOrRemoveEntry(this._preds[w], v);
+ decrementOrRemoveEntry(this._sucs[v], w);
+ delete this._in[w][e];
+ delete this._out[v][e];
+ this._edgeCount--;
+ }
+ return this;
+};
+
+Graph.prototype.inEdges = function(v, u) {
+ var inV = this._in[v];
+ if (inV) {
+ var edges = _.values(inV);
+ if (!u) {
+ return edges;
+ }
+ return _.filter(edges, function(edge) { return edge.v === u; });
+ }
+};
+
+Graph.prototype.outEdges = function(v, w) {
+ var outV = this._out[v];
+ if (outV) {
+ var edges = _.values(outV);
+ if (!w) {
+ return edges;
+ }
+ return _.filter(edges, function(edge) { return edge.w === w; });
+ }
+};
+
+Graph.prototype.nodeEdges = function(v, w) {
+ var inEdges = this.inEdges(v, w);
+ if (inEdges) {
+ return inEdges.concat(this.outEdges(v, w));
+ }
+};
+
+function incrementOrInitEntry(map, k) {
+ if (_.has(map, k)) {
+ map[k]++;
+ } else {
+ map[k] = 1;
+ }
+}
+
+function decrementOrRemoveEntry(map, k) {
+ if (!--map[k]) { delete map[k]; }
+}
+
+function edgeArgsToId(isDirected, v, w, name) {
+ if (!isDirected && v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
+ (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name);
+}
+
+function edgeArgsToObj(isDirected, v, w, name) {
+ if (!isDirected && v > w) {
+ var tmp = v;
+ v = w;
+ w = tmp;
+ }
+ var edgeObj = { v: v, w: w };
+ if (name) {
+ edgeObj.name = name;
+ }
+ return edgeObj;
+}
+
+function edgeObjToId(isDirected, edgeObj) {
+ return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
+}
+
+},{"./lodash":49}],47:[function(require,module,exports){
+// Includes only the "core" of graphlib
+module.exports = {
+ Graph: require("./graph"),
+ version: require("./version")
+};
+
+},{"./graph":46,"./version":50}],48:[function(require,module,exports){
+var _ = require("./lodash"),
+ Graph = require("./graph");
+
+module.exports = {
+ write: write,
+ read: read
+};
+
+function write(g) {
+ var json = {
+ options: {
+ directed: g.isDirected(),
+ multigraph: g.isMultigraph(),
+ compound: g.isCompound()
+ },
+ nodes: writeNodes(g),
+ edges: writeEdges(g)
+ };
+ if (!_.isUndefined(g.graph())) {
+ json.value = _.clone(g.graph());
+ }
+ return json;
+}
+
+function writeNodes(g) {
+ return _.map(g.nodes(), function(v) {
+ var nodeValue = g.node(v),
+ parent = g.parent(v),
+ node = { v: v };
+ if (!_.isUndefined(nodeValue)) {
+ node.value = nodeValue;
+ }
+ if (!_.isUndefined(parent)) {
+ node.parent = parent;
+ }
+ return node;
+ });
+}
+
+function writeEdges(g) {
+ return _.map(g.edges(), function(e) {
+ var edgeValue = g.edge(e),
+ edge = { v: e.v, w: e.w };
+ if (!_.isUndefined(e.name)) {
+ edge.name = e.name;
+ }
+ if (!_.isUndefined(edgeValue)) {
+ edge.value = edgeValue;
+ }
+ return edge;
+ });
+}
+
+function read(json) {
+ var g = new Graph(json.options).setGraph(json.value);
+ _.each(json.nodes, function(entry) {
+ g.setNode(entry.v, entry.value);
+ if (entry.parent) {
+ g.setParent(entry.v, entry.parent);
+ }
+ });
+ _.each(json.edges, function(entry) {
+ g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value);
+ });
+ return g;
+}
+
+},{"./graph":46,"./lodash":49}],49:[function(require,module,exports){
+module.exports=require(10)
+},{"/Users/cpettitt/projects/dagre/lib/lodash.js":10,"lodash":51}],50:[function(require,module,exports){
+module.exports = '1.0.1';
+
+},{}],51:[function(require,module,exports){
+(function (global){
+/**
+ * @license
+ * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
+ * Build: `lodash modern -o ./dist/lodash.js`
+ * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function() {
+
+ /** Used as a safe reference for `undefined` in pre ES5 environments */
+ var undefined;
+
+ /** Used to pool arrays and objects used internally */
+ var arrayPool = [],
+ objectPool = [];
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
+ var keyPrefix = +new Date + '';
+
+ /** Used as the size when optimizations are enabled for large arrays */
+ var largeArraySize = 75;
+
+ /** Used as the max size of the `arrayPool` and `objectPool` */
+ var maxPoolSize = 40;
+
+ /** Used to detect and test whitespace */
+ var whitespace = (
+ // whitespace
+ ' \t\x0B\f\xA0\ufeff' +
+
+ // line terminators
+ '\n\r\u2028\u2029' +
+
+ // unicode category "Zs" space separators
+ '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
+ );
+
+ /** Used to match empty string literals in compiled template source */
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+ /**
+ * Used to match ES6 template delimiters
+ * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
+ */
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+ /** Used to match regexp flags from their coerced string values */
+ var reFlags = /\w*$/;
+
+ /** Used to detected named functions */
+ var reFuncName = /^\s*function[ \n\r\t]+\w/;
+
+ /** Used to match "interpolate" template delimiters */
+ var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+ /** Used to match leading whitespace and zeros to be removed */
+ var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');
+
+ /** Used to ensure capturing order of template delimiters */
+ var reNoMatch = /($^)/;
+
+ /** Used to detect functions containing a `this` reference */
+ var reThis = /\bthis\b/;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to assign default `context` object properties */
+ var contextProps = [
+ 'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object',
+ 'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
+ 'parseInt', 'setTimeout'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** `Object#toString` result shortcuts */
+ var argsClass = '[object Arguments]',
+ arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ objectClass = '[object Object]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Used to identify object classifications that `_.clone` supports */
+ var cloneableClasses = {};
+ cloneableClasses[funcClass] = false;
+ cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+ cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+ cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+ cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+ /** Used as an internal `_.debounce` options object */
+ var debounceOptions = {
+ 'leading': false,
+ 'maxWait': 0,
+ 'trailing': false
+ };
+
+ /** Used as the property descriptor for `__bindData__` */
+ var descriptor = {
+ 'configurable': false,
+ 'enumerable': false,
+ 'value': null,
+ 'writable': false
+ };
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Used as a reference to the global object */
+ var root = (objectTypes[typeof window] && window) || this;
+
+ /** Detect free variable `exports` */
+ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+ /** Detect free variable `module` */
+ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+ /** Detect the popular CommonJS extension `module.exports` */
+ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+ /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
+ var freeGlobal = objectTypes[typeof global] && global;
+ if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.indexOf` without support for binary searches
+ * or `fromIndex` constraints.
+ *
+ * @private
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ */
+ function baseIndexOf(array, value, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0;
+
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * An implementation of `_.contains` for cache objects that mimics the return
+ * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
+ *
+ * @private
+ * @param {Object} cache The cache object to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `0` if `value` is found, else `-1`.
+ */
+ function cacheIndexOf(cache, value) {
+ var type = typeof value;
+ cache = cache.cache;
+
+ if (type == 'boolean' || value == null) {
+ return cache[value] ? 0 : -1;
+ }
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value;
+ cache = (cache = cache[type]) && cache[key];
+
+ return type == 'object'
+ ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
+ : (cache ? 0 : -1);
+ }
+
+ /**
+ * Adds a given value to the corresponding cache object.
+ *
+ * @private
+ * @param {*} value The value to add to the cache.
+ */
+ function cachePush(value) {
+ var cache = this.cache,
+ type = typeof value;
+
+ if (type == 'boolean' || value == null) {
+ cache[value] = true;
+ } else {
+ if (type != 'number' && type != 'string') {
+ type = 'object';
+ }
+ var key = type == 'number' ? value : keyPrefix + value,
+ typeCache = cache[type] || (cache[type] = {});
+
+ if (type == 'object') {
+ (typeCache[key] || (typeCache[key] = [])).push(value);
+ } else {
+ typeCache[key] = true;
+ }
+ }
+ }
+
+ /**
+ * Used by `_.max` and `_.min` as the default callback when a given
+ * collection is a string value.
+ *
+ * @private
+ * @param {string} value The character to inspect.
+ * @returns {number} Returns the code unit of given character.
+ */
+ function charAtCallback(value) {
+ return value.charCodeAt(0);
+ }
+
+ /**
+ * Used by `sortBy` to compare transformed `collection` elements, stable sorting
+ * them in ascending order.
+ *
+ * @private
+ * @param {Object} a The object to compare to `b`.
+ * @param {Object} b The object to compare to `a`.
+ * @returns {number} Returns the sort order indicator of `1` or `-1`.
+ */
+ function compareAscending(a, b) {
+ var ac = a.criteria,
+ bc = b.criteria,
+ index = -1,
+ length = ac.length;
+
+ while (++index < length) {
+ var value = ac[index],
+ other = bc[index];
+
+ if (value !== other) {
+ if (value > other || typeof value == 'undefined') {
+ return 1;
+ }
+ if (value < other || typeof other == 'undefined') {
+ return -1;
+ }
+ }
+ }
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to return the same value for
+ // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See http://code.google.com/p/v8/issues/detail?id=90
+ return a.index - b.index;
+ }
+
+ /**
+ * Creates a cache object to optimize linear searches of large arrays.
+ *
+ * @private
+ * @param {Array} [array=[]] The array to search.
+ * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
+ */
+ function createCache(array) {
+ var index = -1,
+ length = array.length,
+ first = array[0],
+ mid = array[(length / 2) | 0],
+ last = array[length - 1];
+
+ if (first && typeof first == 'object' &&
+ mid && typeof mid == 'object' && last && typeof last == 'object') {
+ return false;
+ }
+ var cache = getObject();
+ cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;
+
+ var result = getObject();
+ result.array = array;
+ result.cache = cache;
+ result.push = cachePush;
+
+ while (++index < length) {
+ result.push(array[index]);
+ }
+ return result;
+ }
+
+ /**
+ * Used by `template` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Gets an array from the array pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Array} The array from the pool.
+ */
+ function getArray() {
+ return arrayPool.pop() || [];
+ }
+
+ /**
+ * Gets an object from the object pool or creates a new one if the pool is empty.
+ *
+ * @private
+ * @returns {Object} The object from the pool.
+ */
+ function getObject() {
+ return objectPool.pop() || {
+ 'array': null,
+ 'cache': null,
+ 'criteria': null,
+ 'false': false,
+ 'index': 0,
+ 'null': false,
+ 'number': null,
+ 'object': null,
+ 'push': null,
+ 'string': null,
+ 'true': false,
+ 'undefined': false,
+ 'value': null
+ };
+ }
+
+ /**
+ * Releases the given array back to the array pool.
+ *
+ * @private
+ * @param {Array} [array] The array to release.
+ */
+ function releaseArray(array) {
+ array.length = 0;
+ if (arrayPool.length < maxPoolSize) {
+ arrayPool.push(array);
+ }
+ }
+
+ /**
+ * Releases the given object back to the object pool.
+ *
+ * @private
+ * @param {Object} [object] The object to release.
+ */
+ function releaseObject(object) {
+ var cache = object.cache;
+ if (cache) {
+ releaseObject(cache);
+ }
+ object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
+ if (objectPool.length < maxPoolSize) {
+ objectPool.push(object);
+ }
+ }
+
+ /**
+ * Slices the `collection` from the `start` index up to, but not including,
+ * the `end` index.
+ *
+ * Note: This function is used instead of `Array#slice` to support node lists
+ * in IE < 9 and to ensure dense arrays are returned.
+ *
+ * @private
+ * @param {Array|Object|string} collection The collection to slice.
+ * @param {number} start The start index.
+ * @param {number} end The end index.
+ * @returns {Array} Returns the new array.
+ */
+ function slice(array, start, end) {
+ start || (start = 0);
+ if (typeof end == 'undefined') {
+ end = array ? array.length : 0;
+ }
+ var index = -1,
+ length = end - start || 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = array[start + index];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a new `lodash` function using the given context object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns the `lodash` function.
+ */
+ function runInContext(context) {
+ // Avoid issues with some ES3 environments that attempt to use values, named
+ // after built-in constructors like `Object`, for the creation of literals.
+ // ES5 clears this up by stating that literals must use built-in constructors.
+ // See http://es5.github.io/#x11.1.5.
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+ /** Native constructor references */
+ var Array = context.Array,
+ Boolean = context.Boolean,
+ Date = context.Date,
+ Function = context.Function,
+ Math = context.Math,
+ Number = context.Number,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String,
+ TypeError = context.TypeError;
+
+ /**
+ * Used for `Array` method references.
+ *
+ * Normally `Array.prototype` would suffice, however, using an array literal
+ * avoids issues in Narwhal.
+ */
+ var arrayRef = [];
+
+ /** Used for native method references */
+ var objectProto = Object.prototype;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = context._;
+
+ /** Used to resolve the internal [[Class]] of values */
+ var toString = objectProto.toString;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' +
+ String(toString)
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+ .replace(/toString| for [^\]]+/g, '.*?') + '$'
+ );
+
+ /** Native method shortcuts */
+ var ceil = Math.ceil,
+ clearTimeout = context.clearTimeout,
+ floor = Math.floor,
+ fnToString = Function.prototype.toString,
+ getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+ hasOwnProperty = objectProto.hasOwnProperty,
+ push = arrayRef.push,
+ setTimeout = context.setTimeout,
+ splice = arrayRef.splice,
+ unshift = arrayRef.unshift;
+
+ /** Used to set meta data on functions */
+ var defineProperty = (function() {
+ // IE 8 only accepts DOM elements
+ try {
+ var o = {},
+ func = isNative(func = Object.defineProperty) && func,
+ result = func(o, o, o) && func;
+ } catch(e) { }
+ return result;
+ }());
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
+ nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = context.isFinite,
+ nativeIsNaN = context.isNaN,
+ nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random;
+
+ /** Used to lookup a built-in constructor by [[Class]] */
+ var ctorByClass = {};
+ ctorByClass[arrayClass] = Array;
+ ctorByClass[boolClass] = Boolean;
+ ctorByClass[dateClass] = Date;
+ ctorByClass[funcClass] = Function;
+ ctorByClass[objectClass] = Object;
+ ctorByClass[numberClass] = Number;
+ ctorByClass[regexpClass] = RegExp;
+ ctorByClass[stringClass] = String;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object which wraps the given value to enable intuitive
+ * method chaining.
+ *
+ * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
+ * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
+ * and `unshift`
+ *
+ * Chaining is supported in custom builds as long as the `value` method is
+ * implicitly or explicitly included in the build.
+ *
+ * The chainable wrapper functions are:
+ * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
+ * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
+ * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
+ * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+ * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+ * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
+ * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
+ * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+ * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
+ * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
+ * and `zip`
+ *
+ * The non-chainable wrapper functions are:
+ * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
+ * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
+ * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+ * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
+ * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
+ * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
+ * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
+ * `template`, `unescape`, `uniqueId`, and `value`
+ *
+ * The wrapper functions `first` and `last` return wrapped values when `n` is
+ * provided, otherwise they return unwrapped values.
+ *
+ * Explicit chaining can be enabled by using the `_.chain` method.
+ *
+ * @name _
+ * @constructor
+ * @category Chaining
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns a `lodash` instance.
+ * @example
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // returns an unwrapped value
+ * wrapped.reduce(function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * // returns a wrapped value
+ * var squares = wrapped.map(function(num) {
+ * return num * num;
+ * });
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
+ function lodash(value) {
+ // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
+ return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
+ ? value
+ : new lodashWrapper(value);
+ }
+
+ /**
+ * A fast path for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @param {boolean} chainAll A flag to enable chaining for all methods
+ * @returns {Object} Returns a `lodash` instance.
+ */
+ function lodashWrapper(value, chainAll) {
+ this.__chain__ = !!chainAll;
+ this.__wrapped__ = value;
+ }
+ // ensure `new lodashWrapper` is an instance of `lodash`
+ lodashWrapper.prototype = lodash.prototype;
+
+ /**
+ * An object used to flag environments features.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ var support = lodash.support = {};
+
+ /**
+ * Detect if functions can be decompiled by `Function#toString`
+ * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);
+
+ /**
+ * Detect if `Function#name` is supported (all but IE).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.funcNames = typeof Function.name == 'string';
+
+ /**
+ * By default, the template delimiters used by Lo-Dash are similar to those in
+ * embedded Ruby (ERB). Change the following template settings to use alternative
+ * delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': reInterpolate,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type string
+ */
+ 'variable': '',
+
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type Object
+ */
+ 'imports': {
+
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type Function
+ */
+ '_': lodash
+ }
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The base implementation of `_.bind` that creates the bound function and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new bound function.
+ */
+ function baseBind(bindData) {
+ var func = bindData[0],
+ partialArgs = bindData[2],
+ thisArg = bindData[4];
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.io/#x15.3.4.5
+ if (partialArgs) {
+ // avoid `arguments` object deoptimizations by using `slice` instead
+ // of `Array.prototype.slice.call` and not assigning `arguments` to a
+ // variable as a ternary expression
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ // mimic the constructor's `return` behavior
+ // http://es5.github.io/#x13.2.2
+ if (this instanceof bound) {
+ // ensure `new bound` is an instance of `func`
+ var thisBinding = baseCreate(func.prototype),
+ result = func.apply(thisBinding, args || arguments);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisArg, args || arguments);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.clone` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates clones with source counterparts.
+ * @returns {*} Returns the cloned value.
+ */
+ function baseClone(value, isDeep, callback, stackA, stackB) {
+ if (callback) {
+ var result = callback(value);
+ if (typeof result != 'undefined') {
+ return result;
+ }
+ }
+ // inspect [[Class]]
+ var isObj = isObject(value);
+ if (isObj) {
+ var className = toString.call(value);
+ if (!cloneableClasses[className]) {
+ return value;
+ }
+ var ctor = ctorByClass[className];
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ return new ctor(+value);
+
+ case numberClass:
+ case stringClass:
+ return new ctor(value);
+
+ case regexpClass:
+ result = ctor(value.source, reFlags.exec(value));
+ result.lastIndex = value.lastIndex;
+ return result;
+ }
+ } else {
+ return value;
+ }
+ var isArr = isArray(value);
+ if (isDeep) {
+ // check for circular references and return corresponding clone
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == value) {
+ return stackB[length];
+ }
+ }
+ result = isArr ? ctor(value.length) : {};
+ }
+ else {
+ result = isArr ? slice(value) : assign({}, value);
+ }
+ // add array properties assigned by `RegExp#exec`
+ if (isArr) {
+ if (hasOwnProperty.call(value, 'index')) {
+ result.index = value.index;
+ }
+ if (hasOwnProperty.call(value, 'input')) {
+ result.input = value.input;
+ }
+ }
+ // exit for shallow clone
+ if (!isDeep) {
+ return result;
+ }
+ // add the source value to the stack of traversed objects
+ // and associate it with its clone
+ stackA.push(value);
+ stackB.push(result);
+
+ // recursively populate clone (susceptible to call stack limits)
+ (isArr ? forEach : forOwn)(value, function(objValue, key) {
+ result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
+ });
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} prototype The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
+ function baseCreate(prototype, properties) {
+ return isObject(prototype) ? nativeCreate(prototype) : {};
+ }
+ // fallback for browsers without `Object.create`
+ if (!nativeCreate) {
+ baseCreate = (function() {
+ function Object() {}
+ return function(prototype) {
+ if (isObject(prototype)) {
+ Object.prototype = prototype;
+ var result = new Object;
+ Object.prototype = null;
+ }
+ return result || context.Object();
+ };
+ }());
+ }
+
+ /**
+ * The base implementation of `_.createCallback` without support for creating
+ * "_.pluck" or "_.where" style callbacks.
+ *
+ * @private
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ */
+ function baseCreateCallback(func, thisArg, argCount) {
+ if (typeof func != 'function') {
+ return identity;
+ }
+ // exit early for no `thisArg` or already bound by `Function#bind`
+ if (typeof thisArg == 'undefined' || !('prototype' in func)) {
+ return func;
+ }
+ var bindData = func.__bindData__;
+ if (typeof bindData == 'undefined') {
+ if (support.funcNames) {
+ bindData = !func.name;
+ }
+ bindData = bindData || !support.funcDecomp;
+ if (!bindData) {
+ var source = fnToString.call(func);
+ if (!support.funcNames) {
+ bindData = !reFuncName.test(source);
+ }
+ if (!bindData) {
+ // checks if `func` references the `this` keyword and stores the result
+ bindData = reThis.test(source);
+ setBindData(func, bindData);
+ }
+ }
+ }
+ // exit early if there are no `this` references or `func` is bound
+ if (bindData === false || (bindData !== true && bindData[1] & 1)) {
+ return func;
+ }
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(thisArg, value);
+ };
+ case 2: return function(a, b) {
+ return func.call(thisArg, a, b);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(thisArg, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(thisArg, accumulator, value, index, collection);
+ };
+ }
+ return bind(func, thisArg);
+ }
+
+ /**
+ * The base implementation of `createWrapper` that creates the wrapper and
+ * sets its meta data.
+ *
+ * @private
+ * @param {Array} bindData The bind data array.
+ * @returns {Function} Returns the new function.
+ */
+ function baseCreateWrapper(bindData) {
+ var func = bindData[0],
+ bitmask = bindData[1],
+ partialArgs = bindData[2],
+ partialRightArgs = bindData[3],
+ thisArg = bindData[4],
+ arity = bindData[5];
+
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ key = func;
+
+ function bound() {
+ var thisBinding = isBind ? thisArg : this;
+ if (partialArgs) {
+ var args = slice(partialArgs);
+ push.apply(args, arguments);
+ }
+ if (partialRightArgs || isCurry) {
+ args || (args = slice(arguments));
+ if (partialRightArgs) {
+ push.apply(args, partialRightArgs);
+ }
+ if (isCurry && args.length < arity) {
+ bitmask |= 16 & ~32;
+ return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
+ }
+ }
+ args || (args = arguments);
+ if (isBindKey) {
+ func = thisBinding[key];
+ }
+ if (this instanceof bound) {
+ thisBinding = baseCreate(func.prototype);
+ var result = func.apply(thisBinding, args);
+ return isObject(result) ? result : thisBinding;
+ }
+ return func.apply(thisBinding, args);
+ }
+ setBindData(bound, bindData);
+ return bound;
+ }
+
+ /**
+ * The base implementation of `_.difference` that accepts a single array
+ * of values to exclude.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {Array} [values] The array of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ */
+ function baseDifference(array, values) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ isLarge = length >= largeArraySize && indexOf === baseIndexOf,
+ result = [];
+
+ if (isLarge) {
+ var cache = createCache(values);
+ if (cache) {
+ indexOf = cacheIndexOf;
+ values = cache;
+ } else {
+ isLarge = false;
+ }
+ }
+ while (++index < length) {
+ var value = array[index];
+ if (indexOf(values, value) < 0) {
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseObject(values);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.flatten` without support for callback
+ * shorthands or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
+ * @param {number} [fromIndex=0] The index to start from.
+ * @returns {Array} Returns a new flattened array.
+ */
+ function baseFlatten(array, isShallow, isStrict, fromIndex) {
+ var index = (fromIndex || 0) - 1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (value && typeof value == 'object' && typeof value.length == 'number'
+ && (isArray(value) || isArguments(value))) {
+ // recursively flatten arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ value = baseFlatten(value, isShallow, isStrict);
+ }
+ var valIndex = -1,
+ valLength = value.length,
+ resIndex = result.length;
+
+ result.length += valLength;
+ while (++valIndex < valLength) {
+ result[resIndex++] = value[valIndex];
+ }
+ } else if (!isStrict) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.isEqual`, without support for `thisArg` binding,
+ * that allows partial "_.where" style comparisons.
+ *
+ * @private
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
+ * @param {Array} [stackA=[]] Tracks traversed `a` objects.
+ * @param {Array} [stackB=[]] Tracks traversed `b` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
+ function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
+ // used to indicate that when comparing objects, `a` has at least the properties of `b`
+ if (callback) {
+ var result = callback(a, b);
+ if (typeof result != 'undefined') {
+ return !!result;
+ }
+ }
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ var type = typeof a,
+ otherType = typeof b;
+
+ // exit early for unlike primitive values
+ if (a === a &&
+ !(a && objectTypes[type]) &&
+ !(b && objectTypes[otherType])) {
+ return false;
+ }
+ // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
+ // http://es5.github.io/#x15.3.4.4
+ if (a == null || b == null) {
+ return a === b;
+ }
+ // compare [[Class]] names
+ var className = toString.call(a),
+ otherClass = toString.call(b);
+
+ if (className == argsClass) {
+ className = objectClass;
+ }
+ if (otherClass == argsClass) {
+ otherClass = objectClass;
+ }
+ if (className != otherClass) {
+ return false;
+ }
+ switch (className) {
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numbers, dates to milliseconds and booleans
+ // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return (a != +a)
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case regexpClass:
+ case stringClass:
+ // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
+ // treat string primitives and their corresponding object instances as equal
+ return a == String(b);
+ }
+ var isArr = className == arrayClass;
+ if (!isArr) {
+ // unwrap any `lodash` wrapped values
+ var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
+ bWrapped = hasOwnProperty.call(b, '__wrapped__');
+
+ if (aWrapped || bWrapped) {
+ return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
+ }
+ // exit for functions and DOM nodes
+ if (className != objectClass) {
+ return false;
+ }
+ // in older versions of Opera, `arguments` objects have `Array` constructors
+ var ctorA = a.constructor,
+ ctorB = b.constructor;
+
+ // non `Object` object instances with different constructors are not equal
+ if (ctorA != ctorB &&
+ !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
+ ('constructor' in a && 'constructor' in b)
+ ) {
+ return false;
+ }
+ }
+ // assume cyclic structures are equal
+ // the algorithm for detecting cyclic structures is adapted from ES 5.1
+ // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
+ var initedStack = !stackA;
+ stackA || (stackA = getArray());
+ stackB || (stackB = getArray());
+
+ var length = stackA.length;
+ while (length--) {
+ if (stackA[length] == a) {
+ return stackB[length] == b;
+ }
+ }
+ var size = 0;
+ result = true;
+
+ // add `a` and `b` to the stack of traversed objects
+ stackA.push(a);
+ stackB.push(b);
+
+ // recursively compare objects and arrays (susceptible to call stack limits)
+ if (isArr) {
+ // compare lengths to determine if a deep comparison is necessary
+ length = a.length;
+ size = b.length;
+ result = size == length;
+
+ if (result || isWhere) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ var index = length,
+ value = b[size];
+
+ if (isWhere) {
+ while (index--) {
+ if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
+ // which, in this case, is more costly
+ forIn(b, function(value, key, b) {
+ if (hasOwnProperty.call(b, key)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
+ }
+ });
+
+ if (result && !isWhere) {
+ // ensure both objects have the same number of properties
+ forIn(a, function(value, key, a) {
+ if (hasOwnProperty.call(a, key)) {
+ // `size` will be `-1` if `a` has more properties than `b`
+ return (result = --size > -1);
+ }
+ });
+ }
+ }
+ stackA.pop();
+ stackB.pop();
+
+ if (initedStack) {
+ releaseArray(stackA);
+ releaseArray(stackB);
+ }
+ return result;
+ }
+
+ /**
+ * The base implementation of `_.merge` without argument juggling or support
+ * for `thisArg` binding.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
+ */
+ function baseMerge(object, source, callback, stackA, stackB) {
+ (isArray(source) ? forEach : forOwn)(source, function(source, key) {
+ var found,
+ isArr,
+ result = source,
+ value = object[key];
+
+ if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
+ // avoid merging previously merged cyclic sources
+ var stackLength = stackA.length;
+ while (stackLength--) {
+ if ((found = stackA[stackLength] == source)) {
+ value = stackB[stackLength];
+ break;
+ }
+ }
+ if (!found) {
+ var isShallow;
+ if (callback) {
+ result = callback(value, source);
+ if ((isShallow = typeof result != 'undefined')) {
+ value = result;
+ }
+ }
+ if (!isShallow) {
+ value = isArr
+ ? (isArray(value) ? value : [])
+ : (isPlainObject(value) ? value : {});
+ }
+ // add `source` and associated `value` to the stack of traversed objects
+ stackA.push(source);
+ stackB.push(value);
+
+ // recursively merge objects and arrays (susceptible to call stack limits)
+ if (!isShallow) {
+ baseMerge(value, source, callback, stackA, stackB);
+ }
+ }
+ }
+ else {
+ if (callback) {
+ result = callback(value, source);
+ if (typeof result == 'undefined') {
+ result = source;
+ }
+ }
+ if (typeof result != 'undefined') {
+ value = result;
+ }
+ }
+ object[key] = value;
+ });
+ }
+
+ /**
+ * The base implementation of `_.random` without argument juggling or support
+ * for returning floating-point numbers.
+ *
+ * @private
+ * @param {number} min The minimum possible value.
+ * @param {number} max The maximum possible value.
+ * @returns {number} Returns a random number.
+ */
+ function baseRandom(min, max) {
+ return min + floor(nativeRandom() * (max - min + 1));
+ }
+
+ /**
+ * The base implementation of `_.uniq` without support for callback shorthands
+ * or `thisArg` binding.
+ *
+ * @private
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function} [callback] The function called per iteration.
+ * @returns {Array} Returns a duplicate-value-free array.
+ */
+ function baseUniq(array, isSorted, callback) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = array ? array.length : 0,
+ result = [];
+
+ var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
+ seen = (callback || isLarge) ? getArray() : result;
+
+ if (isLarge) {
+ var cache = createCache(seen);
+ indexOf = cacheIndexOf;
+ seen = cache;
+ }
+ while (++index < length) {
+ var value = array[index],
+ computed = callback ? callback(value, index, array) : value;
+
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : indexOf(seen, computed) < 0
+ ) {
+ if (callback || isLarge) {
+ seen.push(computed);
+ }
+ result.push(value);
+ }
+ }
+ if (isLarge) {
+ releaseArray(seen.array);
+ releaseObject(seen);
+ } else if (callback) {
+ releaseArray(seen);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a function that aggregates a collection, creating an object composed
+ * of keys generated from the results of running each element of the collection
+ * through a callback. The given `setter` function sets the keys and values
+ * of the composed object.
+ *
+ * @private
+ * @param {Function} setter The setter function.
+ * @returns {Function} Returns the new aggregator function.
+ */
+ function createAggregator(setter) {
+ return function(collection, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ setter(result, value, callback(value, index, collection), collection);
+ }
+ } else {
+ forOwn(collection, function(value, key, collection) {
+ setter(result, value, callback(value, key, collection), collection);
+ });
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, either curries or invokes `func`
+ * with an optional `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to reference.
+ * @param {number} bitmask The bitmask of method flags to compose.
+ * The bitmask may be composed of the following flags:
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry`
+ * 8 - `_.curry` (bound)
+ * 16 - `_.partial`
+ * 32 - `_.partialRight`
+ * @param {Array} [partialArgs] An array of arguments to prepend to those
+ * provided to the new function.
+ * @param {Array} [partialRightArgs] An array of arguments to append to those
+ * provided to the new function.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new function.
+ */
+ function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
+ var isBind = bitmask & 1,
+ isBindKey = bitmask & 2,
+ isCurry = bitmask & 4,
+ isCurryBound = bitmask & 8,
+ isPartial = bitmask & 16,
+ isPartialRight = bitmask & 32;
+
+ if (!isBindKey && !isFunction(func)) {
+ throw new TypeError;
+ }
+ if (isPartial && !partialArgs.length) {
+ bitmask &= ~16;
+ isPartial = partialArgs = false;
+ }
+ if (isPartialRight && !partialRightArgs.length) {
+ bitmask &= ~32;
+ isPartialRight = partialRightArgs = false;
+ }
+ var bindData = func && func.__bindData__;
+ if (bindData && bindData !== true) {
+ // clone `bindData`
+ bindData = slice(bindData);
+ if (bindData[2]) {
+ bindData[2] = slice(bindData[2]);
+ }
+ if (bindData[3]) {
+ bindData[3] = slice(bindData[3]);
+ }
+ // set `thisBinding` is not previously bound
+ if (isBind && !(bindData[1] & 1)) {
+ bindData[4] = thisArg;
+ }
+ // set if previously bound but not currently (subsequent curried functions)
+ if (!isBind && bindData[1] & 1) {
+ bitmask |= 8;
+ }
+ // set curried arity if not yet set
+ if (isCurry && !(bindData[1] & 4)) {
+ bindData[5] = arity;
+ }
+ // append partial left arguments
+ if (isPartial) {
+ push.apply(bindData[2] || (bindData[2] = []), partialArgs);
+ }
+ // append partial right arguments
+ if (isPartialRight) {
+ unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
+ }
+ // merge flags
+ bindData[1] |= bitmask;
+ return createWrapper.apply(null, bindData);
+ }
+ // fast path for `_.bind`
+ var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
+ return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
+ }
+
+ /**
+ * Used by `escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} match The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+ * customized, this method returns the custom method, otherwise it returns
+ * the `baseIndexOf` function.
+ *
+ * @private
+ * @returns {Function} Returns the "indexOf" function.
+ */
+ function getIndexOf() {
+ var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a native function.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
+ */
+ function isNative(value) {
+ return typeof value == 'function' && reNative.test(value);
+ }
+
+ /**
+ * Sets `this` binding data on a given function.
+ *
+ * @private
+ * @param {Function} func The function to set data on.
+ * @param {Array} value The data array to set.
+ */
+ var setBindData = !defineProperty ? noop : function(func, value) {
+ descriptor.value = value;
+ defineProperty(func, '__bindData__', descriptor);
+ };
+
+ /**
+ * A fallback implementation of `isPlainObject` which checks if a given value
+ * is an object created by the `Object` constructor, assuming objects created
+ * by the `Object` constructor have no inherited enumerable properties and that
+ * there are no `Object.prototype` extensions.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ */
+ function shimIsPlainObject(value) {
+ var ctor,
+ result;
+
+ // avoid non Object objects, `arguments` objects, and DOM elements
+ if (!(value && toString.call(value) == objectClass) ||
+ (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
+ return false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ forIn(value, function(value, key) {
+ result = key;
+ });
+ return typeof result == 'undefined' || hasOwnProperty.call(value, result);
+ }
+
+ /**
+ * Used by `unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} match The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
+ function unescapeHtmlChar(match) {
+ return htmlUnescapes[match];
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ function isArguments(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == argsClass || false;
+ }
+
+ /**
+ * Checks if `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ return value && typeof value == 'object' && typeof value.length == 'number' &&
+ toString.call(value) == arrayClass || false;
+ };
+
+ /**
+ * A fallback implementation of `Object.keys` which produces an array of the
+ * given object's own enumerable property names.
+ *
+ * @private
+ * @type Function
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ */
+ var shimKeys = function(object) {
+ var index, iterable = object, result = [];
+ if (!iterable) return result;
+ if (!(objectTypes[typeof object])) return result;
+ for (index in iterable) {
+ if (hasOwnProperty.call(iterable, index)) {
+ result.push(index);
+ }
+ }
+ return result
+ };
+
+ /**
+ * Creates an array composed of the own enumerable property names of an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ if (!isObject(object)) {
+ return [];
+ }
+ return nativeKeys(object);
+ };
+
+ /**
+ * Used to convert characters to HTML entities:
+ *
+ * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+ * don't require escaping in HTML and have no special meaning unless they're part
+ * of a tag or an unquoted attribute value.
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+ */
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+
+ /** Used to convert HTML entities to characters */
+ var htmlUnescapes = invert(htmlEscapes);
+
+ /** Used to match HTML entities and HTML characters */
+ var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
+ reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object. Subsequent sources will overwrite property assignments of previous
+ * sources. If a callback is provided it will be executed to produce the
+ * assigned values. The callback is bound to `thisArg` and invoked with two
+ * arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @alias extend
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize assigning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
+ * // => { 'name': 'fred', 'employer': 'slate' }
+ *
+ * var defaults = _.partialRight(_.assign, function(a, b) {
+ * return typeof a == 'undefined' ? b : a;
+ * });
+ *
+ * var object = { 'name': 'barney' };
+ * defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var assign = function(object, source, guard) {
+ var index, iterable = object, result = iterable;
+ if (!iterable) return result;
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = typeof guard == 'number' ? 2 : args.length;
+ if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
+ var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
+ } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
+ callback = args[--argsLength];
+ }
+ while (++argsIndex < argsLength) {
+ iterable = args[argsIndex];
+ if (iterable && objectTypes[typeof iterable]) {
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
+ }
+ }
+ }
+ return result
+ };
+
+ /**
+ * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
+ * be cloned, otherwise they will be assigned by reference. If a callback
+ * is provided it will be executed to produce the cloned values. If the
+ * callback returns `undefined` cloning will be handled by the method instead.
+ * The callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to clone.
+ * @param {boolean} [isDeep=false] Specify a deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var shallow = _.clone(characters);
+ * shallow[0] === characters[0];
+ * // => true
+ *
+ * var deep = _.clone(characters, true);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * _.mixin({
+ * 'clone': _.partialRight(_.clone, function(value) {
+ * return _.isElement(value) ? value.cloneNode(false) : undefined;
+ * })
+ * });
+ *
+ * var clone = _.clone(document.body);
+ * clone.childNodes.length;
+ * // => 0
+ */
+ function clone(value, isDeep, callback, thisArg) {
+ // allows working with "Collections" methods without using their `index`
+ // and `collection` arguments for `isDeep` and `callback`
+ if (typeof isDeep != 'boolean' && isDeep != null) {
+ thisArg = callback;
+ callback = isDeep;
+ isDeep = false;
+ }
+ return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates a deep clone of `value`. If a callback is provided it will be
+ * executed to produce the cloned values. If the callback returns `undefined`
+ * cloning will be handled by the method instead. The callback is bound to
+ * `thisArg` and invoked with one argument; (value).
+ *
+ * Note: This method is loosely based on the structured clone algorithm. Functions
+ * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
+ * objects created by constructors other than `Object` are cloned to plain `Object` objects.
+ * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to deep clone.
+ * @param {Function} [callback] The function to customize cloning values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the deep cloned value.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * var deep = _.cloneDeep(characters);
+ * deep[0] === characters[0];
+ * // => false
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'node': element
+ * };
+ *
+ * var clone = _.cloneDeep(view, function(value) {
+ * return _.isElement(value) ? value.cloneNode(true) : undefined;
+ * });
+ *
+ * clone.node == view.node;
+ * // => false
+ */
+ function cloneDeep(value, callback, thisArg) {
+ return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
+ }
+
+ /**
+ * Creates an object that inherits from the given `prototype` object. If a
+ * `properties` object is provided its own enumerable properties are assigned
+ * to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties ? assign(result, properties) : result;
+ }
+
+ /**
+ * Assigns own enumerable properties of source object(s) to the destination
+ * object for all destination properties that resolve to `undefined`. Once a
+ * property is set, additional defaults of the same property will be ignored.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param- {Object} [guard] Allows working with `_.reduce` without using its
+ * `key` and `object` arguments as sources.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var object = { 'name': 'barney' };
+ * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
+ * // => { 'name': 'barney', 'employer': 'slate' }
+ */
+ var defaults = function(object, source, guard) {
+ var index, iterable = object, result = iterable;
+ if (!iterable) return result;
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = typeof guard == 'number' ? 2 : args.length;
+ while (++argsIndex < argsLength) {
+ iterable = args[argsIndex];
+ if (iterable && objectTypes[typeof iterable]) {
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ if (typeof result[index] == 'undefined') result[index] = iterable[index];
+ }
+ }
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.findIndex` except that it returns the key of the
+ * first element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': false },
+ * 'fred': { 'age': 40, 'blocked': true },
+ * 'pebbles': { 'age': 1, 'blocked': false }
+ * };
+ *
+ * _.findKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => 'barney' (property order is not guaranteed across environments)
+ *
+ * // using "_.where" callback shorthand
+ * _.findKey(characters, { 'age': 1 });
+ * // => 'pebbles'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findKey(characters, 'blocked');
+ * // => 'fred'
+ */
+ function findKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * This method is like `_.findKey` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to search.
+ * @param {Function|Object|string} [callback=identity] The function called per
+ * iteration. If a property name or object is provided it will be used to
+ * create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {string|undefined} Returns the key of the found element, else `undefined`.
+ * @example
+ *
+ * var characters = {
+ * 'barney': { 'age': 36, 'blocked': true },
+ * 'fred': { 'age': 40, 'blocked': false },
+ * 'pebbles': { 'age': 1, 'blocked': true }
+ * };
+ *
+ * _.findLastKey(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => returns `pebbles`, assuming `_.findKey` returns `barney`
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastKey(characters, { 'age': 40 });
+ * // => 'fred'
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastKey(characters, 'blocked');
+ * // => 'pebbles'
+ */
+ function findLastKey(object, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forOwnRight(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over own and inherited enumerable properties of an object,
+ * executing the callback for each property. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, key, object). Callbacks may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forIn(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
+ */
+ var forIn = function(collection, callback, thisArg) {
+ var index, iterable = collection, result = iterable;
+ if (!iterable) return result;
+ if (!objectTypes[typeof iterable]) return result;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ for (index in iterable) {
+ if (callback(iterable[index], index, collection) === false) return result;
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.forIn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * Shape.prototype.move = function(x, y) {
+ * this.x += x;
+ * this.y += y;
+ * };
+ *
+ * _.forInRight(new Shape, function(value, key) {
+ * console.log(key);
+ * });
+ * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
+ */
+ function forInRight(object, callback, thisArg) {
+ var pairs = [];
+
+ forIn(object, function(value, key) {
+ pairs.push(key, value);
+ });
+
+ var length = pairs.length;
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(pairs[length--], pairs[length], object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Iterates over own enumerable properties of an object, executing the callback
+ * for each property. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, key, object). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
+ */
+ var forOwn = function(collection, callback, thisArg) {
+ var index, iterable = collection, result = iterable;
+ if (!iterable) return result;
+ if (!objectTypes[typeof iterable]) return result;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ var ownIndex = -1,
+ ownProps = objectTypes[typeof iterable] && keys(iterable),
+ length = ownProps ? ownProps.length : 0;
+
+ while (++ownIndex < length) {
+ index = ownProps[ownIndex];
+ if (callback(iterable[index], index, collection) === false) return result;
+ }
+ return result
+ };
+
+ /**
+ * This method is like `_.forOwn` except that it iterates over elements
+ * of a `collection` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * console.log(key);
+ * });
+ * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
+ */
+ function forOwnRight(object, callback, thisArg) {
+ var props = keys(object),
+ length = props.length;
+
+ callback = baseCreateCallback(callback, thisArg, 3);
+ while (length--) {
+ var key = props[length];
+ if (callback(object[key], key, object) === false) {
+ break;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Creates a sorted array of property names of all enumerable properties,
+ * own and inherited, of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ function functions(object) {
+ var result = [];
+ forIn(object, function(value, key) {
+ if (isFunction(value)) {
+ result.push(key);
+ }
+ });
+ return result.sort();
+ }
+
+ /**
+ * Checks if the specified property name exists as a direct property of `object`,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to check.
+ * @returns {boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, key) {
+ return object ? hasOwnProperty.call(object, key) : false;
+ }
+
+ /**
+ * Creates an object composed of the inverted keys and values of the given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the created inverted object.
+ * @example
+ *
+ * _.invert({ 'first': 'fred', 'second': 'barney' });
+ * // => { 'fred': 'first', 'barney': 'second' }
+ */
+ function invert(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = {};
+
+ while (++index < length) {
+ var key = props[index];
+ result[object[key]] = key;
+ }
+ return result;
+ }
+
+ /**
+ * Checks if `value` is a boolean value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false ||
+ value && typeof value == 'object' && toString.call(value) == boolClass || false;
+ }
+
+ /**
+ * Checks if `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return value && typeof value == 'object' && toString.call(value) == dateClass || false;
+ }
+
+ /**
+ * Checks if `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return value && value.nodeType === 1 || false;
+ }
+
+ /**
+ * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
+ * length of `0` and objects with no own enumerable properties are considered
+ * "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|string} value The value to inspect.
+ * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ *
+ * _.isEmpty('');
+ * // => true
+ */
+ function isEmpty(value) {
+ var result = true;
+ if (!value) {
+ return result;
+ }
+ var className = toString.call(value),
+ length = value.length;
+
+ if ((className == arrayClass || className == stringClass || className == argsClass ) ||
+ (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
+ return !length;
+ }
+ forOwn(value, function() {
+ return (result = false);
+ });
+ return result;
+ }
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other. If a callback is provided it will be executed
+ * to compare values. If the callback returns `undefined` comparisons will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (a, b).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} a The value to compare.
+ * @param {*} b The other value to compare.
+ * @param {Function} [callback] The function to customize comparing values.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var copy = { 'name': 'fred' };
+ *
+ * object == copy;
+ * // => false
+ *
+ * _.isEqual(object, copy);
+ * // => true
+ *
+ * var words = ['hello', 'goodbye'];
+ * var otherWords = ['hi', 'goodbye'];
+ *
+ * _.isEqual(words, otherWords, function(a, b) {
+ * var reGreet = /^(?:hello|hi)$/i,
+ * aGreet = _.isString(a) && reGreet.test(a),
+ * bGreet = _.isString(b) && reGreet.test(b);
+ *
+ * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
+ * });
+ * // => true
+ */
+ function isEqual(a, b, callback, thisArg) {
+ return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
+ }
+
+ /**
+ * Checks if `value` is, or can be coerced to, a finite number.
+ *
+ * Note: This is not the same as native `isFinite` which will return true for
+ * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => true
+ *
+ * _.isFinite(true);
+ * // => false
+ *
+ * _.isFinite('');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
+ }
+
+ /**
+ * Checks if `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ */
+ function isFunction(value) {
+ return typeof value == 'function';
+ }
+
+ /**
+ * Checks if `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.io/#x8
+ // and avoid a V8 bug
+ // http://code.google.com/p/v8/issues/detail?id=2291
+ return !!(value && objectTypes[typeof value]);
+ }
+
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * Note: This is not the same as native `isNaN` which will return `true` for
+ * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return isNumber(value) && value != +value;
+ }
+
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if `value` is a number.
+ *
+ * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5);
+ * // => true
+ */
+ function isNumber(value) {
+ return typeof value == 'number' ||
+ value && typeof value == 'object' && toString.call(value) == numberClass || false;
+ }
+
+ /**
+ * Checks if `value` is an object created by the `Object` constructor.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * _.isPlainObject(new Shape);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ */
+ var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
+ if (!(value && toString.call(value) == objectClass)) {
+ return false;
+ }
+ var valueOf = value.valueOf,
+ objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);
+
+ return objProto
+ ? (value == objProto || getPrototypeOf(value) == objProto)
+ : shimIsPlainObject(value);
+ };
+
+ /**
+ * Checks if `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/fred/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return value && typeof value == 'object' && toString.call(value) == regexpClass || false;
+ }
+
+ /**
+ * Checks if `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('fred');
+ * // => true
+ */
+ function isString(value) {
+ return typeof value == 'string' ||
+ value && typeof value == 'object' && toString.call(value) == stringClass || false;
+ }
+
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return typeof value == 'undefined';
+ }
+
+ /**
+ * Creates an object with the same keys as `object` and values generated by
+ * running each own enumerable property of `object` through the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new object with values of the results of each `callback` execution.
+ * @example
+ *
+ * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ *
+ * var characters = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.mapValues(characters, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 }
+ */
+ function mapValues(object, callback, thisArg) {
+ var result = {};
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ forOwn(object, function(value, key, object) {
+ result[key] = callback(value, key, object);
+ });
+ return result;
+ }
+
+ /**
+ * Recursively merges own enumerable properties of the source object(s), that
+ * don't resolve to `undefined` into the destination object. Subsequent sources
+ * will overwrite property assignments of previous sources. If a callback is
+ * provided it will be executed to produce the merged values of the destination
+ * and source properties. If the callback returns `undefined` merging will
+ * be handled by the method instead. The callback is bound to `thisArg` and
+ * invoked with two arguments; (objectValue, sourceValue).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {...Object} [source] The source objects.
+ * @param {Function} [callback] The function to customize merging properties.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * var names = {
+ * 'characters': [
+ * { 'name': 'barney' },
+ * { 'name': 'fred' }
+ * ]
+ * };
+ *
+ * var ages = {
+ * 'characters': [
+ * { 'age': 36 },
+ * { 'age': 40 }
+ * ]
+ * };
+ *
+ * _.merge(names, ages);
+ * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
+ *
+ * var food = {
+ * 'fruits': ['apple'],
+ * 'vegetables': ['beet']
+ * };
+ *
+ * var otherFood = {
+ * 'fruits': ['banana'],
+ * 'vegetables': ['carrot']
+ * };
+ *
+ * _.merge(food, otherFood, function(a, b) {
+ * return _.isArray(a) ? a.concat(b) : undefined;
+ * });
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
+ */
+ function merge(object) {
+ var args = arguments,
+ length = 2;
+
+ if (!isObject(object)) {
+ return object;
+ }
+ // allows working with `_.reduce` and `_.reduceRight` without using
+ // their `index` and `collection` arguments
+ if (typeof args[2] != 'number') {
+ length = args.length;
+ }
+ if (length > 3 && typeof args[length - 2] == 'function') {
+ var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
+ } else if (length > 2 && typeof args[length - 1] == 'function') {
+ callback = args[--length];
+ }
+ var sources = slice(arguments, 1, length),
+ index = -1,
+ stackA = getArray(),
+ stackB = getArray();
+
+ while (++index < length) {
+ baseMerge(object, sources[index], callback, stackA, stackB);
+ }
+ releaseArray(stackA);
+ releaseArray(stackB);
+ return object;
+ }
+
+ /**
+ * Creates a shallow clone of `object` excluding the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` omitting the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The properties to omit or the
+ * function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object without the omitted properties.
+ * @example
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
+ * // => { 'name': 'fred' }
+ *
+ * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
+ * return typeof value == 'number';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function omit(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var props = [];
+ forIn(object, function(value, key) {
+ props.push(key);
+ });
+ props = baseDifference(props, baseFlatten(arguments, true, false, 1));
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+ result[key] = object[key];
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (!callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates a two dimensional array of an object's key-value pairs,
+ * i.e. `[[key1, value1], [key2, value2]]`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns new array of key-value pairs.
+ * @example
+ *
+ * _.pairs({ 'barney': 36, 'fred': 40 });
+ * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
+ */
+ function pairs(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ var key = props[index];
+ result[index] = [key, object[key]];
+ }
+ return result;
+ }
+
+ /**
+ * Creates a shallow clone of `object` composed of the specified properties.
+ * Property names may be specified as individual arguments or as arrays of
+ * property names. If a callback is provided it will be executed for each
+ * property of `object` picking the properties the callback returns truey
+ * for. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The source object.
+ * @param {Function|...string|string[]} [callback] The function called per
+ * iteration or property names to pick, specified as individual property
+ * names or arrays of property names.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
+ * // => { 'name': 'fred' }
+ *
+ * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
+ * return key.charAt(0) != '_';
+ * });
+ * // => { 'name': 'fred' }
+ */
+ function pick(object, callback, thisArg) {
+ var result = {};
+ if (typeof callback != 'function') {
+ var index = -1,
+ props = baseFlatten(arguments, true, false, 1),
+ length = isObject(object) ? props.length : 0;
+
+ while (++index < length) {
+ var key = props[index];
+ if (key in object) {
+ result[key] = object[key];
+ }
+ }
+ } else {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forIn(object, function(value, key, object) {
+ if (callback(value, key, object)) {
+ result[key] = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * An alternative to `_.reduce` this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own
+ * enumerable properties through a callback, with each callback execution
+ * potentially mutating the `accumulator` object. The callback is bound to
+ * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
+ * Callbacks may exit iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object} object The object to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
+ * num *= num;
+ * if (num % 2) {
+ * return result.push(num) < 3;
+ * }
+ * });
+ * // => [1, 9, 25]
+ *
+ * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * });
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function transform(object, callback, accumulator, thisArg) {
+ var isArr = isArray(object);
+ if (accumulator == null) {
+ if (isArr) {
+ accumulator = [];
+ } else {
+ var ctor = object && object.constructor,
+ proto = ctor && ctor.prototype;
+
+ accumulator = baseCreate(proto);
+ }
+ }
+ if (callback) {
+ callback = lodash.createCallback(callback, thisArg, 4);
+ (isArr ? forEach : forOwn)(object, function(value, index, object) {
+ return callback(accumulator, value, index, object);
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * Creates an array composed of the own enumerable property values of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns an array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3] (property order is not guaranteed across environments)
+ */
+ function values(object) {
+ var index = -1,
+ props = keys(object),
+ length = props.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = object[props[index]];
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of elements from the specified indexes, or keys, of the
+ * `collection`. Indexes may be specified as individual arguments or as arrays
+ * of indexes.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
+ * to retrieve, specified as individual indexes or arrays of indexes.
+ * @returns {Array} Returns a new array of elements corresponding to the
+ * provided indexes.
+ * @example
+ *
+ * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
+ * // => ['a', 'c', 'e']
+ *
+ * _.at(['fred', 'barney', 'pebbles'], 0, 2);
+ * // => ['fred', 'pebbles']
+ */
+ function at(collection) {
+ var args = arguments,
+ index = -1,
+ props = baseFlatten(args, true, false, 1),
+ length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
+ result = Array(length);
+
+ while(++index < length) {
+ result[index] = collection[props[index]];
+ }
+ return result;
+ }
+
+ /**
+ * Checks if a given value is present in a collection using strict equality
+ * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
+ * offset from the end of the collection.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {*} target The value to check for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 1);
+ * // => true
+ *
+ * _.contains([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
+ * // => true
+ *
+ * _.contains('pebbles', 'eb');
+ * // => true
+ */
+ function contains(collection, target, fromIndex) {
+ var index = -1,
+ indexOf = getIndexOf(),
+ length = collection ? collection.length : 0,
+ result = false;
+
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
+ if (isArray(collection)) {
+ result = indexOf(collection, target, fromIndex) > -1;
+ } else if (typeof length == 'number') {
+ result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
+ } else {
+ forOwn(collection, function(value) {
+ if (++index >= fromIndex) {
+ return !(result = value === target);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` through the callback. The corresponding value
+ * of each key is the number of times the key was returned by the callback.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': 1, '6': 2 }
+ *
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
+ var countBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
+ });
+
+ /**
+ * Checks if the given callback returns truey value for **all** elements of
+ * a collection. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if all elements passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes']);
+ * // => false
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.every(characters, 'age');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.every(characters, { 'age': 36 });
+ * // => false
+ */
+ function every(collection, callback, thisArg) {
+ var result = true;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if (!(result = !!callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ return (result = !!callback(value, index, collection));
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning an array of all elements
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that passed the callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.filter(characters, 'blocked');
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ *
+ * // using "_.where" callback shorthand
+ * _.filter(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ */
+ function filter(collection, callback, thisArg) {
+ var result = [];
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, returning the first element that
+ * the callback returns truey for. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias detect, findWhere
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.find(characters, function(chr) {
+ * return chr.age < 40;
+ * });
+ * // => { 'name': 'barney', 'age': 36, 'blocked': false }
+ *
+ * // using "_.where" callback shorthand
+ * _.find(characters, { 'age': 1 });
+ * // => { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.find(characters, 'blocked');
+ * // => { 'name': 'fred', 'age': 40, 'blocked': true }
+ */
+ function find(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ var value = collection[index];
+ if (callback(value, index, collection)) {
+ return value;
+ }
+ }
+ } else {
+ var result;
+ forOwn(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+ }
+
+ /**
+ * This method is like `_.find` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the found element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(num) {
+ * return num % 2 == 1;
+ * });
+ * // => 3
+ */
+ function findLast(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ forEachRight(collection, function(value, index, collection) {
+ if (callback(value, index, collection)) {
+ result = value;
+ return false;
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Iterates over elements of a collection, executing the callback for each
+ * element. The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection). Callbacks may exit iteration early by
+ * explicitly returning `false`.
+ *
+ * Note: As with other "Collections" methods, objects with a `length` property
+ * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+ * may be used for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
+ * // => logs each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
+ * // => logs each number and returns the object (property order is not guaranteed across environments)
+ */
+ function forEach(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if (callback(collection[index], index, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, callback);
+ }
+ return collection;
+ }
+
+ /**
+ * This method is like `_.forEach` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias eachRight
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array|Object|string} Returns `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
+ * // => logs each number from right to left and returns '3,2,1'
+ */
+ function forEachRight(collection, callback, thisArg) {
+ var length = collection ? collection.length : 0;
+ callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ while (length--) {
+ if (callback(collection[length], length, collection) === false) {
+ break;
+ }
+ }
+ } else {
+ var props = keys(collection);
+ length = props.length;
+ forOwn(collection, function(value, key, collection) {
+ key = props ? props[--length] : --length;
+ return callback(collection[key], key, collection);
+ });
+ }
+ return collection;
+ }
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of a collection through the callback. The corresponding value
+ * of each key is an array of the elements responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
+ *
+ * // using "_.pluck" callback shorthand
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ var groupBy = createAggregator(function(result, value, key) {
+ (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
+ });
+
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of the collection through the given callback. The corresponding
+ * value of each key is the last element responsible for generating the key.
+ * The callback is bound to `thisArg` and invoked with three arguments;
+ * (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var keys = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.indexBy(keys, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ */
+ var indexBy = createAggregator(function(result, value, key) {
+ result[key] = value;
+ });
+
+ /**
+ * Invokes the method named by `methodName` on each element in the `collection`
+ * returning an array of the results of each invoked method. Additional arguments
+ * will be provided to each invoked method. If `methodName` is a function it
+ * will be invoked for, and `this` bound to, each element in the `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|string} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [arg] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of the results of each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ function invoke(collection, methodName) {
+ var args = slice(arguments, 2),
+ index = -1,
+ isFunc = typeof methodName == 'function',
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
+ });
+ return result;
+ }
+
+ /**
+ * Creates an array of values by running each element in the collection
+ * through the callback. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of the results of each `callback` execution.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (property order is not guaranteed across environments)
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ function map(collection, callback, thisArg) {
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ if (typeof length == 'number') {
+ var result = Array(length);
+ while (++index < length) {
+ result[index] = callback(collection[index], index, collection);
+ }
+ } else {
+ result = [];
+ forOwn(collection, function(value, key, collection) {
+ result[++index] = callback(value, key, collection);
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the maximum value of a collection. If the collection is empty or
+ * falsey `-Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the maximum value.
+ * @example
+ *
+ * _.max([4, 2, 8, 6]);
+ * // => 8
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.max(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'fred', 'age': 40 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.max(characters, 'age');
+ * // => { 'name': 'fred', 'age': 40 };
+ */
+ function max(collection, callback, thisArg) {
+ var computed = -Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value > result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ forEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current > computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of a collection. If the collection is empty or
+ * falsey `Infinity` is returned. If a callback is provided it will be executed
+ * for each value in the collection to generate the criterion by which the value
+ * is ranked. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the minimum value.
+ * @example
+ *
+ * _.min([4, 2, 8, 6]);
+ * // => 2
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.min(characters, function(chr) { return chr.age; });
+ * // => { 'name': 'barney', 'age': 36 };
+ *
+ * // using "_.pluck" callback shorthand
+ * _.min(characters, 'age');
+ * // => { 'name': 'barney', 'age': 36 };
+ */
+ function min(collection, callback, thisArg) {
+ var computed = Infinity,
+ result = computed;
+
+ // allows working with functions like `_.map` without using
+ // their `index` argument as a callback
+ if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
+ callback = null;
+ }
+ if (callback == null && isArray(collection)) {
+ var index = -1,
+ length = collection.length;
+
+ while (++index < length) {
+ var value = collection[index];
+ if (value < result) {
+ result = value;
+ }
+ }
+ } else {
+ callback = (callback == null && isString(collection))
+ ? charAtCallback
+ : lodash.createCallback(callback, thisArg, 3);
+
+ forEach(collection, function(value, index, collection) {
+ var current = callback(value, index, collection);
+ if (current < computed) {
+ computed = current;
+ result = value;
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the value of a specified property from all elements in the collection.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {string} property The name of the property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * _.pluck(characters, 'name');
+ * // => ['barney', 'fred']
+ */
+ var pluck = map;
+
+ /**
+ * Reduces a collection to a value which is the accumulated result of running
+ * each element in the collection through the callback, where each successive
+ * callback execution consumes the return value of the previous execution. If
+ * `accumulator` is not provided the first element of the collection will be
+ * used as the initial `accumulator` value. The callback is bound to `thisArg`
+ * and invoked with four arguments; (accumulator, value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(sum, num) {
+ * return sum + num;
+ * });
+ * // => 6
+ *
+ * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
+ * result[key] = num * 3;
+ * return result;
+ * }, {});
+ * // => { 'a': 3, 'b': 6, 'c': 9 }
+ */
+ function reduce(collection, callback, accumulator, thisArg) {
+ if (!collection) return accumulator;
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+
+ var index = -1,
+ length = collection.length;
+
+ if (typeof length == 'number') {
+ if (noaccum) {
+ accumulator = collection[++index];
+ }
+ while (++index < length) {
+ accumulator = callback(accumulator, collection[index], index, collection);
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection)
+ });
+ }
+ return accumulator;
+ }
+
+ /**
+ * This method is like `_.reduce` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {*} [accumulator] Initial value of the accumulator.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ var noaccum = arguments.length < 3;
+ callback = lodash.createCallback(callback, thisArg, 4);
+ forEachRight(collection, function(value, index, collection) {
+ accumulator = noaccum
+ ? (noaccum = false, value)
+ : callback(accumulator, value, index, collection);
+ });
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter` this method returns the elements of a
+ * collection that the callback does **not** return truey for.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of elements that failed the callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.reject(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
+ *
+ * // using "_.where" callback shorthand
+ * _.reject(characters, { 'age': 36 });
+ * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
+ */
+ function reject(collection, callback, thisArg) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ return filter(collection, function(value, index, collection) {
+ return !callback(value, index, collection);
+ });
+ }
+
+ /**
+ * Retrieves a random element or `n` random elements from a collection.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to sample.
+ * @param {number} [n] The number of elements to sample.
+ * @param- {Object} [guard] Allows working with functions like `_.map`
+ * without using their `index` arguments as `n`.
+ * @returns {Array} Returns the random sample(s) of `collection`.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ *
+ * _.sample([1, 2, 3, 4], 2);
+ * // => [3, 1]
+ */
+ function sample(collection, n, guard) {
+ if (collection && typeof collection.length != 'number') {
+ collection = values(collection);
+ }
+ if (n == null || guard) {
+ return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
+ }
+ var result = shuffle(collection);
+ result.length = nativeMin(nativeMax(0, n), result.length);
+ return result;
+ }
+
+ /**
+ * Creates an array of shuffled values, using a version of the Fisher-Yates
+ * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to shuffle.
+ * @returns {Array} Returns a new shuffled collection.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(collection) {
+ var index = -1,
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ forEach(collection, function(value) {
+ var rand = baseRandom(0, ++index);
+ result[index] = result[rand];
+ result[rand] = value;
+ });
+ return result;
+ }
+
+ /**
+ * Gets the size of the `collection` by returning `collection.length` for arrays
+ * and array-like objects or the number of own enumerable properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @returns {number} Returns `collection.length` or number of own enumerable properties.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
+ function size(collection) {
+ var length = collection ? collection.length : 0;
+ return typeof length == 'number' ? length : keys(collection).length;
+ }
+
+ /**
+ * Checks if the callback returns a truey value for **any** element of a
+ * collection. The function returns as soon as it finds a passing value and
+ * does not iterate over the entire collection. The callback is bound to
+ * `thisArg` and invoked with three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {boolean} Returns `true` if any element passed the callback check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.some(characters, 'blocked');
+ * // => true
+ *
+ * // using "_.where" callback shorthand
+ * _.some(characters, { 'age': 1 });
+ * // => false
+ */
+ function some(collection, callback, thisArg) {
+ var result;
+ callback = lodash.createCallback(callback, thisArg, 3);
+
+ var index = -1,
+ length = collection ? collection.length : 0;
+
+ if (typeof length == 'number') {
+ while (++index < length) {
+ if ((result = callback(collection[index], index, collection))) {
+ break;
+ }
+ }
+ } else {
+ forOwn(collection, function(value, index, collection) {
+ return !(result = callback(value, index, collection));
+ });
+ }
+ return !!result;
+ }
+
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection through the callback. This method
+ * performs a stable sort, that is, it will preserve the original sort order
+ * of equal elements. The callback is bound to `thisArg` and invoked with
+ * three arguments; (value, index|key, collection).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an array of property names is provided for `callback` the collection
+ * will be sorted by each property value.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Array|Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of sorted elements.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 26 },
+ * { 'name': 'fred', 'age': 30 }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.map(_.sortBy(characters, 'age'), _.values);
+ * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
+ *
+ * // sorting by multiple properties
+ * _.map(_.sortBy(characters, ['name', 'age']), _.values);
+ * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
+ */
+ function sortBy(collection, callback, thisArg) {
+ var index = -1,
+ isArr = isArray(callback),
+ length = collection ? collection.length : 0,
+ result = Array(typeof length == 'number' ? length : 0);
+
+ if (!isArr) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ forEach(collection, function(value, key, collection) {
+ var object = result[++index] = getObject();
+ if (isArr) {
+ object.criteria = map(callback, function(key) { return value[key]; });
+ } else {
+ (object.criteria = getArray())[0] = callback(value, key, collection);
+ }
+ object.index = index;
+ object.value = value;
+ });
+
+ length = result.length;
+ result.sort(compareAscending);
+ while (length--) {
+ var object = result[length];
+ result[length] = object.value;
+ if (!isArr) {
+ releaseArray(object.criteria);
+ }
+ releaseObject(object);
+ }
+ return result;
+ }
+
+ /**
+ * Converts the `collection` to an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ if (collection && typeof collection.length == 'number') {
+ return slice(collection);
+ }
+ return values(collection);
+ }
+
+ /**
+ * Performs a deep comparison of each element in a `collection` to the given
+ * `properties` object, returning an array of all elements that have equivalent
+ * property values.
+ *
+ * @static
+ * @memberOf _
+ * @type Function
+ * @category Collections
+ * @param {Array|Object|string} collection The collection to iterate over.
+ * @param {Object} props The object of property values to filter by.
+ * @returns {Array} Returns a new array of elements that have the given properties.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * _.where(characters, { 'age': 36 });
+ * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
+ *
+ * _.where(characters, { 'pets': ['dino'] });
+ * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
+ */
+ var where = filter;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+ if (value) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array excluding all values of the provided arrays using strict
+ * equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {...Array} [values] The arrays of values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ return baseDifference(array, baseFlatten(arguments, true, true, 1));
+ }
+
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element that passes the callback check, instead of the element itself.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': false },
+ * { 'name': 'fred', 'age': 40, 'blocked': true },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': false }
+ * ];
+ *
+ * _.findIndex(characters, function(chr) {
+ * return chr.age < 20;
+ * });
+ * // => 2
+ *
+ * // using "_.where" callback shorthand
+ * _.findIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findIndex(characters, 'blocked');
+ * // => 1
+ */
+ function findIndex(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ if (callback(array[index], index, array)) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of a `collection` from right to left.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36, 'blocked': true },
+ * { 'name': 'fred', 'age': 40, 'blocked': false },
+ * { 'name': 'pebbles', 'age': 1, 'blocked': true }
+ * ];
+ *
+ * _.findLastIndex(characters, function(chr) {
+ * return chr.age > 30;
+ * });
+ * // => 1
+ *
+ * // using "_.where" callback shorthand
+ * _.findLastIndex(characters, { 'age': 36 });
+ * // => 0
+ *
+ * // using "_.pluck" callback shorthand
+ * _.findLastIndex(characters, 'blocked');
+ * // => 2
+ */
+ function findLastIndex(array, callback, thisArg) {
+ var length = array ? array.length : 0;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (length--) {
+ if (callback(array[length], length, array)) {
+ return length;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets the first element or first `n` elements of an array. If a callback
+ * is provided elements at the beginning of the array are returned as long
+ * as the callback returns truey. The callback is bound to `thisArg` and
+ * invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the first element(s) of `array`.
+ * @example
+ *
+ * _.first([1, 2, 3]);
+ * // => 1
+ *
+ * _.first([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.first([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [1, 2]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.first(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function first(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = -1;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[0] : undefined;
+ }
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, n), length));
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `isShallow`
+ * is truey, the array will only be flattened a single level. If a callback
+ * is provided each element of the array is passed through the callback before
+ * flattening. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to flatten.
+ * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
+ * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.flatten(characters, 'pets');
+ * // => ['hoppy', 'baby puss', 'dino']
+ */
+ function flatten(array, isShallow, callback, thisArg) {
+ // juggle arguments
+ if (typeof isShallow != 'boolean' && isShallow != null) {
+ thisArg = callback;
+ callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
+ isShallow = false;
+ }
+ if (callback != null) {
+ array = map(array, callback, thisArg);
+ }
+ return baseFlatten(array, isShallow);
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the array is already sorted
+ * providing `true` for `fromIndex` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+ * to perform a binary search on a sorted array.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ if (typeof fromIndex == 'number') {
+ var length = array ? array.length : 0;
+ fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
+ } else if (fromIndex) {
+ var index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ return baseIndexOf(array, value, fromIndex);
+ }
+
+ /**
+ * Gets all but the last element or last `n` elements of an array. If a
+ * callback is provided elements at the end of the array are excluded from
+ * the result as long as the callback returns truey. The callback is bound
+ * to `thisArg` and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.initial([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.initial([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [1]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.initial(characters, 'blocked');
+ * // => [{ 'name': 'barney', 'blocked': false, 'employer': 'slate' }]
+ *
+ * // using "_.where" callback shorthand
+ * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
+ * // => ['barney', 'fred']
+ */
+ function initial(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : callback || n;
+ }
+ return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
+ }
+
+ /**
+ * Creates an array of unique values present in all provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of shared values.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection() {
+ var args = [],
+ argsIndex = -1,
+ argsLength = arguments.length,
+ caches = getArray(),
+ indexOf = getIndexOf(),
+ trustIndexOf = indexOf === baseIndexOf,
+ seen = getArray();
+
+ while (++argsIndex < argsLength) {
+ var value = arguments[argsIndex];
+ if (isArray(value) || isArguments(value)) {
+ args.push(value);
+ caches.push(trustIndexOf && value.length >= largeArraySize &&
+ createCache(argsIndex ? args[argsIndex] : seen));
+ }
+ }
+ var array = args[0],
+ index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ outer:
+ while (++index < length) {
+ var cache = caches[0];
+ value = array[index];
+
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
+ argsIndex = argsLength;
+ (cache || seen).push(value);
+ while (--argsIndex) {
+ cache = caches[argsIndex];
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ }
+ }
+ while (argsLength--) {
+ cache = caches[argsLength];
+ if (cache) {
+ releaseObject(cache);
+ }
+ }
+ releaseArray(caches);
+ releaseArray(seen);
+ return result;
+ }
+
+ /**
+ * Gets the last element or last `n` elements of an array. If a callback is
+ * provided elements at the end of the array are returned as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback] The function called
+ * per element or the number of elements to return. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {*} Returns the last element(s) of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ *
+ * _.last([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.last([1, 2, 3], function(num) {
+ * return num > 1;
+ * });
+ * // => [2, 3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.last(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.last(characters, { 'employer': 'na' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function last(array, callback, thisArg) {
+ var n = 0,
+ length = array ? array.length : 0;
+
+ if (typeof callback != 'number' && callback != null) {
+ var index = length;
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (index-- && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = callback;
+ if (n == null || thisArg) {
+ return array ? array[length - 1] : undefined;
+ }
+ }
+ return slice(array, nativeMax(0, length - n));
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using strict
+ * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
+ * as the offset from the end of the collection.
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ var index = array ? array.length : 0;
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Removes all provided values from the given array using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {...*} [value] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3, 1, 2, 3];
+ * _.pull(array, 2, 3);
+ * console.log(array);
+ * // => [1, 1]
+ */
+ function pull(array) {
+ var args = arguments,
+ argsIndex = 0,
+ argsLength = args.length,
+ length = array ? array.length : 0;
+
+ while (++argsIndex < argsLength) {
+ var index = -1,
+ value = args[argsIndex];
+ while (++index < length) {
+ if (array[index] === value) {
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `end`. If `start` is less than `stop` a
+ * zero-length range is created unless a negative `step` is specified.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} [step=1] The value to increment or decrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(4);
+ * // => [0, 1, 2, 3]
+ *
+ * _.range(1, 5);
+ * // => [1, 2, 3, 4]
+ *
+ * _.range(0, 20, 5);
+ * // => [0, 5, 10, 15]
+ *
+ * _.range(0, -4, -1);
+ * // => [0, -1, -2, -3]
+ *
+ * _.range(1, 4, 0);
+ * // => [1, 1, 1]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ start = +start || 0;
+ step = typeof step == 'number' ? step : (+step || 1);
+
+ if (end == null) {
+ end = start;
+ start = 0;
+ }
+ // use `Array(length)` so engines like Chakra and V8 avoid slower modes
+ // http://youtu.be/XAqIpGU8ZZk#t=17m25s
+ var index = -1,
+ length = nativeMax(0, ceil((end - start) / (step || 1))),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * Removes all elements from an array that the callback returns truey for
+ * and returns an array of removed elements. The callback is bound to `thisArg`
+ * and invoked with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to modify.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4, 5, 6];
+ * var evens = _.remove(array, function(num) { return num % 2 == 0; });
+ *
+ * console.log(array);
+ * // => [1, 3, 5]
+ *
+ * console.log(evens);
+ * // => [2, 4, 6]
+ */
+ function remove(array, callback, thisArg) {
+ var index = -1,
+ length = array ? array.length : 0,
+ result = [];
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length) {
+ var value = array[index];
+ if (callback(value, index, array)) {
+ result.push(value);
+ splice.call(array, index--, 1);
+ length--;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial` this method gets all but the first element or
+ * first `n` elements of an array. If a callback function is provided elements
+ * at the beginning of the array are excluded from the result as long as the
+ * callback returns truey. The callback is bound to `thisArg` and invoked
+ * with three arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias drop, tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Function|Object|number|string} [callback=1] The function called
+ * per element or the number of elements to exclude. If a property name or
+ * object is provided it will be used to create a "_.pluck" or "_.where"
+ * style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a slice of `array`.
+ * @example
+ *
+ * _.rest([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.rest([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.rest([1, 2, 3], function(num) {
+ * return num < 3;
+ * });
+ * // => [3]
+ *
+ * var characters = [
+ * { 'name': 'barney', 'blocked': true, 'employer': 'slate' },
+ * { 'name': 'fred', 'blocked': false, 'employer': 'slate' },
+ * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
+ * ];
+ *
+ * // using "_.pluck" callback shorthand
+ * _.pluck(_.rest(characters, 'blocked'), 'name');
+ * // => ['fred', 'pebbles']
+ *
+ * // using "_.where" callback shorthand
+ * _.rest(characters, { 'employer': 'slate' });
+ * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
+ */
+ function rest(array, callback, thisArg) {
+ if (typeof callback != 'number' && callback != null) {
+ var n = 0,
+ index = -1,
+ length = array ? array.length : 0;
+
+ callback = lodash.createCallback(callback, thisArg, 3);
+ while (++index < length && callback(array[index], index, array)) {
+ n++;
+ }
+ } else {
+ n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
+ }
+ return slice(array, n);
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which a value
+ * should be inserted into a given sorted array in order to maintain the sort
+ * order of the array. If a callback is provided it will be executed for
+ * `value` and each element of `array` to compute their sort ranking. The
+ * callback is bound to `thisArg` and invoked with one argument; (value).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 50], 40);
+ * // => 2
+ *
+ * // using "_.pluck" callback shorthand
+ * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ var low = 0,
+ high = array ? array.length : low;
+
+ // explicitly reference `identity` for better inlining in Firefox
+ callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
+ value = callback(value);
+
+ while (low < high) {
+ var mid = (low + high) >>> 1;
+ (callback(array[mid]) < value)
+ ? low = mid + 1
+ : high = mid;
+ }
+ return low;
+ }
+
+ /**
+ * Creates an array of unique values, in order, of the provided arrays using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of combined values.
+ * @example
+ *
+ * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
+ * // => [1, 2, 3, 5, 4]
+ */
+ function union() {
+ return baseUniq(baseFlatten(arguments, true, true));
+ }
+
+ /**
+ * Creates a duplicate-value-free version of an array using strict equality
+ * for comparisons, i.e. `===`. If the array is sorted, providing
+ * `true` for `isSorted` will use a faster algorithm. If a callback is provided
+ * each element of `array` is passed through the callback before uniqueness
+ * is computed. The callback is bound to `thisArg` and invoked with three
+ * arguments; (value, index, array).
+ *
+ * If a property name is provided for `callback` the created "_.pluck" style
+ * callback will return the property value of the given element.
+ *
+ * If an object is provided for `callback` the created "_.where" style callback
+ * will return `true` for elements that have the properties of the given object,
+ * else `false`.
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
+ * @param {Function|Object|string} [callback=identity] The function called
+ * per iteration. If a property name or object is provided it will be used
+ * to create a "_.pluck" or "_.where" style callback, respectively.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
+ * // => ['A', 'b', 'C']
+ *
+ * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2.5, 3]
+ *
+ * // using "_.pluck" callback shorthand
+ * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ // juggle arguments
+ if (typeof isSorted != 'boolean' && isSorted != null) {
+ thisArg = callback;
+ callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
+ isSorted = false;
+ }
+ if (callback != null) {
+ callback = lodash.createCallback(callback, thisArg, 3);
+ }
+ return baseUniq(array, isSorted, callback);
+ }
+
+ /**
+ * Creates an array excluding all provided values using strict equality for
+ * comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {...*} [value] The values to exclude.
+ * @returns {Array} Returns a new array of filtered values.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ return baseDifference(array, slice(arguments, 1));
+ }
+
+ /**
+ * Creates an array that is the symmetric difference of the provided arrays.
+ * See http://en.wikipedia.org/wiki/Symmetric_difference.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {...Array} [array] The arrays to inspect.
+ * @returns {Array} Returns an array of values.
+ * @example
+ *
+ * _.xor([1, 2, 3], [5, 2, 1, 4]);
+ * // => [3, 5, 4]
+ *
+ * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+ * // => [1, 4, 5]
+ */
+ function xor() {
+ var index = -1,
+ length = arguments.length;
+
+ while (++index < length) {
+ var array = arguments[index];
+ if (isArray(array) || isArguments(array)) {
+ var result = result
+ ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
+ : array;
+ }
+ }
+ return result || [];
+ }
+
+ /**
+ * Creates an array of grouped elements, the first of which contains the first
+ * elements of the given arrays, the second of which contains the second
+ * elements of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @alias unzip
+ * @category Arrays
+ * @param {...Array} [array] Arrays to process.
+ * @returns {Array} Returns a new array of grouped elements.
+ * @example
+ *
+ * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+ * // => [['fred', 30, true], ['barney', 40, false]]
+ */
+ function zip() {
+ var array = arguments.length > 1 ? arguments : arguments[0],
+ index = -1,
+ length = array ? max(pluck(array, 'length')) : 0,
+ result = Array(length < 0 ? 0 : length);
+
+ while (++index < length) {
+ result[index] = pluck(array, index);
+ }
+ return result;
+ }
+
+ /**
+ * Creates an object composed from arrays of `keys` and `values`. Provide
+ * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
+ * or two arrays, one of `keys` and one of corresponding `values`.
+ *
+ * @static
+ * @memberOf _
+ * @alias object
+ * @category Arrays
+ * @param {Array} keys The array of keys.
+ * @param {Array} [values=[]] The array of values.
+ * @returns {Object} Returns an object composed of the given keys and
+ * corresponding values.
+ * @example
+ *
+ * _.zipObject(['fred', 'barney'], [30, 40]);
+ * // => { 'fred': 30, 'barney': 40 }
+ */
+ function zipObject(keys, values) {
+ var index = -1,
+ length = keys ? keys.length : 0,
+ result = {};
+
+ if (!values && length && !isArray(keys[0])) {
+ values = [];
+ }
+ while (++index < length) {
+ var key = keys[index];
+ if (values) {
+ result[key] = values[index];
+ } else if (key) {
+ result[key[0]] = key[1];
+ }
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that executes `func`, with the `this` binding and
+ * arguments of the created function, only after being called `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {number} n The number of times the function must be called before
+ * `func` is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('Done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => logs 'Done saving!', after all saves have completed
+ */
+ function after(n, func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * provided to the bound function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to bind.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var func = function(greeting) {
+ * return greeting + ' ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'fred' }, 'hi');
+ * func();
+ * // => 'hi fred'
+ */
+ function bind(func, thisArg) {
+ return arguments.length > 2
+ ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
+ : createWrapper(func, 1, null, null, thisArg);
+ }
+
+ /**
+ * Binds methods of an object to the object itself, overwriting the existing
+ * method. Method names may be specified as individual arguments or as arrays
+ * of method names. If no method names are provided all the function properties
+ * of `object` will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {...string} [methodName] The object method names to
+ * bind, specified as individual method names or arrays of method names.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var view = {
+ * 'label': 'docs',
+ * 'onClick': function() { console.log('clicked ' + this.label); }
+ * };
+ *
+ * _.bindAll(view);
+ * jQuery('#docs').on('click', view.onClick);
+ * // => logs 'clicked docs', when the button is clicked
+ */
+ function bindAll(object) {
+ var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
+ index = -1,
+ length = funcs.length;
+
+ while (++index < length) {
+ var key = funcs[index];
+ object[key] = createWrapper(object[key], 1, null, null, object);
+ }
+ return object;
+ }
+
+ /**
+ * Creates a function that, when called, invokes the method at `object[key]`
+ * and prepends any additional `bindKey` arguments to those provided to the bound
+ * function. This method differs from `_.bind` by allowing bound functions to
+ * reference methods that will be redefined or don't yet exist.
+ * See http://michaux.ca/articles/lazy-function-definition-pattern.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object the method belongs to.
+ * @param {string} key The key of the method.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'name': 'fred',
+ * 'greet': function(greeting) {
+ * return greeting + ' ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bindKey(object, 'greet', 'hi');
+ * func();
+ * // => 'hi fred'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + 'ya ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hiya fred!'
+ */
+ function bindKey(object, key) {
+ return arguments.length > 2
+ ? createWrapper(key, 19, slice(arguments, 2), null, object)
+ : createWrapper(key, 3, null, null, object);
+ }
+
+ /**
+ * Creates a function that is the composition of the provided functions,
+ * where each function consumes the return value of the function that follows.
+ * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ * Each function is executed with the `this` binding of the composed function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {...Function} [func] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var realNameMap = {
+ * 'pebbles': 'penelope'
+ * };
+ *
+ * var format = function(name) {
+ * name = realNameMap[name.toLowerCase()] || name;
+ * return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
+ * };
+ *
+ * var greet = function(formatted) {
+ * return 'Hiya ' + formatted + '!';
+ * };
+ *
+ * var welcome = _.compose(greet, format);
+ * welcome('pebbles');
+ * // => 'Hiya Penelope!'
+ */
+ function compose() {
+ var funcs = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ if (!isFunction(funcs[length])) {
+ throw new TypeError;
+ }
+ }
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a function which accepts one or more arguments of `func` that when
+ * invoked either executes `func` returning its result, if all `func` arguments
+ * have been provided, or returns a function that accepts one or more of the
+ * remaining `func` arguments, and so on. The arity of `func` can be specified
+ * if `func.length` is not sufficient.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var curried = _.curry(function(a, b, c) {
+ * console.log(a + b + c);
+ * });
+ *
+ * curried(1)(2)(3);
+ * // => 6
+ *
+ * curried(1, 2)(3);
+ * // => 6
+ *
+ * curried(1, 2, 3);
+ * // => 6
+ */
+ function curry(func, arity) {
+ arity = typeof arity == 'number' ? arity : (+arity || func.length);
+ return createWrapper(func, 4, null, null, null, arity);
+ }
+
+ /**
+ * Creates a function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked.
+ * Provide an options object to indicate that `func` should be invoked on
+ * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
+ * to the debounced function will return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the debounced function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {number} wait The number of milliseconds to delay.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
+ * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // avoid costly calculations while the window size is in flux
+ * var lazyLayout = _.debounce(calculateLayout, 150);
+ * jQuery(window).on('resize', lazyLayout);
+ *
+ * // execute `sendMail` when the click event is fired, debouncing subsequent calls
+ * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * });
+ *
+ * // ensure `batchLog` is executed once after 1 second of debounced calls
+ * var source = new EventSource('/stream');
+ * source.addEventListener('message', _.debounce(batchLog, 250, {
+ * 'maxWait': 1000
+ * }, false);
+ */
+ function debounce(func, wait, options) {
+ var args,
+ maxTimeoutId,
+ result,
+ stamp,
+ thisArg,
+ timeoutId,
+ trailingCall,
+ lastCalled = 0,
+ maxWait = false,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ wait = nativeMax(0, wait) || 0;
+ if (options === true) {
+ var leading = true;
+ trailing = false;
+ } else if (isObject(options)) {
+ leading = options.leading;
+ maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ var delayed = function() {
+ var remaining = wait - (now() - stamp);
+ if (remaining <= 0) {
+ if (maxTimeoutId) {
+ clearTimeout(maxTimeoutId);
+ }
+ var isCalled = trailingCall;
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (isCalled) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ } else {
+ timeoutId = setTimeout(delayed, remaining);
+ }
+ };
+
+ var maxDelayed = function() {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ }
+ maxTimeoutId = timeoutId = trailingCall = undefined;
+ if (trailing || (maxWait !== wait)) {
+ lastCalled = now();
+ result = func.apply(thisArg, args);
+ if (!timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ }
+ };
+
+ return function() {
+ args = arguments;
+ stamp = now();
+ thisArg = this;
+ trailingCall = trailing && (timeoutId || !leading);
+
+ if (maxWait === false) {
+ var leadingCall = leading && !timeoutId;
+ } else {
+ if (!maxTimeoutId && !leading) {
+ lastCalled = stamp;
+ }
+ var remaining = maxWait - (stamp - lastCalled),
+ isCalled = remaining <= 0;
+
+ if (isCalled) {
+ if (maxTimeoutId) {
+ maxTimeoutId = clearTimeout(maxTimeoutId);
+ }
+ lastCalled = stamp;
+ result = func.apply(thisArg, args);
+ }
+ else if (!maxTimeoutId) {
+ maxTimeoutId = setTimeout(maxDelayed, remaining);
+ }
+ }
+ if (isCalled && timeoutId) {
+ timeoutId = clearTimeout(timeoutId);
+ }
+ else if (!timeoutId && wait !== maxWait) {
+ timeoutId = setTimeout(delayed, wait);
+ }
+ if (leadingCall) {
+ isCalled = true;
+ result = func.apply(thisArg, args);
+ }
+ if (isCalled && !timeoutId && !maxTimeoutId) {
+ args = thisArg = null;
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) { console.log(text); }, 'deferred');
+ * // logs 'deferred' after one or more milliseconds
+ */
+ function defer(func) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 1);
+ return setTimeout(function() { func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * will be provided to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay execution.
+ * @param {...*} [arg] Arguments to invoke the function with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) { console.log(text); }, 1000, 'later');
+ * // => logs 'later' after one second
+ */
+ function delay(func, wait) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var args = slice(arguments, 2);
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided it will be used to determine the cache key for storing the result
+ * based on the arguments provided to the memoized function. By default, the
+ * first argument provided to the memoized function is used as the cache key.
+ * The `func` is executed with the `this` binding of the memoized function.
+ * The result cache is exposed as the `cache` property on the memoized function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ *
+ * fibonacci(9)
+ * // => 34
+ *
+ * var data = {
+ * 'fred': { 'name': 'fred', 'age': 40 },
+ * 'pebbles': { 'name': 'pebbles', 'age': 1 }
+ * };
+ *
+ * // modifying the result cache
+ * var get = _.memoize(function(name) { return data[name]; }, _.identity);
+ * get('pebbles');
+ * // => { 'name': 'pebbles', 'age': 1 }
+ *
+ * get.cache.pebbles.name = 'penelope';
+ * get('pebbles');
+ * // => { 'name': 'penelope', 'age': 1 }
+ */
+ function memoize(func, resolver) {
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ var memoized = function() {
+ var cache = memoized.cache,
+ key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];
+
+ return hasOwnProperty.call(cache, key)
+ ? cache[key]
+ : (cache[key] = func.apply(this, arguments));
+ }
+ memoized.cache = {};
+ return memoized;
+ }
+
+ /**
+ * Creates a function that is restricted to execute `func` once. Repeat calls to
+ * the function will return the value of the first call. The `func` is executed
+ * with the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // `initialize` executes `createApplication` once
+ */
+ function once(func) {
+ var ran,
+ result;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+
+ // clear the `func` variable so the function may be garbage collected
+ func = null;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those provided to the new function. This
+ * method is similar to `_.bind` except it does **not** alter the `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ' ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('fred');
+ * // => 'hi fred'
+ */
+ function partial(func) {
+ return createWrapper(func, 16, slice(arguments, 1));
+ }
+
+ /**
+ * This method is like `_.partial` except that `partial` arguments are
+ * appended to those provided to the new function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [arg] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var defaultsDeep = _.partialRight(_.merge, _.defaults);
+ *
+ * var options = {
+ * 'variable': 'data',
+ * 'imports': { 'jq': $ }
+ * };
+ *
+ * defaultsDeep(options, _.templateSettings);
+ *
+ * options.variable
+ * // => 'data'
+ *
+ * options.imports
+ * // => { '_': _, 'jq': $ }
+ */
+ function partialRight(func) {
+ return createWrapper(func, 32, null, slice(arguments, 1));
+ }
+
+ /**
+ * Creates a function that, when executed, will only call the `func` function
+ * at most once per every `wait` milliseconds. Provide an options object to
+ * indicate that `func` should be invoked on the leading and/or trailing edge
+ * of the `wait` timeout. Subsequent calls to the throttled function will
+ * return the result of the last `func` call.
+ *
+ * Note: If `leading` and `trailing` options are `true` `func` will be called
+ * on the trailing edge of the timeout only if the the throttled function is
+ * invoked more than once during the `wait` timeout.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {number} wait The number of milliseconds to throttle executions to.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // avoid excessively updating the position while scrolling
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ *
+ * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
+ * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+ * 'trailing': false
+ * }));
+ */
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
+
+ if (!isFunction(func)) {
+ throw new TypeError;
+ }
+ if (options === false) {
+ leading = false;
+ } else if (isObject(options)) {
+ leading = 'leading' in options ? options.leading : leading;
+ trailing = 'trailing' in options ? options.trailing : trailing;
+ }
+ debounceOptions.leading = leading;
+ debounceOptions.maxWait = wait;
+ debounceOptions.trailing = trailing;
+
+ return debounce(func, wait, debounceOptions);
+ }
+
+ /**
+ * Creates a function that provides `value` to the wrapper function as its
+ * first argument. Additional arguments provided to the function are appended
+ * to those provided to the wrapper function. The wrapper is executed with
+ * the `this` binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {*} value The value to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '<p>' + func(text) + '</p>';
+ * });
+ *
+ * p('Fred, Wilma, & Pebbles');
+ * // => '<p>Fred, Wilma, & Pebbles</p>'
+ */
+ function wrap(value, wrapper) {
+ return createWrapper(wrapper, 16, [value]);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a function that returns `value`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value The value to return from the new function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * var getter = _.constant(object);
+ * getter() === object;
+ * // => true
+ */
+ function constant(value) {
+ return function() {
+ return value;
+ };
+ }
+
+ /**
+ * Produces a callback bound to an optional `thisArg`. If `func` is a property
+ * name the created callback will return the property value for a given element.
+ * If `func` is an object the created callback will return `true` for elements
+ * that contain the equivalent object properties, otherwise it will return `false`.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} [func=identity] The value to convert to a callback.
+ * @param {*} [thisArg] The `this` binding of the created callback.
+ * @param {number} [argCount] The number of arguments the callback accepts.
+ * @returns {Function} Returns a callback function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // wrap to create custom callback shorthands
+ * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
+ * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
+ * return !match ? func(callback, thisArg) : function(object) {
+ * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
+ * };
+ * });
+ *
+ * _.filter(characters, 'age__gt38');
+ * // => [{ 'name': 'fred', 'age': 40 }]
+ */
+ function createCallback(func, thisArg, argCount) {
+ var type = typeof func;
+ if (func == null || type == 'function') {
+ return baseCreateCallback(func, thisArg, argCount);
+ }
+ // handle "_.pluck" style callback shorthands
+ if (type != 'object') {
+ return property(func);
+ }
+ var props = keys(func),
+ key = props[0],
+ a = func[key];
+
+ // handle "_.where" style callback shorthands
+ if (props.length == 1 && a === a && !isObject(a)) {
+ // fast path the common case of providing an object with a single
+ // property containing a primitive value
+ return function(object) {
+ var b = object[key];
+ return a === b && (a !== 0 || (1 / a == 1 / b));
+ };
+ }
+ return function(object) {
+ var length = props.length,
+ result = false;
+
+ while (length--) {
+ if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
+ break;
+ }
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding HTML entities.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Fred, Wilma, & Pebbles');
+ * // => 'Fred, Wilma, & Pebbles'
+ */
+ function escape(string) {
+ return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.identity(object) === object;
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds function properties of a source object to the destination object.
+ * If `object` is a function methods will be added to its prototype as well.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Function|Object} [object=lodash] object The destination object.
+ * @param {Object} source The object of functions to add.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
+ * @example
+ *
+ * function capitalize(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ *
+ * _.mixin({ 'capitalize': capitalize });
+ * _.capitalize('fred');
+ * // => 'Fred'
+ *
+ * _('fred').capitalize().value();
+ * // => 'Fred'
+ *
+ * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
+ * _('fred').capitalize();
+ * // => 'Fred'
+ */
+ function mixin(object, source, options) {
+ var chain = true,
+ methodNames = source && functions(source);
+
+ if (!source || (!options && !methodNames.length)) {
+ if (options == null) {
+ options = source;
+ }
+ ctor = lodashWrapper;
+ source = object;
+ object = lodash;
+ methodNames = functions(source);
+ }
+ if (options === false) {
+ chain = false;
+ } else if (isObject(options) && 'chain' in options) {
+ chain = options.chain;
+ }
+ var ctor = object,
+ isFunc = isFunction(ctor);
+
+ forEach(methodNames, function(methodName) {
+ var func = object[methodName] = source[methodName];
+ if (isFunc) {
+ ctor.prototype[methodName] = function() {
+ var chainAll = this.__chain__,
+ value = this.__wrapped__,
+ args = [value];
+
+ push.apply(args, arguments);
+ var result = func.apply(object, args);
+ if (chain || chainAll) {
+ if (value === result && isObject(result)) {
+ return this;
+ }
+ result = new ctor(result);
+ result.__chain__ = chainAll;
+ }
+ return result;
+ };
+ }
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ context._ = oldDash;
+ return this;
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var object = { 'name': 'fred' };
+ * _.noop(object) === undefined;
+ * // => true
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
+ * (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @example
+ *
+ * var stamp = _.now();
+ * _.defer(function() { console.log(_.now() - stamp); });
+ * // => logs the number of milliseconds it took for the deferred function to be called
+ */
+ var now = isNative(now = Date.now) && now || function() {
+ return new Date().getTime();
+ };
+
+ /**
+ * Converts the given value into an integer of the specified radix.
+ * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
+ * `value` is a hexadecimal, in which case a `radix` of `16` is used.
+ *
+ * Note: This method avoids differences in native ES3 and ES5 `parseInt`
+ * implementations. See http://es5.github.io/#E.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} value The value to parse.
+ * @param {number} [radix] The radix used to interpret the value to parse.
+ * @returns {number} Returns the new integer value.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ */
+ var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
+ // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
+ return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
+ };
+
+ /**
+ * Creates a "_.pluck" style function, which returns the `key` value of a
+ * given object.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} key The name of the property to retrieve.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'barney', 'age': 36 }
+ * ];
+ *
+ * var getName = _.property('name');
+ *
+ * _.map(characters, getName);
+ * // => ['barney', 'fred']
+ *
+ * _.sortBy(characters, getName);
+ * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }]
+ */
+ function property(key) {
+ return function(object) {
+ return object[key];
+ };
+ }
+
+ /**
+ * Produces a random number between `min` and `max` (inclusive). If only one
+ * argument is provided a number between `0` and the given number will be
+ * returned. If `floating` is truey or either `min` or `max` are floats a
+ * floating-point number will be returned instead of an integer.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {number} [min=0] The minimum possible value.
+ * @param {number} [max=1] The maximum possible value.
+ * @param {boolean} [floating=false] Specify returning a floating-point number.
+ * @returns {number} Returns a random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
+ function random(min, max, floating) {
+ var noMin = min == null,
+ noMax = max == null;
+
+ if (floating == null) {
+ if (typeof min == 'boolean' && noMax) {
+ floating = min;
+ min = 1;
+ }
+ else if (!noMax && typeof max == 'boolean') {
+ floating = max;
+ noMax = true;
+ }
+ }
+ if (noMin && noMax) {
+ max = 1;
+ }
+ min = +min || 0;
+ if (noMax) {
+ max = min;
+ min = 0;
+ } else {
+ max = +max || 0;
+ }
+ if (floating || min % 1 || max % 1) {
+ var rand = nativeRandom();
+ return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
+ }
+ return baseRandom(min, max);
+ }
+
+ /**
+ * Resolves the value of property `key` on `object`. If `key` is a function
+ * it will be invoked with the `this` binding of `object` and its result returned,
+ * else the property value is returned. If `object` is falsey then `undefined`
+ * is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {string} key The name of the property to resolve.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, key) {
+ if (object) {
+ var value = object[key];
+ return isFunction(value) ? object[key]() : value;
+ }
+ }
+
+ /**
+ * A micro-templating method that handles arbitrary delimiters, preserves
+ * whitespace, and correctly escapes quotes within interpolated code.
+ *
+ * Note: In the development build, `_.template` utilizes sourceURLs for easier
+ * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ *
+ * For more information on precompiling templates see:
+ * http://lodash.com/custom-builds
+ *
+ * For more information on Chrome extension sandboxes see:
+ * http://developer.chrome.com/stable/extensions/sandboxingEval.html
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} text The template text.
+ * @param {Object} data The data object used to populate the text.
+ * @param {Object} [options] The options object.
+ * @param {RegExp} [options.escape] The "escape" delimiter.
+ * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+ * @param {Object} [options.imports] An object to import into the template as local variables.
+ * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+ * @param {string} [sourceURL] The sourceURL of the template's compiled source.
+ * @param {string} [variable] The data object variable name.
+ * @returns {Function|string} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using the "interpolate" delimiter to create a compiled template
+ * var compiled = _.template('hello <%= name %>');
+ * compiled({ 'name': 'fred' });
+ * // => 'hello fred'
+ *
+ * // using the "escape" delimiter to escape HTML in data property values
+ * _.template('<b><%- value %></b>', { 'value': '<script>' });
+ * // => '<b><script></b>'
+ *
+ * // using the "evaluate" delimiter to generate HTML
+ * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
+ * _.template(list, { 'people': ['fred', 'barney'] });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
+ * _.template('hello ${ name }', { 'name': 'pebbles' });
+ * // => 'hello pebbles'
+ *
+ * // using the internal `print` function in "evaluate" delimiters
+ * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
+ * // => 'hello barney!'
+ *
+ * // using a custom template delimiters
+ * _.templateSettings = {
+ * 'interpolate': /{{([\s\S]+?)}}/g
+ * };
+ *
+ * _.template('hello {{ name }}!', { 'name': 'mustache' });
+ * // => 'hello mustache!'
+ *
+ * // using the `imports` option to import jQuery
+ * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
+ * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
+ * // => '<li>fred</li><li>barney</li>'
+ *
+ * // using the `sourceURL` option to specify a custom sourceURL for the template
+ * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
+ * compiled(data);
+ * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+ *
+ * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+ * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
+ * compiled.source;
+ * // => function(data) {
+ * var __t, __p = '', __e = _.escape;
+ * __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
+ * return __p;
+ * }
+ *
+ * // using the `source` property to inline compiled templates for meaningful
+ * // line numbers in error messages and a stack trace
+ * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+ * var JST = {\
+ * "main": ' + _.template(mainText).source + '\
+ * };\
+ * ');
+ */
+ function template(text, data, options) {
+ // based on John Resig's `tmpl` implementation
+ // http://ejohn.org/blog/javascript-micro-templating/
+ // and Laura Doktorova's doT.js
+ // https://github.com/olado/doT
+ var settings = lodash.templateSettings;
+ text = String(text || '');
+
+ // avoid missing dependencies when `iteratorTemplate` is not defined
+ options = defaults({}, options, settings);
+
+ var imports = defaults({}, options.imports, settings.imports),
+ importsKeys = keys(imports),
+ importsValues = values(imports);
+
+ var isEvaluating,
+ index = 0,
+ interpolate = options.interpolate || reNoMatch,
+ source = "__p += '";
+
+ // compile the regexp to match each delimiter
+ var reDelimiters = RegExp(
+ (options.escape || reNoMatch).source + '|' +
+ interpolate.source + '|' +
+ (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+ (options.evaluate || reNoMatch).source + '|$'
+ , 'g');
+
+ text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+ interpolateValue || (interpolateValue = esTemplateValue);
+
+ // escape characters that cannot be included in string literals
+ source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+ // replace delimiters with snippets
+ if (escapeValue) {
+ source += "' +\n__e(" + escapeValue + ") +\n'";
+ }
+ if (evaluateValue) {
+ isEvaluating = true;
+ source += "';\n" + evaluateValue + ";\n__p += '";
+ }
+ if (interpolateValue) {
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+ }
+ index = offset + match.length;
+
+ // the JS engine embedded in Adobe products requires returning the `match`
+ // string in order to produce the correct `offset` value
+ return match;
+ });
+
+ source += "';\n";
+
+ // if `variable` is not specified, wrap a with-statement around the generated
+ // code to add the data object to the top of the scope chain
+ var variable = options.variable,
+ hasVariable = variable;
+
+ if (!hasVariable) {
+ variable = 'obj';
+ source = 'with (' + variable + ') {\n' + source + '\n}\n';
+ }
+ // cleanup code by stripping empty strings
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+ .replace(reEmptyStringMiddle, '$1')
+ .replace(reEmptyStringTrailing, '$1;');
+
+ // frame code as the function body
+ source = 'function(' + variable + ') {\n' +
+ (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
+ "var __t, __p = '', __e = _.escape" +
+ (isEvaluating
+ ? ', __j = Array.prototype.join;\n' +
+ "function print() { __p += __j.call(arguments, '') }\n"
+ : ';\n'
+ ) +
+ source +
+ 'return __p\n}';
+
+ // Use a sourceURL for easier debugging.
+ // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';
+
+ try {
+ var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
+ } catch(e) {
+ e.source = source;
+ throw e;
+ }
+ if (data) {
+ return result(data);
+ }
+ // provide the compiled function's source by its `toString` method, in
+ // supported environments, or the `source` property as a convenience for
+ // inlining compiled templates during the build process
+ result.source = source;
+ return result;
+ }
+
+ /**
+ * Executes the callback `n` times, returning an array of the results
+ * of each callback execution. The callback is bound to `thisArg` and invoked
+ * with one argument; (index).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {number} n The number of times to execute the callback.
+ * @param {Function} callback The function called per iteration.
+ * @param {*} [thisArg] The `this` binding of `callback`.
+ * @returns {Array} Returns an array of the results of each `callback` execution.
+ * @example
+ *
+ * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
+ * // => [3, 6, 4]
+ *
+ * _.times(3, function(n) { mage.castSpell(n); });
+ * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
+ *
+ * _.times(3, function(n) { this.cast(n); }, mage);
+ * // => also calls `mage.castSpell(n)` three times
+ */
+ function times(n, callback, thisArg) {
+ n = (n = +n) > -1 ? n : 0;
+ var index = -1,
+ result = Array(n);
+
+ callback = baseCreateCallback(callback, thisArg, 1);
+ while (++index < n) {
+ result[index] = callback(index);
+ }
+ return result;
+ }
+
+ /**
+ * The inverse of `_.escape` this method converts the HTML entities
+ * `&`, `<`, `>`, `"`, and `'` in `string` to their
+ * corresponding characters.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} string The string to unescape.
+ * @returns {string} Returns the unescaped string.
+ * @example
+ *
+ * _.unescape('Fred, Barney & Pebbles');
+ * // => 'Fred, Barney & Pebbles'
+ */
+ function unescape(string) {
+ return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
+ }
+
+ /**
+ * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {string} [prefix] The value to prefix the ID with.
+ * @returns {string} Returns the unique ID.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ *
+ * _.uniqueId();
+ * // => '105'
+ */
+ function uniqueId(prefix) {
+ var id = ++idCounter;
+ return String(prefix == null ? '' : prefix) + id;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a `lodash` object that wraps the given value with explicit
+ * method chaining enabled.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {*} value The value to wrap.
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 },
+ * { 'name': 'pebbles', 'age': 1 }
+ * ];
+ *
+ * var youngest = _.chain(characters)
+ * .sortBy('age')
+ * .map(function(chr) { return chr.name + ' is ' + chr.age; })
+ * .first()
+ * .value();
+ * // => 'pebbles is 1'
+ */
+ function chain(value) {
+ value = new lodashWrapper(value);
+ value.__chain__ = true;
+ return value;
+ }
+
+ /**
+ * Invokes `interceptor` with the `value` as the first argument and then
+ * returns `value`. The purpose of this method is to "tap into" a method
+ * chain in order to perform operations on intermediate results within
+ * the chain.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * _([1, 2, 3, 4])
+ * .tap(function(array) { array.pop(); })
+ * .reverse()
+ * .value();
+ * // => [3, 2, 1]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * Enables explicit method chaining on the wrapper object.
+ *
+ * @name chain
+ * @memberOf _
+ * @category Chaining
+ * @returns {*} Returns the wrapper object.
+ * @example
+ *
+ * var characters = [
+ * { 'name': 'barney', 'age': 36 },
+ * { 'name': 'fred', 'age': 40 }
+ * ];
+ *
+ * // without explicit chaining
+ * _(characters).first();
+ * // => { 'name': 'barney', 'age': 36 }
+ *
+ * // with explicit chaining
+ * _(characters).chain()
+ * .first()
+ * .pick('age')
+ * .value();
+ * // => { 'age': 36 }
+ */
+ function wrapperChain() {
+ this.__chain__ = true;
+ return this;
+ }
+
+ /**
+ * Produces the `toString` result of the wrapped value.
+ *
+ * @name toString
+ * @memberOf _
+ * @category Chaining
+ * @returns {string} Returns the string result.
+ * @example
+ *
+ * _([1, 2, 3]).toString();
+ * // => '1,2,3'
+ */
+ function wrapperToString() {
+ return String(this.__wrapped__);
+ }
+
+ /**
+ * Extracts the wrapped value.
+ *
+ * @name valueOf
+ * @memberOf _
+ * @alias value
+ * @category Chaining
+ * @returns {*} Returns the wrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).valueOf();
+ * // => [1, 2, 3]
+ */
+ function wrapperValueOf() {
+ return this.__wrapped__;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions that return wrapped values when chaining
+ lodash.after = after;
+ lodash.assign = assign;
+ lodash.at = at;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.bindKey = bindKey;
+ lodash.chain = chain;
+ lodash.compact = compact;
+ lodash.compose = compose;
+ lodash.constant = constant;
+ lodash.countBy = countBy;
+ lodash.create = create;
+ lodash.createCallback = createCallback;
+ lodash.curry = curry;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.filter = filter;
+ lodash.flatten = flatten;
+ lodash.forEach = forEach;
+ lodash.forEachRight = forEachRight;
+ lodash.forIn = forIn;
+ lodash.forInRight = forInRight;
+ lodash.forOwn = forOwn;
+ lodash.forOwnRight = forOwnRight;
+ lodash.functions = functions;
+ lodash.groupBy = groupBy;
+ lodash.indexBy = indexBy;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.invert = invert;
+ lodash.invoke = invoke;
+ lodash.keys = keys;
+ lodash.map = map;
+ lodash.mapValues = mapValues;
+ lodash.max = max;
+ lodash.memoize = memoize;
+ lodash.merge = merge;
+ lodash.min = min;
+ lodash.omit = omit;
+ lodash.once = once;
+ lodash.pairs = pairs;
+ lodash.partial = partial;
+ lodash.partialRight = partialRight;
+ lodash.pick = pick;
+ lodash.pluck = pluck;
+ lodash.property = property;
+ lodash.pull = pull;
+ lodash.range = range;
+ lodash.reject = reject;
+ lodash.remove = remove;
+ lodash.rest = rest;
+ lodash.shuffle = shuffle;
+ lodash.sortBy = sortBy;
+ lodash.tap = tap;
+ lodash.throttle = throttle;
+ lodash.times = times;
+ lodash.toArray = toArray;
+ lodash.transform = transform;
+ lodash.union = union;
+ lodash.uniq = uniq;
+ lodash.values = values;
+ lodash.where = where;
+ lodash.without = without;
+ lodash.wrap = wrap;
+ lodash.xor = xor;
+ lodash.zip = zip;
+ lodash.zipObject = zipObject;
+
+ // add aliases
+ lodash.collect = map;
+ lodash.drop = rest;
+ lodash.each = forEach;
+ lodash.eachRight = forEachRight;
+ lodash.extend = assign;
+ lodash.methods = functions;
+ lodash.object = zipObject;
+ lodash.select = filter;
+ lodash.tail = rest;
+ lodash.unique = uniq;
+ lodash.unzip = zip;
+
+ // add functions to `lodash.prototype`
+ mixin(lodash);
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions that return unwrapped values when chaining
+ lodash.clone = clone;
+ lodash.cloneDeep = cloneDeep;
+ lodash.contains = contains;
+ lodash.escape = escape;
+ lodash.every = every;
+ lodash.find = find;
+ lodash.findIndex = findIndex;
+ lodash.findKey = findKey;
+ lodash.findLast = findLast;
+ lodash.findLastIndex = findLastIndex;
+ lodash.findLastKey = findLastKey;
+ lodash.has = has;
+ lodash.identity = identity;
+ lodash.indexOf = indexOf;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isBoolean = isBoolean;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isNaN = isNaN;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isPlainObject = isPlainObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isString = isString;
+ lodash.isUndefined = isUndefined;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.mixin = mixin;
+ lodash.noConflict = noConflict;
+ lodash.noop = noop;
+ lodash.now = now;
+ lodash.parseInt = parseInt;
+ lodash.random = random;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.result = result;
+ lodash.runInContext = runInContext;
+ lodash.size = size;
+ lodash.some = some;
+ lodash.sortedIndex = sortedIndex;
+ lodash.template = template;
+ lodash.unescape = unescape;
+ lodash.uniqueId = uniqueId;
+
+ // add aliases
+ lodash.all = every;
+ lodash.any = some;
+ lodash.detect = find;
+ lodash.findWhere = find;
+ lodash.foldl = reduce;
+ lodash.foldr = reduceRight;
+ lodash.include = contains;
+ lodash.inject = reduce;
+
+ mixin(function() {
+ var source = {}
+ forOwn(lodash, function(func, methodName) {
+ if (!lodash.prototype[methodName]) {
+ source[methodName] = func;
+ }
+ });
+ return source;
+ }(), false);
+
+ /*--------------------------------------------------------------------------*/
+
+ // add functions capable of returning wrapped and unwrapped values when chaining
+ lodash.first = first;
+ lodash.last = last;
+ lodash.sample = sample;
+
+ // add aliases
+ lodash.take = first;
+ lodash.head = first;
+
+ forOwn(lodash, function(func, methodName) {
+ var callbackable = methodName !== 'sample';
+ if (!lodash.prototype[methodName]) {
+ lodash.prototype[methodName]= function(n, guard) {
+ var chainAll = this.__chain__,
+ result = func(this.__wrapped__, n, guard);
+
+ return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
+ ? result
+ : new lodashWrapper(result, chainAll);
+ };
+ }
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type string
+ */
+ lodash.VERSION = '2.4.1';
+
+ // add "Chaining" functions to the wrapper
+ lodash.prototype.chain = wrapperChain;
+ lodash.prototype.toString = wrapperToString;
+ lodash.prototype.value = wrapperValueOf;
+ lodash.prototype.valueOf = wrapperValueOf;
+
+ // add `Array` functions that return unwrapped values
+ forEach(['join', 'pop', 'shift'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ var chainAll = this.__chain__,
+ result = func.apply(this.__wrapped__, arguments);
+
+ return chainAll
+ ? new lodashWrapper(result, chainAll)
+ : result;
+ };
+ });
+
+ // add `Array` functions that return the existing wrapped value
+ forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ func.apply(this.__wrapped__, arguments);
+ return this;
+ };
+ });
+
+ // add `Array` functions that return new wrapped values
+ forEach(['concat', 'slice', 'splice'], function(methodName) {
+ var func = arrayRef[methodName];
+ lodash.prototype[methodName] = function() {
+ return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
+ };
+ });
+
+ return lodash;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Lo-Dash
+ var _ = runInContext();
+
+ // some AMD build optimizers like r.js check for condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // Expose Lo-Dash to the global object even when an AMD loader is present in
+ // case Lo-Dash is loaded with a RequireJS shim config.
+ // See http://requirejs.org/docs/api.html#config-shim
+ root._ = _;
+
+ // define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module
+ define(function() {
+ return _;
+ });
+ }
+ // check for `exports` after `define` in case a build optimizer adds an `exports` object
+ else if (freeExports && freeModule) {
+ // in Node.js or RingoJS
+ if (moduleExports) {
+ (freeModule.exports = _)._ = _;
+ }
+ // in Narwhal or Rhino -require
+ else {
+ freeExports._ = _;
+ }
+ }
+ else {
+ // in a browser or Rhino
+ root._ = _;
+ }
+}.call(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/dev/js/lib/dagre/dagre.min.js b/dev/js/lib/dagre/dagre.min.js
new file mode 100644
index 0000000..cb562a2
--- /dev/null
+++ b/dev/js/lib/dagre/dagre.min.js
@@ -0,0 +1,4 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.dagre=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){module.exports={graphlib:require("./lib/graphlib"),layout:require("./lib/layout"),debug:require("./lib/debug"),util:{time:require("./lib/util").time,notime:require("./lib/util").notime},version:require("./lib/version")}},{"./lib/debug":6,"./lib/graphlib":7,"./lib/layout":9,"./lib/util":29,"./lib/version":30}],2:[function(require,module,exports){"use strict";var _=require("./lodash"),greedyFAS=require("./greedy-fas");module.exports={run:run,undo:undo};function run(g){var fas=g.graph().acyclicer==="greedy"?greedyFAS(g,weightFn(g)):dfsFAS(g);_.each(fas,function(e){var label=g.edge(e);g.removeEdge(e);label.forwardName=e.name;label.reversed=true;g.setEdge(e.w,e.v,label,_.uniqueId("rev"))});function weightFn(g){return function(e){return g.edge(e).weight}}}function dfsFAS(g){var fas=[],stack={},visited={};function dfs(v){if(_.has(visited,v)){return}visited[v]=true;stack[v]=true;_.each(g.outEdges(v),function(e){if(_.has(stack,e.w)){fas.push(e)}else{dfs(e.w)}});delete stack[v]}_.each(g.nodes(),dfs);return fas}function undo(g){_.each(g.edges(),function(e){var label=g.edge(e);if(label.reversed){g.removeEdge(e);var forwardName=label.forwardName;delete label.reversed;delete label.forwardName;g.setEdge(e.w,e.v,label,forwardName)}})}},{"./greedy-fas":8,"./lodash":10}],3:[function(require,module,exports){var _=require("./lodash"),util=require("./util");module.exports=addBorderSegments;function addBorderSegments(g){function dfs(v){var children=g.children(v),node=g.node(v);if(children.length){_.each(children,dfs)}if(_.has(node,"minRank")){node.borderLeft=[];node.borderRight=[];for(var rank=node.minRank,maxRank=node.maxRank+1;rank<maxRank;++rank){addBorderNode(g,"borderLeft","_bl",v,node,rank);addBorderNode(g,"borderRight","_br",v,node,rank)}}}_.each(g.children(),dfs)}function addBorderNode(g,prop,prefix,sg,sgNode,rank){var label={width:0,height:0,rank:rank},prev=sgNode[prop][rank-1],curr=util.addDummyNode(g,"border",label,prefix);sgNode[prop][rank]=curr;g.setParent(curr,sg);if(prev){g.setEdge(prev,curr,{weight:1})}}},{"./lodash":10,"./util":29}],4:[function(require,module,exports){"use strict";var _=require("./lodash");module.exports={adjust:adjust,undo:undo};function adjust(g){var rankDir=g.graph().rankdir.toLowerCase();if(rankDir==="lr"||rankDir==="rl"){swapWidthHeight(g)}}function undo(g){var rankDir=g.graph().rankdir.toLowerCase();if(rankDir==="bt"||rankDir==="rl"){reverseY(g)}if(rankDir==="lr"||rankDir==="rl"){swapXY(g);swapWidthHeight(g)}}function swapWidthHeight(g){_.each(g.nodes(),function(v){swapWidthHeightOne(g.node(v))});_.each(g.edges(),function(e){swapWidthHeightOne(g.edge(e))})}function swapWidthHeightOne(attrs){var w=attrs.width;attrs.width=attrs.height;attrs.height=w}function reverseY(g){_.each(g.nodes(),function(v){reverseYOne(g.node(v))});_.each(g.edges(),function(e){var edge=g.edge(e);_.each(edge.points,reverseYOne);if(_.has(edge,"y")){reverseYOne(edge)}})}function reverseYOne(attrs){attrs.y=-attrs.y}function swapXY(g){_.each(g.nodes(),function(v){swapXYOne(g.node(v))});_.each(g.edges(),function(e){var edge=g.edge(e);_.each(edge.points,swapXYOne);if(_.has(edge,"x")){swapXYOne(edge)}})}function swapXYOne(attrs){var x=attrs.x;attrs.x=attrs.y;attrs.y=x}},{"./lodash":10}],5:[function(require,module,exports){module.exports=List;function List(){var sentinel={};sentinel._next=sentinel._prev=sentinel;this._sentinel=sentinel}List.prototype.dequeue=function(){var sentinel=this._sentinel,entry=sentinel._prev;if(entry!==sentinel){unlink(entry);return entry}};List.prototype.enqueue=function(entry){var sentinel=this._sentinel;if(entry._prev&&entry._next){unlink(entry)}entry._next=sentinel._next;sentinel._next._prev=entry;sentinel._next=entry;entry._prev=sentinel};List.prototype.toString=function(){var strs=[],sentinel=this._sentinel,curr=sentinel._prev;while(curr!==sentinel){strs.push(JSON.stringify(curr,filterOutLinks));curr=curr._prev}return"["+strs.join(", ")+"]"};function unlink(entry){entry._prev._next=entry._next;entry._next._prev=entry._prev;delete entry._next;delete entry._prev}function filterOutLinks(k,v){if(k!=="_next"&&k!=="_prev"){return v}}},{}],6:[function(require,module,exports){var _=require("./lodash"),util=require("./util"),Graph=require("./graphlib").Graph;module.exports={debugOrdering:debugOrdering};function debugOrdering(g){var layerMatrix=util.buildLayerMatrix(g);var h=new Graph({compound:true,multigraph:true}).setGraph({});_.each(g.nodes(),function(v){h.setNode(v,{label:v});h.setParent(v,"layer"+g.node(v).rank)});_.each(g.edges(),function(e){h.setEdge(e.v,e.w,{},e.name)});_.each(layerMatrix,function(layer,i){var layerV="layer"+i;h.setNode(layerV,{rank:"same"});_.reduce(layer,function(u,v){h.setEdge(u,v,{style:"invis"});return v})});return h}},{"./graphlib":7,"./lodash":10,"./util":29}],7:[function(require,module,exports){var graphlib;if(require){try{graphlib=require("graphlib")}catch(e){}}if(!graphlib){graphlib=window.graphlib}module.exports=graphlib},{graphlib:31}],8:[function(require,module,exports){var _=require("./lodash"),Graph=require("./graphlib").Graph,List=require("./data/list");module.exports=greedyFAS;var DEFAULT_WEIGHT_FN=_.constant(1);function greedyFAS(g,weightFn){if(g.nodeCount()<=1){return[]}var state=buildState(g,weightFn||DEFAULT_WEIGHT_FN);var results=doGreedyFAS(state.graph,state.buckets,state.zeroIdx);return _.flatten(_.map(results,function(e){return g.outEdges(e.v,e.w)}),true)}function doGreedyFAS(g,buckets,zeroIdx){var results=[],sources=buckets[buckets.length-1],sinks=buckets[0];var entry;while(g.nodeCount()){while(entry=sinks.dequeue()){removeNode(g,buckets,zeroIdx,entry)}while(entry=sources.dequeue()){removeNode(g,buckets,zeroIdx,entry)}if(g.nodeCount()){for(var i=buckets.length-2;i>0;--i){entry=buckets[i].dequeue();if(entry){results=results.concat(removeNode(g,buckets,zeroIdx,entry,true));break}}}}return results}function removeNode(g,buckets,zeroIdx,entry,collectPredecessors){var results=collectPredecessors?[]:undefined;_.each(g.inEdges(entry.v),function(edge){var weight=g.edge(edge),uEntry=g.node(edge.v);if(collectPredecessors){results.push({v:edge.v,w:edge.w})}uEntry.out-=weight;assignBucket(buckets,zeroIdx,uEntry)});_.each(g.outEdges(entry.v),function(edge){var weight=g.edge(edge),w=edge.w,wEntry=g.node(w);wEntry["in"]-=weight;assignBucket(buckets,zeroIdx,wEntry)});g.removeNode(entry.v);return results}function buildState(g,weightFn){var fasGraph=new Graph,maxIn=0,maxOut=0;_.each(g.nodes(),function(v){fasGraph.setNode(v,{v:v,"in":0,out:0})});_.each(g.edges(),function(e){var prevWeight=fasGraph.edge(e.v,e.w)||0,weight=weightFn(e),edgeWeight=prevWeight+weight;fasGraph.setEdge(e.v,e.w,edgeWeight);maxOut=Math.max(maxOut,fasGraph.node(e.v).out+=weight);maxIn=Math.max(maxIn,fasGraph.node(e.w)["in"]+=weight)});var buckets=_.range(maxOut+maxIn+3).map(function(){return new List});var zeroIdx=maxIn+1;_.each(fasGraph.nodes(),function(v){assignBucket(buckets,zeroIdx,fasGraph.node(v))});return{graph:fasGraph,buckets:buckets,zeroIdx:zeroIdx}}function assignBucket(buckets,zeroIdx,entry){if(!entry.out){buckets[0].enqueue(entry)}else if(!entry["in"]){buckets[buckets.length-1].enqueue(entry)}else{buckets[entry.out-entry["in"]+zeroIdx].enqueue(entry)}}},{"./data/list":5,"./graphlib":7,"./lodash":10}],9:[function(require,module,exports){"use strict";var _=require("./lodash"),acyclic=require("./acyclic"),normalize=require("./normalize"),rank=require("./rank"),normalizeRanks=require("./util").normalizeRanks,parentDummyChains=require("./parent-dummy-chains"),removeEmptyRanks=require("./util").removeEmptyRanks,nestingGraph=require("./nesting-graph"),addBorderSegments=require("./add-border-segments"),coordinateSystem=require("./coordinate-system"),order=require("./order"),position=require("./position"),util=require("./util"),Graph=require("./graphlib").Graph;module.exports=layout;function layout(g,opts){var time=opts&&opts.debugTiming?util.time:util.notime;time("layout",function(){var layoutGraph=time(" buildLayoutGraph",function(){return buildLayoutGraph(g)});time(" runLayout",function(){runLayout(layoutGraph,time)});time(" updateInputGraph",function(){updateInputGraph(g,layoutGraph)})})}function runLayout(g,time){time(" makeSpaceForEdgeLabels",function(){makeSpaceForEdgeLabels(g)});time(" removeSelfEdges",function(){removeSelfEdges(g)});time(" acyclic",function(){acyclic.run(g)});time(" nestingGraph.run",function(){nestingGraph.run(g)});time(" rank",function(){rank(util.asNonCompoundGraph(g))});time(" injectEdgeLabelProxies",function(){injectEdgeLabelProxies(g)});time(" removeEmptyRanks",function(){removeEmptyRanks(g)});time(" nestingGraph.cleanup",function(){nestingGraph.cleanup(g)});time(" normalizeRanks",function(){normalizeRanks(g)});time(" assignRankMinMax",function(){assignRankMinMax(g)});time(" removeEdgeLabelProxies",function(){removeEdgeLabelProxies(g)});time(" normalize.run",function(){normalize.run(g)});time(" parentDummyChains",function(){parentDummyChains(g)});time(" addBorderSegments",function(){addBorderSegments(g)});time(" order",function(){order(g)});time(" insertSelfEdges",function(){insertSelfEdges(g)});time(" adjustCoordinateSystem",function(){coordinateSystem.adjust(g)});time(" position",function(){position(g)});time(" positionSelfEdges",function(){positionSelfEdges(g)});time(" removeBorderNodes",function(){removeBorderNodes(g)});time(" normalize.undo",function(){normalize.undo(g)});time(" fixupEdgeLabelCoords",function(){fixupEdgeLabelCoords(g)});time(" undoCoordinateSystem",function(){coordinateSystem.undo(g)});time(" translateGraph",function(){translateGraph(g)});time(" assignNodeIntersects",function(){assignNodeIntersects(g)});time(" reversePoints",function(){reversePointsForReversedEdges(g)});time(" acyclic.undo",function(){acyclic.undo(g)})}function updateInputGraph(inputGraph,layoutGraph){_.each(inputGraph.nodes(),function(v){var inputLabel=inputGraph.node(v),layoutLabel=layoutGraph.node(v);if(inputLabel){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y;if(layoutGraph.children(v).length){inputLabel.width=layoutLabel.width;inputLabel.height=layoutLabel.height}}});_.each(inputGraph.edges(),function(e){var inputLabel=inputGraph.edge(e),layoutLabel=layoutGraph.edge(e);inputLabel.points=layoutLabel.points;if(_.has(layoutLabel,"x")){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y}});inputGraph.graph().width=layoutGraph.graph().width;inputGraph.graph().height=layoutGraph.graph().height}var graphNumAttrs=["nodesep","edgesep","ranksep","marginx","marginy"],graphDefaults={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},graphAttrs=["acyclicer","ranker","rankdir","align"],nodeNumAttrs=["width","height"],nodeDefaults={width:0,height:0},edgeNumAttrs=["minlen","weight","width","height","labeloffset"],edgeDefaults={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},edgeAttrs=["labelpos"];function buildLayoutGraph(inputGraph){var g=new Graph({multigraph:true,compound:true}),graph=canonicalize(inputGraph.graph());g.setGraph(_.merge({},graphDefaults,selectNumberAttrs(graph,graphNumAttrs),_.pick(graph,graphAttrs)));_.each(inputGraph.nodes(),function(v){var node=canonicalize(inputGraph.node(v));g.setNode(v,_.defaults(selectNumberAttrs(node,nodeNumAttrs),nodeDefaults));g.setParent(v,inputGraph.parent(v))});_.each(inputGraph.edges(),function(e){var edge=canonicalize(inputGraph.edge(e));g.setEdge(e,_.merge({},edgeDefaults,selectNumberAttrs(edge,edgeNumAttrs),_.pick(edge,edgeAttrs)))});return g}function makeSpaceForEdgeLabels(g){var graph=g.graph();graph.ranksep/=2;_.each(g.edges(),function(e){var edge=g.edge(e);edge.minlen*=2;if(edge.labelpos.toLowerCase()!=="c"){if(graph.rankdir==="TB"||graph.rankdir==="BT"){edge.width+=edge.labeloffset}else{edge.height+=edge.labeloffset}}})}function injectEdgeLabelProxies(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.width&&edge.height){var v=g.node(e.v),w=g.node(e.w),label={rank:(w.rank-v.rank)/2+v.rank,e:e};util.addDummyNode(g,"edge-proxy",label,"_ep")}})}function assignRankMinMax(g){var maxRank=0;_.each(g.nodes(),function(v){var node=g.node(v);if(node.borderTop){node.minRank=g.node(node.borderTop).rank;node.maxRank=g.node(node.borderBottom).rank;maxRank=_.max(maxRank,node.maxRank)}});g.graph().maxRank=maxRank}function removeEdgeLabelProxies(g){_.each(g.nodes(),function(v){var node=g.node(v);if(node.dummy==="edge-proxy"){g.edge(node.e).labelRank=node.rank;g.removeNode(v)}})}function translateGraph(g){var minX=Number.POSITIVE_INFINITY,maxX=0,minY=Number.POSITIVE_INFINITY,maxY=0,graphLabel=g.graph(),marginX=graphLabel.marginx||0,marginY=graphLabel.marginy||0;function getExtremes(attrs){var x=attrs.x,y=attrs.y,w=attrs.width,h=attrs.height;minX=Math.min(minX,x-w/2);maxX=Math.max(maxX,x+w/2);minY=Math.min(minY,y-h/2);maxY=Math.max(maxY,y+h/2)}_.each(g.nodes(),function(v){getExtremes(g.node(v))});_.each(g.edges(),function(e){var edge=g.edge(e);if(_.has(edge,"x")){getExtremes(edge)}});minX-=marginX;minY-=marginY;_.each(g.nodes(),function(v){var node=g.node(v);node.x-=minX;node.y-=minY});_.each(g.edges(),function(e){var edge=g.edge(e);_.each(edge.points,function(p){p.x-=minX;p.y-=minY});if(_.has(edge,"x")){edge.x-=minX}if(_.has(edge,"y")){edge.y-=minY}});graphLabel.width=maxX-minX+marginX;graphLabel.height=maxY-minY+marginY}function assignNodeIntersects(g){_.each(g.edges(),function(e){var edge=g.edge(e),nodeV=g.node(e.v),nodeW=g.node(e.w),p1,p2;if(!edge.points){edge.points=[];p1=nodeW;p2=nodeV}else{p1=edge.points[0];p2=edge.points[edge.points.length-1]}edge.points.unshift(util.intersectRect(nodeV,p1));edge.points.push(util.intersectRect(nodeW,p2))})}function fixupEdgeLabelCoords(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(_.has(edge,"x")){if(edge.labelpos==="l"||edge.labelpos==="r"){edge.width-=edge.labeloffset}switch(edge.labelpos){case"l":edge.x-=edge.width/2+edge.labeloffset;break;case"r":edge.x+=edge.width/2+edge.labeloffset;break}}})}function reversePointsForReversedEdges(g){_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.reversed){edge.points.reverse()}})}function removeBorderNodes(g){_.each(g.nodes(),function(v){if(g.children(v).length){var node=g.node(v),t=g.node(node.borderTop),b=g.node(node.borderBottom),l=g.node(_.last(node.borderLeft)),r=g.node(_.last(node.borderRight));node.width=Math.abs(r.x-l.x);node.height=Math.abs(b.y-t.y);node.x=l.x+node.width/2;node.y=t.y+node.height/2}});_.each(g.nodes(),function(v){if(g.node(v).dummy==="border"){g.removeNode(v)}})}function removeSelfEdges(g){_.each(g.edges(),function(e){if(e.v===e.w){var node=g.node(e.v);if(!node.selfEdges){node.selfEdges=[]}node.selfEdges.push({e:e,label:g.edge(e)});g.removeEdge(e)}})}function insertSelfEdges(g){var layers=util.buildLayerMatrix(g);_.each(layers,function(layer){var orderShift=0;_.each(layer,function(v,i){var node=g.node(v);node.order=i+orderShift;_.each(node.selfEdges,function(selfEdge){util.addDummyNode(g,"selfedge",{width:selfEdge.label.width,height:selfEdge.label.height,rank:node.rank,order:i+ ++orderShift,e:selfEdge.e,label:selfEdge.label},"_se")});delete node.selfEdges})})}function positionSelfEdges(g){_.each(g.nodes(),function(v){var node=g.node(v);if(node.dummy==="selfedge"){var selfNode=g.node(node.e.v),x=selfNode.x+selfNode.width/2,y=selfNode.y,dx=node.x-x,dy=selfNode.height/2;g.setEdge(node.e,node.label);g.removeNode(v);node.label.points=[{x:x+2*dx/3,y:y-dy},{x:x+5*dx/6,y:y-dy},{x:x+dx,y:y},{x:x+5*dx/6,y:y+dy},{x:x+2*dx/3,y:y+dy}];node.label.x=node.x;node.label.y=node.y}})}function selectNumberAttrs(obj,attrs){return _.mapValues(_.pick(obj,attrs),Number)}function canonicalize(attrs){var newAttrs={};_.each(attrs,function(v,k){newAttrs[k.toLowerCase()]=v});return newAttrs}},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./graphlib":7,"./lodash":10,"./nesting-graph":11,"./normalize":12,"./order":17,"./parent-dummy-chains":22,"./position":24,"./rank":26,"./util":29}],10:[function(require,module,exports){var lodash;if(require){try{lodash=require("lodash")}catch(e){}}if(!lodash){lodash=window._}module.exports=lodash},{lodash:51}],11:[function(require,module,exports){var _=require("./lodash"),util=require("./util");module.exports={run:run,cleanup:cleanup};function run(g){var root=util.addDummyNode(g,"root",{},"_root"),depths=treeDepths(g),height=_.max(depths)-1,nodeSep=2*height+1;g.graph().nestingRoot=root;_.each(g.edges(),function(e){g.edge(e).minlen*=nodeSep});var weight=sumWeights(g)+1;_.each(g.children(),function(child){dfs(g,root,nodeSep,weight,height,depths,child)});g.graph().nodeRankFactor=nodeSep}function dfs(g,root,nodeSep,weight,height,depths,v){var children=g.children(v);if(!children.length){if(v!==root){g.setEdge(root,v,{weight:0,minlen:nodeSep})}return}var top=util.addBorderNode(g,"_bt"),bottom=util.addBorderNode(g,"_bb"),label=g.node(v);g.setParent(top,v);label.borderTop=top;g.setParent(bottom,v);label.borderBottom=bottom;_.each(children,function(child){dfs(g,root,nodeSep,weight,height,depths,child);var childNode=g.node(child),childTop=childNode.borderTop?childNode.borderTop:child,childBottom=childNode.borderBottom?childNode.borderBottom:child,thisWeight=childNode.borderTop?weight:2*weight,minlen=childTop!==childBottom?1:height-depths[v]+1;g.setEdge(top,childTop,{weight:thisWeight,minlen:minlen,nestingEdge:true});g.setEdge(childBottom,bottom,{weight:thisWeight,minlen:minlen,nestingEdge:true})});if(!g.parent(v)){g.setEdge(root,top,{weight:0,minlen:height+depths[v]})}}function treeDepths(g){var depths={};function dfs(v,depth){var children=g.children(v);if(children&&children.length){_.each(children,function(child){dfs(child,depth+1)})}depths[v]=depth}_.each(g.children(),function(v){dfs(v,1)});return depths}function sumWeights(g){return _.reduce(g.edges(),function(acc,e){return acc+g.edge(e).weight},0)}function cleanup(g){var graphLabel=g.graph();g.removeNode(graphLabel.nestingRoot);delete graphLabel.nestingRoot;_.each(g.edges(),function(e){var edge=g.edge(e);if(edge.nestingEdge){g.removeEdge(e)}})}},{"./lodash":10,"./util":29}],12:[function(require,module,exports){"use strict";var _=require("./lodash"),util=require("./util");module.exports={run:run,undo:undo};function run(g){g.graph().dummyChains=[];_.each(g.edges(),function(edge){normalizeEdge(g,edge)})}function normalizeEdge(g,e){var v=e.v,vRank=g.node(v).rank,w=e.w,wRank=g.node(w).rank,name=e.name,edgeLabel=g.edge(e),labelRank=edgeLabel.labelRank;if(wRank===vRank+1)return;g.removeEdge(e);var dummy,attrs,i;for(i=0,++vRank;vRank<wRank;++i,++vRank){edgeLabel.points=[];attrs={width:0,height:0,edgeLabel:edgeLabel,edgeObj:e,rank:vRank};dummy=util.addDummyNode(g,"edge",attrs,"_d");if(vRank===labelRank){attrs.width=edgeLabel.width;attrs.height=edgeLabel.height;attrs.dummy="edge-label";attrs.labelpos=edgeLabel.labelpos}g.setEdge(v,dummy,{weight:edgeLabel.weight},name);if(i===0){g.graph().dummyChains.push(dummy)}v=dummy}g.setEdge(v,w,{weight:edgeLabel.weight},name)}function undo(g){_.each(g.graph().dummyChains,function(v){var node=g.node(v),origLabel=node.edgeLabel,w;g.setEdge(node.edgeObj,origLabel);while(node.dummy){w=g.successors(v)[0];g.removeNode(v);origLabel.points.push({x:node.x,y:node.y});if(node.dummy==="edge-label"){origLabel.x=node.x;origLabel.y=node.y;origLabel.width=node.width;origLabel.height=node.height}v=w;node=g.node(v)}})}},{"./lodash":10,"./util":29}],13:[function(require,module,exports){var _=require("../lodash");module.exports=addSubgraphConstraints;function addSubgraphConstraints(g,cg,vs){var prev={},rootPrev;_.each(vs,function(v){var child=g.parent(v),parent,prevChild;while(child){parent=g.parent(child);if(parent){prevChild=prev[parent];prev[parent]=child}else{prevChild=rootPrev;rootPrev=child}if(prevChild&&prevChild!==child){cg.setEdge(prevChild,child);return}child=parent}})}},{"../lodash":10}],14:[function(require,module,exports){var _=require("../lodash");module.exports=barycenter;function barycenter(g,movable){return _.map(movable,function(v){var inV=g.inEdges(v);if(!inV.length){return{v:v}}else{var result=_.reduce(inV,function(acc,e){var edge=g.edge(e),nodeU=g.node(e.v);return{sum:acc.sum+edge.weight*nodeU.order,weight:acc.weight+edge.weight}},{sum:0,weight:0});return{v:v,barycenter:result.sum/result.weight,weight:result.weight}}})}},{"../lodash":10}],15:[function(require,module,exports){var _=require("../lodash"),Graph=require("../graphlib").Graph;module.exports=buildLayerGraph;function buildLayerGraph(g,rank,relationship){var root=createRootNode(g),result=new Graph({compound:true}).setGraph({root:root}).setDefaultNodeLabel(function(v){return g.node(v)});_.each(g.nodes(),function(v){var node=g.node(v),parent=g.parent(v);if(node.rank===rank||node.minRank<=rank&&rank<=node.maxRank){result.setNode(v);result.setParent(v,parent||root);_.each(g[relationship](v),function(e){var u=e.v===v?e.w:e.v,edge=result.edge(u,v),weight=!_.isUndefined(edge)?edge.weight:0;result.setEdge(u,v,{weight:g.edge(e).weight+weight})});if(_.has(node,"minRank")){result.setNode(v,{borderLeft:node.borderLeft[rank],borderRight:node.borderRight[rank]})}}});return result}function createRootNode(g){var v;while(g.hasNode(v=_.uniqueId("_root")));return v}},{"../graphlib":7,"../lodash":10}],16:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports=crossCount;function crossCount(g,layering){var cc=0;for(var i=1;i<layering.length;++i){cc+=twoLayerCrossCount(g,layering[i-1],layering[i])}return cc}function twoLayerCrossCount(g,northLayer,southLayer){var southPos=_.zipObject(southLayer,_.map(southLayer,function(v,i){return i}));var southEntries=_.flatten(_.map(northLayer,function(v){return _.chain(g.outEdges(v)).map(function(e){return{pos:southPos[e.w],weight:g.edge(e).weight}}).sortBy("pos").value()}),true);var firstIndex=1;while(firstIndex<southLayer.length)firstIndex<<=1;var treeSize=2*firstIndex-1;firstIndex-=1;var tree=_.map(new Array(treeSize),function(){return 0});var cc=0;_.each(southEntries.forEach(function(entry){var index=entry.pos+firstIndex;tree[index]+=entry.weight;var weightSum=0;while(index>0){if(index%2){weightSum+=tree[index+1]}index=index-1>>1;tree[index]+=entry.weight}cc+=entry.weight*weightSum}));return cc}},{"../lodash":10}],17:[function(require,module,exports){"use strict";var _=require("../lodash"),initOrder=require("./init-order"),crossCount=require("./cross-count"),sortSubgraph=require("./sort-subgraph"),buildLayerGraph=require("./build-layer-graph"),addSubgraphConstraints=require("./add-subgraph-constraints"),Graph=require("../graphlib").Graph,util=require("../util");module.exports=order;function order(g){var maxRank=util.maxRank(g),downLayerGraphs=buildLayerGraphs(g,_.range(1,maxRank+1),"inEdges"),upLayerGraphs=buildLayerGraphs(g,_.range(maxRank-1,-1,-1),"outEdges");var layering=initOrder(g);assignOrder(g,layering);var bestCC=Number.POSITIVE_INFINITY,best;for(var i=0,lastBest=0;lastBest<4;++i,++lastBest){sweepLayerGraphs(i%2?downLayerGraphs:upLayerGraphs,i%4>=2);layering=util.buildLayerMatrix(g);var cc=crossCount(g,layering);if(cc<bestCC){lastBest=0;best=_.cloneDeep(layering);bestCC=cc}}assignOrder(g,best)}function buildLayerGraphs(g,ranks,relationship){return _.map(ranks,function(rank){return buildLayerGraph(g,rank,relationship)})}function sweepLayerGraphs(layerGraphs,biasRight){var cg=new Graph;_.each(layerGraphs,function(lg){var root=lg.graph().root;var sorted=sortSubgraph(lg,root,cg,biasRight);_.each(sorted.vs,function(v,i){lg.node(v).order=i});addSubgraphConstraints(lg,cg,sorted.vs)})}function assignOrder(g,layering){_.each(layering,function(layer){_.each(layer,function(v,i){g.node(v).order=i})})}},{"../graphlib":7,"../lodash":10,"../util":29,"./add-subgraph-constraints":13,"./build-layer-graph":15,"./cross-count":16,"./init-order":18,"./sort-subgraph":20}],18:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports=initOrder;function initOrder(g){var visited={},simpleNodes=_.filter(g.nodes(),function(v){return!g.children(v).length}),maxRank=_.max(_.map(simpleNodes,function(v){return g.node(v).rank})),layers=_.map(_.range(maxRank+1),function(){return[]});function dfs(v){if(_.has(visited,v))return;visited[v]=true;var node=g.node(v);layers[node.rank].push(v);_.each(g.successors(v),dfs)}var orderedVs=_.sortBy(simpleNodes,function(v){return g.node(v).rank});_.each(orderedVs,dfs);return layers}},{"../lodash":10}],19:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports=resolveConflicts;function resolveConflicts(entries,cg){var mappedEntries={};_.each(entries,function(entry,i){var tmp=mappedEntries[entry.v]={indegree:0,"in":[],out:[],vs:[entry.v],i:i};if(!_.isUndefined(entry.barycenter)){tmp.barycenter=entry.barycenter;tmp.weight=entry.weight}});_.each(cg.edges(),function(e){var entryV=mappedEntries[e.v],entryW=mappedEntries[e.w];if(!_.isUndefined(entryV)&&!_.isUndefined(entryW)){entryW.indegree++;entryV.out.push(mappedEntries[e.w])}});var sourceSet=_.filter(mappedEntries,function(entry){return!entry.indegree});return doResolveConflicts(sourceSet)}function doResolveConflicts(sourceSet){var entries=[];function handleIn(vEntry){return function(uEntry){if(uEntry.merged){return}if(_.isUndefined(uEntry.barycenter)||_.isUndefined(vEntry.barycenter)||uEntry.barycenter>=vEntry.barycenter){mergeEntries(vEntry,uEntry)}}}function handleOut(vEntry){return function(wEntry){wEntry["in"].push(vEntry);if(--wEntry.indegree===0){sourceSet.push(wEntry)}}}while(sourceSet.length){var entry=sourceSet.pop();entries.push(entry);_.each(entry["in"].reverse(),handleIn(entry));_.each(entry.out,handleOut(entry))}return _.chain(entries).filter(function(entry){return!entry.merged}).map(function(entry){return _.pick(entry,["vs","i","barycenter","weight"])}).value()}function mergeEntries(target,source){var sum=0,weight=0;if(target.weight){sum+=target.barycenter*target.weight;weight+=target.weight}if(source.weight){sum+=source.barycenter*source.weight;weight+=source.weight}target.vs=source.vs.concat(target.vs);target.barycenter=sum/weight;target.weight=weight;target.i=Math.min(source.i,target.i);source.merged=true}},{"../lodash":10}],20:[function(require,module,exports){var _=require("../lodash"),barycenter=require("./barycenter"),resolveConflicts=require("./resolve-conflicts"),sort=require("./sort");module.exports=sortSubgraph;function sortSubgraph(g,v,cg,biasRight){var movable=g.children(v),node=g.node(v),bl=node?node.borderLeft:undefined,br=node?node.borderRight:undefined,subgraphs={};if(bl){movable=_.filter(movable,function(w){return w!==bl&&w!==br})}var barycenters=barycenter(g,movable);_.each(barycenters,function(entry){if(g.children(entry.v).length){var subgraphResult=sortSubgraph(g,entry.v,cg,biasRight);subgraphs[entry.v]=subgraphResult;if(_.has(subgraphResult,"barycenter")){mergeBarycenters(entry,subgraphResult)}}});var entries=resolveConflicts(barycenters,cg);expandSubgraphs(entries,subgraphs);var result=sort(entries,biasRight);if(bl){result.vs=_.flatten([bl,result.vs,br],true);if(g.predecessors(bl).length){var blPred=g.node(g.predecessors(bl)[0]),brPred=g.node(g.predecessors(br)[0]);if(!_.has(result,"barycenter")){result.barycenter=0;result.weight=0}result.barycenter=(result.barycenter*result.weight+blPred.order+brPred.order)/(result.weight+2);result.weight+=2}}return result}function expandSubgraphs(entries,subgraphs){_.each(entries,function(entry){entry.vs=_.flatten(entry.vs.map(function(v){if(subgraphs[v]){return subgraphs[v].vs}return v}),true)})}function mergeBarycenters(target,other){if(!_.isUndefined(target.barycenter)){target.barycenter=(target.barycenter*target.weight+other.barycenter*other.weight)/(target.weight+other.weight);target.weight+=other.weight}else{target.barycenter=other.barycenter;target.weight=other.weight}}},{"../lodash":10,"./barycenter":14,"./resolve-conflicts":19,"./sort":21}],21:[function(require,module,exports){var _=require("../lodash"),util=require("../util");module.exports=sort;function sort(entries,biasRight){var parts=util.partition(entries,function(entry){return _.has(entry,"barycenter")});var sortable=parts.lhs,unsortable=_.sortBy(parts.rhs,function(entry){return-entry.i}),vs=[],sum=0,weight=0,vsIndex=0;sortable.sort(compareWithBias(!!biasRight));vsIndex=consumeUnsortable(vs,unsortable,vsIndex);_.each(sortable,function(entry){vsIndex+=entry.vs.length;vs.push(entry.vs);sum+=entry.barycenter*entry.weight;weight+=entry.weight;vsIndex=consumeUnsortable(vs,unsortable,vsIndex)});var result={vs:_.flatten(vs,true)};if(weight){result.barycenter=sum/weight;result.weight=weight}return result}function consumeUnsortable(vs,unsortable,index){var last;while(unsortable.length&&(last=_.last(unsortable)).i<=index){unsortable.pop();vs.push(last.vs);index++}return index}function compareWithBias(bias){return function(entryV,entryW){if(entryV.barycenter<entryW.barycenter){return-1}else if(entryV.barycenter>entryW.barycenter){return 1}return!bias?entryV.i-entryW.i:entryW.i-entryV.i}}},{"../lodash":10,"../util":29}],22:[function(require,module,exports){var _=require("./lodash");module.exports=parentDummyChains;function parentDummyChains(g){var postorderNums=postorder(g);_.each(g.graph().dummyChains,function(v){var node=g.node(v),edgeObj=node.edgeObj,pathData=findPath(g,postorderNums,edgeObj.v,edgeObj.w),path=pathData.path,lca=pathData.lca,pathIdx=0,pathV=path[pathIdx],ascending=true;while(v!==edgeObj.w){node=g.node(v);if(ascending){while((pathV=path[pathIdx])!==lca&&g.node(pathV).maxRank<node.rank){pathIdx++}if(pathV===lca){ascending=false}}if(!ascending){while(pathIdx<path.length-1&&g.node(pathV=path[pathIdx+1]).minRank<=node.rank){pathIdx++}pathV=path[pathIdx]}g.setParent(v,pathV);v=g.successors(v)[0]}})}function findPath(g,postorderNums,v,w){var vPath=[],wPath=[],low=Math.min(postorderNums[v].low,postorderNums[w].low),lim=Math.max(postorderNums[v].lim,postorderNums[w].lim),parent,lca;parent=v;do{parent=g.parent(parent);vPath.push(parent)}while(parent&&(postorderNums[parent].low>low||lim>postorderNums[parent].lim));lca=parent;parent=w;while((parent=g.parent(parent))!==lca){wPath.push(parent)}return{path:vPath.concat(wPath.reverse()),lca:lca}}function postorder(g){var result={},lim=0;function dfs(v){var low=lim;_.each(g.children(v),dfs);result[v]={low:low,lim:lim++}}_.each(g.children(),dfs);return result}},{"./lodash":10}],23:[function(require,module,exports){"use strict";var _=require("../lodash"),Graph=require("../graphlib").Graph,util=require("../util");module.exports={positionX:positionX,findType1Conflicts:findType1Conflicts,findType2Conflicts:findType2Conflicts,addConflict:addConflict,hasConflict:hasConflict,verticalAlignment:verticalAlignment,horizontalCompaction:horizontalCompaction,alignCoordinates:alignCoordinates,findSmallestWidthAlignment:findSmallestWidthAlignment,balance:balance};function findType1Conflicts(g,layering){var conflicts={};function visitLayer(prevLayer,layer){var k0=0,scanPos=0,prevLayerLength=prevLayer.length,lastNode=_.last(layer);_.each(layer,function(v,i){var w=findOtherInnerSegmentNode(g,v),k1=w?g.node(w).order:prevLayerLength;if(w||v===lastNode){_.each(layer.slice(scanPos,i+1),function(scanNode){_.each(g.predecessors(scanNode),function(u){var uLabel=g.node(u),uPos=uLabel.order;
+if((uPos<k0||k1<uPos)&&!(uLabel.dummy&&g.node(scanNode).dummy)){addConflict(conflicts,u,scanNode)}})});scanPos=i+1;k0=k1}});return layer}_.reduce(layering,visitLayer);return conflicts}function findType2Conflicts(g,layering){var conflicts={};function scan(south,southPos,southEnd,prevNorthBorder,nextNorthBorder){var v;_.each(_.range(southPos,southEnd),function(i){v=south[i];if(g.node(v).dummy){_.each(g.predecessors(v),function(u){var uNode=g.node(u);if(uNode.dummy&&(uNode.order<prevNorthBorder||uNode.order>nextNorthBorder)){addConflict(conflicts,u,v)}})}})}function visitLayer(north,south){var prevNorthPos=-1,nextNorthPos,southPos=0;_.each(south,function(v,southLookahead){if(g.node(v).dummy==="border"){var predecessors=g.predecessors(v);if(predecessors.length){nextNorthPos=g.node(predecessors[0]).order;scan(south,southPos,southLookahead,prevNorthPos,nextNorthPos);southPos=southLookahead;prevNorthPos=nextNorthPos}}scan(south,southPos,south.length,nextNorthPos,north.length)});return south}_.reduce(layering,visitLayer);return conflicts}function findOtherInnerSegmentNode(g,v){if(g.node(v).dummy){return _.find(g.predecessors(v),function(u){return g.node(u).dummy})}}function addConflict(conflicts,v,w){if(v>w){var tmp=v;v=w;w=tmp}var conflictsV=conflicts[v];if(!conflictsV){conflicts[v]=conflictsV={}}conflictsV[w]=true}function hasConflict(conflicts,v,w){if(v>w){var tmp=v;v=w;w=tmp}return _.has(conflicts[v],w)}function verticalAlignment(g,layering,conflicts,neighborFn){var root={},align={},pos={};_.each(layering,function(layer){_.each(layer,function(v,order){root[v]=v;align[v]=v;pos[v]=order})});_.each(layering,function(layer){var prevIdx=-1;_.each(layer,function(v){var ws=neighborFn(v);if(ws.length){ws=_.sortBy(ws,function(w){return pos[w]});var mp=(ws.length-1)/2;for(var i=Math.floor(mp),il=Math.ceil(mp);i<=il;++i){var w=ws[i];if(align[v]===v&&prevIdx<pos[w]&&!hasConflict(conflicts,v,w)){align[w]=v;align[v]=root[v]=root[w];prevIdx=pos[w]}}}})});return{root:root,align:align}}function horizontalCompaction(g,layering,root,align,reverseSep){var xs={},blockG=buildBlockGraph(g,layering,root,reverseSep);var visited={};function pass1(v){if(!_.has(visited,v)){visited[v]=true;xs[v]=_.reduce(blockG.inEdges(v),function(max,e){pass1(e.v);return Math.max(max,xs[e.v]+blockG.edge(e))},0)}}_.each(blockG.nodes(),pass1);function pass2(v){if(visited[v]!==2){visited[v]++;var min=_.reduce(blockG.outEdges(v),function(min,e){pass2(e.w);return Math.min(min,xs[e.w]-blockG.edge(e))},Number.POSITIVE_INFINITY);if(min!==Number.POSITIVE_INFINITY){xs[v]=Math.max(xs[v],min)}}}_.each(blockG.nodes(),pass2);_.each(align,function(v){xs[v]=xs[root[v]]});return xs}function buildBlockGraph(g,layering,root,reverseSep){var blockGraph=new Graph,graphLabel=g.graph(),sepFn=sep(graphLabel.nodesep,graphLabel.edgesep,reverseSep);_.each(layering,function(layer){var u;_.each(layer,function(v){var vRoot=root[v];blockGraph.setNode(vRoot);if(u){var uRoot=root[u],prevMax=blockGraph.edge(uRoot,vRoot);blockGraph.setEdge(uRoot,vRoot,Math.max(sepFn(g,v,u),prevMax||0))}u=v})});return blockGraph}function findSmallestWidthAlignment(g,xss){return _.min(xss,function(xs){var min=_.min(xs,function(x,v){return x-width(g,v)/2}),max=_.max(xs,function(x,v){return x+width(g,v)/2});return max-min})}function alignCoordinates(xss,alignTo){var alignToMin=_.min(alignTo),alignToMax=_.max(alignTo);_.each(["u","d"],function(vert){_.each(["l","r"],function(horiz){var alignment=vert+horiz,xs=xss[alignment],delta;if(xs===alignTo)return;delta=horiz==="l"?alignToMin-_.min(xs):alignToMax-_.max(xs);if(delta){xss[alignment]=_.mapValues(xs,function(x){return x+delta})}})})}function balance(xss,align){return _.mapValues(xss.ul,function(ignore,v){if(align){return xss[align.toLowerCase()][v]}else{var xs=_.sortBy(_.pluck(xss,v));return(xs[1]+xs[2])/2}})}function positionX(g){var layering=util.buildLayerMatrix(g),conflicts=_.merge(findType1Conflicts(g,layering),findType2Conflicts(g,layering));var xss={},adjustedLayering;_.each(["u","d"],function(vert){adjustedLayering=vert==="u"?layering:_.values(layering).reverse();_.each(["l","r"],function(horiz){if(horiz==="r"){adjustedLayering=_.map(adjustedLayering,function(inner){return _.values(inner).reverse()})}var neighborFn=_.bind(vert==="u"?g.predecessors:g.successors,g);var align=verticalAlignment(g,adjustedLayering,conflicts,neighborFn);var xs=horizontalCompaction(g,adjustedLayering,align.root,align.align,horiz==="r");if(horiz==="r"){xs=_.mapValues(xs,function(x){return-x})}xss[vert+horiz]=xs})});var smallestWidth=findSmallestWidthAlignment(g,xss);alignCoordinates(xss,smallestWidth);return balance(xss,g.graph().align)}function sep(nodeSep,edgeSep,reverseSep){return function(g,v,w){var vLabel=g.node(v),wLabel=g.node(w),sum=0,delta;sum+=vLabel.width/2;if(_.has(vLabel,"labelpos")){switch(vLabel.labelpos.toLowerCase()){case"l":delta=-vLabel.width/2;break;case"r":delta=vLabel.width/2;break}}if(delta){sum+=reverseSep?delta:-delta}delta=0;sum+=(vLabel.dummy?edgeSep:nodeSep)/2;sum+=(wLabel.dummy?edgeSep:nodeSep)/2;sum+=wLabel.width/2;if(_.has(wLabel,"labelpos")){switch(wLabel.labelpos.toLowerCase()){case"l":delta=wLabel.width/2;break;case"r":delta=-wLabel.width/2;break}}if(delta){sum+=reverseSep?delta:-delta}delta=0;return sum}}function width(g,v){return g.node(v).width}},{"../graphlib":7,"../lodash":10,"../util":29}],24:[function(require,module,exports){"use strict";var _=require("../lodash"),util=require("../util"),positionX=require("./bk").positionX;module.exports=position;function position(g){g=util.asNonCompoundGraph(g);positionY(g);_.each(positionX(g),function(x,v){g.node(v).x=x})}function positionY(g){var layering=util.buildLayerMatrix(g),rankSep=g.graph().ranksep,prevY=0;_.each(layering,function(layer){var maxHeight=_.max(_.map(layer,function(v){return g.node(v).height}));_.each(layer,function(v){g.node(v).y=prevY+maxHeight/2});prevY+=maxHeight+rankSep})}},{"../lodash":10,"../util":29,"./bk":23}],25:[function(require,module,exports){"use strict";var _=require("../lodash"),Graph=require("../graphlib").Graph,slack=require("./util").slack;module.exports=feasibleTree;function feasibleTree(g){var t=new Graph({directed:false});var start=g.nodes()[0],size=g.nodeCount();t.setNode(start,{});var edge,delta;while(tightTree(t,g)<size){edge=findMinSlackEdge(t,g);delta=t.hasNode(edge.v)?slack(g,edge):-slack(g,edge);shiftRanks(t,g,delta)}return t}function tightTree(t,g){function dfs(v){_.each(g.nodeEdges(v),function(e){var edgeV=e.v,w=v===edgeV?e.w:edgeV;if(!t.hasNode(w)&&!slack(g,e)){t.setNode(w,{});t.setEdge(v,w,{});dfs(w)}})}_.each(t.nodes(),dfs);return t.nodeCount()}function findMinSlackEdge(t,g){return _.min(g.edges(),function(e){if(t.hasNode(e.v)!==t.hasNode(e.w)){return slack(g,e)}})}function shiftRanks(t,g,delta){_.each(t.nodes(),function(v){g.node(v).rank+=delta})}},{"../graphlib":7,"../lodash":10,"./util":28}],26:[function(require,module,exports){"use strict";var rankUtil=require("./util"),longestPath=rankUtil.longestPath,feasibleTree=require("./feasible-tree"),networkSimplex=require("./network-simplex");module.exports=rank;function rank(g){switch(g.graph().ranker){case"network-simplex":networkSimplexRanker(g);break;case"tight-tree":tightTreeRanker(g);break;case"longest-path":longestPathRanker(g);break;default:networkSimplexRanker(g)}}var longestPathRanker=longestPath;function tightTreeRanker(g){longestPath(g);feasibleTree(g)}function networkSimplexRanker(g){networkSimplex(g)}},{"./feasible-tree":25,"./network-simplex":27,"./util":28}],27:[function(require,module,exports){"use strict";var _=require("../lodash"),feasibleTree=require("./feasible-tree"),slack=require("./util").slack,initRank=require("./util").longestPath,preorder=require("../graphlib").alg.preorder,postorder=require("../graphlib").alg.postorder,simplify=require("../util").simplify;module.exports=networkSimplex;networkSimplex.initLowLimValues=initLowLimValues;networkSimplex.initCutValues=initCutValues;networkSimplex.calcCutValue=calcCutValue;networkSimplex.leaveEdge=leaveEdge;networkSimplex.enterEdge=enterEdge;networkSimplex.exchangeEdges=exchangeEdges;function networkSimplex(g){g=simplify(g);initRank(g);var t=feasibleTree(g);initLowLimValues(t);initCutValues(t,g);var e,f;while(e=leaveEdge(t)){f=enterEdge(t,g,e);exchangeEdges(t,g,e,f)}}function initCutValues(t,g){var vs=postorder(t,t.nodes());vs=vs.slice(0,vs.length-1);_.each(vs,function(v){assignCutValue(t,g,v)})}function assignCutValue(t,g,child){var childLab=t.node(child),parent=childLab.parent;t.edge(child,parent).cutvalue=calcCutValue(t,g,child)}function calcCutValue(t,g,child){var childLab=t.node(child),parent=childLab.parent,childIsTail=true,graphEdge=g.edge(child,parent),cutValue=0;if(!graphEdge){childIsTail=false;graphEdge=g.edge(parent,child)}cutValue=graphEdge.weight;_.each(g.nodeEdges(child),function(e){var isOutEdge=e.v===child,other=isOutEdge?e.w:e.v;if(other!==parent){var pointsToHead=isOutEdge===childIsTail,otherWeight=g.edge(e).weight;cutValue+=pointsToHead?otherWeight:-otherWeight;if(isTreeEdge(t,child,other)){var otherCutValue=t.edge(child,other).cutvalue;cutValue+=pointsToHead?-otherCutValue:otherCutValue}}});return cutValue}function initLowLimValues(tree,root){if(arguments.length<2){root=tree.nodes()[0]}dfsAssignLowLim(tree,{},1,root)}function dfsAssignLowLim(tree,visited,nextLim,v,parent){var low=nextLim,label=tree.node(v);visited[v]=true;_.each(tree.neighbors(v),function(w){if(!_.has(visited,w)){nextLim=dfsAssignLowLim(tree,visited,nextLim,w,v)}});label.low=low;label.lim=nextLim++;if(parent){label.parent=parent}else{delete label.parent}return nextLim}function leaveEdge(tree){return _.find(tree.edges(),function(e){return tree.edge(e).cutvalue<0})}function enterEdge(t,g,edge){var v=edge.v,w=edge.w;if(!g.hasEdge(v,w)){v=edge.w;w=edge.v}var vLabel=t.node(v),wLabel=t.node(w),tailLabel=vLabel,flip=false;if(vLabel.lim>wLabel.lim){tailLabel=wLabel;flip=true}var candidates=_.filter(g.edges(),function(edge){return flip===isDescendant(t,t.node(edge.v),tailLabel)&&flip!==isDescendant(t,t.node(edge.w),tailLabel)});return _.min(candidates,function(edge){return slack(g,edge)})}function exchangeEdges(t,g,e,f){var v=e.v,w=e.w;t.removeEdge(v,w);t.setEdge(f.v,f.w,{});initLowLimValues(t);initCutValues(t,g);updateRanks(t,g)}function updateRanks(t,g){var root=_.find(t.nodes(),function(v){return!g.node(v).parent}),vs=preorder(t,root);vs=vs.slice(1);_.each(vs,function(v){var parent=t.node(v).parent,edge=g.edge(v,parent),flipped=false;if(!edge){edge=g.edge(parent,v);flipped=true}g.node(v).rank=g.node(parent).rank+(flipped?edge.minlen:-edge.minlen)})}function isTreeEdge(tree,u,v){return tree.hasEdge(u,v)}function isDescendant(tree,vLabel,rootLabel){return rootLabel.low<=vLabel.lim&&vLabel.lim<=rootLabel.lim}},{"../graphlib":7,"../lodash":10,"../util":29,"./feasible-tree":25,"./util":28}],28:[function(require,module,exports){"use strict";var _=require("../lodash");module.exports={longestPath:longestPath,slack:slack};function longestPath(g){var visited={};function dfs(v){var label=g.node(v);if(_.has(visited,v)){return label.rank}visited[v]=true;var rank=_.min(_.map(g.outEdges(v),function(e){return dfs(e.w)-g.edge(e).minlen}));if(rank===Number.POSITIVE_INFINITY){rank=0}return label.rank=rank}_.each(g.sources(),dfs)}function slack(g,e){return g.node(e.w).rank-g.node(e.v).rank-g.edge(e).minlen}},{"../lodash":10}],29:[function(require,module,exports){"use strict";var _=require("./lodash"),Graph=require("./graphlib").Graph;module.exports={addDummyNode:addDummyNode,simplify:simplify,asNonCompoundGraph:asNonCompoundGraph,successorWeights:successorWeights,predecessorWeights:predecessorWeights,intersectRect:intersectRect,buildLayerMatrix:buildLayerMatrix,normalizeRanks:normalizeRanks,removeEmptyRanks:removeEmptyRanks,addBorderNode:addBorderNode,maxRank:maxRank,partition:partition,time:time,notime:notime};function addDummyNode(g,type,attrs,name){var v;do{v=_.uniqueId(name)}while(g.hasNode(v));attrs.dummy=type;g.setNode(v,attrs);return v}function simplify(g){var simplified=(new Graph).setGraph(g.graph());_.each(g.nodes(),function(v){simplified.setNode(v,g.node(v))});_.each(g.edges(),function(e){var simpleLabel=simplified.edge(e.v,e.w)||{weight:0,minlen:1},label=g.edge(e);simplified.setEdge(e.v,e.w,{weight:simpleLabel.weight+label.weight,minlen:Math.max(simpleLabel.minlen,label.minlen)})});return simplified}function asNonCompoundGraph(g){var simplified=new Graph({multigraph:g.isMultigraph()}).setGraph(g.graph());_.each(g.nodes(),function(v){if(!g.children(v).length){simplified.setNode(v,g.node(v))}});_.each(g.edges(),function(e){simplified.setEdge(e,g.edge(e))});return simplified}function successorWeights(g){var weightMap=_.map(g.nodes(),function(v){var sucs={};_.each(g.outEdges(v),function(e){sucs[e.w]=(sucs[e.w]||0)+g.edge(e).weight});return sucs});return _.zipObject(g.nodes(),weightMap)}function predecessorWeights(g){var weightMap=_.map(g.nodes(),function(v){var preds={};_.each(g.inEdges(v),function(e){preds[e.v]=(preds[e.v]||0)+g.edge(e).weight});return preds});return _.zipObject(g.nodes(),weightMap)}function intersectRect(rect,point){var x=rect.x;var y=rect.y;var dx=point.x-x;var dy=point.y-y;var w=rect.width/2;var h=rect.height/2;if(!dx&&!dy){throw new Error("Not possible to find intersection inside of the rectangle")}var sx,sy;if(Math.abs(dy)*w>Math.abs(dx)*h){if(dy<0){h=-h}sx=h*dx/dy;sy=h}else{if(dx<0){w=-w}sx=w;sy=w*dy/dx}return{x:x+sx,y:y+sy}}function buildLayerMatrix(g){var layering=_.map(_.range(maxRank(g)+1),function(){return[]});_.each(g.nodes(),function(v){var node=g.node(v),rank=node.rank;if(!_.isUndefined(rank)){layering[rank][node.order]=v}});return layering}function normalizeRanks(g){var min=_.min(_.map(g.nodes(),function(v){return g.node(v).rank}));_.each(g.nodes(),function(v){var node=g.node(v);if(_.has(node,"rank")){node.rank-=min}})}function removeEmptyRanks(g){var offset=_.min(_.map(g.nodes(),function(v){return g.node(v).rank}));var layers=[];_.each(g.nodes(),function(v){var rank=g.node(v).rank-offset;if(!_.has(layers,rank)){layers[rank]=[]}layers[rank].push(v)});var delta=0,nodeRankFactor=g.graph().nodeRankFactor;_.each(layers,function(vs,i){if(_.isUndefined(vs)&&i%nodeRankFactor!==0){--delta}else if(delta){_.each(vs,function(v){g.node(v).rank+=delta})}})}function addBorderNode(g,prefix,rank,order){var node={width:0,height:0};if(arguments.length>=4){node.rank=rank;node.order=order}return addDummyNode(g,"border",node,prefix)}function maxRank(g){return _.max(_.map(g.nodes(),function(v){var rank=g.node(v).rank;if(!_.isUndefined(rank)){return rank}}))}function partition(collection,fn){var result={lhs:[],rhs:[]};_.each(collection,function(value){if(fn(value)){result.lhs.push(value)}else{result.rhs.push(value)}});return result}function time(name,fn){var start=_.now();try{return fn()}finally{console.log(name+" time: "+(_.now()-start)+"ms")}}function notime(name,fn){return fn()}},{"./graphlib":7,"./lodash":10}],30:[function(require,module,exports){module.exports="0.7.1"},{}],31:[function(require,module,exports){var lib=require("./lib");module.exports={Graph:lib.Graph,json:require("./lib/json"),alg:require("./lib/alg"),version:lib.version}},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){var _=require("../lodash");module.exports=components;function components(g){var visited={},cmpts=[],cmpt;function dfs(v){if(_.has(visited,v))return;visited[v]=true;cmpt.push(v);_.each(g.successors(v),dfs);_.each(g.predecessors(v),dfs)}_.each(g.nodes(),function(v){cmpt=[];dfs(v);if(cmpt.length){cmpts.push(cmpt)}});return cmpts}},{"../lodash":49}],33:[function(require,module,exports){var _=require("../lodash");module.exports=dfs;function dfs(g,vs,order){if(!_.isArray(vs)){vs=[vs]}var acc=[],visited={};_.each(vs,function(v){if(!g.hasNode(v)){throw new Error("Graph does not have node: "+v)}doDfs(g,v,order==="post",visited,acc)});return acc}function doDfs(g,v,postorder,visited,acc){if(!_.has(visited,v)){visited[v]=true;if(!postorder){acc.push(v)}_.each(g.neighbors(v),function(w){doDfs(g,w,postorder,visited,acc)});if(postorder){acc.push(v)}}}},{"../lodash":49}],34:[function(require,module,exports){var dijkstra=require("./dijkstra"),_=require("../lodash");module.exports=dijkstraAll;function dijkstraAll(g,weightFunc,edgeFunc){return _.transform(g.nodes(),function(acc,v){acc[v]=dijkstra(g,v,weightFunc,edgeFunc)},{})}},{"../lodash":49,"./dijkstra":35}],35:[function(require,module,exports){var _=require("../lodash"),PriorityQueue=require("../data/priority-queue");module.exports=dijkstra;var DEFAULT_WEIGHT_FUNC=_.constant(1);function dijkstra(g,source,weightFn,edgeFn){return runDijkstra(g,String(source),weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runDijkstra(g,source,weightFn,edgeFn){var results={},pq=new PriorityQueue,v,vEntry;var updateNeighbors=function(edge){var w=edge.v!==v?edge.v:edge.w,wEntry=results[w],weight=weightFn(edge),distance=vEntry.distance+weight;if(weight<0){throw new Error("dijkstra does not allow negative edge weights. "+"Bad edge: "+edge+" Weight: "+weight)}if(distance<wEntry.distance){wEntry.distance=distance;wEntry.predecessor=v;pq.decrease(w,distance)}};g.nodes().forEach(function(v){var distance=v===source?0:Number.POSITIVE_INFINITY;results[v]={distance:distance};pq.add(v,distance)});while(pq.size()>0){v=pq.removeMin();vEntry=results[v];if(vEntry.distance===Number.POSITIVE_INFINITY){break}edgeFn(v).forEach(updateNeighbors)}return results}},{"../data/priority-queue":45,"../lodash":49}],36:[function(require,module,exports){var _=require("../lodash"),tarjan=require("./tarjan");module.exports=findCycles;function findCycles(g){return _.filter(tarjan(g),function(cmpt){return cmpt.length>1})}},{"../lodash":49,"./tarjan":43}],37:[function(require,module,exports){var _=require("../lodash");module.exports=floydWarshall;var DEFAULT_WEIGHT_FUNC=_.constant(1);function floydWarshall(g,weightFn,edgeFn){return runFloydWarshall(g,weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runFloydWarshall(g,weightFn,edgeFn){var results={},nodes=g.nodes();nodes.forEach(function(v){results[v]={};results[v][v]={distance:0};nodes.forEach(function(w){if(v!==w){results[v][w]={distance:Number.POSITIVE_INFINITY}}});edgeFn(v).forEach(function(edge){var w=edge.v===v?edge.w:edge.v,d=weightFn(edge);results[v][w]={distance:d,predecessor:v}})});nodes.forEach(function(k){var rowK=results[k];nodes.forEach(function(i){var rowI=results[i];nodes.forEach(function(j){var ik=rowI[k];var kj=rowK[j];var ij=rowI[j];var altDistance=ik.distance+kj.distance;if(altDistance<ij.distance){ij.distance=altDistance;ij.predecessor=kj.predecessor}})})});return results}},{"../lodash":49}],38:[function(require,module,exports){module.exports={components:require("./components"),dijkstra:require("./dijkstra"),dijkstraAll:require("./dijkstra-all"),findCycles:require("./find-cycles"),floydWarshall:require("./floyd-warshall"),isAcyclic:require("./is-acyclic"),postorder:require("./postorder"),preorder:require("./preorder"),prim:require("./prim"),tarjan:require("./tarjan"),topsort:require("./topsort")}},{"./components":32,"./dijkstra":35,"./dijkstra-all":34,"./find-cycles":36,"./floyd-warshall":37,"./is-acyclic":39,"./postorder":40,"./preorder":41,"./prim":42,"./tarjan":43,"./topsort":44}],39:[function(require,module,exports){var topsort=require("./topsort");module.exports=isAcyclic;function isAcyclic(g){try{topsort(g)}catch(e){if(e instanceof topsort.CycleException){return false}throw e}return true}},{"./topsort":44}],40:[function(require,module,exports){var dfs=require("./dfs");module.exports=postorder;function postorder(g,vs){return dfs(g,vs,"post")}},{"./dfs":33}],41:[function(require,module,exports){var dfs=require("./dfs");module.exports=preorder;function preorder(g,vs){return dfs(g,vs,"pre")}},{"./dfs":33}],42:[function(require,module,exports){var _=require("../lodash"),Graph=require("../graph"),PriorityQueue=require("../data/priority-queue");module.exports=prim;function prim(g,weightFunc){var result=new Graph,parents={},pq=new PriorityQueue,v;function updateNeighbors(edge){var w=edge.v===v?edge.w:edge.v,pri=pq.priority(w);if(pri!==undefined){var edgeWeight=weightFunc(edge);if(edgeWeight<pri){parents[w]=v;pq.decrease(w,edgeWeight)}}}if(g.nodeCount()===0){return result}_.each(g.nodes(),function(v){pq.add(v,Number.POSITIVE_INFINITY);result.setNode(v)});pq.decrease(g.nodes()[0],0);var init=false;while(pq.size()>0){v=pq.removeMin();if(_.has(parents,v)){result.setEdge(v,parents[v])}else if(init){throw new Error("Input graph is not connected: "+g)}else{init=true}g.nodeEdges(v).forEach(updateNeighbors)}return result}},{"../data/priority-queue":45,"../graph":46,"../lodash":49}],43:[function(require,module,exports){var _=require("../lodash");module.exports=tarjan;function tarjan(g){var index=0,stack=[],visited={},results=[];function dfs(v){var entry=visited[v]={onStack:true,lowlink:index,index:index++};stack.push(v);g.successors(v).forEach(function(w){if(!_.has(visited,w)){dfs(w);entry.lowlink=Math.min(entry.lowlink,visited[w].lowlink)}else if(visited[w].onStack){entry.lowlink=Math.min(entry.lowlink,visited[w].index)}});if(entry.lowlink===entry.index){var cmpt=[],w;do{w=stack.pop();visited[w].onStack=false;cmpt.push(w)}while(v!==w);results.push(cmpt)}}g.nodes().forEach(function(v){if(!_.has(visited,v)){dfs(v)}});return results}},{"../lodash":49}],44:[function(require,module,exports){var _=require("../lodash");module.exports=topsort;topsort.CycleException=CycleException;function topsort(g){var visited={},stack={},results=[];function visit(node){if(_.has(stack,node)){throw new CycleException}if(!_.has(visited,node)){stack[node]=true;visited[node]=true;_.each(g.predecessors(node),visit);delete stack[node];results.push(node)}}_.each(g.sinks(),visit);if(_.size(visited)!==g.nodeCount()){throw new CycleException}return results}function CycleException(){}},{"../lodash":49}],45:[function(require,module,exports){var _=require("../lodash");module.exports=PriorityQueue;function PriorityQueue(){this._arr=[];this._keyIndices={}}PriorityQueue.prototype.size=function(){return this._arr.length};PriorityQueue.prototype.keys=function(){return this._arr.map(function(x){return x.key})};PriorityQueue.prototype.has=function(key){return _.has(this._keyIndices,key)};PriorityQueue.prototype.priority=function(key){var index=this._keyIndices[key];if(index!==undefined){return this._arr[index].priority}};PriorityQueue.prototype.min=function(){if(this.size()===0){throw new Error("Queue underflow")}return this._arr[0].key};PriorityQueue.prototype.add=function(key,priority){var keyIndices=this._keyIndices;key=String(key);if(!_.has(keyIndices,key)){var arr=this._arr;var index=arr.length;keyIndices[key]=index;arr.push({key:key,priority:priority});this._decrease(index);return true}return false};PriorityQueue.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var min=this._arr.pop();delete this._keyIndices[min.key];this._heapify(0);return min.key};PriorityQueue.prototype.decrease=function(key,priority){var index=this._keyIndices[key];if(priority>this._arr[index].priority){throw new Error("New priority is greater than current priority. "+"Key: "+key+" Old: "+this._arr[index].priority+" New: "+priority)}this._arr[index].priority=priority;this._decrease(index)};PriorityQueue.prototype._heapify=function(i){var arr=this._arr;var l=2*i,r=l+1,largest=i;if(l<arr.length){largest=arr[l].priority<arr[largest].priority?l:largest;if(r<arr.length){largest=arr[r].priority<arr[largest].priority?r:largest}if(largest!==i){this._swap(i,largest);this._heapify(largest)}}};PriorityQueue.prototype._decrease=function(index){var arr=this._arr;var priority=arr[index].priority;var parent;while(index!==0){parent=index>>1;if(arr[parent].priority<priority){break}this._swap(index,parent);index=parent}};PriorityQueue.prototype._swap=function(i,j){var arr=this._arr;var keyIndices=this._keyIndices;var origArrI=arr[i];var origArrJ=arr[j];arr[i]=origArrJ;arr[j]=origArrI;keyIndices[origArrJ.key]=i;keyIndices[origArrI.key]=j}},{"../lodash":49}],46:[function(require,module,exports){"use strict";var _=require("./lodash");module.exports=Graph;var DEFAULT_EDGE_NAME="\x00",GRAPH_NODE="\x00",EDGE_KEY_DELIM="";function Graph(opts){this._isDirected=_.has(opts,"directed")?opts.directed:true;this._isMultigraph=_.has(opts,"multigraph")?opts.multigraph:false;this._isCompound=_.has(opts,"compound")?opts.compound:false;this._label=undefined;this._defaultNodeLabelFn=_.constant(undefined);this._defaultEdgeLabelFn=_.constant(undefined);this._nodes={};if(this._isCompound){this._parent={};this._children={};this._children[GRAPH_NODE]={}}this._in={};this._preds={};this._out={};this._sucs={};this._edgeObjs={};this._edgeLabels={}}Graph.prototype._nodeCount=0;Graph.prototype._edgeCount=0;Graph.prototype.isDirected=function(){return this._isDirected};Graph.prototype.isMultigraph=function(){return this._isMultigraph};Graph.prototype.isCompound=function(){return this._isCompound};Graph.prototype.setGraph=function(label){this._label=label;return this};Graph.prototype.graph=function(){return this._label};Graph.prototype.setDefaultNodeLabel=function(newDefault){if(!_.isFunction(newDefault)){newDefault=_.constant(newDefault)}this._defaultNodeLabelFn=newDefault;return this};Graph.prototype.nodeCount=function(){return this._nodeCount};Graph.prototype.nodes=function(){return _.keys(this._nodes)};Graph.prototype.sources=function(){return _.filter(this.nodes(),function(v){return _.isEmpty(this._in[v])},this)};Graph.prototype.sinks=function(){return _.filter(this.nodes(),function(v){return _.isEmpty(this._out[v])},this)};Graph.prototype.setNodes=function(vs,value){var args=arguments;_.each(vs,function(v){if(args.length>1){this.setNode(v,value)}else{this.setNode(v)}},this);return this};Graph.prototype.setNode=function(v,value){if(_.has(this._nodes,v)){if(arguments.length>1){this._nodes[v]=value}return this}this._nodes[v]=arguments.length>1?value:this._defaultNodeLabelFn(v);if(this._isCompound){this._parent[v]=GRAPH_NODE;this._children[v]={};this._children[GRAPH_NODE][v]=true}this._in[v]={};this._preds[v]={};this._out[v]={};this._sucs[v]={};++this._nodeCount;return this};Graph.prototype.node=function(v){return this._nodes[v]};Graph.prototype.hasNode=function(v){return _.has(this._nodes,v)};Graph.prototype.removeNode=function(v){var self=this;if(_.has(this._nodes,v)){var removeEdge=function(e){self.removeEdge(self._edgeObjs[e])};delete this._nodes[v];if(this._isCompound){this._removeFromParentsChildList(v);delete this._parent[v];_.each(this.children(v),function(child){this.setParent(child)},this);delete this._children[v]}_.each(_.keys(this._in[v]),removeEdge);delete this._in[v];delete this._preds[v];_.each(_.keys(this._out[v]),removeEdge);delete this._out[v];delete this._sucs[v];--this._nodeCount}return this};Graph.prototype.setParent=function(v,parent){if(!this._isCompound){throw new Error("Cannot set parent in a non-compound graph")}if(_.isUndefined(parent)){parent=GRAPH_NODE}else{for(var ancestor=parent;!_.isUndefined(ancestor);ancestor=this.parent(ancestor)){if(ancestor===v){throw new Error("Setting "+parent+" as parent of "+v+" would create create a cycle")}}this.setNode(parent)}this.setNode(v);this._removeFromParentsChildList(v);this._parent[v]=parent;this._children[parent][v]=true;return this};Graph.prototype._removeFromParentsChildList=function(v){delete this._children[this._parent[v]][v]};Graph.prototype.parent=function(v){if(this._isCompound){var parent=this._parent[v];if(parent!==GRAPH_NODE){return parent}}};Graph.prototype.children=function(v){if(_.isUndefined(v)){v=GRAPH_NODE}if(this._isCompound){var children=this._children[v];if(children){return _.keys(children)}}else if(v===GRAPH_NODE){return this.nodes()}else if(this.hasNode(v)){return[]}};Graph.prototype.predecessors=function(v){var predsV=this._preds[v];if(predsV){return _.keys(predsV)}};Graph.prototype.successors=function(v){var sucsV=this._sucs[v];if(sucsV){return _.keys(sucsV)}};Graph.prototype.neighbors=function(v){var preds=this.predecessors(v);if(preds){return _.union(preds,this.successors(v))}};Graph.prototype.setDefaultEdgeLabel=function(newDefault){if(!_.isFunction(newDefault)){newDefault=_.constant(newDefault)}this._defaultEdgeLabelFn=newDefault;return this};Graph.prototype.edgeCount=function(){return this._edgeCount};Graph.prototype.edges=function(){return _.values(this._edgeObjs)};Graph.prototype.setPath=function(vs,value){var self=this,args=arguments;_.reduce(vs,function(v,w){if(args.length>1){self.setEdge(v,w,value)}else{self.setEdge(v,w)}return w});return this};Graph.prototype.setEdge=function(){var v,w,name,value,valueSpecified=false;if(_.isPlainObject(arguments[0])){v=arguments[0].v;w=arguments[0].w;name=arguments[0].name;if(arguments.length===2){value=arguments[1];valueSpecified=true}}else{v=arguments[0];w=arguments[1];name=arguments[3];if(arguments.length>2){value=arguments[2];valueSpecified=true}}v=""+v;w=""+w;if(!_.isUndefined(name)){name=""+name}var e=edgeArgsToId(this._isDirected,v,w,name);if(_.has(this._edgeLabels,e)){if(valueSpecified){this._edgeLabels[e]=value}return this}if(!_.isUndefined(name)&&!this._isMultigraph){throw new Error("Cannot set a named edge when isMultigraph = false")}this.setNode(v);this.setNode(w);this._edgeLabels[e]=valueSpecified?value:this._defaultEdgeLabelFn(v,w,name);var edgeObj=edgeArgsToObj(this._isDirected,v,w,name);v=edgeObj.v;w=edgeObj.w;Object.freeze(edgeObj);this._edgeObjs[e]=edgeObj;incrementOrInitEntry(this._preds[w],v);incrementOrInitEntry(this._sucs[v],w);this._in[w][e]=edgeObj;this._out[v][e]=edgeObj;this._edgeCount++;return this};Graph.prototype.edge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return this._edgeLabels[e]};Graph.prototype.hasEdge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return _.has(this._edgeLabels,e)};Graph.prototype.removeEdge=function(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name),edge=this._edgeObjs[e];if(edge){v=edge.v;w=edge.w;delete this._edgeLabels[e];delete this._edgeObjs[e];decrementOrRemoveEntry(this._preds[w],v);decrementOrRemoveEntry(this._sucs[v],w);delete this._in[w][e];delete this._out[v][e];this._edgeCount--}return this};Graph.prototype.inEdges=function(v,u){var inV=this._in[v];if(inV){var edges=_.values(inV);if(!u){return edges}return _.filter(edges,function(edge){return edge.v===u})}};Graph.prototype.outEdges=function(v,w){var outV=this._out[v];if(outV){var edges=_.values(outV);if(!w){return edges}return _.filter(edges,function(edge){return edge.w===w})}};Graph.prototype.nodeEdges=function(v,w){var inEdges=this.inEdges(v,w);if(inEdges){return inEdges.concat(this.outEdges(v,w))}};function incrementOrInitEntry(map,k){if(_.has(map,k)){map[k]++}else{map[k]=1}}function decrementOrRemoveEntry(map,k){if(!--map[k]){delete map[k]}}function edgeArgsToId(isDirected,v,w,name){if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}return v+EDGE_KEY_DELIM+w+EDGE_KEY_DELIM+(_.isUndefined(name)?DEFAULT_EDGE_NAME:name)}function edgeArgsToObj(isDirected,v,w,name){if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}var edgeObj={v:v,w:w};if(name){edgeObj.name=name}return edgeObj}function edgeObjToId(isDirected,edgeObj){return edgeArgsToId(isDirected,edgeObj.v,edgeObj.w,edgeObj.name)}},{"./lodash":49}],47:[function(require,module,exports){module.exports={Graph:require("./graph"),version:require("./version")}},{"./graph":46,"./version":50}],48:[function(require,module,exports){var _=require("./lodash"),Graph=require("./graph");module.exports={write:write,read:read};function write(g){var json={options:{directed:g.isDirected(),multigraph:g.isMultigraph(),compound:g.isCompound()},nodes:writeNodes(g),edges:writeEdges(g)};if(!_.isUndefined(g.graph())){json.value=_.clone(g.graph())
+}return json}function writeNodes(g){return _.map(g.nodes(),function(v){var nodeValue=g.node(v),parent=g.parent(v),node={v:v};if(!_.isUndefined(nodeValue)){node.value=nodeValue}if(!_.isUndefined(parent)){node.parent=parent}return node})}function writeEdges(g){return _.map(g.edges(),function(e){var edgeValue=g.edge(e),edge={v:e.v,w:e.w};if(!_.isUndefined(e.name)){edge.name=e.name}if(!_.isUndefined(edgeValue)){edge.value=edgeValue}return edge})}function read(json){var g=new Graph(json.options).setGraph(json.value);_.each(json.nodes,function(entry){g.setNode(entry.v,entry.value);if(entry.parent){g.setParent(entry.v,entry.parent)}});_.each(json.edges,function(entry){g.setEdge({v:entry.v,w:entry.w,name:entry.name},entry.value)});return g}},{"./graph":46,"./lodash":49}],49:[function(require,module,exports){module.exports=require(10)},{"/Users/cpettitt/projects/dagre/lib/lodash.js":10,lodash:51}],50:[function(require,module,exports){module.exports="1.0.1"},{}],51:[function(require,module,exports){(function(global){(function(){var undefined;var arrayPool=[],objectPool=[];var idCounter=0;var keyPrefix=+new Date+"";var largeArraySize=75;var maxPoolSize=40;var whitespace=" \f "+"\n\r\u2028\u2029"+" ";var reEmptyStringLeading=/\b__p \+= '';/g,reEmptyStringMiddle=/\b(__p \+=) '' \+/g,reEmptyStringTrailing=/(__e\(.*?\)|\b__t\)) \+\n'';/g;var reEsTemplate=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;var reFlags=/\w*$/;var reFuncName=/^\s*function[ \n\r\t]+\w/;var reInterpolate=/<%=([\s\S]+?)%>/g;var reLeadingSpacesAndZeros=RegExp("^["+whitespace+"]*0+(?=.$)");var reNoMatch=/($^)/;var reThis=/\bthis\b/;var reUnescapedString=/['\n\r\t\u2028\u2029\\]/g;var contextProps=["Array","Boolean","Date","Function","Math","Number","Object","RegExp","String","_","attachEvent","clearTimeout","isFinite","isNaN","parseInt","setTimeout"];var templateCounter=0;var argsClass="[object Arguments]",arrayClass="[object Array]",boolClass="[object Boolean]",dateClass="[object Date]",funcClass="[object Function]",numberClass="[object Number]",objectClass="[object Object]",regexpClass="[object RegExp]",stringClass="[object String]";var cloneableClasses={};cloneableClasses[funcClass]=false;cloneableClasses[argsClass]=cloneableClasses[arrayClass]=cloneableClasses[boolClass]=cloneableClasses[dateClass]=cloneableClasses[numberClass]=cloneableClasses[objectClass]=cloneableClasses[regexpClass]=cloneableClasses[stringClass]=true;var debounceOptions={leading:false,maxWait:0,trailing:false};var descriptor={configurable:false,enumerable:false,value:null,writable:false};var objectTypes={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false};var stringEscapes={"\\":"\\","'":"'","\n":"n","\r":"r"," ":"t","\u2028":"u2028","\u2029":"u2029"};var root=objectTypes[typeof window]&&window||this;var freeExports=objectTypes[typeof exports]&&exports&&!exports.nodeType&&exports;var freeModule=objectTypes[typeof module]&&module&&!module.nodeType&&module;var moduleExports=freeModule&&freeModule.exports===freeExports&&freeExports;var freeGlobal=objectTypes[typeof global]&&global;if(freeGlobal&&(freeGlobal.global===freeGlobal||freeGlobal.window===freeGlobal)){root=freeGlobal}function baseIndexOf(array,value,fromIndex){var index=(fromIndex||0)-1,length=array?array.length:0;while(++index<length){if(array[index]===value){return index}}return-1}function cacheIndexOf(cache,value){var type=typeof value;cache=cache.cache;if(type=="boolean"||value==null){return cache[value]?0:-1}if(type!="number"&&type!="string"){type="object"}var key=type=="number"?value:keyPrefix+value;cache=(cache=cache[type])&&cache[key];return type=="object"?cache&&baseIndexOf(cache,value)>-1?0:-1:cache?0:-1}function cachePush(value){var cache=this.cache,type=typeof value;if(type=="boolean"||value==null){cache[value]=true}else{if(type!="number"&&type!="string"){type="object"}var key=type=="number"?value:keyPrefix+value,typeCache=cache[type]||(cache[type]={});if(type=="object"){(typeCache[key]||(typeCache[key]=[])).push(value)}else{typeCache[key]=true}}}function charAtCallback(value){return value.charCodeAt(0)}function compareAscending(a,b){var ac=a.criteria,bc=b.criteria,index=-1,length=ac.length;while(++index<length){var value=ac[index],other=bc[index];if(value!==other){if(value>other||typeof value=="undefined"){return 1}if(value<other||typeof other=="undefined"){return-1}}}return a.index-b.index}function createCache(array){var index=-1,length=array.length,first=array[0],mid=array[length/2|0],last=array[length-1];if(first&&typeof first=="object"&&mid&&typeof mid=="object"&&last&&typeof last=="object"){return false}var cache=getObject();cache["false"]=cache["null"]=cache["true"]=cache["undefined"]=false;var result=getObject();result.array=array;result.cache=cache;result.push=cachePush;while(++index<length){result.push(array[index])}return result}function escapeStringChar(match){return"\\"+stringEscapes[match]}function getArray(){return arrayPool.pop()||[]}function getObject(){return objectPool.pop()||{array:null,cache:null,criteria:null,"false":false,index:0,"null":false,number:null,object:null,push:null,string:null,"true":false,undefined:false,value:null}}function releaseArray(array){array.length=0;if(arrayPool.length<maxPoolSize){arrayPool.push(array)}}function releaseObject(object){var cache=object.cache;if(cache){releaseObject(cache)}object.array=object.cache=object.criteria=object.object=object.number=object.string=object.value=null;if(objectPool.length<maxPoolSize){objectPool.push(object)}}function slice(array,start,end){start||(start=0);if(typeof end=="undefined"){end=array?array.length:0}var index=-1,length=end-start||0,result=Array(length<0?0:length);while(++index<length){result[index]=array[start+index]}return result}function runInContext(context){context=context?_.defaults(root.Object(),context,_.pick(root,contextProps)):root;var Array=context.Array,Boolean=context.Boolean,Date=context.Date,Function=context.Function,Math=context.Math,Number=context.Number,Object=context.Object,RegExp=context.RegExp,String=context.String,TypeError=context.TypeError;var arrayRef=[];var objectProto=Object.prototype;var oldDash=context._;var toString=objectProto.toString;var reNative=RegExp("^"+String(toString).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$");var ceil=Math.ceil,clearTimeout=context.clearTimeout,floor=Math.floor,fnToString=Function.prototype.toString,getPrototypeOf=isNative(getPrototypeOf=Object.getPrototypeOf)&&getPrototypeOf,hasOwnProperty=objectProto.hasOwnProperty,push=arrayRef.push,setTimeout=context.setTimeout,splice=arrayRef.splice,unshift=arrayRef.unshift;var defineProperty=function(){try{var o={},func=isNative(func=Object.defineProperty)&&func,result=func(o,o,o)&&func}catch(e){}return result}();var nativeCreate=isNative(nativeCreate=Object.create)&&nativeCreate,nativeIsArray=isNative(nativeIsArray=Array.isArray)&&nativeIsArray,nativeIsFinite=context.isFinite,nativeIsNaN=context.isNaN,nativeKeys=isNative(nativeKeys=Object.keys)&&nativeKeys,nativeMax=Math.max,nativeMin=Math.min,nativeParseInt=context.parseInt,nativeRandom=Math.random;var ctorByClass={};ctorByClass[arrayClass]=Array;ctorByClass[boolClass]=Boolean;ctorByClass[dateClass]=Date;ctorByClass[funcClass]=Function;ctorByClass[objectClass]=Object;ctorByClass[numberClass]=Number;ctorByClass[regexpClass]=RegExp;ctorByClass[stringClass]=String;function lodash(value){return value&&typeof value=="object"&&!isArray(value)&&hasOwnProperty.call(value,"__wrapped__")?value:new lodashWrapper(value)}function lodashWrapper(value,chainAll){this.__chain__=!!chainAll;this.__wrapped__=value}lodashWrapper.prototype=lodash.prototype;var support=lodash.support={};support.funcDecomp=!isNative(context.WinRTError)&&reThis.test(runInContext);support.funcNames=typeof Function.name=="string";lodash.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:reInterpolate,variable:"",imports:{_:lodash}};function baseBind(bindData){var func=bindData[0],partialArgs=bindData[2],thisArg=bindData[4];function bound(){if(partialArgs){var args=slice(partialArgs);push.apply(args,arguments)}if(this instanceof bound){var thisBinding=baseCreate(func.prototype),result=func.apply(thisBinding,args||arguments);return isObject(result)?result:thisBinding}return func.apply(thisArg,args||arguments)}setBindData(bound,bindData);return bound}function baseClone(value,isDeep,callback,stackA,stackB){if(callback){var result=callback(value);if(typeof result!="undefined"){return result}}var isObj=isObject(value);if(isObj){var className=toString.call(value);if(!cloneableClasses[className]){return value}var ctor=ctorByClass[className];switch(className){case boolClass:case dateClass:return new ctor(+value);case numberClass:case stringClass:return new ctor(value);case regexpClass:result=ctor(value.source,reFlags.exec(value));result.lastIndex=value.lastIndex;return result}}else{return value}var isArr=isArray(value);if(isDeep){var initedStack=!stackA;stackA||(stackA=getArray());stackB||(stackB=getArray());var length=stackA.length;while(length--){if(stackA[length]==value){return stackB[length]}}result=isArr?ctor(value.length):{}}else{result=isArr?slice(value):assign({},value)}if(isArr){if(hasOwnProperty.call(value,"index")){result.index=value.index}if(hasOwnProperty.call(value,"input")){result.input=value.input}}if(!isDeep){return result}stackA.push(value);stackB.push(result);(isArr?forEach:forOwn)(value,function(objValue,key){result[key]=baseClone(objValue,isDeep,callback,stackA,stackB)});if(initedStack){releaseArray(stackA);releaseArray(stackB)}return result}function baseCreate(prototype,properties){return isObject(prototype)?nativeCreate(prototype):{}}if(!nativeCreate){baseCreate=function(){function Object(){}return function(prototype){if(isObject(prototype)){Object.prototype=prototype;var result=new Object;Object.prototype=null}return result||context.Object()}}()}function baseCreateCallback(func,thisArg,argCount){if(typeof func!="function"){return identity}if(typeof thisArg=="undefined"||!("prototype"in func)){return func}var bindData=func.__bindData__;if(typeof bindData=="undefined"){if(support.funcNames){bindData=!func.name}bindData=bindData||!support.funcDecomp;if(!bindData){var source=fnToString.call(func);if(!support.funcNames){bindData=!reFuncName.test(source)}if(!bindData){bindData=reThis.test(source);setBindData(func,bindData)}}}if(bindData===false||bindData!==true&&bindData[1]&1){return func}switch(argCount){case 1:return function(value){return func.call(thisArg,value)};case 2:return function(a,b){return func.call(thisArg,a,b)};case 3:return function(value,index,collection){return func.call(thisArg,value,index,collection)};case 4:return function(accumulator,value,index,collection){return func.call(thisArg,accumulator,value,index,collection)}}return bind(func,thisArg)}function baseCreateWrapper(bindData){var func=bindData[0],bitmask=bindData[1],partialArgs=bindData[2],partialRightArgs=bindData[3],thisArg=bindData[4],arity=bindData[5];var isBind=bitmask&1,isBindKey=bitmask&2,isCurry=bitmask&4,isCurryBound=bitmask&8,key=func;function bound(){var thisBinding=isBind?thisArg:this;if(partialArgs){var args=slice(partialArgs);push.apply(args,arguments)}if(partialRightArgs||isCurry){args||(args=slice(arguments));if(partialRightArgs){push.apply(args,partialRightArgs)}if(isCurry&&args.length<arity){bitmask|=16&~32;return baseCreateWrapper([func,isCurryBound?bitmask:bitmask&~3,args,null,thisArg,arity])}}args||(args=arguments);if(isBindKey){func=thisBinding[key]}if(this instanceof bound){thisBinding=baseCreate(func.prototype);var result=func.apply(thisBinding,args);return isObject(result)?result:thisBinding}return func.apply(thisBinding,args)}setBindData(bound,bindData);return bound}function baseDifference(array,values){var index=-1,indexOf=getIndexOf(),length=array?array.length:0,isLarge=length>=largeArraySize&&indexOf===baseIndexOf,result=[];if(isLarge){var cache=createCache(values);if(cache){indexOf=cacheIndexOf;values=cache}else{isLarge=false}}while(++index<length){var value=array[index];if(indexOf(values,value)<0){result.push(value)}}if(isLarge){releaseObject(values)}return result}function baseFlatten(array,isShallow,isStrict,fromIndex){var index=(fromIndex||0)-1,length=array?array.length:0,result=[];while(++index<length){var value=array[index];if(value&&typeof value=="object"&&typeof value.length=="number"&&(isArray(value)||isArguments(value))){if(!isShallow){value=baseFlatten(value,isShallow,isStrict)}var valIndex=-1,valLength=value.length,resIndex=result.length;result.length+=valLength;while(++valIndex<valLength){result[resIndex++]=value[valIndex]}}else if(!isStrict){result.push(value)}}return result}function baseIsEqual(a,b,callback,isWhere,stackA,stackB){if(callback){var result=callback(a,b);if(typeof result!="undefined"){return!!result}}if(a===b){return a!==0||1/a==1/b}var type=typeof a,otherType=typeof b;if(a===a&&!(a&&objectTypes[type])&&!(b&&objectTypes[otherType])){return false}if(a==null||b==null){return a===b}var className=toString.call(a),otherClass=toString.call(b);if(className==argsClass){className=objectClass}if(otherClass==argsClass){otherClass=objectClass}if(className!=otherClass){return false}switch(className){case boolClass:case dateClass:return+a==+b;case numberClass:return a!=+a?b!=+b:a==0?1/a==1/b:a==+b;case regexpClass:case stringClass:return a==String(b)}var isArr=className==arrayClass;if(!isArr){var aWrapped=hasOwnProperty.call(a,"__wrapped__"),bWrapped=hasOwnProperty.call(b,"__wrapped__");if(aWrapped||bWrapped){return baseIsEqual(aWrapped?a.__wrapped__:a,bWrapped?b.__wrapped__:b,callback,isWhere,stackA,stackB)}if(className!=objectClass){return false}var ctorA=a.constructor,ctorB=b.constructor;if(ctorA!=ctorB&&!(isFunction(ctorA)&&ctorA instanceof ctorA&&isFunction(ctorB)&&ctorB instanceof ctorB)&&("constructor"in a&&"constructor"in b)){return false}}var initedStack=!stackA;stackA||(stackA=getArray());stackB||(stackB=getArray());var length=stackA.length;while(length--){if(stackA[length]==a){return stackB[length]==b}}var size=0;result=true;stackA.push(a);stackB.push(b);if(isArr){length=a.length;size=b.length;result=size==length;if(result||isWhere){while(size--){var index=length,value=b[size];if(isWhere){while(index--){if(result=baseIsEqual(a[index],value,callback,isWhere,stackA,stackB)){break}}}else if(!(result=baseIsEqual(a[size],value,callback,isWhere,stackA,stackB))){break}}}}else{forIn(b,function(value,key,b){if(hasOwnProperty.call(b,key)){size++;return result=hasOwnProperty.call(a,key)&&baseIsEqual(a[key],value,callback,isWhere,stackA,stackB)}});if(result&&!isWhere){forIn(a,function(value,key,a){if(hasOwnProperty.call(a,key)){return result=--size>-1}})}}stackA.pop();stackB.pop();if(initedStack){releaseArray(stackA);releaseArray(stackB)}return result}function baseMerge(object,source,callback,stackA,stackB){(isArray(source)?forEach:forOwn)(source,function(source,key){var found,isArr,result=source,value=object[key];if(source&&((isArr=isArray(source))||isPlainObject(source))){var stackLength=stackA.length;while(stackLength--){if(found=stackA[stackLength]==source){value=stackB[stackLength];break}}if(!found){var isShallow;if(callback){result=callback(value,source);if(isShallow=typeof result!="undefined"){value=result}}if(!isShallow){value=isArr?isArray(value)?value:[]:isPlainObject(value)?value:{}}stackA.push(source);stackB.push(value);if(!isShallow){baseMerge(value,source,callback,stackA,stackB)}}}else{if(callback){result=callback(value,source);if(typeof result=="undefined"){result=source}}if(typeof result!="undefined"){value=result}}object[key]=value})}function baseRandom(min,max){return min+floor(nativeRandom()*(max-min+1))}function baseUniq(array,isSorted,callback){var index=-1,indexOf=getIndexOf(),length=array?array.length:0,result=[];var isLarge=!isSorted&&length>=largeArraySize&&indexOf===baseIndexOf,seen=callback||isLarge?getArray():result;if(isLarge){var cache=createCache(seen);indexOf=cacheIndexOf;seen=cache}while(++index<length){var value=array[index],computed=callback?callback(value,index,array):value;if(isSorted?!index||seen[seen.length-1]!==computed:indexOf(seen,computed)<0){if(callback||isLarge){seen.push(computed)}result.push(value)}}if(isLarge){releaseArray(seen.array);releaseObject(seen)}else if(callback){releaseArray(seen)}return result}function createAggregator(setter){return function(collection,callback,thisArg){var result={};callback=lodash.createCallback(callback,thisArg,3);var index=-1,length=collection?collection.length:0;if(typeof length=="number"){while(++index<length){var value=collection[index];setter(result,value,callback(value,index,collection),collection)}}else{forOwn(collection,function(value,key,collection){setter(result,value,callback(value,key,collection),collection)})}return result}}function createWrapper(func,bitmask,partialArgs,partialRightArgs,thisArg,arity){var isBind=bitmask&1,isBindKey=bitmask&2,isCurry=bitmask&4,isCurryBound=bitmask&8,isPartial=bitmask&16,isPartialRight=bitmask&32;if(!isBindKey&&!isFunction(func)){throw new TypeError}if(isPartial&&!partialArgs.length){bitmask&=~16;isPartial=partialArgs=false}if(isPartialRight&&!partialRightArgs.length){bitmask&=~32;isPartialRight=partialRightArgs=false}var bindData=func&&func.__bindData__;if(bindData&&bindData!==true){bindData=slice(bindData);if(bindData[2]){bindData[2]=slice(bindData[2])}if(bindData[3]){bindData[3]=slice(bindData[3])}if(isBind&&!(bindData[1]&1)){bindData[4]=thisArg}if(!isBind&&bindData[1]&1){bitmask|=8}if(isCurry&&!(bindData[1]&4)){bindData[5]=arity}if(isPartial){push.apply(bindData[2]||(bindData[2]=[]),partialArgs)}if(isPartialRight){unshift.apply(bindData[3]||(bindData[3]=[]),partialRightArgs)}bindData[1]|=bitmask;return createWrapper.apply(null,bindData)}var creater=bitmask==1||bitmask===17?baseBind:baseCreateWrapper;return creater([func,bitmask,partialArgs,partialRightArgs,thisArg,arity])}function escapeHtmlChar(match){return htmlEscapes[match]}function getIndexOf(){var result=(result=lodash.indexOf)===indexOf?baseIndexOf:result;return result}function isNative(value){return typeof value=="function"&&reNative.test(value)}var setBindData=!defineProperty?noop:function(func,value){descriptor.value=value;defineProperty(func,"__bindData__",descriptor)};function shimIsPlainObject(value){var ctor,result;if(!(value&&toString.call(value)==objectClass)||(ctor=value.constructor,isFunction(ctor)&&!(ctor instanceof ctor))){return false}forIn(value,function(value,key){result=key});return typeof result=="undefined"||hasOwnProperty.call(value,result)}function unescapeHtmlChar(match){return htmlUnescapes[match]}function isArguments(value){return value&&typeof value=="object"&&typeof value.length=="number"&&toString.call(value)==argsClass||false}var isArray=nativeIsArray||function(value){return value&&typeof value=="object"&&typeof value.length=="number"&&toString.call(value)==arrayClass||false};var shimKeys=function(object){var index,iterable=object,result=[];if(!iterable)return result;if(!objectTypes[typeof object])return result;for(index in iterable){if(hasOwnProperty.call(iterable,index)){result.push(index)}}return result};var keys=!nativeKeys?shimKeys:function(object){if(!isObject(object)){return[]}return nativeKeys(object)};var htmlEscapes={"&":"&","<":"<",">":">",'"':""","'":"'"};var htmlUnescapes=invert(htmlEscapes);var reEscapedHtml=RegExp("("+keys(htmlUnescapes).join("|")+")","g"),reUnescapedHtml=RegExp("["+keys(htmlEscapes).join("")+"]","g");var assign=function(object,source,guard){var index,iterable=object,result=iterable;if(!iterable)return result;var args=arguments,argsIndex=0,argsLength=typeof guard=="number"?2:args.length;if(argsLength>3&&typeof args[argsLength-2]=="function"){var callback=baseCreateCallback(args[--argsLength-1],args[argsLength--],2)}else if(argsLength>2&&typeof args[argsLength-1]=="function"){callback=args[--argsLength]}while(++argsIndex<argsLength){iterable=args[argsIndex];if(iterable&&objectTypes[typeof iterable]){var ownIndex=-1,ownProps=objectTypes[typeof iterable]&&keys(iterable),length=ownProps?ownProps.length:0;while(++ownIndex<length){index=ownProps[ownIndex];result[index]=callback?callback(result[index],iterable[index]):iterable[index]}}}return result};function clone(value,isDeep,callback,thisArg){if(typeof isDeep!="boolean"&&isDeep!=null){thisArg=callback;callback=isDeep;isDeep=false}return baseClone(value,isDeep,typeof callback=="function"&&baseCreateCallback(callback,thisArg,1))}function cloneDeep(value,callback,thisArg){return baseClone(value,true,typeof callback=="function"&&baseCreateCallback(callback,thisArg,1))}function create(prototype,properties){var result=baseCreate(prototype);return properties?assign(result,properties):result}var defaults=function(object,source,guard){var index,iterable=object,result=iterable;if(!iterable)return result;var args=arguments,argsIndex=0,argsLength=typeof guard=="number"?2:args.length;while(++argsIndex<argsLength){iterable=args[argsIndex];if(iterable&&objectTypes[typeof iterable]){var ownIndex=-1,ownProps=objectTypes[typeof iterable]&&keys(iterable),length=ownProps?ownProps.length:0;while(++ownIndex<length){index=ownProps[ownIndex];if(typeof result[index]=="undefined")result[index]=iterable[index]}}}return result};function findKey(object,callback,thisArg){var result;callback=lodash.createCallback(callback,thisArg,3);forOwn(object,function(value,key,object){if(callback(value,key,object)){result=key;return false}});return result}function findLastKey(object,callback,thisArg){var result;callback=lodash.createCallback(callback,thisArg,3);forOwnRight(object,function(value,key,object){if(callback(value,key,object)){result=key;return false}});return result}var forIn=function(collection,callback,thisArg){var index,iterable=collection,result=iterable;if(!iterable)return result;if(!objectTypes[typeof iterable])return result;callback=callback&&typeof thisArg=="undefined"?callback:baseCreateCallback(callback,thisArg,3);for(index in iterable){if(callback(iterable[index],index,collection)===false)return result}return result};function forInRight(object,callback,thisArg){var pairs=[];forIn(object,function(value,key){pairs.push(key,value)});var length=pairs.length;callback=baseCreateCallback(callback,thisArg,3);while(length--){if(callback(pairs[length--],pairs[length],object)===false){break}}return object}var forOwn=function(collection,callback,thisArg){var index,iterable=collection,result=iterable;if(!iterable)return result;if(!objectTypes[typeof iterable])return result;callback=callback&&typeof thisArg=="undefined"?callback:baseCreateCallback(callback,thisArg,3);var ownIndex=-1,ownProps=objectTypes[typeof iterable]&&keys(iterable),length=ownProps?ownProps.length:0;while(++ownIndex<length){index=ownProps[ownIndex];if(callback(iterable[index],index,collection)===false)return result}return result};function forOwnRight(object,callback,thisArg){var props=keys(object),length=props.length;callback=baseCreateCallback(callback,thisArg,3);while(length--){var key=props[length];if(callback(object[key],key,object)===false){break}}return object}function functions(object){var result=[];forIn(object,function(value,key){if(isFunction(value)){result.push(key)}});return result.sort()}function has(object,key){return object?hasOwnProperty.call(object,key):false}function invert(object){var index=-1,props=keys(object),length=props.length,result={};while(++index<length){var key=props[index];result[object[key]]=key}return result}function isBoolean(value){return value===true||value===false||value&&typeof value=="object"&&toString.call(value)==boolClass||false}function isDate(value){return value&&typeof value=="object"&&toString.call(value)==dateClass||false}function isElement(value){return value&&value.nodeType===1||false}function isEmpty(value){var result=true;if(!value){return result}var className=toString.call(value),length=value.length;if(className==arrayClass||className==stringClass||className==argsClass||className==objectClass&&typeof length=="number"&&isFunction(value.splice)){return!length}forOwn(value,function(){return result=false});return result}function isEqual(a,b,callback,thisArg){return baseIsEqual(a,b,typeof callback=="function"&&baseCreateCallback(callback,thisArg,2))}function isFinite(value){return nativeIsFinite(value)&&!nativeIsNaN(parseFloat(value))}function isFunction(value){return typeof value=="function"}function isObject(value){return!!(value&&objectTypes[typeof value])}function isNaN(value){return isNumber(value)&&value!=+value}function isNull(value){return value===null}function isNumber(value){return typeof value=="number"||value&&typeof value=="object"&&toString.call(value)==numberClass||false}var isPlainObject=!getPrototypeOf?shimIsPlainObject:function(value){if(!(value&&toString.call(value)==objectClass)){return false}var valueOf=value.valueOf,objProto=isNative(valueOf)&&(objProto=getPrototypeOf(valueOf))&&getPrototypeOf(objProto);return objProto?value==objProto||getPrototypeOf(value)==objProto:shimIsPlainObject(value)};function isRegExp(value){return value&&typeof value=="object"&&toString.call(value)==regexpClass||false}function isString(value){return typeof value=="string"||value&&typeof value=="object"&&toString.call(value)==stringClass||false}function isUndefined(value){return typeof value=="undefined"}function mapValues(object,callback,thisArg){var result={};callback=lodash.createCallback(callback,thisArg,3);forOwn(object,function(value,key,object){result[key]=callback(value,key,object)});return result}function merge(object){var args=arguments,length=2;if(!isObject(object)){return object}if(typeof args[2]!="number"){length=args.length}if(length>3&&typeof args[length-2]=="function"){var callback=baseCreateCallback(args[--length-1],args[length--],2)}else if(length>2&&typeof args[length-1]=="function"){callback=args[--length]}var sources=slice(arguments,1,length),index=-1,stackA=getArray(),stackB=getArray();while(++index<length){baseMerge(object,sources[index],callback,stackA,stackB)}releaseArray(stackA);releaseArray(stackB);return object}function omit(object,callback,thisArg){var result={};if(typeof callback!="function"){var props=[];forIn(object,function(value,key){props.push(key)});props=baseDifference(props,baseFlatten(arguments,true,false,1));var index=-1,length=props.length;while(++index<length){var key=props[index];result[key]=object[key]}}else{callback=lodash.createCallback(callback,thisArg,3);forIn(object,function(value,key,object){if(!callback(value,key,object)){result[key]=value}})}return result}function pairs(object){var index=-1,props=keys(object),length=props.length,result=Array(length);while(++index<length){var key=props[index];result[index]=[key,object[key]]}return result}function pick(object,callback,thisArg){var result={};if(typeof callback!="function"){var index=-1,props=baseFlatten(arguments,true,false,1),length=isObject(object)?props.length:0;while(++index<length){var key=props[index];if(key in object){result[key]=object[key]}}}else{callback=lodash.createCallback(callback,thisArg,3);forIn(object,function(value,key,object){if(callback(value,key,object)){result[key]=value}})}return result}function transform(object,callback,accumulator,thisArg){var isArr=isArray(object);if(accumulator==null){if(isArr){accumulator=[]}else{var ctor=object&&object.constructor,proto=ctor&&ctor.prototype;accumulator=baseCreate(proto)}}if(callback){callback=lodash.createCallback(callback,thisArg,4);(isArr?forEach:forOwn)(object,function(value,index,object){return callback(accumulator,value,index,object)})}return accumulator}function values(object){var index=-1,props=keys(object),length=props.length,result=Array(length);while(++index<length){result[index]=object[props[index]]}return result}function at(collection){var args=arguments,index=-1,props=baseFlatten(args,true,false,1),length=args[2]&&args[2][args[1]]===collection?1:props.length,result=Array(length);while(++index<length){result[index]=collection[props[index]]}return result}function contains(collection,target,fromIndex){var index=-1,indexOf=getIndexOf(),length=collection?collection.length:0,result=false;fromIndex=(fromIndex<0?nativeMax(0,length+fromIndex):fromIndex)||0;if(isArray(collection)){result=indexOf(collection,target,fromIndex)>-1}else if(typeof length=="number"){result=(isString(collection)?collection.indexOf(target,fromIndex):indexOf(collection,target,fromIndex))>-1}else{forOwn(collection,function(value){if(++index>=fromIndex){return!(result=value===target)}})}return result}var countBy=createAggregator(function(result,value,key){hasOwnProperty.call(result,key)?result[key]++:result[key]=1});function every(collection,callback,thisArg){var result=true;callback=lodash.createCallback(callback,thisArg,3);var index=-1,length=collection?collection.length:0;if(typeof length=="number"){while(++index<length){if(!(result=!!callback(collection[index],index,collection))){break}}}else{forOwn(collection,function(value,index,collection){return result=!!callback(value,index,collection)})}return result}function filter(collection,callback,thisArg){var result=[];callback=lodash.createCallback(callback,thisArg,3);var index=-1,length=collection?collection.length:0;if(typeof length=="number"){while(++index<length){var value=collection[index];if(callback(value,index,collection)){result.push(value)}}}else{forOwn(collection,function(value,index,collection){if(callback(value,index,collection)){result.push(value)}})}return result}function find(collection,callback,thisArg){callback=lodash.createCallback(callback,thisArg,3);var index=-1,length=collection?collection.length:0;if(typeof length=="number"){while(++index<length){var value=collection[index];if(callback(value,index,collection)){return value}}}else{var result;forOwn(collection,function(value,index,collection){if(callback(value,index,collection)){result=value;return false}});return result}}function findLast(collection,callback,thisArg){var result;callback=lodash.createCallback(callback,thisArg,3);forEachRight(collection,function(value,index,collection){if(callback(value,index,collection)){result=value;return false}});return result}function forEach(collection,callback,thisArg){var index=-1,length=collection?collection.length:0;callback=callback&&typeof thisArg=="undefined"?callback:baseCreateCallback(callback,thisArg,3);if(typeof length=="number"){while(++index<length){if(callback(collection[index],index,collection)===false){break}}}else{forOwn(collection,callback)}return collection}function forEachRight(collection,callback,thisArg){var length=collection?collection.length:0;callback=callback&&typeof thisArg=="undefined"?callback:baseCreateCallback(callback,thisArg,3);if(typeof length=="number"){while(length--){if(callback(collection[length],length,collection)===false){break}}}else{var props=keys(collection);length=props.length;forOwn(collection,function(value,key,collection){key=props?props[--length]:--length;return callback(collection[key],key,collection)})}return collection}var groupBy=createAggregator(function(result,value,key){(hasOwnProperty.call(result,key)?result[key]:result[key]=[]).push(value)});var indexBy=createAggregator(function(result,value,key){result[key]=value});function invoke(collection,methodName){var args=slice(arguments,2),index=-1,isFunc=typeof methodName=="function",length=collection?collection.length:0,result=Array(typeof length=="number"?length:0);forEach(collection,function(value){result[++index]=(isFunc?methodName:value[methodName]).apply(value,args)});return result}function map(collection,callback,thisArg){var index=-1,length=collection?collection.length:0;callback=lodash.createCallback(callback,thisArg,3);if(typeof length=="number"){var result=Array(length);while(++index<length){result[index]=callback(collection[index],index,collection)}}else{result=[];forOwn(collection,function(value,key,collection){result[++index]=callback(value,key,collection)})}return result
+}function max(collection,callback,thisArg){var computed=-Infinity,result=computed;if(typeof callback!="function"&&thisArg&&thisArg[callback]===collection){callback=null}if(callback==null&&isArray(collection)){var index=-1,length=collection.length;while(++index<length){var value=collection[index];if(value>result){result=value}}}else{callback=callback==null&&isString(collection)?charAtCallback:lodash.createCallback(callback,thisArg,3);forEach(collection,function(value,index,collection){var current=callback(value,index,collection);if(current>computed){computed=current;result=value}})}return result}function min(collection,callback,thisArg){var computed=Infinity,result=computed;if(typeof callback!="function"&&thisArg&&thisArg[callback]===collection){callback=null}if(callback==null&&isArray(collection)){var index=-1,length=collection.length;while(++index<length){var value=collection[index];if(value<result){result=value}}}else{callback=callback==null&&isString(collection)?charAtCallback:lodash.createCallback(callback,thisArg,3);forEach(collection,function(value,index,collection){var current=callback(value,index,collection);if(current<computed){computed=current;result=value}})}return result}var pluck=map;function reduce(collection,callback,accumulator,thisArg){if(!collection)return accumulator;var noaccum=arguments.length<3;callback=lodash.createCallback(callback,thisArg,4);var index=-1,length=collection.length;if(typeof length=="number"){if(noaccum){accumulator=collection[++index]}while(++index<length){accumulator=callback(accumulator,collection[index],index,collection)}}else{forOwn(collection,function(value,index,collection){accumulator=noaccum?(noaccum=false,value):callback(accumulator,value,index,collection)})}return accumulator}function reduceRight(collection,callback,accumulator,thisArg){var noaccum=arguments.length<3;callback=lodash.createCallback(callback,thisArg,4);forEachRight(collection,function(value,index,collection){accumulator=noaccum?(noaccum=false,value):callback(accumulator,value,index,collection)});return accumulator}function reject(collection,callback,thisArg){callback=lodash.createCallback(callback,thisArg,3);return filter(collection,function(value,index,collection){return!callback(value,index,collection)})}function sample(collection,n,guard){if(collection&&typeof collection.length!="number"){collection=values(collection)}if(n==null||guard){return collection?collection[baseRandom(0,collection.length-1)]:undefined}var result=shuffle(collection);result.length=nativeMin(nativeMax(0,n),result.length);return result}function shuffle(collection){var index=-1,length=collection?collection.length:0,result=Array(typeof length=="number"?length:0);forEach(collection,function(value){var rand=baseRandom(0,++index);result[index]=result[rand];result[rand]=value});return result}function size(collection){var length=collection?collection.length:0;return typeof length=="number"?length:keys(collection).length}function some(collection,callback,thisArg){var result;callback=lodash.createCallback(callback,thisArg,3);var index=-1,length=collection?collection.length:0;if(typeof length=="number"){while(++index<length){if(result=callback(collection[index],index,collection)){break}}}else{forOwn(collection,function(value,index,collection){return!(result=callback(value,index,collection))})}return!!result}function sortBy(collection,callback,thisArg){var index=-1,isArr=isArray(callback),length=collection?collection.length:0,result=Array(typeof length=="number"?length:0);if(!isArr){callback=lodash.createCallback(callback,thisArg,3)}forEach(collection,function(value,key,collection){var object=result[++index]=getObject();if(isArr){object.criteria=map(callback,function(key){return value[key]})}else{(object.criteria=getArray())[0]=callback(value,key,collection)}object.index=index;object.value=value});length=result.length;result.sort(compareAscending);while(length--){var object=result[length];result[length]=object.value;if(!isArr){releaseArray(object.criteria)}releaseObject(object)}return result}function toArray(collection){if(collection&&typeof collection.length=="number"){return slice(collection)}return values(collection)}var where=filter;function compact(array){var index=-1,length=array?array.length:0,result=[];while(++index<length){var value=array[index];if(value){result.push(value)}}return result}function difference(array){return baseDifference(array,baseFlatten(arguments,true,true,1))}function findIndex(array,callback,thisArg){var index=-1,length=array?array.length:0;callback=lodash.createCallback(callback,thisArg,3);while(++index<length){if(callback(array[index],index,array)){return index}}return-1}function findLastIndex(array,callback,thisArg){var length=array?array.length:0;callback=lodash.createCallback(callback,thisArg,3);while(length--){if(callback(array[length],length,array)){return length}}return-1}function first(array,callback,thisArg){var n=0,length=array?array.length:0;if(typeof callback!="number"&&callback!=null){var index=-1;callback=lodash.createCallback(callback,thisArg,3);while(++index<length&&callback(array[index],index,array)){n++}}else{n=callback;if(n==null||thisArg){return array?array[0]:undefined}}return slice(array,0,nativeMin(nativeMax(0,n),length))}function flatten(array,isShallow,callback,thisArg){if(typeof isShallow!="boolean"&&isShallow!=null){thisArg=callback;callback=typeof isShallow!="function"&&thisArg&&thisArg[isShallow]===array?null:isShallow;isShallow=false}if(callback!=null){array=map(array,callback,thisArg)}return baseFlatten(array,isShallow)}function indexOf(array,value,fromIndex){if(typeof fromIndex=="number"){var length=array?array.length:0;fromIndex=fromIndex<0?nativeMax(0,length+fromIndex):fromIndex||0}else if(fromIndex){var index=sortedIndex(array,value);return array[index]===value?index:-1}return baseIndexOf(array,value,fromIndex)}function initial(array,callback,thisArg){var n=0,length=array?array.length:0;if(typeof callback!="number"&&callback!=null){var index=length;callback=lodash.createCallback(callback,thisArg,3);while(index--&&callback(array[index],index,array)){n++}}else{n=callback==null||thisArg?1:callback||n}return slice(array,0,nativeMin(nativeMax(0,length-n),length))}function intersection(){var args=[],argsIndex=-1,argsLength=arguments.length,caches=getArray(),indexOf=getIndexOf(),trustIndexOf=indexOf===baseIndexOf,seen=getArray();while(++argsIndex<argsLength){var value=arguments[argsIndex];if(isArray(value)||isArguments(value)){args.push(value);caches.push(trustIndexOf&&value.length>=largeArraySize&&createCache(argsIndex?args[argsIndex]:seen))}}var array=args[0],index=-1,length=array?array.length:0,result=[];outer:while(++index<length){var cache=caches[0];value=array[index];if((cache?cacheIndexOf(cache,value):indexOf(seen,value))<0){argsIndex=argsLength;(cache||seen).push(value);while(--argsIndex){cache=caches[argsIndex];if((cache?cacheIndexOf(cache,value):indexOf(args[argsIndex],value))<0){continue outer}}result.push(value)}}while(argsLength--){cache=caches[argsLength];if(cache){releaseObject(cache)}}releaseArray(caches);releaseArray(seen);return result}function last(array,callback,thisArg){var n=0,length=array?array.length:0;if(typeof callback!="number"&&callback!=null){var index=length;callback=lodash.createCallback(callback,thisArg,3);while(index--&&callback(array[index],index,array)){n++}}else{n=callback;if(n==null||thisArg){return array?array[length-1]:undefined}}return slice(array,nativeMax(0,length-n))}function lastIndexOf(array,value,fromIndex){var index=array?array.length:0;if(typeof fromIndex=="number"){index=(fromIndex<0?nativeMax(0,index+fromIndex):nativeMin(fromIndex,index-1))+1}while(index--){if(array[index]===value){return index}}return-1}function pull(array){var args=arguments,argsIndex=0,argsLength=args.length,length=array?array.length:0;while(++argsIndex<argsLength){var index=-1,value=args[argsIndex];while(++index<length){if(array[index]===value){splice.call(array,index--,1);length--}}}return array}function range(start,end,step){start=+start||0;step=typeof step=="number"?step:+step||1;if(end==null){end=start;start=0}var index=-1,length=nativeMax(0,ceil((end-start)/(step||1))),result=Array(length);while(++index<length){result[index]=start;start+=step}return result}function remove(array,callback,thisArg){var index=-1,length=array?array.length:0,result=[];callback=lodash.createCallback(callback,thisArg,3);while(++index<length){var value=array[index];if(callback(value,index,array)){result.push(value);splice.call(array,index--,1);length--}}return result}function rest(array,callback,thisArg){if(typeof callback!="number"&&callback!=null){var n=0,index=-1,length=array?array.length:0;callback=lodash.createCallback(callback,thisArg,3);while(++index<length&&callback(array[index],index,array)){n++}}else{n=callback==null||thisArg?1:nativeMax(0,callback)}return slice(array,n)}function sortedIndex(array,value,callback,thisArg){var low=0,high=array?array.length:low;callback=callback?lodash.createCallback(callback,thisArg,1):identity;value=callback(value);while(low<high){var mid=low+high>>>1;callback(array[mid])<value?low=mid+1:high=mid}return low}function union(){return baseUniq(baseFlatten(arguments,true,true))}function uniq(array,isSorted,callback,thisArg){if(typeof isSorted!="boolean"&&isSorted!=null){thisArg=callback;callback=typeof isSorted!="function"&&thisArg&&thisArg[isSorted]===array?null:isSorted;isSorted=false}if(callback!=null){callback=lodash.createCallback(callback,thisArg,3)}return baseUniq(array,isSorted,callback)}function without(array){return baseDifference(array,slice(arguments,1))}function xor(){var index=-1,length=arguments.length;while(++index<length){var array=arguments[index];if(isArray(array)||isArguments(array)){var result=result?baseUniq(baseDifference(result,array).concat(baseDifference(array,result))):array}}return result||[]}function zip(){var array=arguments.length>1?arguments:arguments[0],index=-1,length=array?max(pluck(array,"length")):0,result=Array(length<0?0:length);while(++index<length){result[index]=pluck(array,index)}return result}function zipObject(keys,values){var index=-1,length=keys?keys.length:0,result={};if(!values&&length&&!isArray(keys[0])){values=[]}while(++index<length){var key=keys[index];if(values){result[key]=values[index]}else if(key){result[key[0]]=key[1]}}return result}function after(n,func){if(!isFunction(func)){throw new TypeError}return function(){if(--n<1){return func.apply(this,arguments)}}}function bind(func,thisArg){return arguments.length>2?createWrapper(func,17,slice(arguments,2),null,thisArg):createWrapper(func,1,null,null,thisArg)}function bindAll(object){var funcs=arguments.length>1?baseFlatten(arguments,true,false,1):functions(object),index=-1,length=funcs.length;while(++index<length){var key=funcs[index];object[key]=createWrapper(object[key],1,null,null,object)}return object}function bindKey(object,key){return arguments.length>2?createWrapper(key,19,slice(arguments,2),null,object):createWrapper(key,3,null,null,object)}function compose(){var funcs=arguments,length=funcs.length;while(length--){if(!isFunction(funcs[length])){throw new TypeError}}return function(){var args=arguments,length=funcs.length;while(length--){args=[funcs[length].apply(this,args)]}return args[0]}}function curry(func,arity){arity=typeof arity=="number"?arity:+arity||func.length;return createWrapper(func,4,null,null,null,arity)}function debounce(func,wait,options){var args,maxTimeoutId,result,stamp,thisArg,timeoutId,trailingCall,lastCalled=0,maxWait=false,trailing=true;if(!isFunction(func)){throw new TypeError}wait=nativeMax(0,wait)||0;if(options===true){var leading=true;trailing=false}else if(isObject(options)){leading=options.leading;maxWait="maxWait"in options&&(nativeMax(wait,options.maxWait)||0);trailing="trailing"in options?options.trailing:trailing}var delayed=function(){var remaining=wait-(now()-stamp);if(remaining<=0){if(maxTimeoutId){clearTimeout(maxTimeoutId)}var isCalled=trailingCall;maxTimeoutId=timeoutId=trailingCall=undefined;if(isCalled){lastCalled=now();result=func.apply(thisArg,args);if(!timeoutId&&!maxTimeoutId){args=thisArg=null}}}else{timeoutId=setTimeout(delayed,remaining)}};var maxDelayed=function(){if(timeoutId){clearTimeout(timeoutId)}maxTimeoutId=timeoutId=trailingCall=undefined;if(trailing||maxWait!==wait){lastCalled=now();result=func.apply(thisArg,args);if(!timeoutId&&!maxTimeoutId){args=thisArg=null}}};return function(){args=arguments;stamp=now();thisArg=this;trailingCall=trailing&&(timeoutId||!leading);if(maxWait===false){var leadingCall=leading&&!timeoutId}else{if(!maxTimeoutId&&!leading){lastCalled=stamp}var remaining=maxWait-(stamp-lastCalled),isCalled=remaining<=0;if(isCalled){if(maxTimeoutId){maxTimeoutId=clearTimeout(maxTimeoutId)}lastCalled=stamp;result=func.apply(thisArg,args)}else if(!maxTimeoutId){maxTimeoutId=setTimeout(maxDelayed,remaining)}}if(isCalled&&timeoutId){timeoutId=clearTimeout(timeoutId)}else if(!timeoutId&&wait!==maxWait){timeoutId=setTimeout(delayed,wait)}if(leadingCall){isCalled=true;result=func.apply(thisArg,args)}if(isCalled&&!timeoutId&&!maxTimeoutId){args=thisArg=null}return result}}function defer(func){if(!isFunction(func)){throw new TypeError}var args=slice(arguments,1);return setTimeout(function(){func.apply(undefined,args)},1)}function delay(func,wait){if(!isFunction(func)){throw new TypeError}var args=slice(arguments,2);return setTimeout(function(){func.apply(undefined,args)},wait)}function memoize(func,resolver){if(!isFunction(func)){throw new TypeError}var memoized=function(){var cache=memoized.cache,key=resolver?resolver.apply(this,arguments):keyPrefix+arguments[0];return hasOwnProperty.call(cache,key)?cache[key]:cache[key]=func.apply(this,arguments)};memoized.cache={};return memoized}function once(func){var ran,result;if(!isFunction(func)){throw new TypeError}return function(){if(ran){return result}ran=true;result=func.apply(this,arguments);func=null;return result}}function partial(func){return createWrapper(func,16,slice(arguments,1))}function partialRight(func){return createWrapper(func,32,null,slice(arguments,1))}function throttle(func,wait,options){var leading=true,trailing=true;if(!isFunction(func)){throw new TypeError}if(options===false){leading=false}else if(isObject(options)){leading="leading"in options?options.leading:leading;trailing="trailing"in options?options.trailing:trailing}debounceOptions.leading=leading;debounceOptions.maxWait=wait;debounceOptions.trailing=trailing;return debounce(func,wait,debounceOptions)}function wrap(value,wrapper){return createWrapper(wrapper,16,[value])}function constant(value){return function(){return value}}function createCallback(func,thisArg,argCount){var type=typeof func;if(func==null||type=="function"){return baseCreateCallback(func,thisArg,argCount)}if(type!="object"){return property(func)}var props=keys(func),key=props[0],a=func[key];if(props.length==1&&a===a&&!isObject(a)){return function(object){var b=object[key];return a===b&&(a!==0||1/a==1/b)}}return function(object){var length=props.length,result=false;while(length--){if(!(result=baseIsEqual(object[props[length]],func[props[length]],null,true))){break}}return result}}function escape(string){return string==null?"":String(string).replace(reUnescapedHtml,escapeHtmlChar)}function identity(value){return value}function mixin(object,source,options){var chain=true,methodNames=source&&functions(source);if(!source||!options&&!methodNames.length){if(options==null){options=source}ctor=lodashWrapper;source=object;object=lodash;methodNames=functions(source)}if(options===false){chain=false}else if(isObject(options)&&"chain"in options){chain=options.chain}var ctor=object,isFunc=isFunction(ctor);forEach(methodNames,function(methodName){var func=object[methodName]=source[methodName];if(isFunc){ctor.prototype[methodName]=function(){var chainAll=this.__chain__,value=this.__wrapped__,args=[value];push.apply(args,arguments);var result=func.apply(object,args);if(chain||chainAll){if(value===result&&isObject(result)){return this}result=new ctor(result);result.__chain__=chainAll}return result}}})}function noConflict(){context._=oldDash;return this}function noop(){}var now=isNative(now=Date.now)&&now||function(){return(new Date).getTime()};var parseInt=nativeParseInt(whitespace+"08")==8?nativeParseInt:function(value,radix){return nativeParseInt(isString(value)?value.replace(reLeadingSpacesAndZeros,""):value,radix||0)};function property(key){return function(object){return object[key]}}function random(min,max,floating){var noMin=min==null,noMax=max==null;if(floating==null){if(typeof min=="boolean"&&noMax){floating=min;min=1}else if(!noMax&&typeof max=="boolean"){floating=max;noMax=true}}if(noMin&&noMax){max=1}min=+min||0;if(noMax){max=min;min=0}else{max=+max||0}if(floating||min%1||max%1){var rand=nativeRandom();return nativeMin(min+rand*(max-min+parseFloat("1e-"+((rand+"").length-1))),max)}return baseRandom(min,max)}function result(object,key){if(object){var value=object[key];return isFunction(value)?object[key]():value}}function template(text,data,options){var settings=lodash.templateSettings;text=String(text||"");options=defaults({},options,settings);var imports=defaults({},options.imports,settings.imports),importsKeys=keys(imports),importsValues=values(imports);var isEvaluating,index=0,interpolate=options.interpolate||reNoMatch,source="__p += '";var reDelimiters=RegExp((options.escape||reNoMatch).source+"|"+interpolate.source+"|"+(interpolate===reInterpolate?reEsTemplate:reNoMatch).source+"|"+(options.evaluate||reNoMatch).source+"|$","g");text.replace(reDelimiters,function(match,escapeValue,interpolateValue,esTemplateValue,evaluateValue,offset){interpolateValue||(interpolateValue=esTemplateValue);source+=text.slice(index,offset).replace(reUnescapedString,escapeStringChar);if(escapeValue){source+="' +\n__e("+escapeValue+") +\n'"}if(evaluateValue){isEvaluating=true;source+="';\n"+evaluateValue+";\n__p += '"}if(interpolateValue){source+="' +\n((__t = ("+interpolateValue+")) == null ? '' : __t) +\n'"}index=offset+match.length;return match});source+="';\n";var variable=options.variable,hasVariable=variable;if(!hasVariable){variable="obj";source="with ("+variable+") {\n"+source+"\n}\n"}source=(isEvaluating?source.replace(reEmptyStringLeading,""):source).replace(reEmptyStringMiddle,"$1").replace(reEmptyStringTrailing,"$1;");source="function("+variable+") {\n"+(hasVariable?"":variable+" || ("+variable+" = {});\n")+"var __t, __p = '', __e = _.escape"+(isEvaluating?", __j = Array.prototype.join;\n"+"function print() { __p += __j.call(arguments, '') }\n":";\n")+source+"return __p\n}";var sourceURL="\n/*\n//# sourceURL="+(options.sourceURL||"/lodash/template/source["+templateCounter++ +"]")+"\n*/";try{var result=Function(importsKeys,"return "+source+sourceURL).apply(undefined,importsValues)}catch(e){e.source=source;throw e}if(data){return result(data)}result.source=source;return result}function times(n,callback,thisArg){n=(n=+n)>-1?n:0;var index=-1,result=Array(n);callback=baseCreateCallback(callback,thisArg,1);while(++index<n){result[index]=callback(index)}return result}function unescape(string){return string==null?"":String(string).replace(reEscapedHtml,unescapeHtmlChar)}function uniqueId(prefix){var id=++idCounter;return String(prefix==null?"":prefix)+id}function chain(value){value=new lodashWrapper(value);value.__chain__=true;return value}function tap(value,interceptor){interceptor(value);return value}function wrapperChain(){this.__chain__=true;return this}function wrapperToString(){return String(this.__wrapped__)}function wrapperValueOf(){return this.__wrapped__}lodash.after=after;lodash.assign=assign;lodash.at=at;lodash.bind=bind;lodash.bindAll=bindAll;lodash.bindKey=bindKey;lodash.chain=chain;lodash.compact=compact;lodash.compose=compose;lodash.constant=constant;lodash.countBy=countBy;lodash.create=create;lodash.createCallback=createCallback;lodash.curry=curry;lodash.debounce=debounce;lodash.defaults=defaults;lodash.defer=defer;lodash.delay=delay;lodash.difference=difference;lodash.filter=filter;lodash.flatten=flatten;lodash.forEach=forEach;lodash.forEachRight=forEachRight;lodash.forIn=forIn;lodash.forInRight=forInRight;lodash.forOwn=forOwn;lodash.forOwnRight=forOwnRight;lodash.functions=functions;lodash.groupBy=groupBy;lodash.indexBy=indexBy;lodash.initial=initial;lodash.intersection=intersection;lodash.invert=invert;lodash.invoke=invoke;lodash.keys=keys;lodash.map=map;lodash.mapValues=mapValues;lodash.max=max;lodash.memoize=memoize;lodash.merge=merge;lodash.min=min;lodash.omit=omit;lodash.once=once;lodash.pairs=pairs;lodash.partial=partial;lodash.partialRight=partialRight;lodash.pick=pick;lodash.pluck=pluck;lodash.property=property;lodash.pull=pull;lodash.range=range;lodash.reject=reject;lodash.remove=remove;lodash.rest=rest;lodash.shuffle=shuffle;lodash.sortBy=sortBy;lodash.tap=tap;lodash.throttle=throttle;lodash.times=times;lodash.toArray=toArray;lodash.transform=transform;lodash.union=union;lodash.uniq=uniq;lodash.values=values;lodash.where=where;lodash.without=without;lodash.wrap=wrap;lodash.xor=xor;lodash.zip=zip;lodash.zipObject=zipObject;lodash.collect=map;lodash.drop=rest;lodash.each=forEach;lodash.eachRight=forEachRight;lodash.extend=assign;lodash.methods=functions;lodash.object=zipObject;lodash.select=filter;lodash.tail=rest;lodash.unique=uniq;lodash.unzip=zip;mixin(lodash);lodash.clone=clone;lodash.cloneDeep=cloneDeep;lodash.contains=contains;lodash.escape=escape;lodash.every=every;lodash.find=find;lodash.findIndex=findIndex;lodash.findKey=findKey;lodash.findLast=findLast;lodash.findLastIndex=findLastIndex;lodash.findLastKey=findLastKey;lodash.has=has;lodash.identity=identity;lodash.indexOf=indexOf;lodash.isArguments=isArguments;lodash.isArray=isArray;lodash.isBoolean=isBoolean;lodash.isDate=isDate;lodash.isElement=isElement;lodash.isEmpty=isEmpty;lodash.isEqual=isEqual;lodash.isFinite=isFinite;lodash.isFunction=isFunction;lodash.isNaN=isNaN;lodash.isNull=isNull;lodash.isNumber=isNumber;lodash.isObject=isObject;lodash.isPlainObject=isPlainObject;lodash.isRegExp=isRegExp;lodash.isString=isString;lodash.isUndefined=isUndefined;lodash.lastIndexOf=lastIndexOf;lodash.mixin=mixin;lodash.noConflict=noConflict;lodash.noop=noop;lodash.now=now;lodash.parseInt=parseInt;lodash.random=random;lodash.reduce=reduce;lodash.reduceRight=reduceRight;lodash.result=result;lodash.runInContext=runInContext;lodash.size=size;lodash.some=some;lodash.sortedIndex=sortedIndex;lodash.template=template;lodash.unescape=unescape;lodash.uniqueId=uniqueId;lodash.all=every;lodash.any=some;lodash.detect=find;lodash.findWhere=find;lodash.foldl=reduce;lodash.foldr=reduceRight;lodash.include=contains;lodash.inject=reduce;mixin(function(){var source={};forOwn(lodash,function(func,methodName){if(!lodash.prototype[methodName]){source[methodName]=func}});return source}(),false);lodash.first=first;lodash.last=last;lodash.sample=sample;lodash.take=first;lodash.head=first;forOwn(lodash,function(func,methodName){var callbackable=methodName!=="sample";if(!lodash.prototype[methodName]){lodash.prototype[methodName]=function(n,guard){var chainAll=this.__chain__,result=func(this.__wrapped__,n,guard);return!chainAll&&(n==null||guard&&!(callbackable&&typeof n=="function"))?result:new lodashWrapper(result,chainAll)}}});lodash.VERSION="2.4.1";lodash.prototype.chain=wrapperChain;lodash.prototype.toString=wrapperToString;lodash.prototype.value=wrapperValueOf;lodash.prototype.valueOf=wrapperValueOf;forEach(["join","pop","shift"],function(methodName){var func=arrayRef[methodName];lodash.prototype[methodName]=function(){var chainAll=this.__chain__,result=func.apply(this.__wrapped__,arguments);return chainAll?new lodashWrapper(result,chainAll):result}});forEach(["push","reverse","sort","unshift"],function(methodName){var func=arrayRef[methodName];lodash.prototype[methodName]=function(){func.apply(this.__wrapped__,arguments);return this}});forEach(["concat","slice","splice"],function(methodName){var func=arrayRef[methodName];lodash.prototype[methodName]=function(){return new lodashWrapper(func.apply(this.__wrapped__,arguments),this.__chain__)}});return lodash}var _=runInContext();if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){root._=_;define(function(){return _})}else if(freeExports&&freeModule){if(moduleExports){(freeModule.exports=_)._=_}else{freeExports._=_}}else{root._=_}}).call(this)}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}]},{},[1])(1)});
\ No newline at end of file
diff --git a/dev/js/lib/highlight/highlight.pack.js b/dev/js/lib/highlight/highlight.pack.js
new file mode 100644
index 0000000..6c71f03
--- /dev/null
+++ b/dev/js/lib/highlight/highlight.pack.js
@@ -0,0 +1 @@
+var hljs=new function(){function k(v){return v.replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">")}function t(v){return v.nodeName.toLowerCase()}function i(w,x){var v=w&&w.exec(x);return v&&v.index==0}function d(v){return Array.prototype.map.call(v.childNodes,function(w){if(w.nodeType==3){return b.useBR?w.nodeValue.replace(/\n/g,""):w.nodeValue}if(t(w)=="br"){return"\n"}return d(w)}).join("")}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^language-/,"")});return v.filter(function(x){return j(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset<y[0].offset)?w:y}return y[0].event=="start"?w:y}function A(H){function G(I){return" "+I.nodeName+'="'+k(I.value)+'"'}F+="<"+t(H)+Array.prototype.map.call(H.attributes,G).join("")+">"}function E(G){F+="</"+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=k(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+k(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};function E(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})}if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b=D.bK.split(" ").join("|")}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?\\b("+F.b+")\\b\\.?":F.b}).concat([D.tE]).concat([D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T<V.c.length;T++){if(i(V.c[T].bR,U)){return V.c[T]}}}function z(U,T){if(i(U.eR,T)){return U}if(U.eW){return z(U.parent,T)}}function A(T,U){return !J&&i(U.iR,T)}function E(V,T){var U=M.cI?T[0].toLowerCase():T[0];return V.k.hasOwnProperty(U)&&V.k[U]}function w(Z,X,W,V){var T=V?"":b.classPrefix,U='<span class="'+T,Y=W?"":"</span>";U+=Z+'">';return U+X+Y}function N(){var U=k(C);if(!I.k){return U}var T="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(U);while(V){T+=U.substr(X,V.index-X);var W=E(I,V);if(W){H+=W[1];T+=w(W[0],V[0])}else{T+=V[0]}X=I.lR.lastIndex;V=I.lR.exec(U)}return T+U.substr(X)}function F(){if(I.sL&&!f[I.sL]){return k(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):g(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=k(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+="</span>"}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=k(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"<unnamed>")+'"')}C+=X;return X.length||1}var M=j(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+="</span>"}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:k(L)}}else{throw O}}}function g(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:k(y)};var w=v;x.forEach(function(z){if(!j(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function h(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"<br>")}return v}function p(z){var y=d(z);var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):g(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=h(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function e(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function j(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=g;this.fixMarkup=h;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=e;this.getLanguage=j;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/</,r:0,c:[d,{cN:"attribute",b:c,r:0},{b:"=",r:0,c:[{cN:"value",v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html"],cI:true,c:[{cN:"doctype",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"<!--",e:"-->",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{title:"style"},c:[b],starts:{e:"</style>",rE:true,sL:"css"}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true,}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",e:"\\)",c:["self",a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.NM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});
\ No newline at end of file
diff --git a/dev/js/lib/jasmine-2.1.1/boot.js b/dev/js/lib/jasmine-2.1.1/boot.js
new file mode 100644
index 0000000..164f068
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/boot.js
@@ -0,0 +1,120 @@
+/**
+ Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
+
+ If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
+
+ The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
+
+ [jasmine-gem]: http://github.com/pivotal/jasmine-gem
+ */
+
+(function() {
+
+ /**
+ * ## Require & Instantiate
+ *
+ * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
+ */
+ window.jasmine = jasmineRequire.core(jasmineRequire);
+
+ /**
+ * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
+ */
+ jasmineRequire.html(jasmine);
+
+ /**
+ * Create the Jasmine environment. This is used to run all specs in a project.
+ */
+ var env = jasmine.getEnv();
+
+ /**
+ * ## The Global Interface
+ *
+ * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
+ */
+ var jasmineInterface = jasmineRequire.interface(jasmine, env);
+
+ /**
+ * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
+ */
+ if (typeof window == "undefined" && typeof exports == "object") {
+ extend(exports, jasmineInterface);
+ } else {
+ extend(window, jasmineInterface);
+ }
+
+ /**
+ * ## Runner Parameters
+ *
+ * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
+ */
+
+ var queryString = new jasmine.QueryString({
+ getWindowLocation: function() { return window.location; }
+ });
+
+ var catchingExceptions = queryString.getParam("catch");
+ env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
+
+ /**
+ * ## Reporters
+ * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
+ */
+ var htmlReporter = new jasmine.HtmlReporter({
+ env: env,
+ onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
+ getContainer: function() { return document.body; },
+ createElement: function() { return document.createElement.apply(document, arguments); },
+ createTextNode: function() { return document.createTextNode.apply(document, arguments); },
+ timer: new jasmine.Timer()
+ });
+
+ /**
+ * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
+ */
+ env.addReporter(jasmineInterface.jsApiReporter);
+ env.addReporter(htmlReporter);
+
+ /**
+ * Filter which specs will be run by matching the start of the full name against the `spec` query param.
+ */
+ var specFilter = new jasmine.HtmlSpecFilter({
+ filterString: function() { return queryString.getParam("spec"); }
+ });
+
+ env.specFilter = function(spec) {
+ return specFilter.matches(spec.getFullName());
+ };
+
+ /**
+ * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
+ */
+ window.setTimeout = window.setTimeout;
+ window.setInterval = window.setInterval;
+ window.clearTimeout = window.clearTimeout;
+ window.clearInterval = window.clearInterval;
+
+ /**
+ * ## Execution
+ *
+ * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
+ */
+ var currentWindowOnload = window.onload;
+
+ window.onload = function() {
+ if (currentWindowOnload) {
+ currentWindowOnload();
+ }
+ htmlReporter.initialize();
+ env.execute();
+ };
+
+ /**
+ * Helper function for readability above.
+ */
+ function extend(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+ }
+
+}());
diff --git a/dev/js/lib/jasmine-2.1.1/console.js b/dev/js/lib/jasmine-2.1.1/console.js
new file mode 100644
index 0000000..a65876e
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/console.js
@@ -0,0 +1,190 @@
+/*
+Copyright (c) 2008-2014 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+function getJasmineRequireObj() {
+ if (typeof module !== 'undefined' && module.exports) {
+ return exports;
+ } else {
+ window.jasmineRequire = window.jasmineRequire || {};
+ return window.jasmineRequire;
+ }
+}
+
+getJasmineRequireObj().console = function(jRequire, j$) {
+ j$.ConsoleReporter = jRequire.ConsoleReporter();
+};
+
+getJasmineRequireObj().ConsoleReporter = function() {
+
+ var noopTimer = {
+ start: function(){},
+ elapsed: function(){ return 0; }
+ };
+
+ function ConsoleReporter(options) {
+ var print = options.print,
+ showColors = options.showColors || false,
+ onComplete = options.onComplete || function() {},
+ timer = options.timer || noopTimer,
+ specCount,
+ failureCount,
+ failedSpecs = [],
+ pendingCount,
+ ansi = {
+ green: '\x1B[32m',
+ red: '\x1B[31m',
+ yellow: '\x1B[33m',
+ none: '\x1B[0m'
+ },
+ failedSuites = [];
+
+ print('ConsoleReporter is deprecated and will be removed in a future version.');
+
+ this.jasmineStarted = function() {
+ specCount = 0;
+ failureCount = 0;
+ pendingCount = 0;
+ print('Started');
+ printNewline();
+ timer.start();
+ };
+
+ this.jasmineDone = function() {
+ printNewline();
+ for (var i = 0; i < failedSpecs.length; i++) {
+ specFailureDetails(failedSpecs[i]);
+ }
+
+ if(specCount > 0) {
+ printNewline();
+
+ var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' +
+ failureCount + ' ' + plural('failure', failureCount);
+
+ if (pendingCount) {
+ specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount);
+ }
+
+ print(specCounts);
+ } else {
+ print('No specs found');
+ }
+
+ printNewline();
+ var seconds = timer.elapsed() / 1000;
+ print('Finished in ' + seconds + ' ' + plural('second', seconds));
+ printNewline();
+
+ for(i = 0; i < failedSuites.length; i++) {
+ suiteFailureDetails(failedSuites[i]);
+ }
+
+ onComplete(failureCount === 0);
+ };
+
+ this.specDone = function(result) {
+ specCount++;
+
+ if (result.status == 'pending') {
+ pendingCount++;
+ print(colored('yellow', '*'));
+ return;
+ }
+
+ if (result.status == 'passed') {
+ print(colored('green', '.'));
+ return;
+ }
+
+ if (result.status == 'failed') {
+ failureCount++;
+ failedSpecs.push(result);
+ print(colored('red', 'F'));
+ }
+ };
+
+ this.suiteDone = function(result) {
+ if (result.failedExpectations && result.failedExpectations.length > 0) {
+ failureCount++;
+ failedSuites.push(result);
+ }
+ };
+
+ return this;
+
+ function printNewline() {
+ print('\n');
+ }
+
+ function colored(color, str) {
+ return showColors ? (ansi[color] + str + ansi.none) : str;
+ }
+
+ function plural(str, count) {
+ return count == 1 ? str : str + 's';
+ }
+
+ function repeat(thing, times) {
+ var arr = [];
+ for (var i = 0; i < times; i++) {
+ arr.push(thing);
+ }
+ return arr;
+ }
+
+ function indent(str, spaces) {
+ var lines = (str || '').split('\n');
+ var newArr = [];
+ for (var i = 0; i < lines.length; i++) {
+ newArr.push(repeat(' ', spaces).join('') + lines[i]);
+ }
+ return newArr.join('\n');
+ }
+
+ function specFailureDetails(result) {
+ printNewline();
+ print(result.fullName);
+
+ for (var i = 0; i < result.failedExpectations.length; i++) {
+ var failedExpectation = result.failedExpectations[i];
+ printNewline();
+ print(indent(failedExpectation.message, 2));
+ print(indent(failedExpectation.stack, 2));
+ }
+
+ printNewline();
+ }
+
+ function suiteFailureDetails(result) {
+ for (var i = 0; i < result.failedExpectations.length; i++) {
+ printNewline();
+ print(colored('red', 'An error was thrown in an afterAll'));
+ printNewline();
+ print(colored('red', 'AfterAll ' + result.failedExpectations[i].message));
+
+ }
+ printNewline();
+ }
+ }
+
+ return ConsoleReporter;
+};
diff --git a/dev/js/lib/jasmine-2.1.1/jasmine-html.js b/dev/js/lib/jasmine-2.1.1/jasmine-html.js
new file mode 100644
index 0000000..898108b
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/jasmine-html.js
@@ -0,0 +1,404 @@
+/*
+Copyright (c) 2008-2014 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+jasmineRequire.html = function(j$) {
+ j$.ResultsNode = jasmineRequire.ResultsNode();
+ j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
+ j$.QueryString = jasmineRequire.QueryString();
+ j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
+};
+
+jasmineRequire.HtmlReporter = function(j$) {
+
+ var noopTimer = {
+ start: function() {},
+ elapsed: function() { return 0; }
+ };
+
+ function HtmlReporter(options) {
+ var env = options.env || {},
+ getContainer = options.getContainer,
+ createElement = options.createElement,
+ createTextNode = options.createTextNode,
+ onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
+ timer = options.timer || noopTimer,
+ results = [],
+ specsExecuted = 0,
+ failureCount = 0,
+ pendingSpecCount = 0,
+ htmlReporterMain,
+ symbols,
+ failedSuites = [];
+
+ this.initialize = function() {
+ clearPrior();
+ htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
+ createDom('div', {className: 'banner'},
+ createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
+ createDom('span', {className: 'version'}, j$.version)
+ ),
+ createDom('ul', {className: 'symbol-summary'}),
+ createDom('div', {className: 'alert'}),
+ createDom('div', {className: 'results'},
+ createDom('div', {className: 'failures'})
+ )
+ );
+ getContainer().appendChild(htmlReporterMain);
+
+ symbols = find('.symbol-summary');
+ };
+
+ var totalSpecsDefined;
+ this.jasmineStarted = function(options) {
+ totalSpecsDefined = options.totalSpecsDefined || 0;
+ timer.start();
+ };
+
+ var summary = createDom('div', {className: 'summary'});
+
+ var topResults = new j$.ResultsNode({}, '', null),
+ currentParent = topResults;
+
+ this.suiteStarted = function(result) {
+ currentParent.addChild(result, 'suite');
+ currentParent = currentParent.last();
+ };
+
+ this.suiteDone = function(result) {
+ if (result.status == 'failed') {
+ failedSuites.push(result);
+ }
+
+ if (currentParent == topResults) {
+ return;
+ }
+
+ currentParent = currentParent.parent;
+ };
+
+ this.specStarted = function(result) {
+ currentParent.addChild(result, 'spec');
+ };
+
+ var failures = [];
+ this.specDone = function(result) {
+ if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
+ console.error('Spec \'' + result.fullName + '\' has no expectations.');
+ }
+
+ if (result.status != 'disabled') {
+ specsExecuted++;
+ }
+
+ symbols.appendChild(createDom('li', {
+ className: noExpectations(result) ? 'empty' : result.status,
+ id: 'spec_' + result.id,
+ title: result.fullName
+ }
+ ));
+
+ if (result.status == 'failed') {
+ failureCount++;
+
+ var failure =
+ createDom('div', {className: 'spec-detail failed'},
+ createDom('div', {className: 'description'},
+ createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
+ ),
+ createDom('div', {className: 'messages'})
+ );
+ var messages = failure.childNodes[1];
+
+ for (var i = 0; i < result.failedExpectations.length; i++) {
+ var expectation = result.failedExpectations[i];
+ messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
+ messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
+ }
+
+ failures.push(failure);
+ }
+
+ if (result.status == 'pending') {
+ pendingSpecCount++;
+ }
+ };
+
+ this.jasmineDone = function() {
+ var banner = find('.banner');
+ banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
+
+ var alert = find('.alert');
+
+ alert.appendChild(createDom('span', { className: 'exceptions' },
+ createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'),
+ createDom('input', {
+ className: 'raise',
+ id: 'raise-exceptions',
+ type: 'checkbox'
+ })
+ ));
+ var checkbox = find('#raise-exceptions');
+
+ checkbox.checked = !env.catchingExceptions();
+ checkbox.onclick = onRaiseExceptionsClick;
+
+ if (specsExecuted < totalSpecsDefined) {
+ var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
+ alert.appendChild(
+ createDom('span', {className: 'bar skipped'},
+ createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
+ )
+ );
+ }
+ var statusBarMessage = '';
+ var statusBarClassName = 'bar ';
+
+ if (totalSpecsDefined > 0) {
+ statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
+ if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
+ statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
+ } else {
+ statusBarClassName += 'skipped';
+ statusBarMessage += 'No specs found';
+ }
+
+ alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
+
+ for(i = 0; i < failedSuites.length; i++) {
+ var failedSuite = failedSuites[i];
+ for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
+ var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
+ var errorBarClassName = 'bar errored';
+ alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
+ }
+ }
+
+ var results = find('.results');
+ results.appendChild(summary);
+
+ summaryList(topResults, summary);
+
+ function summaryList(resultsTree, domParent) {
+ var specListNode;
+ for (var i = 0; i < resultsTree.children.length; i++) {
+ var resultNode = resultsTree.children[i];
+ if (resultNode.type == 'suite') {
+ var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
+ createDom('li', {className: 'suite-detail'},
+ createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
+ )
+ );
+
+ summaryList(resultNode, suiteListNode);
+ domParent.appendChild(suiteListNode);
+ }
+ if (resultNode.type == 'spec') {
+ if (domParent.getAttribute('class') != 'specs') {
+ specListNode = createDom('ul', {className: 'specs'});
+ domParent.appendChild(specListNode);
+ }
+ var specDescription = resultNode.result.description;
+ if(noExpectations(resultNode.result)) {
+ specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
+ }
+ specListNode.appendChild(
+ createDom('li', {
+ className: resultNode.result.status,
+ id: 'spec-' + resultNode.result.id
+ },
+ createDom('a', {href: specHref(resultNode.result)}, specDescription)
+ )
+ );
+ }
+ }
+ }
+
+ if (failures.length) {
+ alert.appendChild(
+ createDom('span', {className: 'menu bar spec-list'},
+ createDom('span', {}, 'Spec List | '),
+ createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
+ alert.appendChild(
+ createDom('span', {className: 'menu bar failure-list'},
+ createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
+ createDom('span', {}, ' | Failures ')));
+
+ find('.failures-menu').onclick = function() {
+ setMenuModeTo('failure-list');
+ };
+ find('.spec-list-menu').onclick = function() {
+ setMenuModeTo('spec-list');
+ };
+
+ setMenuModeTo('failure-list');
+
+ var failureNode = find('.failures');
+ for (var i = 0; i < failures.length; i++) {
+ failureNode.appendChild(failures[i]);
+ }
+ }
+ };
+
+ return this;
+
+ function find(selector) {
+ return getContainer().querySelector('.jasmine_html-reporter ' + selector);
+ }
+
+ function clearPrior() {
+ // return the reporter
+ var oldReporter = find('');
+
+ if(oldReporter) {
+ getContainer().removeChild(oldReporter);
+ }
+ }
+
+ function createDom(type, attrs, childrenVarArgs) {
+ var el = createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == 'className') {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+ }
+
+ function pluralize(singular, count) {
+ var word = (count == 1 ? singular : singular + 's');
+
+ return '' + count + ' ' + word;
+ }
+
+ function specHref(result) {
+ return '?spec=' + encodeURIComponent(result.fullName);
+ }
+
+ function setMenuModeTo(mode) {
+ htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
+ }
+
+ function noExpectations(result) {
+ return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
+ result.status === 'passed';
+ }
+ }
+
+ return HtmlReporter;
+};
+
+jasmineRequire.HtmlSpecFilter = function() {
+ function HtmlSpecFilter(options) {
+ var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ var filterPattern = new RegExp(filterString);
+
+ this.matches = function(specName) {
+ return filterPattern.test(specName);
+ };
+ }
+
+ return HtmlSpecFilter;
+};
+
+jasmineRequire.ResultsNode = function() {
+ function ResultsNode(result, type, parent) {
+ this.result = result;
+ this.type = type;
+ this.parent = parent;
+
+ this.children = [];
+
+ this.addChild = function(result, type) {
+ this.children.push(new ResultsNode(result, type, this));
+ };
+
+ this.last = function() {
+ return this.children[this.children.length - 1];
+ };
+ }
+
+ return ResultsNode;
+};
+
+jasmineRequire.QueryString = function() {
+ function QueryString(options) {
+
+ this.setParam = function(key, value) {
+ var paramMap = queryStringToParamMap();
+ paramMap[key] = value;
+ options.getWindowLocation().search = toQueryString(paramMap);
+ };
+
+ this.getParam = function(key) {
+ return queryStringToParamMap()[key];
+ };
+
+ return this;
+
+ function toQueryString(paramMap) {
+ var qStrPairs = [];
+ for (var prop in paramMap) {
+ qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
+ }
+ return '?' + qStrPairs.join('&');
+ }
+
+ function queryStringToParamMap() {
+ var paramStr = options.getWindowLocation().search.substring(1),
+ params = [],
+ paramMap = {};
+
+ if (paramStr.length > 0) {
+ params = paramStr.split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ var value = decodeURIComponent(p[1]);
+ if (value === 'true' || value === 'false') {
+ value = JSON.parse(value);
+ }
+ paramMap[decodeURIComponent(p[0])] = value;
+ }
+ }
+
+ return paramMap;
+ }
+
+ }
+
+ return QueryString;
+};
diff --git a/dev/js/lib/jasmine-2.1.1/jasmine.css b/dev/js/lib/jasmine-2.1.1/jasmine.css
new file mode 100644
index 0000000..ecc5f5e
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/jasmine.css
@@ -0,0 +1,62 @@
+body { overflow-y: scroll; }
+
+.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; }
+.jasmine_html-reporter a { text-decoration: none; }
+.jasmine_html-reporter a:hover { text-decoration: underline; }
+.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
+.jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
+.jasmine_html-reporter .banner { position: relative; }
+.jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
+.jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; }
+.jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; }
+.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
+.jasmine_html-reporter .version { color: #aaa; }
+.jasmine_html-reporter .banner { margin-top: 14px; }
+.jasmine_html-reporter .duration { color: #aaa; float: right; }
+.jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+.jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
+.jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; }
+.jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; }
+.jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; }
+.jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
+.jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; }
+.jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
+.jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; }
+.jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
+.jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; }
+.jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; }
+.jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
+.jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+.jasmine_html-reporter .bar.failed { background-color: #ca3a11; }
+.jasmine_html-reporter .bar.passed { background-color: #007069; }
+.jasmine_html-reporter .bar.skipped { background-color: #bababa; }
+.jasmine_html-reporter .bar.errored { background-color: #ca3a11; }
+.jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaa; }
+.jasmine_html-reporter .bar.menu a { color: #333; }
+.jasmine_html-reporter .bar a { color: white; }
+.jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; }
+.jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; }
+.jasmine_html-reporter .running-alert { background-color: #666; }
+.jasmine_html-reporter .results { margin-top: 14px; }
+.jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+.jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+.jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+.jasmine_html-reporter.showDetails .summary { display: none; }
+.jasmine_html-reporter.showDetails #details { display: block; }
+.jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+.jasmine_html-reporter .summary { margin-top: 14px; }
+.jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
+.jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
+.jasmine_html-reporter .summary li.passed a { color: #007069; }
+.jasmine_html-reporter .summary li.failed a { color: #ca3a11; }
+.jasmine_html-reporter .summary li.empty a { color: #ba9d37; }
+.jasmine_html-reporter .summary li.pending a { color: #ba9d37; }
+.jasmine_html-reporter .description + .suite { margin-top: 0; }
+.jasmine_html-reporter .suite { margin-top: 14px; }
+.jasmine_html-reporter .suite a { color: #333; }
+.jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; }
+.jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; }
+.jasmine_html-reporter .failures .spec-detail .description a { color: white; }
+.jasmine_html-reporter .result-message { padding-top: 14px; color: #333; white-space: pre; }
+.jasmine_html-reporter .result-message span.result { display: block; }
+.jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/dev/js/lib/jasmine-2.1.1/jasmine.js b/dev/js/lib/jasmine-2.1.1/jasmine.js
new file mode 100644
index 0000000..7da2cc3
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/jasmine.js
@@ -0,0 +1,2910 @@
+/*
+Copyright (c) 2008-2014 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+getJasmineRequireObj = (function (jasmineGlobal) {
+ var jasmineRequire;
+
+ if (typeof module !== 'undefined' && module.exports) {
+ jasmineGlobal = global;
+ jasmineRequire = exports;
+ } else {
+ jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
+ }
+
+ function getJasmineRequire() {
+ return jasmineRequire;
+ }
+
+ getJasmineRequire().core = function(jRequire) {
+ var j$ = {};
+
+ jRequire.base(j$, jasmineGlobal);
+ j$.util = jRequire.util();
+ j$.Any = jRequire.Any();
+ j$.CallTracker = jRequire.CallTracker();
+ j$.MockDate = jRequire.MockDate();
+ j$.Clock = jRequire.Clock();
+ j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+ j$.Env = jRequire.Env(j$);
+ j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+ j$.Expectation = jRequire.Expectation();
+ j$.buildExpectationResult = jRequire.buildExpectationResult();
+ j$.JsApiReporter = jRequire.JsApiReporter();
+ j$.matchersUtil = jRequire.matchersUtil(j$);
+ j$.ObjectContaining = jRequire.ObjectContaining(j$);
+ j$.pp = jRequire.pp(j$);
+ j$.QueueRunner = jRequire.QueueRunner(j$);
+ j$.ReportDispatcher = jRequire.ReportDispatcher();
+ j$.Spec = jRequire.Spec(j$);
+ j$.SpyRegistry = jRequire.SpyRegistry(j$);
+ j$.SpyStrategy = jRequire.SpyStrategy();
+ j$.Suite = jRequire.Suite();
+ j$.Timer = jRequire.Timer();
+ j$.version = jRequire.version();
+
+ j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+ return j$;
+ };
+
+ return getJasmineRequire;
+})(this);
+
+getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
+ var availableMatchers = [
+ 'toBe',
+ 'toBeCloseTo',
+ 'toBeDefined',
+ 'toBeFalsy',
+ 'toBeGreaterThan',
+ 'toBeLessThan',
+ 'toBeNaN',
+ 'toBeNull',
+ 'toBeTruthy',
+ 'toBeUndefined',
+ 'toContain',
+ 'toEqual',
+ 'toHaveBeenCalled',
+ 'toHaveBeenCalledWith',
+ 'toMatch',
+ 'toThrow',
+ 'toThrowError'
+ ],
+ matchers = {};
+
+ for (var i = 0; i < availableMatchers.length; i++) {
+ var name = availableMatchers[i];
+ matchers[name] = jRequire[name](j$);
+ }
+
+ return matchers;
+};
+
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+ j$.unimplementedMethod_ = function() {
+ throw new Error('unimplemented method');
+ };
+
+ j$.MAX_PRETTY_PRINT_DEPTH = 40;
+ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+ j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+ j$.getGlobal = function() {
+ return jasmineGlobal;
+ };
+
+ j$.getEnv = function(options) {
+ var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+ //jasmine. singletons in here (setTimeout blah blah).
+ return env;
+ };
+
+ j$.isArray_ = function(value) {
+ return j$.isA_('Array', value);
+ };
+
+ j$.isString_ = function(value) {
+ return j$.isA_('String', value);
+ };
+
+ j$.isNumber_ = function(value) {
+ return j$.isA_('Number', value);
+ };
+
+ j$.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+ };
+
+ j$.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+ };
+
+ j$.any = function(clazz) {
+ return new j$.Any(clazz);
+ };
+
+ j$.objectContaining = function(sample) {
+ return new j$.ObjectContaining(sample);
+ };
+
+ j$.createSpy = function(name, originalFn) {
+
+ var spyStrategy = new j$.SpyStrategy({
+ name: name,
+ fn: originalFn,
+ getSpy: function() { return spy; }
+ }),
+ callTracker = new j$.CallTracker(),
+ spy = function() {
+ var callData = {
+ object: this,
+ args: Array.prototype.slice.apply(arguments)
+ };
+
+ callTracker.track(callData);
+ var returnValue = spyStrategy.exec.apply(this, arguments);
+ callData.returnValue = returnValue;
+
+ return returnValue;
+ };
+
+ for (var prop in originalFn) {
+ if (prop === 'and' || prop === 'calls') {
+ throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
+ }
+
+ spy[prop] = originalFn[prop];
+ }
+
+ spy.and = spyStrategy;
+ spy.calls = callTracker;
+
+ return spy;
+ };
+
+ j$.isSpy = function(putativeSpy) {
+ if (!putativeSpy) {
+ return false;
+ }
+ return putativeSpy.and instanceof j$.SpyStrategy &&
+ putativeSpy.calls instanceof j$.CallTracker;
+ };
+
+ j$.createSpyObj = function(baseName, methodNames) {
+ if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+ throw 'createSpyObj requires a non-empty array of method names to create spies for';
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+ };
+};
+
+getJasmineRequireObj().util = function() {
+
+ var util = {};
+
+ util.inherit = function(childClass, parentClass) {
+ var Subclass = function() {
+ };
+ Subclass.prototype = parentClass.prototype;
+ childClass.prototype = new Subclass();
+ };
+
+ util.htmlEscape = function(str) {
+ if (!str) {
+ return str;
+ }
+ return str.replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+ };
+
+ util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) {
+ arrayOfArgs.push(args[i]);
+ }
+ return arrayOfArgs;
+ };
+
+ util.isUndefined = function(obj) {
+ return obj === void 0;
+ };
+
+ util.arrayContains = function(array, search) {
+ var i = array.length;
+ while (i--) {
+ if (array[i] === search) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ util.clone = function(obj) {
+ if (Object.prototype.toString.apply(obj) === '[object Array]') {
+ return obj.slice();
+ }
+
+ var cloned = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ cloned[prop] = obj[prop];
+ }
+ }
+
+ return cloned;
+ };
+
+ return util;
+};
+
+getJasmineRequireObj().Spec = function(j$) {
+ function Spec(attrs) {
+ this.expectationFactory = attrs.expectationFactory;
+ this.resultCallback = attrs.resultCallback || function() {};
+ this.id = attrs.id;
+ this.description = attrs.description || '';
+ this.queueableFn = attrs.queueableFn;
+ this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+ this.userContext = attrs.userContext || function() { return {}; };
+ this.onStart = attrs.onStart || function() {};
+ this.getSpecName = attrs.getSpecName || function() { return ''; };
+ this.expectationResultFactory = attrs.expectationResultFactory || function() { };
+ this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
+ this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+
+ if (!this.queueableFn.fn) {
+ this.pend();
+ }
+
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: [],
+ passedExpectations: []
+ };
+ }
+
+ Spec.prototype.addExpectationResult = function(passed, data) {
+ var expectationResult = this.expectationResultFactory(data);
+ if (passed) {
+ this.result.passedExpectations.push(expectationResult);
+ } else {
+ this.result.failedExpectations.push(expectationResult);
+ }
+ };
+
+ Spec.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Spec.prototype.execute = function(onComplete) {
+ var self = this;
+
+ this.onStart(this);
+
+ if (this.markedPending || this.disabled) {
+ complete();
+ return;
+ }
+
+ var fns = this.beforeAndAfterFns();
+ var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
+
+ this.queueRunnerFactory({
+ queueableFns: allFns,
+ onException: function() { self.onException.apply(self, arguments); },
+ onComplete: complete,
+ userContext: this.userContext()
+ });
+
+ function complete() {
+ self.result.status = self.status();
+ self.resultCallback(self.result);
+
+ if (onComplete) {
+ onComplete();
+ }
+ }
+ };
+
+ Spec.prototype.onException = function onException(e) {
+ if (Spec.isPendingSpecException(e)) {
+ this.pend();
+ return;
+ }
+
+ this.addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: e
+ });
+ };
+
+ Spec.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Spec.prototype.pend = function() {
+ this.markedPending = true;
+ };
+
+ Spec.prototype.status = function() {
+ if (this.disabled) {
+ return 'disabled';
+ }
+
+ if (this.markedPending) {
+ return 'pending';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'passed';
+ }
+ };
+
+ Spec.prototype.isExecutable = function() {
+ return !this.disabled && !this.markedPending;
+ };
+
+ Spec.prototype.getFullName = function() {
+ return this.getSpecName(this);
+ };
+
+ Spec.pendingSpecExceptionMessage = '=> marked Pending';
+
+ Spec.isPendingSpecException = function(e) {
+ return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1);
+ };
+
+ return Spec;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+ exports.Spec = jasmineRequire.Spec;
+}
+
+getJasmineRequireObj().Env = function(j$) {
+ function Env(options) {
+ options = options || {};
+
+ var self = this;
+ var global = options.global || j$.getGlobal();
+
+ var totalSpecsDefined = 0;
+
+ var catchExceptions = true;
+
+ var realSetTimeout = j$.getGlobal().setTimeout;
+ var realClearTimeout = j$.getGlobal().clearTimeout;
+ this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global));
+
+ var runnableLookupTable = {};
+ var runnableResources = {};
+
+ var currentSpec = null;
+ var currentlyExecutingSuites = [];
+ var currentDeclarationSuite = null;
+
+ var currentSuite = function() {
+ return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+ };
+
+ var currentRunnable = function() {
+ return currentSpec || currentSuite();
+ };
+
+ var reporter = new j$.ReportDispatcher([
+ 'jasmineStarted',
+ 'jasmineDone',
+ 'suiteStarted',
+ 'suiteDone',
+ 'specStarted',
+ 'specDone'
+ ]);
+
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.addCustomEqualityTester = function(tester) {
+ if(!currentRunnable()) {
+ throw new Error('Custom Equalities must be added in a before function or a spec');
+ }
+ runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+ };
+
+ this.addMatchers = function(matchersToAdd) {
+ if(!currentRunnable()) {
+ throw new Error('Matchers must be added in a before function or a spec');
+ }
+ var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+ for (var matcherName in matchersToAdd) {
+ customMatchers[matcherName] = matchersToAdd[matcherName];
+ }
+ };
+
+ j$.Expectation.addCoreMatchers(j$.matchers);
+
+ var nextSpecId = 0;
+ var getNextSpecId = function() {
+ return 'spec' + nextSpecId++;
+ };
+
+ var nextSuiteId = 0;
+ var getNextSuiteId = function() {
+ return 'suite' + nextSuiteId++;
+ };
+
+ var expectationFactory = function(actual, spec) {
+ return j$.Expectation.Factory({
+ util: j$.matchersUtil,
+ customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+ customMatchers: runnableResources[spec.id].customMatchers,
+ actual: actual,
+ addExpectationResult: addExpectationResult
+ });
+
+ function addExpectationResult(passed, result) {
+ return spec.addExpectationResult(passed, result);
+ }
+ };
+
+ var defaultResourcesForRunnable = function(id, parentRunnableId) {
+ var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+ if(runnableResources[parentRunnableId]){
+ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+ resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+ }
+
+ runnableResources[id] = resources;
+ };
+
+ var clearResourcesForRunnable = function(id) {
+ spyRegistry.clearSpies();
+ delete runnableResources[id];
+ };
+
+ var beforeAndAfterFns = function(suite, runnablesExplictlySet) {
+ return function() {
+ var befores = [],
+ afters = [],
+ beforeAlls = [],
+ afterAlls = [];
+
+ while(suite) {
+ befores = befores.concat(suite.beforeFns);
+ afters = afters.concat(suite.afterFns);
+
+ if (runnablesExplictlySet()) {
+ beforeAlls = beforeAlls.concat(suite.beforeAllFns);
+ afterAlls = afterAlls.concat(suite.afterAllFns);
+ }
+
+ suite = suite.parentSuite;
+ }
+ return {
+ befores: beforeAlls.reverse().concat(befores.reverse()),
+ afters: afters.concat(afterAlls)
+ };
+ };
+ };
+
+ var getSpecName = function(spec, suite) {
+ return suite.getFullName() + ' ' + spec.description;
+ };
+
+ // TODO: we may just be able to pass in the fn instead of wrapping here
+ var buildExpectationResult = j$.buildExpectationResult,
+ exceptionFormatter = new j$.ExceptionFormatter(),
+ expectationResultFactory = function(attrs) {
+ attrs.messageFormatter = exceptionFormatter.message;
+ attrs.stackFormatter = exceptionFormatter.stack;
+
+ return buildExpectationResult(attrs);
+ };
+
+ // TODO: fix this naming, and here's where the value comes in
+ this.catchExceptions = function(value) {
+ catchExceptions = !!value;
+ return catchExceptions;
+ };
+
+ this.catchingExceptions = function() {
+ return catchExceptions;
+ };
+
+ var maximumSpecCallbackDepth = 20;
+ var currentSpecCallbackDepth = 0;
+
+ function clearStack(fn) {
+ currentSpecCallbackDepth++;
+ if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) {
+ currentSpecCallbackDepth = 0;
+ realSetTimeout(fn, 0);
+ } else {
+ fn();
+ }
+ }
+
+ var catchException = function(e) {
+ return j$.Spec.isPendingSpecException(e) || catchExceptions;
+ };
+
+ var queueRunnerFactory = function(options) {
+ options.catchException = catchException;
+ options.clearStack = options.clearStack || clearStack;
+ options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+ options.fail = self.fail;
+
+ new j$.QueueRunner(options).execute();
+ };
+
+ var topSuite = new j$.Suite({
+ env: this,
+ id: getNextSuiteId(),
+ description: 'Jasmine__TopLevel__Suite',
+ queueRunner: queueRunnerFactory,
+ onStart: function(suite) {
+ reporter.suiteStarted(suite.result);
+ },
+ resultCallback: function(attrs) {
+ reporter.suiteDone(attrs);
+ }
+ });
+ runnableLookupTable[topSuite.id] = topSuite;
+ defaultResourcesForRunnable(topSuite.id);
+ currentDeclarationSuite = topSuite;
+
+ this.topSuite = function() {
+ return topSuite;
+ };
+
+ this.execute = function(runnablesToRun) {
+ if(runnablesToRun) {
+ runnablesExplictlySet = true;
+ } else if (focusedRunnables.length) {
+ runnablesExplictlySet = true;
+ runnablesToRun = focusedRunnables;
+ } else {
+ runnablesToRun = [topSuite.id];
+ }
+
+ var allFns = [];
+ for(var i = 0; i < runnablesToRun.length; i++) {
+ var runnable = runnableLookupTable[runnablesToRun[i]];
+ allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable));
+ }
+
+ reporter.jasmineStarted({
+ totalSpecsDefined: totalSpecsDefined
+ });
+
+ queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone});
+ };
+
+ this.addReporter = function(reporterToAdd) {
+ reporter.addReporter(reporterToAdd);
+ };
+
+ var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+ if(!currentRunnable()) {
+ throw new Error('Spies must be created in a before function or a spec');
+ }
+ return runnableResources[currentRunnable().id].spies;
+ }});
+
+ this.spyOn = function() {
+ return spyRegistry.spyOn.apply(spyRegistry, arguments);
+ };
+
+ var suiteFactory = function(description) {
+ var suite = new j$.Suite({
+ env: self,
+ id: getNextSuiteId(),
+ description: description,
+ parentSuite: currentDeclarationSuite,
+ queueRunner: queueRunnerFactory,
+ onStart: suiteStarted,
+ expectationFactory: expectationFactory,
+ expectationResultFactory: expectationResultFactory,
+ resultCallback: function(attrs) {
+ if (!suite.disabled) {
+ clearResourcesForRunnable(suite.id);
+ currentlyExecutingSuites.pop();
+ }
+ reporter.suiteDone(attrs);
+ }
+ });
+
+ runnableLookupTable[suite.id] = suite;
+ return suite;
+
+ function suiteStarted(suite) {
+ currentlyExecutingSuites.push(suite);
+ defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+ reporter.suiteStarted(suite.result);
+ }
+ };
+
+ this.describe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ addSpecsToSuite(suite, specDefinitions);
+ return suite;
+ };
+
+ this.xdescribe = function(description, specDefinitions) {
+ var suite = this.describe(description, specDefinitions);
+ suite.disable();
+ return suite;
+ };
+
+ var focusedRunnables = [];
+
+ this.fdescribe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ suite.isFocused = true;
+
+ focusedRunnables.push(suite.id);
+ unfocusAncestor();
+ addSpecsToSuite(suite, specDefinitions);
+
+ return suite;
+ };
+
+ function addSpecsToSuite(suite, specDefinitions) {
+ var parentSuite = currentDeclarationSuite;
+ parentSuite.addChild(suite);
+ currentDeclarationSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch (e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ self.it('encountered a declaration exception', function() {
+ throw declarationError;
+ });
+ }
+
+ currentDeclarationSuite = parentSuite;
+ }
+
+ function findFocusedAncestor(suite) {
+ while (suite) {
+ if (suite.isFocused) {
+ return suite.id;
+ }
+ suite = suite.parentSuite;
+ }
+
+ return null;
+ }
+
+ function unfocusAncestor() {
+ var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+ if (focusedAncestor) {
+ for (var i = 0; i < focusedRunnables.length; i++) {
+ if (focusedRunnables[i] === focusedAncestor) {
+ focusedRunnables.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+
+ var runnablesExplictlySet = false;
+
+ var runnablesExplictlySetGetter = function(){
+ return runnablesExplictlySet;
+ };
+
+ var specFactory = function(description, fn, suite, timeout) {
+ totalSpecsDefined++;
+ var spec = new j$.Spec({
+ id: getNextSpecId(),
+ beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter),
+ expectationFactory: expectationFactory,
+ resultCallback: specResultCallback,
+ getSpecName: function(spec) {
+ return getSpecName(spec, suite);
+ },
+ onStart: specStarted,
+ description: description,
+ expectationResultFactory: expectationResultFactory,
+ queueRunnerFactory: queueRunnerFactory,
+ userContext: function() { return suite.clonedSharedUserContext(); },
+ queueableFn: {
+ fn: fn,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ }
+ });
+
+ runnableLookupTable[spec.id] = spec;
+
+ if (!self.specFilter(spec)) {
+ spec.disable();
+ }
+
+ return spec;
+
+ function specResultCallback(result) {
+ clearResourcesForRunnable(spec.id);
+ currentSpec = null;
+ reporter.specDone(result);
+ }
+
+ function specStarted(spec) {
+ currentSpec = spec;
+ defaultResourcesForRunnable(spec.id, suite.id);
+ reporter.specStarted(spec.result);
+ }
+ };
+
+ this.it = function(description, fn, timeout) {
+ var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+ currentDeclarationSuite.addChild(spec);
+ return spec;
+ };
+
+ this.xit = function() {
+ var spec = this.it.apply(this, arguments);
+ spec.pend();
+ return spec;
+ };
+
+ this.fit = function(){
+ var spec = this.it.apply(this, arguments);
+
+ focusedRunnables.push(spec.id);
+ unfocusAncestor();
+ return spec;
+ };
+
+ this.expect = function(actual) {
+ if (!currentRunnable()) {
+ throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
+ }
+
+ return currentRunnable().expect(actual);
+ };
+
+ this.beforeEach = function(beforeEachFunction, timeout) {
+ currentDeclarationSuite.beforeEach({
+ fn: beforeEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.beforeAll = function(beforeAllFunction, timeout) {
+ currentDeclarationSuite.beforeAll({
+ fn: beforeAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterEach = function(afterEachFunction, timeout) {
+ currentDeclarationSuite.afterEach({
+ fn: afterEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterAll = function(afterAllFunction, timeout) {
+ currentDeclarationSuite.afterAll({
+ fn: afterAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.pending = function() {
+ throw j$.Spec.pendingSpecExceptionMessage;
+ };
+
+ this.fail = function(error) {
+ var message = 'Failed';
+ if (error) {
+ message += ': ';
+ message += error.message || error;
+ }
+
+ currentRunnable().addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ message: message
+ });
+ };
+ }
+
+ return Env;
+};
+
+getJasmineRequireObj().JsApiReporter = function() {
+
+ var noopTimer = {
+ start: function(){},
+ elapsed: function(){ return 0; }
+ };
+
+ function JsApiReporter(options) {
+ var timer = options.timer || noopTimer,
+ status = 'loaded';
+
+ this.started = false;
+ this.finished = false;
+
+ this.jasmineStarted = function() {
+ this.started = true;
+ status = 'started';
+ timer.start();
+ };
+
+ var executionTime;
+
+ this.jasmineDone = function() {
+ this.finished = true;
+ executionTime = timer.elapsed();
+ status = 'done';
+ };
+
+ this.status = function() {
+ return status;
+ };
+
+ var suites = [],
+ suites_hash = {};
+
+ this.suiteStarted = function(result) {
+ suites_hash[result.id] = result;
+ };
+
+ this.suiteDone = function(result) {
+ storeSuite(result);
+ };
+
+ this.suiteResults = function(index, length) {
+ return suites.slice(index, index + length);
+ };
+
+ function storeSuite(result) {
+ suites.push(result);
+ suites_hash[result.id] = result;
+ }
+
+ this.suites = function() {
+ return suites_hash;
+ };
+
+ var specs = [];
+
+ this.specDone = function(result) {
+ specs.push(result);
+ };
+
+ this.specResults = function(index, length) {
+ return specs.slice(index, index + length);
+ };
+
+ this.specs = function() {
+ return specs;
+ };
+
+ this.executionTime = function() {
+ return executionTime;
+ };
+
+ }
+
+ return JsApiReporter;
+};
+
+getJasmineRequireObj().Any = function() {
+
+ function Any(expectedObject) {
+ this.expectedObject = expectedObject;
+ }
+
+ Any.prototype.jasmineMatches = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedObject == Object) {
+ return typeof other == 'object';
+ }
+
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
+
+ return other instanceof this.expectedObject;
+ };
+
+ Any.prototype.jasmineToString = function() {
+ return '<jasmine.any(' + this.expectedObject + ')>';
+ };
+
+ return Any;
+};
+
+getJasmineRequireObj().CallTracker = function() {
+
+ function CallTracker() {
+ var calls = [];
+
+ this.track = function(context) {
+ calls.push(context);
+ };
+
+ this.any = function() {
+ return !!calls.length;
+ };
+
+ this.count = function() {
+ return calls.length;
+ };
+
+ this.argsFor = function(index) {
+ var call = calls[index];
+ return call ? call.args : [];
+ };
+
+ this.all = function() {
+ return calls;
+ };
+
+ this.allArgs = function() {
+ var callArgs = [];
+ for(var i = 0; i < calls.length; i++){
+ callArgs.push(calls[i].args);
+ }
+
+ return callArgs;
+ };
+
+ this.first = function() {
+ return calls[0];
+ };
+
+ this.mostRecent = function() {
+ return calls[calls.length - 1];
+ };
+
+ this.reset = function() {
+ calls = [];
+ };
+ }
+
+ return CallTracker;
+};
+
+getJasmineRequireObj().Clock = function() {
+ function Clock(global, delayedFunctionScheduler, mockDate) {
+ var self = this,
+ realTimingFunctions = {
+ setTimeout: global.setTimeout,
+ clearTimeout: global.clearTimeout,
+ setInterval: global.setInterval,
+ clearInterval: global.clearInterval
+ },
+ fakeTimingFunctions = {
+ setTimeout: setTimeout,
+ clearTimeout: clearTimeout,
+ setInterval: setInterval,
+ clearInterval: clearInterval
+ },
+ installed = false,
+ timer;
+
+
+ self.install = function() {
+ replace(global, fakeTimingFunctions);
+ timer = fakeTimingFunctions;
+ installed = true;
+
+ return self;
+ };
+
+ self.uninstall = function() {
+ delayedFunctionScheduler.reset();
+ mockDate.uninstall();
+ replace(global, realTimingFunctions);
+
+ timer = realTimingFunctions;
+ installed = false;
+ };
+
+ self.mockDate = function(initialDate) {
+ mockDate.install(initialDate);
+ };
+
+ self.setTimeout = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill');
+ }
+ return timer.setTimeout(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]);
+ };
+
+ self.setInterval = function(fn, delay, params) {
+ if (legacyIE()) {
+ if (arguments.length > 2) {
+ throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill');
+ }
+ return timer.setInterval(fn, delay);
+ }
+ return Function.prototype.apply.apply(timer.setInterval, [global, arguments]);
+ };
+
+ self.clearTimeout = function(id) {
+ return Function.prototype.call.apply(timer.clearTimeout, [global, id]);
+ };
+
+ self.clearInterval = function(id) {
+ return Function.prototype.call.apply(timer.clearInterval, [global, id]);
+ };
+
+ self.tick = function(millis) {
+ if (installed) {
+ mockDate.tick(millis);
+ delayedFunctionScheduler.tick(millis);
+ } else {
+ throw new Error('Mock clock is not installed, use jasmine.clock().install()');
+ }
+ };
+
+ return self;
+
+ function legacyIE() {
+ //if these methods are polyfilled, apply will be present
+ return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
+ }
+
+ function replace(dest, source) {
+ for (var prop in source) {
+ dest[prop] = source[prop];
+ }
+ }
+
+ function setTimeout(fn, delay) {
+ return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2));
+ }
+
+ function clearTimeout(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function setInterval(fn, interval) {
+ return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true);
+ }
+
+ function clearInterval(id) {
+ return delayedFunctionScheduler.removeFunctionWithId(id);
+ }
+
+ function argSlice(argsObj, n) {
+ return Array.prototype.slice.call(argsObj, n);
+ }
+ }
+
+ return Clock;
+};
+
+getJasmineRequireObj().DelayedFunctionScheduler = function() {
+ function DelayedFunctionScheduler() {
+ var self = this;
+ var scheduledLookup = [];
+ var scheduledFunctions = {};
+ var currentTime = 0;
+ var delayedFnCount = 0;
+
+ self.tick = function(millis) {
+ millis = millis || 0;
+ var endTime = currentTime + millis;
+
+ runScheduledFunctions(endTime);
+ currentTime = endTime;
+ };
+
+ self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) {
+ var f;
+ if (typeof(funcToCall) === 'string') {
+ /* jshint evil: true */
+ f = function() { return eval(funcToCall); };
+ /* jshint evil: false */
+ } else {
+ f = funcToCall;
+ }
+
+ millis = millis || 0;
+ timeoutKey = timeoutKey || ++delayedFnCount;
+ runAtMillis = runAtMillis || (currentTime + millis);
+
+ var funcToSchedule = {
+ runAtMillis: runAtMillis,
+ funcToCall: f,
+ recurring: recurring,
+ params: params,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+
+ if (runAtMillis in scheduledFunctions) {
+ scheduledFunctions[runAtMillis].push(funcToSchedule);
+ } else {
+ scheduledFunctions[runAtMillis] = [funcToSchedule];
+ scheduledLookup.push(runAtMillis);
+ scheduledLookup.sort(function (a, b) {
+ return a - b;
+ });
+ }
+
+ return timeoutKey;
+ };
+
+ self.removeFunctionWithId = function(timeoutKey) {
+ for (var runAtMillis in scheduledFunctions) {
+ var funcs = scheduledFunctions[runAtMillis];
+ var i = indexOfFirstToPass(funcs, function (func) {
+ return func.timeoutKey === timeoutKey;
+ });
+
+ if (i > -1) {
+ if (funcs.length === 1) {
+ delete scheduledFunctions[runAtMillis];
+ deleteFromLookup(runAtMillis);
+ } else {
+ funcs.splice(i, 1);
+ }
+
+ // intervals get rescheduled when executed, so there's never more
+ // than a single scheduled function with a given timeoutKey
+ break;
+ }
+ }
+ };
+
+ self.reset = function() {
+ currentTime = 0;
+ scheduledLookup = [];
+ scheduledFunctions = {};
+ delayedFnCount = 0;
+ };
+
+ return self;
+
+ function indexOfFirstToPass(array, testFn) {
+ var index = -1;
+
+ for (var i = 0; i < array.length; ++i) {
+ if (testFn(array[i])) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ function deleteFromLookup(key) {
+ var value = Number(key);
+ var i = indexOfFirstToPass(scheduledLookup, function (millis) {
+ return millis === value;
+ });
+
+ if (i > -1) {
+ scheduledLookup.splice(i, 1);
+ }
+ }
+
+ function reschedule(scheduledFn) {
+ self.scheduleFunction(scheduledFn.funcToCall,
+ scheduledFn.millis,
+ scheduledFn.params,
+ true,
+ scheduledFn.timeoutKey,
+ scheduledFn.runAtMillis + scheduledFn.millis);
+ }
+
+ function runScheduledFunctions(endTime) {
+ if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
+ return;
+ }
+
+ do {
+ currentTime = scheduledLookup.shift();
+
+ var funcsToRun = scheduledFunctions[currentTime];
+ delete scheduledFunctions[currentTime];
+
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ var funcToRun = funcsToRun[i];
+
+ if (funcToRun.recurring) {
+ reschedule(funcToRun);
+ }
+
+ funcToRun.funcToCall.apply(null, funcToRun.params || []);
+ }
+ } while (scheduledLookup.length > 0 &&
+ // checking first if we're out of time prevents setTimeout(0)
+ // scheduled in a funcToRun from forcing an extra iteration
+ currentTime !== endTime &&
+ scheduledLookup[0] <= endTime);
+ }
+ }
+
+ return DelayedFunctionScheduler;
+};
+
+getJasmineRequireObj().ExceptionFormatter = function() {
+ function ExceptionFormatter() {
+ this.message = function(error) {
+ var message = '';
+
+ if (error.name && error.message) {
+ message += error.name + ': ' + error.message;
+ } else {
+ message += error.toString() + ' thrown';
+ }
+
+ if (error.fileName || error.sourceURL) {
+ message += ' in ' + (error.fileName || error.sourceURL);
+ }
+
+ if (error.line || error.lineNumber) {
+ message += ' (line ' + (error.line || error.lineNumber) + ')';
+ }
+
+ return message;
+ };
+
+ this.stack = function(error) {
+ return error ? error.stack : null;
+ };
+ }
+
+ return ExceptionFormatter;
+};
+
+getJasmineRequireObj().Expectation = function() {
+
+ function Expectation(options) {
+ this.util = options.util || { buildFailureMessage: function() {} };
+ this.customEqualityTesters = options.customEqualityTesters || [];
+ this.actual = options.actual;
+ this.addExpectationResult = options.addExpectationResult || function(){};
+ this.isNot = options.isNot;
+
+ var customMatchers = options.customMatchers || {};
+ for (var matcherName in customMatchers) {
+ this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
+ }
+ }
+
+ Expectation.prototype.wrapCompare = function(name, matcherFactory) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ expected = args.slice(0),
+ message = '';
+
+ args.unshift(this.actual);
+
+ var matcher = matcherFactory(this.util, this.customEqualityTesters),
+ matcherCompare = matcher.compare;
+
+ function defaultNegativeCompare() {
+ var result = matcher.compare.apply(null, args);
+ result.pass = !result.pass;
+ return result;
+ }
+
+ if (this.isNot) {
+ matcherCompare = matcher.negativeCompare || defaultNegativeCompare;
+ }
+
+ var result = matcherCompare.apply(null, args);
+
+ if (!result.pass) {
+ if (!result.message) {
+ args.unshift(this.isNot);
+ args.unshift(name);
+ message = this.util.buildFailureMessage.apply(null, args);
+ } else {
+ if (Object.prototype.toString.apply(result.message) === '[object Function]') {
+ message = result.message();
+ } else {
+ message = result.message;
+ }
+ }
+ }
+
+ if (expected.length == 1) {
+ expected = expected[0];
+ }
+
+ // TODO: how many of these params are needed?
+ this.addExpectationResult(
+ result.pass,
+ {
+ matcherName: name,
+ passed: result.pass,
+ message: message,
+ actual: this.actual,
+ expected: expected // TODO: this may need to be arrayified/sliced
+ }
+ );
+ };
+ };
+
+ Expectation.addCoreMatchers = function(matchers) {
+ var prototype = Expectation.prototype;
+ for (var matcherName in matchers) {
+ var matcher = matchers[matcherName];
+ prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
+ }
+ };
+
+ Expectation.Factory = function(options) {
+ options = options || {};
+
+ var expect = new Expectation(options);
+
+ // TODO: this would be nice as its own Object - NegativeExpectation
+ // TODO: copy instead of mutate options
+ options.isNot = true;
+ expect.not = new Expectation(options);
+
+ return expect;
+ };
+
+ return Expectation;
+};
+
+//TODO: expectation result may make more sense as a presentation of an expectation.
+getJasmineRequireObj().buildExpectationResult = function() {
+ function buildExpectationResult(options) {
+ var messageFormatter = options.messageFormatter || function() {},
+ stackFormatter = options.stackFormatter || function() {};
+
+ return {
+ matcherName: options.matcherName,
+ expected: options.expected,
+ actual: options.actual,
+ message: message(),
+ stack: stack(),
+ passed: options.passed
+ };
+
+ function message() {
+ if (options.passed) {
+ return 'Passed.';
+ } else if (options.message) {
+ return options.message;
+ } else if (options.error) {
+ return messageFormatter(options.error);
+ }
+ return '';
+ }
+
+ function stack() {
+ if (options.passed) {
+ return '';
+ }
+
+ var error = options.error;
+ if (!error) {
+ try {
+ throw new Error(message());
+ } catch (e) {
+ error = e;
+ }
+ }
+ return stackFormatter(error);
+ }
+ }
+
+ return buildExpectationResult;
+};
+
+getJasmineRequireObj().MockDate = function() {
+ function MockDate(global) {
+ var self = this;
+ var currentTime = 0;
+
+ if (!global || !global.Date) {
+ self.install = function() {};
+ self.tick = function() {};
+ self.uninstall = function() {};
+ return self;
+ }
+
+ var GlobalDate = global.Date;
+
+ self.install = function(mockDate) {
+ if (mockDate instanceof GlobalDate) {
+ currentTime = mockDate.getTime();
+ } else {
+ currentTime = new GlobalDate().getTime();
+ }
+
+ global.Date = FakeDate;
+ };
+
+ self.tick = function(millis) {
+ millis = millis || 0;
+ currentTime = currentTime + millis;
+ };
+
+ self.uninstall = function() {
+ currentTime = 0;
+ global.Date = GlobalDate;
+ };
+
+ createDateProperties();
+
+ return self;
+
+ function FakeDate() {
+ switch(arguments.length) {
+ case 0:
+ return new GlobalDate(currentTime);
+ case 1:
+ return new GlobalDate(arguments[0]);
+ case 2:
+ return new GlobalDate(arguments[0], arguments[1]);
+ case 3:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+ case 4:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+ case 5:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4]);
+ case 6:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5]);
+ case 7:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5], arguments[6]);
+ }
+ }
+
+ function createDateProperties() {
+ FakeDate.prototype = GlobalDate.prototype;
+
+ FakeDate.now = function() {
+ if (GlobalDate.now) {
+ return currentTime;
+ } else {
+ throw new Error('Browser does not support Date.now()');
+ }
+ };
+
+ FakeDate.toSource = GlobalDate.toSource;
+ FakeDate.toString = GlobalDate.toString;
+ FakeDate.parse = GlobalDate.parse;
+ FakeDate.UTC = GlobalDate.UTC;
+ }
+ }
+
+ return MockDate;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
+
+ ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
+ if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
+
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && !j$.util.isUndefined(obj[keyName]);
+ };
+
+ for (var property in this.sample) {
+ if (!hasKey(other, property) && hasKey(this.sample, property)) {
+ mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.');
+ }
+ else if (!j$.matchersUtil.equals(other[property], this.sample[property])) {
+ mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.');
+ }
+ }
+
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+ };
+
+ ObjectContaining.prototype.jasmineToString = function() {
+ return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
+ };
+
+ return ObjectContaining;
+};
+
+getJasmineRequireObj().pp = function(j$) {
+
+ function PrettyPrinter() {
+ this.ppNestLevel_ = 0;
+ this.seen = [];
+ }
+
+ PrettyPrinter.prototype.format = function(value) {
+ this.ppNestLevel_++;
+ try {
+ if (j$.util.isUndefined(value)) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === 0 && 1/value === -Infinity) {
+ this.emitScalar('-0');
+ } else if (value === j$.getGlobal()) {
+ this.emitScalar('<global>');
+ } else if (value.jasmineToString) {
+ this.emitScalar(value.jasmineToString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (j$.isSpy(value)) {
+ this.emitScalar('spy on ' + value.and.identity());
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (j$.util.arrayContains(this.seen, value)) {
+ this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
+ } else if (j$.isArray_(value) || j$.isA_('Object', value)) {
+ this.seen.push(value);
+ if (j$.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ this.seen.pop();
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+ };
+
+ PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; }
+ fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+ };
+
+ PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_;
+ PrettyPrinter.prototype.emitString = j$.unimplementedMethod_;
+
+ function StringPrettyPrinter() {
+ PrettyPrinter.call(this);
+
+ this.string = '';
+ }
+
+ j$.util.inherit(StringPrettyPrinter, PrettyPrinter);
+
+ StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+ };
+
+ StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append('\'' + value + '\'');
+ };
+
+ StringPrettyPrinter.prototype.emitArray = function(array) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append('Array');
+ return;
+ }
+ var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH);
+ this.append('[ ');
+ for (var i = 0; i < length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ if(array.length > length){
+ this.append(', ...');
+ }
+ this.append(' ]');
+ };
+
+ StringPrettyPrinter.prototype.emitObject = function(obj) {
+ if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
+ this.append('Object');
+ return;
+ }
+
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(': ');
+ if (isGetter) {
+ self.append('<getter>');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+ };
+
+ StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+ };
+
+ return function(value) {
+ var stringPrettyPrinter = new StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+ };
+};
+
+getJasmineRequireObj().QueueRunner = function(j$) {
+
+ function once(fn) {
+ var called = false;
+ return function() {
+ if (!called) {
+ called = true;
+ fn();
+ }
+ };
+ }
+
+ function QueueRunner(attrs) {
+ this.queueableFns = attrs.queueableFns || [];
+ this.onComplete = attrs.onComplete || function() {};
+ this.clearStack = attrs.clearStack || function(fn) {fn();};
+ this.onException = attrs.onException || function() {};
+ this.catchException = attrs.catchException || function() { return true; };
+ this.userContext = attrs.userContext || {};
+ this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+ this.fail = attrs.fail || function() {};
+ }
+
+ QueueRunner.prototype.execute = function() {
+ this.run(this.queueableFns, 0);
+ };
+
+ QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+ var length = queueableFns.length,
+ self = this,
+ iterativeIndex;
+
+
+ for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
+ var queueableFn = queueableFns[iterativeIndex];
+ if (queueableFn.fn.length > 0) {
+ return attemptAsync(queueableFn);
+ } else {
+ attemptSync(queueableFn);
+ }
+ }
+
+ var runnerDone = iterativeIndex >= length;
+
+ if (runnerDone) {
+ this.clearStack(this.onComplete);
+ }
+
+ function attemptSync(queueableFn) {
+ try {
+ queueableFn.fn.call(self.userContext);
+ } catch (e) {
+ handleException(e, queueableFn);
+ }
+ }
+
+ function attemptAsync(queueableFn) {
+ var clearTimeout = function () {
+ Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+ },
+ next = once(function () {
+ clearTimeout(timeoutId);
+ self.run(queueableFns, iterativeIndex + 1);
+ }),
+ timeoutId;
+
+ next.fail = function() {
+ self.fail.apply(null, arguments);
+ next();
+ };
+
+ if (queueableFn.timeout) {
+ timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() {
+ var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+ onException(error, queueableFn);
+ next();
+ }, queueableFn.timeout()]]);
+ }
+
+ try {
+ queueableFn.fn.call(self.userContext, next);
+ } catch (e) {
+ handleException(e, queueableFn);
+ next();
+ }
+ }
+
+ function onException(e, queueableFn) {
+ self.onException(e);
+ }
+
+ function handleException(e, queueableFn) {
+ onException(e, queueableFn);
+ if (!self.catchException(e)) {
+ //TODO: set a var when we catch an exception and
+ //use a finally block to close the loop in a nice way..
+ throw e;
+ }
+ }
+ };
+
+ return QueueRunner;
+};
+
+getJasmineRequireObj().ReportDispatcher = function() {
+ function ReportDispatcher(methods) {
+
+ var dispatchedMethods = methods || [];
+
+ for (var i = 0; i < dispatchedMethods.length; i++) {
+ var method = dispatchedMethods[i];
+ this[method] = (function(m) {
+ return function() {
+ dispatch(m, arguments);
+ };
+ }(method));
+ }
+
+ var reporters = [];
+
+ this.addReporter = function(reporter) {
+ reporters.push(reporter);
+ };
+
+ return this;
+
+ function dispatch(method, args) {
+ for (var i = 0; i < reporters.length; i++) {
+ var reporter = reporters[i];
+ if (reporter[method]) {
+ reporter[method].apply(reporter, args);
+ }
+ }
+ }
+ }
+
+ return ReportDispatcher;
+};
+
+
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+ function SpyRegistry(options) {
+ options = options || {};
+ var currentSpies = options.currentSpies || function() { return []; };
+
+ this.spyOn = function(obj, methodName) {
+ if (j$.util.isUndefined(obj)) {
+ throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()');
+ }
+
+ if (j$.util.isUndefined(obj[methodName])) {
+ throw new Error(methodName + '() method does not exist');
+ }
+
+ if (obj[methodName] && j$.isSpy(obj[methodName])) {
+ //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spy = j$.createSpy(methodName, obj[methodName]);
+
+ currentSpies().push({
+ spy: spy,
+ baseObj: obj,
+ methodName: methodName,
+ originalValue: obj[methodName]
+ });
+
+ obj[methodName] = spy;
+
+ return spy;
+ };
+
+ this.clearSpies = function() {
+ var spies = currentSpies();
+ for (var i = 0; i < spies.length; i++) {
+ var spyEntry = spies[i];
+ spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
+ }
+ };
+ }
+
+ return SpyRegistry;
+};
+
+getJasmineRequireObj().SpyStrategy = function() {
+
+ function SpyStrategy(options) {
+ options = options || {};
+
+ var identity = options.name || 'unknown',
+ originalFn = options.fn || function() {},
+ getSpy = options.getSpy || function() {},
+ plan = function() {};
+
+ this.identity = function() {
+ return identity;
+ };
+
+ this.exec = function() {
+ return plan.apply(this, arguments);
+ };
+
+ this.callThrough = function() {
+ plan = originalFn;
+ return getSpy();
+ };
+
+ this.returnValue = function(value) {
+ plan = function() {
+ return value;
+ };
+ return getSpy();
+ };
+
+ this.returnValues = function() {
+ var values = Array.prototype.slice.call(arguments);
+ plan = function () {
+ return values.shift();
+ };
+ return getSpy();
+ };
+
+ this.throwError = function(something) {
+ var error = (something instanceof Error) ? something : new Error(something);
+ plan = function() {
+ throw error;
+ };
+ return getSpy();
+ };
+
+ this.callFake = function(fn) {
+ plan = fn;
+ return getSpy();
+ };
+
+ this.stub = function(fn) {
+ plan = function() {};
+ return getSpy();
+ };
+ }
+
+ return SpyStrategy;
+};
+
+getJasmineRequireObj().Suite = function() {
+ function Suite(attrs) {
+ this.env = attrs.env;
+ this.id = attrs.id;
+ this.parentSuite = attrs.parentSuite;
+ this.description = attrs.description;
+ this.onStart = attrs.onStart || function() {};
+ this.resultCallback = attrs.resultCallback || function() {};
+ this.clearStack = attrs.clearStack || function(fn) {fn();};
+ this.expectationFactory = attrs.expectationFactory;
+ this.expectationResultFactory = attrs.expectationResultFactory;
+
+ this.beforeFns = [];
+ this.afterFns = [];
+ this.beforeAllFns = [];
+ this.afterAllFns = [];
+ this.queueRunner = attrs.queueRunner || function() {};
+ this.disabled = false;
+
+ this.children = [];
+
+ this.result = {
+ id: this.id,
+ description: this.description,
+ fullName: this.getFullName(),
+ failedExpectations: []
+ };
+ }
+
+ Suite.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
+ Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ if (parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ }
+ return fullName;
+ };
+
+ Suite.prototype.disable = function() {
+ this.disabled = true;
+ };
+
+ Suite.prototype.beforeEach = function(fn) {
+ this.beforeFns.unshift(fn);
+ };
+
+ Suite.prototype.beforeAll = function(fn) {
+ this.beforeAllFns.push(fn);
+ };
+
+ Suite.prototype.afterEach = function(fn) {
+ this.afterFns.unshift(fn);
+ };
+
+ Suite.prototype.afterAll = function(fn) {
+ this.afterAllFns.push(fn);
+ };
+
+ Suite.prototype.addChild = function(child) {
+ this.children.push(child);
+ };
+
+ Suite.prototype.status = function() {
+ if (this.disabled) {
+ return 'disabled';
+ }
+
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'finished';
+ }
+ };
+
+ Suite.prototype.execute = function(onComplete) {
+ var self = this;
+
+ this.onStart(this);
+
+ if (this.disabled) {
+ complete();
+ return;
+ }
+
+ var allFns = [];
+
+ if (this.isExecutable()) {
+ allFns = allFns.concat(this.beforeAllFns);
+
+ for (var i = 0; i < this.children.length; i++) {
+ allFns.push(wrapChildAsAsync(this.children[i]));
+ }
+
+ allFns = allFns.concat(this.afterAllFns);
+ }
+
+ this.queueRunner({
+ queueableFns: allFns,
+ onComplete: complete,
+ userContext: this.sharedUserContext(),
+ onException: function() { self.onException.apply(self, arguments); }
+ });
+
+ function complete() {
+ self.result.status = self.status();
+ self.resultCallback(self.result);
+
+ if (onComplete) {
+ onComplete();
+ }
+ }
+
+ function wrapChildAsAsync(child) {
+ return { fn: function(done) { child.execute(done); } };
+ }
+ };
+
+ Suite.prototype.isExecutable = function() {
+ var foundActive = false;
+ for(var i = 0; i < this.children.length; i++) {
+ if(this.children[i].isExecutable()) {
+ foundActive = true;
+ break;
+ }
+ }
+ return foundActive;
+ };
+
+ Suite.prototype.sharedUserContext = function() {
+ if (!this.sharedContext) {
+ this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+ }
+
+ return this.sharedContext;
+ };
+
+ Suite.prototype.clonedSharedUserContext = function() {
+ return clone(this.sharedUserContext());
+ };
+
+ Suite.prototype.onException = function() {
+ if(isAfterAll(this.children)) {
+ var data = {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: arguments[0]
+ };
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ child.onException.apply(child, arguments);
+ }
+ }
+ };
+
+ Suite.prototype.addExpectationResult = function () {
+ if(isAfterAll(this.children) && isFailure(arguments)){
+ var data = arguments[1];
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ child.addExpectationResult.apply(child, arguments);
+ }
+ }
+ };
+
+ function isAfterAll(children) {
+ return children && children[0].result.status;
+ }
+
+ function isFailure(args) {
+ return !args[0];
+ }
+
+ function clone(obj) {
+ var clonedObj = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ clonedObj[prop] = obj[prop];
+ }
+ }
+
+ return clonedObj;
+ }
+
+ return Suite;
+};
+
+if (typeof window == void 0 && typeof exports == 'object') {
+ exports.Suite = jasmineRequire.Suite;
+}
+
+getJasmineRequireObj().Timer = function() {
+ var defaultNow = (function(Date) {
+ return function() { return new Date().getTime(); };
+ })(Date);
+
+ function Timer(options) {
+ options = options || {};
+
+ var now = options.now || defaultNow,
+ startTime;
+
+ this.start = function() {
+ startTime = now();
+ };
+
+ this.elapsed = function() {
+ return now() - startTime;
+ };
+ }
+
+ return Timer;
+};
+
+getJasmineRequireObj().matchersUtil = function(j$) {
+ // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
+
+ return {
+ equals: function(a, b, customTesters) {
+ customTesters = customTesters || [];
+
+ return eq(a, b, [], [], customTesters);
+ },
+
+ contains: function(haystack, needle, customTesters) {
+ customTesters = customTesters || [];
+
+ if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+ (!!haystack && !haystack.indexOf))
+ {
+ for (var i = 0; i < haystack.length; i++) {
+ if (eq(haystack[i], needle, [], [], customTesters)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return !!haystack && haystack.indexOf(needle) >= 0;
+ },
+
+ buildFailureMessage: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ matcherName = args[0],
+ isNot = args[1],
+ actual = args[2],
+ expected = args.slice(3),
+ englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+
+ var message = 'Expected ' +
+ j$.pp(actual) +
+ (isNot ? ' not ' : ' ') +
+ englishyPredicate;
+
+ if (expected.length > 0) {
+ for (var i = 0; i < expected.length; i++) {
+ if (i > 0) {
+ message += ',';
+ }
+ message += ' ' + j$.pp(expected[i]);
+ }
+ }
+
+ return message + '.';
+ }
+ };
+
+ // Equality function lovingly adapted from isEqual in
+ // [Underscore](http://underscorejs.org)
+ function eq(a, b, aStack, bStack, customTesters) {
+ var result = true;
+
+ for (var i = 0; i < customTesters.length; i++) {
+ var customTesterResult = customTesters[i](a, b);
+ if (!j$.util.isUndefined(customTesterResult)) {
+ return customTesterResult;
+ }
+ }
+
+ if (a instanceof j$.Any) {
+ result = a.jasmineMatches(b);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (b instanceof j$.Any) {
+ result = b.jasmineMatches(a);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (b instanceof j$.ObjectContaining) {
+ result = b.jasmineMatches(a);
+ if (result) {
+ return true;
+ }
+ }
+
+ if (a instanceof Error && b instanceof Error) {
+ return a.message == b.message;
+ }
+
+ // Identical objects are equal. `0 === -0`, but they aren't identical.
+ // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+ if (a === b) { return a !== 0 || 1 / a == 1 / b; }
+ // A strict comparison is necessary because `null == undefined`.
+ if (a === null || b === null) { return a === b; }
+ var className = Object.prototype.toString.call(a);
+ if (className != Object.prototype.toString.call(b)) { return false; }
+ switch (className) {
+ // Strings, numbers, dates, and booleans are compared by value.
+ case '[object String]':
+ // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+ // equivalent to `new String("5")`.
+ return a == String(b);
+ case '[object Number]':
+ // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
+ // other numeric values.
+ return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
+ case '[object Date]':
+ case '[object Boolean]':
+ // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+ // millisecond representations. Note that invalid dates with millisecond representations
+ // of `NaN` are not equivalent.
+ return +a == +b;
+ // RegExps are compared by their source patterns and flags.
+ case '[object RegExp]':
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') { return false; }
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = aStack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (aStack[length] == a) { return bStack[length] == b; }
+ }
+ // Add the first object to the stack of traversed objects.
+ aStack.push(a);
+ bStack.push(b);
+ var size = 0;
+ // Recursively compare objects and arrays.
+ if (className == '[object Array]') {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ size = a.length;
+ result = size == b.length;
+ if (result) {
+ // Deep compare the contents, ignoring non-numeric properties.
+ while (size--) {
+ if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; }
+ }
+ }
+ } else {
+ // Objects with different constructors are not equivalent, but `Object`s
+ // from different frames are.
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
+ isFunction(bCtor) && (bCtor instanceof bCtor))) {
+ return false;
+ }
+ // Deep compare objects.
+ for (var key in a) {
+ if (has(a, key)) {
+ // Count the expected number of properties.
+ size++;
+ // Deep compare each member.
+ if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; }
+ }
+ }
+ // Ensure that both objects contain the same number of properties.
+ if (result) {
+ for (key in b) {
+ if (has(b, key) && !(size--)) { break; }
+ }
+ result = !size;
+ }
+ }
+ // Remove the first object from the stack of traversed objects.
+ aStack.pop();
+ bStack.pop();
+
+ return result;
+
+ function has(obj, key) {
+ return obj.hasOwnProperty(key);
+ }
+
+ function isFunction(obj) {
+ return typeof obj === 'function';
+ }
+ }
+};
+
+getJasmineRequireObj().toBe = function() {
+ function toBe() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual === expected
+ };
+ }
+ };
+ }
+
+ return toBe;
+};
+
+getJasmineRequireObj().toBeCloseTo = function() {
+
+ function toBeCloseTo() {
+ return {
+ compare: function(actual, expected, precision) {
+ if (precision !== 0) {
+ precision = precision || 2;
+ }
+
+ return {
+ pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
+ };
+ }
+ };
+ }
+
+ return toBeCloseTo;
+};
+
+getJasmineRequireObj().toBeDefined = function() {
+ function toBeDefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: (void 0 !== actual)
+ };
+ }
+ };
+ }
+
+ return toBeDefined;
+};
+
+getJasmineRequireObj().toBeFalsy = function() {
+ function toBeFalsy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!!actual
+ };
+ }
+ };
+ }
+
+ return toBeFalsy;
+};
+
+getJasmineRequireObj().toBeGreaterThan = function() {
+
+ function toBeGreaterThan() {
+ return {
+ compare: function(actual, expected) {
+ return {
+ pass: actual > expected
+ };
+ }
+ };
+ }
+
+ return toBeGreaterThan;
+};
+
+
+getJasmineRequireObj().toBeLessThan = function() {
+ function toBeLessThan() {
+ return {
+
+ compare: function(actual, expected) {
+ return {
+ pass: actual < expected
+ };
+ }
+ };
+ }
+
+ return toBeLessThan;
+};
+getJasmineRequireObj().toBeNaN = function(j$) {
+
+ function toBeNaN() {
+ return {
+ compare: function(actual) {
+ var result = {
+ pass: (actual !== actual)
+ };
+
+ if (result.pass) {
+ result.message = 'Expected actual not to be NaN.';
+ } else {
+ result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toBeNaN;
+};
+
+getJasmineRequireObj().toBeNull = function() {
+
+ function toBeNull() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: actual === null
+ };
+ }
+ };
+ }
+
+ return toBeNull;
+};
+
+getJasmineRequireObj().toBeTruthy = function() {
+
+ function toBeTruthy() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: !!actual
+ };
+ }
+ };
+ }
+
+ return toBeTruthy;
+};
+
+getJasmineRequireObj().toBeUndefined = function() {
+
+ function toBeUndefined() {
+ return {
+ compare: function(actual) {
+ return {
+ pass: void 0 === actual
+ };
+ }
+ };
+ }
+
+ return toBeUndefined;
+};
+
+getJasmineRequireObj().toContain = function() {
+ function toContain(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+
+ return {
+ pass: util.contains(actual, expected, customEqualityTesters)
+ };
+ }
+ };
+ }
+
+ return toContain;
+};
+
+getJasmineRequireObj().toEqual = function() {
+
+ function toEqual(util, customEqualityTesters) {
+ customEqualityTesters = customEqualityTesters || [];
+
+ return {
+ compare: function(actual, expected) {
+ var result = {
+ pass: false
+ };
+
+ result.pass = util.equals(actual, expected, customEqualityTesters);
+
+ return result;
+ }
+ };
+ }
+
+ return toEqual;
+};
+
+getJasmineRequireObj().toHaveBeenCalled = function(j$) {
+
+ function toHaveBeenCalled() {
+ return {
+ compare: function(actual) {
+ var result = {};
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (arguments.length > 1) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ result.pass = actual.calls.any();
+
+ result.message = result.pass ?
+ 'Expected spy ' + actual.and.identity() + ' not to have been called.' :
+ 'Expected spy ' + actual.and.identity() + ' to have been called.';
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalled;
+};
+
+getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
+
+ function toHaveBeenCalledWith(util, customEqualityTesters) {
+ return {
+ compare: function() {
+ var args = Array.prototype.slice.call(arguments, 0),
+ actual = args[0],
+ expectedArgs = args.slice(1),
+ result = { pass: false };
+
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ if (!actual.calls.any()) {
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; };
+ return result;
+ }
+
+ if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) {
+ result.pass = true;
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; };
+ } else {
+ result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalledWith;
+};
+
+getJasmineRequireObj().toMatch = function() {
+
+ function toMatch() {
+ return {
+ compare: function(actual, expected) {
+ var regexp = new RegExp(expected);
+
+ return {
+ pass: regexp.test(actual)
+ };
+ }
+ };
+ }
+
+ return toMatch;
+};
+
+getJasmineRequireObj().toThrow = function(j$) {
+
+ function toThrow(util) {
+ return {
+ compare: function(actual, expected) {
+ var result = { pass: false },
+ threw = false,
+ thrown;
+
+ if (typeof actual != 'function') {
+ throw new Error('Actual is not a Function');
+ }
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ result.message = 'Expected function to throw an exception.';
+ return result;
+ }
+
+ if (arguments.length == 1) {
+ result.pass = true;
+ result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; };
+
+ return result;
+ }
+
+ if (util.equals(thrown, expected)) {
+ result.pass = true;
+ result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; };
+ } else {
+ result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; };
+ }
+
+ return result;
+ }
+ };
+ }
+
+ return toThrow;
+};
+
+getJasmineRequireObj().toThrowError = function(j$) {
+ function toThrowError (util) {
+ return {
+ compare: function(actual) {
+ var threw = false,
+ pass = {pass: true},
+ fail = {pass: false},
+ thrown;
+
+ if (typeof actual != 'function') {
+ throw new Error('Actual is not a Function');
+ }
+
+ var errorMatcher = getMatcher.apply(null, arguments);
+
+ try {
+ actual();
+ } catch (e) {
+ threw = true;
+ thrown = e;
+ }
+
+ if (!threw) {
+ fail.message = 'Expected function to throw an Error.';
+ return fail;
+ }
+
+ if (!(thrown instanceof Error)) {
+ fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; };
+ return fail;
+ }
+
+ if (errorMatcher.hasNoSpecifics()) {
+ pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.';
+ return pass;
+ }
+
+ if (errorMatcher.matches(thrown)) {
+ pass.message = function() {
+ return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
+ };
+ return pass;
+ } else {
+ fail.message = function() {
+ return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
+ ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+ };
+ return fail;
+ }
+ }
+ };
+
+ function getMatcher() {
+ var expected = null,
+ errorType = null;
+
+ if (arguments.length == 2) {
+ expected = arguments[1];
+ if (isAnErrorType(expected)) {
+ errorType = expected;
+ expected = null;
+ }
+ } else if (arguments.length > 2) {
+ errorType = arguments[1];
+ expected = arguments[2];
+ if (!isAnErrorType(errorType)) {
+ throw new Error('Expected error type is not an Error.');
+ }
+ }
+
+ if (expected && !isStringOrRegExp(expected)) {
+ if (errorType) {
+ throw new Error('Expected error message is not a string or RegExp.');
+ } else {
+ throw new Error('Expected is not an Error, string, or RegExp.');
+ }
+ }
+
+ function messageMatch(message) {
+ if (typeof expected == 'string') {
+ return expected == message;
+ } else {
+ return expected.test(message);
+ }
+ }
+
+ return {
+ errorTypeDescription: errorType ? fnNameFor(errorType) : 'an exception',
+ thrownDescription: function(thrown) {
+ var thrownName = errorType ? fnNameFor(thrown.constructor) : 'an exception',
+ thrownMessage = '';
+
+ if (expected) {
+ thrownMessage = ' with message ' + j$.pp(thrown.message);
+ }
+
+ return thrownName + thrownMessage;
+ },
+ messageDescription: function() {
+ if (expected === null) {
+ return '';
+ } else if (expected instanceof RegExp) {
+ return ' with a message matching ' + j$.pp(expected);
+ } else {
+ return ' with message ' + j$.pp(expected);
+ }
+ },
+ hasNoSpecifics: function() {
+ return expected === null && errorType === null;
+ },
+ matches: function(error) {
+ return (errorType === null || error.constructor === errorType) &&
+ (expected === null || messageMatch(error.message));
+ }
+ };
+ }
+
+ function fnNameFor(func) {
+ return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
+ }
+
+ function isStringOrRegExp(potential) {
+ return potential instanceof RegExp || (typeof potential == 'string');
+ }
+
+ function isAnErrorType(type) {
+ if (typeof type !== 'function') {
+ return false;
+ }
+
+ var Surrogate = function() {};
+ Surrogate.prototype = type.prototype;
+ return (new Surrogate()) instanceof Error;
+ }
+ }
+
+ return toThrowError;
+};
+
+getJasmineRequireObj().interface = function(jasmine, env) {
+ var jasmineInterface = {
+ describe: function(description, specDefinitions) {
+ return env.describe(description, specDefinitions);
+ },
+
+ xdescribe: function(description, specDefinitions) {
+ return env.xdescribe(description, specDefinitions);
+ },
+
+ fdescribe: function(description, specDefinitions) {
+ return env.fdescribe(description, specDefinitions);
+ },
+
+ it: function(desc, func) {
+ return env.it(desc, func);
+ },
+
+ xit: function(desc, func) {
+ return env.xit(desc, func);
+ },
+
+ fit: function(desc, func) {
+ return env.fit(desc, func);
+ },
+
+ beforeEach: function(beforeEachFunction) {
+ return env.beforeEach(beforeEachFunction);
+ },
+
+ afterEach: function(afterEachFunction) {
+ return env.afterEach(afterEachFunction);
+ },
+
+ beforeAll: function(beforeAllFunction) {
+ return env.beforeAll(beforeAllFunction);
+ },
+
+ afterAll: function(afterAllFunction) {
+ return env.afterAll(afterAllFunction);
+ },
+
+ expect: function(actual) {
+ return env.expect(actual);
+ },
+
+ pending: function() {
+ return env.pending();
+ },
+
+ fail: function() {
+ return env.fail.apply(env, arguments);
+ },
+
+ spyOn: function(obj, methodName) {
+ return env.spyOn(obj, methodName);
+ },
+
+ jsApiReporter: new jasmine.JsApiReporter({
+ timer: new jasmine.Timer()
+ }),
+
+ jasmine: jasmine
+ };
+
+ jasmine.addCustomEqualityTester = function(tester) {
+ env.addCustomEqualityTester(tester);
+ };
+
+ jasmine.addMatchers = function(matchers) {
+ return env.addMatchers(matchers);
+ };
+
+ jasmine.clock = function() {
+ return env.clock;
+ };
+
+ return jasmineInterface;
+};
+
+getJasmineRequireObj().version = function() {
+ return '2.1.1';
+};
diff --git a/dev/js/lib/jasmine-2.1.1/jasmine_favicon.png b/dev/js/lib/jasmine-2.1.1/jasmine_favicon.png
new file mode 100644
index 0000000..3b84583
--- /dev/null
+++ b/dev/js/lib/jasmine-2.1.1/jasmine_favicon.png
Binary files differ
diff --git a/dev/js/runner/hint.html b/dev/js/runner/hint.html
new file mode 100644
index 0000000..81f0422
--- /dev/null
+++ b/dev/js/runner/hint.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for Hint Helper</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../src/hint.js"></script>
+ <script src="../spec/hintSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/runner/match.html b/dev/js/runner/match.html
new file mode 100644
index 0000000..553fae7
--- /dev/null
+++ b/dev/js/runner/match.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for Morph Table View</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script src="../lib/dagre/dagre.min.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../src/match.js"></script>
+ <script src="../spec/matchSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/runner/menu.html b/dev/js/runner/menu.html
new file mode 100644
index 0000000..2b05abc
--- /dev/null
+++ b/dev/js/runner/menu.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for Menu Helper</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../spec/menuSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/runner/vc.html b/dev/js/runner/vc.html
new file mode 100644
index 0000000..f0e8179
--- /dev/null
+++ b/dev/js/runner/vc.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Spec Runner for Virtual Collection Builder</title>
+ <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+ <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+ <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+ <script src="../lib/jasmine-2.1.1/boot.js"></script>
+ <script src="../src/menu.js"></script>
+ <script src="../src/vc.js"></script>
+ <script src="../spec/vcSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/spec/hintSpec.js b/dev/js/spec/hintSpec.js
new file mode 100644
index 0000000..bb736ce
--- /dev/null
+++ b/dev/js/spec/hintSpec.js
@@ -0,0 +1,414 @@
+function emitKeyboardEvent (element, type, keyCode) {
+ // event type : keydown, keyup, keypress
+ // http://stackoverflow.com/questions/596481/simulate-javascript-key-events
+ // http://stackoverflow.com/questions/961532/firing-a-keyboard-event-in-javascript
+ var keyboardEvent = document.createEvent("KeyboardEvent");
+ var initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ?
+ "initKeyboardEvent" : "initKeyEvent";
+
+ keyboardEvent[initMethod](
+ type,
+ true, // bubbles
+ true, // cancelable
+ window, // viewArg: should be window
+ false, // ctrlKeyArg
+ false, // altKeyArg
+ false, // shiftKeyArg
+ false, // metaKeyArg
+ keyCode, // keyCodeArg : unsigned long the virtual key code, else 0
+ 0 // charCodeArgs : unsigned long the Unicode character
+ // associated with the depressed key, else 0
+ );
+
+ element.dispatchEvent(keyboardEvent);
+};
+
+
+describe('KorAP.InputField', function () {
+ var input;
+
+ beforeEach(function () {
+ input = document.createElement("input");
+ input.setAttribute("type", "text");
+ input.setAttribute("value", "abcdefghijklmno");
+ input.style.position = 'absolute';
+ input.style.top = "20px";
+ input.style.left = "30px";
+ input.focus();
+ input.selectionStart = 5;
+ });
+
+ afterAll(function () {
+ try {
+ // document.getElementsByTagName("body")[0].removeChild(input);
+ document.getElementsByTagName("body")[0].removeChild(
+ document.getElementById("searchMirror")
+ );
+ }
+ catch (e) {};
+ });
+
+ it('should be initializable', function () {
+ // Supports: context, searchField
+ var inputField = KorAP.InputField.create(input);
+ expect(inputField._element).not.toBe(undefined);
+ });
+
+ it('should have text', function () {
+ expect(input.value).toEqual('abcdefghijklmno');
+ var inputField = KorAP.InputField.create(input);
+
+ expect(inputField.value()).toEqual("abcdefghijklmno");
+
+ expect(inputField.element().selectionStart).toEqual(5);
+ expect(inputField.split()[0]).toEqual("abcde");
+ expect(inputField.split()[1]).toEqual("fghijklmno");
+
+ inputField.insert("xyz");
+ expect(inputField.value()).toEqual('abcdexyzfghijklmno');
+ expect(inputField.split()[0]).toEqual("abcdexyz");
+ expect(inputField.split()[1]).toEqual("fghijklmno");
+ });
+
+ it('should be correctly positioned', function () {
+ expect(input.value).toEqual('abcdefghijklmno');
+ var inputField = KorAP.InputField.create(input);
+ document.getElementsByTagName("body")[0].appendChild(input);
+ inputField.reposition();
+ expect(inputField.mirror().style.left).toEqual("30px");
+ expect(inputField.mirror().style.top.match(/^(\d+)px$/)[1]).toBeGreaterThan(20);
+ });
+
+ it('should have a correct context', function () {
+ expect(input.value).toEqual('abcdefghijklmno');
+ var inputField = KorAP.InputField.create(input);
+
+ expect(inputField.value()).toEqual("abcdefghijklmno");
+
+ expect(inputField.element().selectionStart).toEqual(5);
+ expect(inputField.split()[0]).toEqual("abcde");
+ expect(inputField.context()).toEqual("abcde");
+ });
+
+/*
+ it('should be correctly triggerable', function () {
+ // https://developer.mozilla.org/samples/domref/dispatchEvent.html
+ var hint = KorAP.Hint.create({ "inputField" : input });
+ emitKeyboardEvent(hint.inputField.element, "keypress", 20);
+ });
+*/
+});
+
+
+describe('KorAP.ContextAnalyzer', function () {
+ it('should be initializable', function () {
+ var analyzer = KorAP.ContextAnalyzer.create(")");
+ expect(analyzer).toBe(undefined);
+
+ analyzer = KorAP.ContextAnalyzer.create(".+?");
+ expect(analyzer).not.toBe(undefined);
+
+ });
+
+ it('should check correctly', function () {
+ analyzer = KorAP.ContextAnalyzer.create(KorAP.context);
+ expect(analyzer.test("cnx/]cnx/c=")).toEqual("cnx/c=");
+ expect(analyzer.test("cnx/c=")).toEqual("cnx/c=");
+ expect(analyzer.test("cnx/c=np mate/m=mood:")).toEqual("mate/m=mood:");
+ expect(analyzer.test("impcnx/")).toEqual("impcnx/");
+ expect(analyzer.test("cnx/c=npcnx/")).toEqual("npcnx/");
+ expect(analyzer.test("mate/m=degree:pos corenlp/ne_dewac_175m_600="))
+ .toEqual("corenlp/ne_dewac_175m_600=");
+ });
+});
+
+
+describe('KorAP.Hint', function () {
+ KorAP.hintArray = {
+ "corenlp/" : [
+ ["Named Entity", "ne=" , "Combined"],
+ ["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
+ ["Named Entity", "ne_hgc_175m_600=", "ne_hgc_175m_600"]
+ ]
+ };
+
+ beforeEach(function () {
+ input = document.createElement("input");
+ input.setAttribute("type", "text");
+ input.setAttribute("value", "abcdefghijklmno");
+ input.style.position = 'absolute';
+ input.style.top = "20px";
+ input.style.left = "30px";
+ input.focus();
+ input.selectionStart = 5;
+ });
+
+ it('should be initializable', function () {
+ // Supports: context, searchField
+ var hint = KorAP.Hint.create({
+ inputField : input
+ });
+
+ expect(hint).toBeTruthy();
+ });
+});
+
+
+
+
+
+
+
+
+
+
+
+xdescribe('KorAP.MenuItem', function () {
+ it('should be initializable', function () {
+
+ expect(
+ function() { KorAP.MenuItem.create([]) }
+ ).toThrow(new Error("Missing parameters"));
+
+ expect(
+ function() { KorAP.MenuItem.create(['CoreNLP']) }
+ ).toThrow(new Error("Missing parameters"));
+
+ var menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ expect(menuItem.name).toEqual('CoreNLP');
+ expect(menuItem.action).toEqual('corenlp/');
+ expect(menuItem.desc).toBeUndefined();
+ expect(menuItem.lcfield).toEqual(' corenlp');
+
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'It\'s funny']);
+ expect(menuItem.name).toEqual('CoreNLP');
+ expect(menuItem.action).toEqual('corenlp/');
+ expect(menuItem.desc).not.toBeUndefined();
+ expect(menuItem.desc).toEqual('It\'s funny');
+ expect(menuItem.lcfield).toEqual(' corenlp it\'s funny');
+ });
+
+ it('should have an element', function () {
+ var menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ expect(menuItem.element).not.toBe(undefined);
+ expect(menuItem.element.nodeName).toEqual("LI");
+ expect(menuItem.element.getAttribute("data-action")).toEqual("corenlp/");
+
+ var title = menuItem.element.firstChild;
+ expect(title.nodeName).toEqual("STRONG");
+ expect(title.firstChild.nodeType).toEqual(3);
+ expect(title.firstChild.nodeValue).toEqual("CoreNLP");
+
+ expect(menuItem.element.childNodes[0]).not.toBe(undefined);
+ expect(menuItem.element.childNodes[1]).toBe(undefined);
+
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'my DescRiption']);
+ expect(menuItem.element).not.toBe(undefined);
+ expect(menuItem.element.nodeName).toEqual("LI");
+ expect(menuItem.element.getAttribute("data-action")).toEqual("corenlp/");
+
+ title = menuItem.element.firstChild;
+ expect(title.nodeName).toEqual("STRONG");
+ expect(title.firstChild.nodeType).toEqual(3); // TextNode
+ expect(title.firstChild.nodeValue).toEqual("CoreNLP");
+
+ expect(menuItem.element.childNodes[0]).not.toBe(undefined);
+ expect(menuItem.element.childNodes[1]).not.toBe(undefined);
+
+ var desc = menuItem.element.lastChild;
+ expect(desc.nodeName).toEqual("SPAN");
+ expect(desc.firstChild.nodeType).toEqual(3); // TextNode
+ expect(desc.firstChild.nodeValue).toEqual("my DescRiption");
+
+ expect(menuItem.lcfield).toEqual(' corenlp my description');
+ });
+
+ it('should be activatable and deactivateable by class', function () {
+ var menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toBe(null);
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.element.getAttribute("class")).toEqual("active");
+ menuItem.active(false); // Is active
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toEqual("");
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.element.getAttribute("class")).toEqual("active");
+
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toBe(null);
+ menuItem.active(false); // Is not active
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toBe(null);
+ });
+
+ it('should be set to boundary', function () {
+ var menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toBe(null);
+
+ // Set active
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toEqual("active");
+
+ // Set no more
+ menuItem.noMore(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element.getAttribute("class")).toEqual("active no-more");
+
+ // No no more
+ menuItem.noMore(false);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(false);
+ expect(menuItem.element.getAttribute("class")).toEqual("active");
+
+
+ // Set no more, deactivate
+ menuItem.noMore(true);
+ menuItem.active(false);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element.getAttribute("class")).toEqual("no-more");
+
+ // Set active
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element.getAttribute("class")).toEqual("no-more active");
+ });
+
+
+ it('should be highlightable', function () {
+ // Highlight in the middle
+ var menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("ren");
+ expect(menuItem.element.innerHTML).toEqual("<strong>Co<em>reN</em>LP</strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // Starting highlight
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("cor");
+ expect(menuItem.element.innerHTML).toEqual("<strong><em>Cor</em>eNLP</strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // Starting highlight - short
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("c");
+ expect(menuItem.element.innerHTML).toEqual("<strong><em>C</em>oreNLP</strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // Highlight at the end
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("nlp");
+ expect(menuItem.element.innerHTML).toEqual("<strong>Core<em>NLP</em></strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // Highlight at the end - short
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("p");
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNL<em>P</em></strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // No highlight
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("xp");
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong>");
+
+ // Highlight in the middle - first
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("ren");
+ expect(menuItem.element.innerHTML).toEqual("<strong>Co<em>reN</em>LP</strong><span>This is my Example</span>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Example</span>");
+
+ // Highlight in the middle - second
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("ampl");
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Ex<em>ampl</em>e</span>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Example</span>");
+
+ // Highlight in the middle - both
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("e");
+ expect(menuItem.element.innerHTML).toEqual("<strong>Cor<em>e</em>NLP</strong><span>This is my <em>E</em>xample</span>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Example</span>");
+
+ // Highlight in the end - second
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("le");
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Examp<em>le</em></span>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Example</span>");
+
+ // Highlight at the beginning - second
+ menuItem = KorAP.MenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("this");
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span><em>This</em> is my Example</span>");
+
+ menuItem.lowlight();
+ expect(menuItem.element.innerHTML).toEqual("<strong>CoreNLP</strong><span>This is my Example</span>");
+ });
+});
+
+
+describe('KorAP.HintMenu', function () {
+
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
+
+
+ it('should be initializable', function () {
+
+ var menu = KorAP.HintMenu.create(null, "cnx/", list);
+ expect(menu.context()).toEqual('cnx/');
+ expect(menu.element().nodeName).toEqual('UL');
+ expect(menu.element().style.opacity).toEqual("0");
+
+ menu.limit(8);
+
+ // view
+ menu.show();
+
+ // First element in list
+ expect(menu.item(0).active()).toBe(true);
+ expect(menu.item(0).noMore()).toBe(true);
+
+ // Middle element in list
+ expect(menu.item(2).active()).toBe(false);
+ expect(menu.item(2).noMore()).toBe(false);
+
+ // Last element in list
+ expect(menu.item(menu.length() - 1).active()).toBe(false);
+ expect(menu.item(menu.length() - 1).noMore()).toBe(true);
+ });
+});
diff --git a/dev/js/spec/matchSpec.js b/dev/js/spec/matchSpec.js
new file mode 100644
index 0000000..f520557
--- /dev/null
+++ b/dev/js/spec/matchSpec.js
@@ -0,0 +1,560 @@
+var available = [
+ 'base/s=spans',
+ 'corenlp/c=spans',
+ 'corenlp/ne=tokens',
+ 'corenlp/p=tokens',
+ 'corenlp/s=spans',
+ 'glemm/l=tokens',
+ 'mate/l=tokens',
+ 'mate/m=tokens',
+ 'mate/p=tokens',
+ 'opennlp/p=tokens',
+ 'opennlp/s=spans',
+ 'tt/l=tokens',
+ 'tt/p=tokens',
+ 'tt/s=spans'
+];
+
+var match = {
+ 'corpusID' : 'WPD',
+ 'docID' : 'UUU',
+ 'textID' : '01912',
+ 'matchID' : 'p121-122',
+ 'available' : available
+};
+
+var snippet = "<span title=\"cnx/l:meist\">" +
+ " <span title=\"cnx/p:ADV\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:meist\">" +
+ " <span title=\"mate/p:ADV\">" +
+ " <span title=\"opennlp/p:ADV\">meist</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:deutlich\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@PREMOD\">" +
+ " <span title=\"mate/l:deutlich\">" +
+ " <span title=\"mate/m:degree:pos\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">deutlich</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span title=\"cnx/l:fähig\">" +
+ " <span title=\"cnx/l:leistung\">" +
+ " <span title=\"cnx/p:A\">" +
+ " <span title=\"cnx/syn:@NH\">" +
+ " <span title=\"mate/l:leistungsfähig\">" +
+ " <span title=\"mate/m:degree:comp\">" +
+ " <span title=\"mate/p:ADJD\">" +
+ " <span title=\"opennlp/p:ADJD\">leistungsfähiger</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>";
+
+var treeSnippet =
+ "<span class=\"context-left\"></span>" +
+ "<span class=\"match\">" +
+ " <span title=\"xip/c:MC\">" +
+ " <span title=\"xip/c:TOP\">" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">Mit</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:DET\">dieser</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NOUN\">Methode</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:VERB\">ist</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">es</span>" +
+ " </span>" +
+ " <span title=\"xip/c:AP\">" +
+ " <span title=\"xip/c:ADV\">nun</span>" +
+ " <span title=\"xip/c:ADJ\">möglich</span>" +
+ " </span>" +
+ " <span title=\"xip/c:ADV\">z. B.</span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Voice</span>" +
+ " </span>" +
+ " </span>" + "(" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Sprache</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" + ")" +
+ " <span title=\"xip/c:VERB\">bevorzugt</span>" +
+ " <span title=\"xip/c:PP\">" +
+ " <span title=\"xip/c:PREP\">in</span>" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:PRON\">der</span>" +
+ " </span>" +
+ " <span title=\"xip/c:NPA\">" +
+ " <span title=\"xip/c:NP\">" +
+ " <span title=\"xip/c:NOUN\">Bridge</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " <span title=\"xip/c:INFC\">" +
+ " <span title=\"xip/c:INS\">" +
+ " <span title=\"xip/c:VERB\">weiterzugeben</span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ " </span>" +
+ "</span>" +
+ "<span class=\"context-right\"></span>";
+
+
+function matchElementFactory () {
+ var me = document.createElement('li');
+
+ me.setAttribute(
+ 'data-available-info',
+ 'base/s=spans corenlp/c=spans corenlp/ne=tokens corenlp/p=tokens' +
+ ' corenlp/s=spans glemm/l=tokens mate/l=tokens mate/m=tokens' +
+ ' mate/p=tokens opennlp/p=tokens opennlp/s=spans tt/l=tokens' +
+ ' tt/p=tokens tt/s=spans');
+
+ me.setAttribute('data-corpus-id', 'WPD');
+ me.setAttribute('data-doc-id', 'FFF');
+ me.setAttribute('data-text-id', '01460');
+ me.setAttribute('data-match-id', 'p119-120');
+ me.innerHTML = '<div><div class="snippet">check</div></div><p class="ref">me</p>';
+ return me;
+};
+
+
+// Override getMatchInfo API call
+KorAP.API.getMatchInfo = function (x, param, cb) {
+ if (param['spans'] === undefined || param['spans'] === false)
+ cb({ "snippet": snippet });
+ else
+ cb({ "snippet": treeSnippet });
+};
+
+
+describe('KorAP.InfoLayer', function () {
+
+ it('should be initializable', function () {
+ expect(
+ function() { KorAP.InfoLayer.create() }
+ ).toThrow(new Error("Missing parameters"));
+
+ expect(
+ function() { KorAP.InfoLayer.create("base") }
+ ).toThrow(new Error("Missing parameters"));
+
+ var layer = KorAP.InfoLayer.create("base", "s");
+ expect(layer).toBeTruthy();
+ expect(layer.foundry).toEqual("base");
+ expect(layer.layer).toEqual("s");
+ expect(layer.type).toEqual("tokens");
+
+ layer = KorAP.InfoLayer.create("cnx", "syn", "spans");
+ expect(layer).toBeTruthy();
+ expect(layer.foundry).toEqual("cnx");
+ expect(layer.layer).toEqual("syn");
+ expect(layer.type).toEqual("spans");
+ });
+});
+
+
+describe('KorAP.Match', function () {
+ var match = {
+ 'corpusID' : 'WPD',
+ 'docID' : 'UUU',
+ 'textID' : '01912',
+ 'matchID' : 'p121-122',
+ 'available' : available
+ };
+
+ it('should be initializable by Object', function () {
+ expect(function() {
+ KorAP.Match.create()
+ }).toThrow(new Error('Missing parameters'));
+
+ expect(KorAP.Match.create(match)).toBeTruthy();
+
+ var m = KorAP.Match.create(match);
+ expect(m.corpusID).toEqual("WPD");
+ expect(m.docID).toEqual("UUU");
+ expect(m.textID).toEqual("01912");
+ expect(m.matchID).toEqual("p121-122");
+
+ // /corpus/WPD/UUU.01912/p121-122/matchInfo?spans=false&foundry=*
+ var m = KorAP.Match.create(match);
+
+ // Spans:
+ var spans = m.getSpans();
+ expect(spans[0].foundry).toEqual("base");
+ expect(spans[0].layer).toEqual("s");
+
+ expect(spans[1].foundry).toEqual("corenlp");
+ expect(spans[1].layer).toEqual("c");
+
+ expect(spans[2].foundry).toEqual("corenlp");
+ expect(spans[2].layer).toEqual("s");
+
+ expect(spans[spans.length-1].foundry).toEqual("tt");
+ expect(spans[spans.length-1].layer).toEqual("s");
+
+ // Tokens:
+ var tokens = m.getTokens();
+ expect(tokens[0].foundry).toEqual("corenlp");
+ expect(tokens[0].layer).toEqual("ne");
+
+ expect(tokens[1].foundry).toEqual("corenlp");
+ expect(tokens[1].layer).toEqual("p");
+
+ expect(tokens[tokens.length-1].foundry).toEqual("tt");
+ expect(tokens[tokens.length-1].layer).toEqual("p");
+ });
+
+
+ it('should be initializable by Node', function () {
+ var m = KorAP.Match.create(matchElementFactory());
+ expect(m.corpusID).toEqual("WPD");
+ expect(m.docID).toEqual("FFF");
+ expect(m.textID).toEqual("01460");
+ expect(m.matchID).toEqual("p119-120");
+
+ // Spans:
+ var spans = m.getSpans();
+ expect(spans[0].foundry).toEqual("base");
+ expect(spans[0].layer).toEqual("s");
+
+ expect(spans[1].foundry).toEqual("corenlp");
+ expect(spans[1].layer).toEqual("c");
+
+ expect(spans[2].foundry).toEqual("corenlp");
+ expect(spans[2].layer).toEqual("s");
+
+ expect(spans[spans.length-1].foundry).toEqual("tt");
+ expect(spans[spans.length-1].layer).toEqual("s");
+
+ // Tokens:
+ var tokens = m.getTokens();
+ expect(tokens[0].foundry).toEqual("corenlp");
+ expect(tokens[0].layer).toEqual("ne");
+
+ expect(tokens[1].foundry).toEqual("corenlp");
+ expect(tokens[1].layer).toEqual("p");
+
+ expect(tokens[tokens.length-1].foundry).toEqual("tt");
+ expect(tokens[tokens.length-1].layer).toEqual("p");
+
+ });
+
+ it('should react to gui actions', function () {
+ var e = matchElementFactory();
+
+ expect(e.classList.contains('active')).toBe(false);
+ expect(e["_match"]).toBe(undefined);
+
+ var m = KorAP.Match.create(e);
+
+ expect(e.classList.contains('active')).toBe(false);
+ expect(e["_match"]).not.toBe(undefined);
+
+ // Open the match
+ m.open();
+
+ expect(e.classList.contains('active')).toBe(true);
+ expect(e["_match"]).not.toBe(undefined);
+
+ // Close the match
+ m.close();
+ expect(e.classList.contains('active')).toBe(false);
+ expect(e["_match"]).not.toBe(undefined);
+
+ });
+});
+
+
+describe('KorAP.MatchInfo', function () {
+
+ var m = KorAP.Match.create(match);
+ var info = m.info();
+
+ it('should contain a valid info', function () {
+ expect(m._info).toEqual(info);
+ });
+
+ var table1, table2;
+
+ // Async preparation
+ it('should fail to load a table async', function (done) {
+ expect(info).toBeTruthy();
+
+ info.getTable([], function (tablen) {
+ table1 = tablen;
+ done();
+ });
+ });
+
+ it('should\'nt be parsable (async)', function () {
+ expect(table1).not.toBeTruthy();
+ });
+
+ it('should load a working table async', function(done) {
+ expect(info).toBeTruthy();
+ info.getTable(undefined, function (tablem) {
+ table2 = tablem;
+ done();
+ });
+ });
+
+ it('should parse into a table (async)', function () {
+ expect(table2).toBeTruthy();
+
+ expect(table2.length()).toBe(3);
+
+ expect(table2.getToken(0)).toBe("meist");
+ expect(table2.getToken(1)).toBe("deutlich");
+ expect(table2.getToken(2)).toBe("leistungsfähiger");
+
+ expect(table2.getValue(0, "cnx", "p")[0]).toBe("ADV");
+ expect(table2.getValue(0, "cnx", "syn")[0]).toBe("@PREMOD");
+
+ expect(table2.getValue(2, "cnx", "l")[0]).toBe("fähig");
+ expect(table2.getValue(2, "cnx", "l")[1]).toBe("leistung");
+ });
+
+
+ it('should parse into a table view', function () {
+ var matchElement = matchElementFactory();
+ expect(matchElement.tagName).toEqual('LI');
+
+ // Match
+ expect(matchElement.children[0].tagName).toEqual('DIV');
+
+ // snippet
+ expect(matchElement.children[0].children[0].tagName).toEqual('DIV');
+ expect(matchElement.children[0].children[0].classList.contains('snippet')).toBeTruthy();
+ expect(matchElement.children[0].children[0].firstChild.nodeValue).toEqual('check');
+
+ // reference
+ expect(matchElement.children[1].classList.contains('ref')).toBeTruthy();
+ expect(matchElement.children[1].firstChild.nodeValue).toEqual('me');
+
+ // not yet
+ expect(matchElement.children[0].children[1]).toBe(undefined);
+
+ var info = KorAP.Match.create(matchElement).info();
+ info.toggle();
+
+ // Match
+ expect(matchElement.children[0].tagName).toEqual('DIV');
+
+ // snippet
+ expect(matchElement.children[0].children[0].tagName).toEqual('DIV');
+ expect(matchElement.children[0].children[0].classList.contains('snippet')).toBeTruthy();
+ expect(matchElement.children[0].children[0].firstChild.nodeValue).toEqual('check');
+
+ // reference
+ expect(matchElement.children[1].classList.contains('ref')).toBeTruthy();
+ expect(matchElement.children[1].firstChild.nodeValue).toEqual('me');
+
+ // now
+ var infotable = matchElement.children[0].children[1];
+ expect(infotable.tagName).toEqual('DIV');
+ expect(infotable.classList.contains('matchinfo')).toBeTruthy();
+
+ expect(infotable.children[0].classList.contains('matchtable')).toBeTruthy();
+ expect(infotable.children[1].classList.contains('addtree')).toBeTruthy();
+ });
+
+ var tree;
+ it('should parse into a tree (async) 1', function (done) {
+ var info = KorAP.Match.create(match).info();
+ expect(info).toBeTruthy();
+ info.getTree(undefined, undefined, function (treem) {
+ tree = treem;
+ done();
+ });
+ });
+
+ it('should parse into a tree (async) 2', function () {
+ expect(tree).toBeTruthy();
+ expect(tree.nodes()).toEqual(49);
+ });
+
+
+ var info, matchElement;
+ it('should parse into a tree view', function () {
+ matchElement = matchElementFactory();
+ expect(matchElement.tagName).toEqual('LI');
+
+ info = KorAP.Match.create(matchElement).info();
+ info.toggle();
+
+ // Match
+ expect(matchElement.children[0].tagName).toEqual('DIV');
+
+ // snippet
+ expect(matchElement.children[0].children[0].tagName).toEqual('DIV');
+ expect(matchElement.children[0].children[0].classList.contains('snippet')).toBeTruthy();
+ expect(matchElement.children[0].children[0].firstChild.nodeValue).toEqual('check');
+
+ // reference
+ expect(matchElement.children[1].classList.contains('ref')).toBeTruthy();
+ expect(matchElement.children[1].firstChild.nodeValue).toEqual('me');
+
+ // now
+ var infotable = matchElement.children[0].children[1];
+ expect(infotable.tagName).toEqual('DIV');
+ expect(infotable.classList.contains('matchinfo')).toBeTruthy();
+ expect(infotable.children[0].classList.contains('matchtable')).toBeTruthy();
+ expect(infotable.children[1].classList.contains('addtree')).toBeTruthy();
+ });
+
+ it('should add a tree view async 1', function (done) {
+ expect(info).toBeTruthy();
+ info.addTree('mate', 'beebop', function () {
+ done();
+ });
+ });
+
+ it('should add a tree view async 2', function () {
+ // With added tree
+ var infotable = matchElement.children[0].children[1];
+ expect(infotable.tagName).toEqual('DIV');
+ expect(infotable.classList.contains('matchinfo')).toBeTruthy();
+ expect(infotable.children[0].classList.contains('matchtable')).toBeTruthy();
+ expect(infotable.children[1].classList.contains('addtree')).toBe(false);
+
+ var tree = infotable.children[1];
+ expect(tree.tagName).toEqual('DIV');
+ expect(tree.classList.contains('matchtree')).toBeTruthy();
+ expect(tree.children[0].tagName).toEqual('H6');
+ expect(tree.children[0].children[0].tagName).toEqual('SPAN');
+ expect(tree.children[0].children[0].firstChild.nodeValue).toEqual('mate');
+ expect(tree.children[0].children[1].tagName).toEqual('SPAN');
+ expect(tree.children[0].children[1].firstChild.nodeValue).toEqual('beebop');
+
+ expect(tree.children[1].tagName).toEqual('DIV');
+ });
+});
+
+
+describe('KorAP.MatchTable', function () {
+
+ var table;
+ it('should be retrieved async', function (done) {
+ var info = KorAP.Match.create(match).info();
+ expect(info).toBeTruthy();
+ info.getTable(undefined, function (x) {
+ table = x;
+ done();
+ });
+ });
+
+ it('should be rendered async', function () {
+ var e = table.element();
+
+ expect(e.nodeName).toBe('TABLE');
+ expect(e.children[0].nodeName).toBe('THEAD');
+ var tr = e.children[0].children[0];
+ expect(tr.nodeName).toBe('TR');
+ expect(tr.children[0].nodeName).toBe('TH');
+
+ expect(tr.children[0].firstChild.nodeValue).toBe('Foundry');
+ expect(tr.children[1].firstChild.nodeValue).toBe('Layer');
+ expect(tr.children[2].firstChild.nodeValue).toBe('meist');
+ expect(tr.children[3].firstChild.nodeValue).toBe('deutlich');
+ expect(tr.children[4].firstChild.nodeValue).toBe('leistungsfähiger');
+
+ // first row
+ tr = e.children[1].children[0];
+ expect(tr.nodeName).toBe('TR');
+ expect(tr.getAttribute('tabindex')).toEqual('0');
+ expect(tr.children[0].nodeName).toBe('TH');
+ expect(tr.children[0].firstChild.nodeValue).toEqual('cnx');
+ expect(tr.children[1].firstChild.nodeValue).toEqual('l');
+ expect(tr.children[2].firstChild.nodeValue).toEqual('meist');
+ expect(tr.children[3].firstChild.nodeValue).toEqual('deutlich');
+ expect(tr.children[4].firstChild.nodeValue).toEqual('fähig');
+ expect(tr.children[4].lastChild.nodeValue).toEqual('leistung');
+
+ // second row
+ tr = e.children[1].children[1];
+ expect(tr.nodeName).toBe('TR');
+ expect(tr.getAttribute('tabindex')).toEqual('0');
+ expect(tr.children[0].nodeName).toBe('TH');
+ expect(tr.children[0].firstChild.nodeValue).toEqual('cnx');
+ expect(tr.children[1].firstChild.nodeValue).toEqual('p');
+ expect(tr.children[2].firstChild.nodeValue).toEqual('ADV');
+ expect(tr.children[3].firstChild.nodeValue).toEqual('A');
+ expect(tr.children[4].firstChild.nodeValue).toEqual('A');
+ });
+});
+
+describe('KorAP.MatchTree', function () {
+ var tree;
+
+ it('should be rendered async 1', function (done) {
+ var info = KorAP.Match.create(match).info();
+ expect(info).toBeTruthy();
+ info.getTree(undefined, undefined, function (y) {
+ tree = y;
+ done();
+ });
+ });
+
+ it('should be rendered async 2', function () {
+ var e = tree.element();
+ expect(e.nodeName).toEqual('svg');
+ expect(e.getElementsByTagName('g').length).toEqual(48);
+ });
+});
+
+
+describe('KorAP.MatchTreeItem', function () {
+ it('should be initializable', function () {
+ var mi = KorAP.MatchTreeItem.create(['cnx/c', 'cnx', 'c'])
+ expect(mi.element().firstChild.nodeValue).toEqual('cnx/c');
+ expect(mi.lcField()).toEqual(' cnx/c');
+ expect(mi.foundry()).toEqual('cnx');
+ expect(mi.layer()).toEqual('c');
+ });
+});
+
+
+describe('KorAP.MatchTreeMenu', function () {
+ it('should be initializable', function () {
+ var menu = KorAP.MatchTreeMenu.create(undefined, [
+ ['cnx/c', 'cnx', 'c'],
+ ['xip/c', 'xip', 'c']
+ ]);
+
+ expect(menu.itemClass()).toEqual(KorAP.MatchTreeItem);
+ expect(menu.element().nodeName).toEqual('UL');
+ expect(menu.element().style.opacity).toEqual("0");
+ expect(menu.limit()).toEqual(6);
+ menu.show();
+ expect(menu.item(0).active()).toBe(true);
+ });
+});
+
+// table = view.toTable();
+// table.sortBy('');
+// table.element();
+// tree = view.toTree();
+// tree.element();
+
diff --git a/dev/js/spec/menuSpec.js b/dev/js/spec/menuSpec.js
new file mode 100644
index 0000000..6692753
--- /dev/null
+++ b/dev/js/spec/menuSpec.js
@@ -0,0 +1,1057 @@
+// The OwnMenu item
+KorAP.OwnMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem).upgradeTo(KorAP.OwnMenuItem)._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._content = document.createTextNode(params[0]);
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ }
+};
+
+
+// The OwnMenu
+KorAP.OwnMenu = {
+ create : function (params) {
+ return Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.OwnMenu)
+ ._init(KorAP.OwnMenuItem, undefined, params);
+ }
+};
+
+
+// HintMenuItem
+KorAP.HintMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.HintMenuItem)
+ ._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+ _init : function (params) {
+ if (params[0] === undefined || params[1] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._action = params[1];
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ if (params.length > 2) {
+ this._desc = params[2];
+ this._lcField += " " + this._desc.toLowerCase();
+ };
+
+ return this;
+ },
+ name : function () {
+ return this._name;
+ },
+ action : function () {
+ return this._action;
+ },
+ desc : function () {
+ return this._desc;
+ },
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+ li.setAttribute("data-action", this._action);
+
+ // Create title
+ var name = document.createElement("strong");
+ name.appendChild(document.createTextNode(this._name));
+
+ li.appendChild(name);
+
+ // Create description
+ if (this._desc !== undefined) {
+ var desc = document.createElement("span");
+ desc.appendChild(document.createTextNode(this._desc));
+ li.appendChild(desc);
+ };
+ return this._element = li;
+ }
+};
+
+
+// HintMenu
+KorAP.HintMenu = {
+ create : function (context, params) {
+ var obj = Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.HintMenu)
+ ._init(KorAP.HintMenuItem, undefined, params);
+ obj._context = context;
+ return obj;
+ }
+};
+
+
+// The ComplexMenuItem
+KorAP.ComplexMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.ComplexMenuItem)
+ ._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ var r = document.createElement('div');
+ for (var i = 1; i <= params.length; i++) {
+ var h = document.createElement('h' + i);
+ h.appendChild(document.createTextNode(params[i-1]));
+ r.appendChild(h);
+ };
+
+ this._content = r;
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ }
+};
+
+
+
+describe('KorAP.MenuItem', function () {
+ it('should be initializable', function () {
+ expect(
+ function() { KorAP.MenuItem.create([]) }
+ ).toThrow(new Error("Missing parameters"));
+
+ expect(
+ function() { KorAP.OwnMenuItem.create([]) }
+ ).toThrow(new Error("Missing parameters"));
+
+ var mi = KorAP.OwnMenuItem.create(["Baum"]);
+ expect(mi.element().firstChild.nodeValue).toEqual('Baum');
+ expect(mi.lcField()).toEqual(' baum');
+ });
+
+ it('shouldn\'t have a reference to the menu', function () {
+ var menuItem = KorAP.OwnMenuItem.create(['Test']);
+ expect(menuItem.menu()).toBe(undefined);
+ });
+
+ it('should be activatable and deactivateable by class', function () {
+ var menuItem = KorAP.OwnMenuItem.create(['Test']);
+
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toBe(null);
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.element().getAttribute("class")).toEqual("active");
+ menuItem.active(false); // Is active
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toEqual("");
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+ menuItem = KorAP.OwnMenuItem.create(['Spiegel']);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toBe(null);
+ menuItem.active(false); // Is not active
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toBe(null);
+ });
+
+ it('should be set to boundary', function () {
+ var menuItem = KorAP.OwnMenuItem.create(['CoreNLP']);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toBe(null);
+
+ // Set active
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+ // Set no more
+ menuItem.noMore(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element().getAttribute("class")).toEqual("active no-more");
+
+ // No no more
+ menuItem.noMore(false);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(false);
+ expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+ // Set no more, deactivate
+ menuItem.noMore(true);
+ menuItem.active(false);
+ expect(menuItem.active()).toBe(false);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element().getAttribute("class")).toEqual("no-more");
+
+ // Set active
+ menuItem.active(true);
+ expect(menuItem.active()).toBe(true);
+ expect(menuItem.noMore()).toBe(true);
+ expect(menuItem.element().getAttribute("class")).toEqual("no-more active");
+ });
+
+
+ it('should be highlightable', function () {
+ // Highlight in the middle
+ var menuItem = KorAP.OwnMenuItem.create(['CoreNLP']);
+ menuItem.highlight("ren");
+ expect(menuItem.element().innerHTML).toEqual("Co<mark>reN</mark>LP");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual("CoreNLP");
+
+ var plain = "<div><h1>CoreNLP</h1><h2>corenlp/</h2></div>";
+
+ // Starting highlight
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("cor");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1><mark>Cor</mark>eNLP</h1><h2><mark>cor</mark>enlp/</h2></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Starting highlight - short
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("c");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1><mark>C</mark>oreNLP</h1><h2><mark>c</mark>orenlp/</h2></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight at the end
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("nlp");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>Core<mark>NLP</mark></h1><h2>core<mark>nlp</mark>/</h2></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight at the end - short
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("p");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNL<mark>P</mark></h1><h2>corenl<mark>p</mark>/</h2></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // No highlight
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+ menuItem.highlight("xp");
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight in the middle - first
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("ren");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>Co<mark>reN</mark>LP</h1><h2>co<mark>ren</mark>lp/</h2><h3>This is my Example</h3></div>");
+
+ plain = "<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Example</h3></div>"
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight in the middle - second
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("ampl");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Ex<mark>ampl</mark>e</h3></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight in the middle - both
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("e");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>Cor<mark>e</mark>NLP</h1><h2>cor<mark>e</mark>nlp/</h2><h3>This is my <mark>E</mark>xampl<mark>e</mark></h3></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight in the end - second
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("le");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Examp<mark>le</mark></h3></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+
+ // Highlight at the beginning - second
+ menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+ menuItem.highlight("this");
+ expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3><mark>This</mark> is my Example</h3></div>");
+
+ menuItem.lowlight();
+ expect(menuItem.element().innerHTML).toEqual(plain);
+ });
+});
+
+
+describe('KorAP.Menu', function () {
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
+
+ var demolist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author']
+ ];
+
+ var demolonglist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author'],
+ ['Genre', 'genre'],
+ ['corpusID', 'corpusID'],
+ ['docID', 'docID'],
+ ['textID', 'textID'],
+ ];
+
+ it('should be initializable', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = KorAP.OwnMenu.create(list);
+ expect(menu.itemClass()).toEqual(KorAP.OwnMenuItem);
+ expect(menu.element().nodeName).toEqual('UL');
+ expect(menu.element().style.opacity).toEqual("0");
+ expect(menu.limit()).toEqual(8);
+
+ menu.limit(9);
+ expect(menu.limit()).toEqual(9);
+
+ menu.limit(8);
+
+ // view
+ menu.show();
+
+ // First element in list
+ expect(menu.item(0).active()).toBe(true);
+ expect(menu.item(0).noMore()).toBe(true);
+
+ // Middle element in list
+ expect(menu.item(2).active()).toBe(false);
+ expect(menu.item(2).noMore()).toBe(false);
+
+ // Last element in list
+ expect(menu.item(menu.length() - 1).active()).toBe(false);
+ expect(menu.item(menu.length() - 1).noMore()).toBe(true);
+ });
+
+ it('should have a reference to the menu', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ expect(menu.item(0).menu()).toEqual(menu);
+
+ menu = KorAP.HintMenu.create("cnx/", list);
+ expect(menu.element().menu).toEqual(menu);
+ });
+
+
+ it('should be visible', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ expect(menu.delete()).toBe(undefined);
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.element().childNodes[2].getAttribute("data-action")).toEqual("l=");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Check boundaries
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[2].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[3].classList.contains("no-more")).toBe(false);
+ });
+
+ it('should be filterable', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ menu.limit(3);
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("o");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Check boundaries
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[2].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[3].classList.contains("no-more")).toBe(true);
+
+ menu.limit(2);
+
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("o");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[3]).toBe(undefined);
+
+ // Check boundaries
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[2].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[3]).toBe(undefined);
+
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("e");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.element().childNodes[3]).toBe(undefined);
+
+ menu.limit(5);
+ expect(menu.prefix("a").show()).toBe(true);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("a");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Ex<mark>a</mark>mple 1</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemm<mark>a</mark></strong>");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Ex<mark>a</mark>mple 2</span>");
+ expect(menu.element().childNodes[4].innerHTML).toEqual("<strong>P<mark>a</mark>rt-of-Speech</strong>");
+ expect(menu.element().childNodes[5].innerHTML).toEqual("<strong>Synt<mark>a</mark>x</strong>");
+ expect(menu.element().childNodes[6]).toBe(undefined);
+ });
+
+
+ it('should be nextable', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+
+ // Show only 3 items
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (1)
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (2)
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (3)
+ // scroll!
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (4)
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (5) - ROLL
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Active next (6)
+ menu.next();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+ });
+
+ it('should be prevable', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (1) - roll to bottom
+ menu.prev();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (2)
+ menu.prev();
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (3)
+ menu.prev();
+ expect(menu.shownItem(0).name()).toEqual("Morphology");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Syntax");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (4)
+ menu.prev();
+ expect(menu.shownItem(0).name()).toEqual("Lemma");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Part-of-Speech");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (5)
+ menu.prev();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Lemma");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Morphology");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate next (1)
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Lemma");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).name()).toEqual("Morphology");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+
+ // Activate prev (6)
+ menu.prev();
+
+ // Activate prev (7)
+ menu.prev();
+ expect(menu.shownItem(0).name()).toEqual("Morphology");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Syntax");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.element().childNodes[4]).toBe(undefined);
+ });
+
+
+ it('should be navigatable and filterable (prefix = "o")', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ menu.limit(2);
+
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (1)
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (2)
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (3) - to prefix
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+
+ it('should be navigatable and filterable (prefix = "ex", "e")', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+
+ menu.limit(2);
+ expect(menu.prefix("ex").show()).toBe(true);
+
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>ample 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (1)
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>ample 2</span>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (2)
+ menu.next();
+
+ expect(menu.prefix()).toEqual('ex');
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>ample 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Reset limit
+ menu.limit(5);
+
+ // Change show
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (1)
+ menu.next();
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.prefix()).toEqual('e');
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (2)
+ menu.next();
+ expect(menu._prefix.active()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (3)
+ menu.next();
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+
+ it('shouldn\'t be viewable with failing prefix', function () {
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ menu.limit(2);
+ expect(menu.prefix("exit").show()).toBe(false);
+ });
+
+ it('should be navigatable with prefix', function () {
+ var menu = KorAP.HintMenu.create("cnx/", demolist);
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu._prefix.add('a');
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("a");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong><mark>A</mark>utor</strong>");
+
+ menu._prefix.add('u');
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("au");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong><mark>Au</mark>tor</strong>");
+
+ menu._prefix.backspace();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("a");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong><mark>A</mark>utor</strong>");
+
+ menu._prefix.backspace();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu._prefix.backspace();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Länge");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Länge</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Länge");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Länge</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Autor");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Autor</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ });
+
+
+ it('should be navigatable with a prefix (1)', function () {
+ var menu = KorAP.HintMenu.create("cnx/", demolist);
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(true);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+
+ // Backward
+ menu.prev();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+
+ it('should be navigatable with a prefix (2)', function () {
+ var menu = KorAP.HintMenu.create("cnx/", demolist);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(true);
+
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+ it('should be navigatable with a prefix (3)', function () {
+ var menu = KorAP.HintMenu.create("cnx/", demolist);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(true);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ });
+
+ xit('should be page downable');
+ xit('should be page upable');
+
+ xit('should scroll to a chosen value')
+ xit('should highlight a chosen value')
+});
diff --git a/dev/js/spec/vcSpec.js b/dev/js/spec/vcSpec.js
new file mode 100644
index 0000000..1a24fb6
--- /dev/null
+++ b/dev/js/spec/vcSpec.js
@@ -0,0 +1,1898 @@
+/*
+Todo: In demoSpec: Create "and" on the last element of the top "or"-Group
+*/
+
+
+// Helper method for building factories
+buildFactory = function (objClass, defaults) {
+ return {
+ create : function (overwrites) {
+ var newObj = {};
+ for (var prop in defaults) {
+ newObj[prop] = defaults[prop];
+ };
+ for (var prop in overwrites) {
+ newObj[prop] = overwrites[prop];
+ };
+ if (objClass === KorAP.VirtualCollection)
+ return objClass.render(newObj);
+ else
+ return objClass.create().fromJson(newObj);
+ }
+ }
+};
+
+function _andOn (obj) {
+ KorAP._and.bind(obj.element().lastChild.firstChild).apply();
+};
+
+function _orOn (obj) {
+ KorAP._or.bind(obj.element().lastChild.firstChild).apply();
+};
+
+function _delOn (obj) {
+ KorAP._delete.bind(obj.element().lastChild.firstChild).apply();
+};
+
+var demoFactory = buildFactory(KorAP.VirtualCollection, {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:and",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"huhu",
+ "match":"match:eq"
+ }
+ ]
+});
+
+
+describe('KorAP.Doc', function () {
+
+ // Create example factories
+ var stringFactory = buildFactory(KorAP.Doc, {
+ "key" : "author",
+ "value" : "Max Birkendale",
+ "@type" : "koral:doc"
+ });
+
+ // Create example factories
+ var dateFactory = buildFactory(KorAP.Doc, {
+ "key" : "pubDate",
+ "type" : "type:date",
+ "match" : "match:eq",
+ "value" : "2014-11-05",
+ "@type" : "koral:doc"
+ });
+
+ // Create example factories
+ var regexFactory = buildFactory(KorAP.Doc, {
+ "key" : "title",
+ "type" : "type:regex",
+ "value" : "[^b]ee.+?",
+ "@type" : "koral:doc"
+ });
+
+ it('should be initializable', function () {
+ var doc = KorAP.Doc.create();
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toBeUndefined();
+ expect(doc.value()).toBeUndefined();
+ expect(doc.type()).toEqual("string");
+ });
+
+ it('should be definable', function () {
+
+ // Empty doc
+ var doc = KorAP.Doc.create();
+
+ // Set values
+ doc.key("title");
+ doc.value("Der alte Mann");
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("title");
+ expect(doc.type()).toEqual("string");
+ expect(doc.value()).toEqual("Der alte Mann");
+ });
+
+
+ it('should deserialize JSON-LD string', function () {
+ var doc;
+
+ // String default
+ doc = stringFactory.create();
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("author");
+ expect(doc.type()).toEqual("string");
+ expect(doc.value()).toEqual("Max Birkendale");
+
+ // No valid string
+ doc = stringFactory.create({
+ value : undefined
+ });
+ expect(doc).toBeUndefined();
+
+ // No valid string
+ doc = stringFactory.create({
+ value : { "foo" : "bar" }
+ });
+ expect(doc).toBeUndefined();
+
+ // Change match type
+ doc = stringFactory.create({
+ "match" : "match:ne"
+ });
+
+ expect(doc.matchop()).toEqual('ne');
+ expect(doc.key()).toEqual("author");
+ expect(doc.type()).toEqual("string");
+ expect(doc.value()).toEqual("Max Birkendale");
+
+
+ // Invalid match type
+ doc = stringFactory.create({
+ "match" : { "foo" : "bar" }
+ });
+ expect(doc).toBeUndefined();
+ });
+
+ it('should deserialize JSON-LD regex', function () {
+ var doc = regexFactory.create();
+ expect(doc.key()).toEqual("title");
+ expect(doc.type()).toEqual("regex");
+ expect(doc.value()).toEqual("[^b]ee.+?");
+ expect(doc.matchop()).toEqual('eq');
+
+ // change matcher
+ doc = regexFactory.create({
+ match : "match:ne"
+ });
+ expect(doc.matchop()).toEqual('ne');
+
+ // Invalid matcher
+ doc = regexFactory.create({
+ match : "match:chook"
+ });
+ expect(doc).toBeUndefined();
+
+ // Invalid regex
+ doc = regexFactory.create({
+ value : "[^b"
+ });
+ expect(doc).toBeUndefined();
+ });
+
+ it('should deserialize JSON-LD date', function () {
+
+ // Normal date
+ doc = dateFactory.create({});
+
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.type()).toEqual("date");
+ expect(doc.value()).toEqual("2014-11-05");
+
+ // Short date 1
+ doc = dateFactory.create({
+ "value" : "2014-11"
+ });
+
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.type()).toEqual("date");
+ expect(doc.value()).toEqual("2014-11");
+
+ // Short date 2
+ doc = dateFactory.create({
+ "value" : "2014"
+ });
+
+ expect(doc.matchop()).toEqual('eq');
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.type()).toEqual("date");
+ expect(doc.value()).toEqual("2014");
+
+ // Invalid date!
+ doc = dateFactory.create({
+ "value" : "2014-11-050"
+ });
+ expect(doc).toBeUndefined();
+
+ // Invalid matcher!
+ doc = dateFactory.create({
+ "match" : "match:ne",
+ });
+ expect(doc).toBeUndefined();
+ });
+
+ it('should be serializale to JSON', function () {
+
+ // Empty doc
+ var doc = KorAP.Doc.create();
+ expect(doc.toJson()).toEqual(jasmine.any(Object));
+
+ // Serialize string
+ doc = stringFactory.create();
+ expect(doc.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "type" : "type:string",
+ "key" : "author",
+ "value" : "Max Birkendale",
+ "match" : "match:eq"
+ }));
+
+ // Serialize regex
+ doc = regexFactory.create();
+ expect(doc.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "type" : "type:regex",
+ "value" : "[^b]ee.+?",
+ "match" : "match:eq",
+ "key" : 'title'
+ }));
+
+ doc = regexFactory.create({
+ match: "match:ne"
+ });
+ expect(doc.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "type" : "type:regex",
+ "value" : "[^b]ee.+?",
+ "match" : "match:ne",
+ "key" : 'title'
+ }));
+
+ doc = dateFactory.create();
+ expect(doc.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "type" : "type:date",
+ "value" : "2014-11-05",
+ "match" : "match:eq",
+ "key" : 'pubDate'
+ }));
+
+ doc = dateFactory.create({
+ value : "2014"
+ });
+ expect(doc.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "type" : "type:date",
+ "value" : "2014",
+ "match" : "match:eq",
+ "key" : 'pubDate'
+ }));
+ });
+
+ it('should be serializale to String', function () {
+
+ // Empty doc
+ var doc = KorAP.Doc.create();
+ expect(doc.toQuery()).toEqual("");
+
+ // Serialize string
+ doc = stringFactory.create();
+ expect(doc.toQuery()).toEqual('author = "Max Birkendale"');
+
+ // Serialize string with quotes
+ doc = stringFactory.create({ "value" : 'Max "Der Coole" Birkendate'});
+ expect(doc.toQuery()).toEqual('author = "Max \\"Der Coole\\" Birkendate"');
+
+ // Serialize regex
+ doc = regexFactory.create();
+ expect(doc.toQuery()).toEqual('title = /[^b]ee.+?/');
+
+ doc = regexFactory.create({
+ match: "match:ne"
+ });
+ expect(doc.toQuery()).toEqual('title != /[^b]ee.+?/');
+
+ doc = dateFactory.create();
+ expect(doc.toQuery()).toEqual('pubDate in 2014-11-05');
+
+ doc = dateFactory.create({
+ value : "2014"
+ });
+ expect(doc.toQuery()).toEqual('pubDate in 2014');
+ });
+});
+
+
+describe('KorAP.DocGroup', function () {
+ // Create example factories
+ var docFactory = buildFactory(
+ KorAP.Doc,
+ {
+ "@type" : "koral:doc",
+ "match":"match:eq",
+ "key" : "author",
+ "value" : "Max Birkendale"
+ }
+ );
+
+ var docGroupFactory = buildFactory(
+ KorAP.DocGroup, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ docFactory.create().toJson(),
+ docFactory.create({
+ "key" : "pubDate",
+ "type" : "type:date",
+ "value" : "2014-12-05"
+ }).toJson()
+ ]
+ });
+
+
+ it('should be initializable', function () {
+ // Create empty group
+ var docGroup = KorAP.DocGroup.create();
+ expect(docGroup.operation()).toEqual('and');
+
+ // Create empty group
+ docGroup = KorAP.DocGroup.create();
+ docGroup.operation('or');
+ expect(docGroup.operation()).toEqual('or');
+ });
+
+ it('should be definable', function () {
+
+ // Empty group
+ var docGroup = KorAP.DocGroup.create();
+ expect(docGroup.operation()).toEqual('and');
+
+ // Set values
+ docGroup.operation("or");
+ expect(docGroup.operation()).toEqual('or');
+
+ // Set invalid values
+ docGroup.operation("hui");
+ expect(docGroup.operation()).toEqual('or');
+ });
+
+ it('should be deserializable', function () {
+ var docGroup = docGroupFactory.create();
+ expect(docGroup.operation()).toEqual("and");
+ expect(docGroup.operands().length).toEqual(2);
+
+ var op1 = docGroup.getOperand(0);
+ expect(op1.type()).toEqual("string");
+ expect(op1.key()).toEqual("author");
+ expect(op1.value()).toEqual("Max Birkendale");
+ expect(op1.matchop()).toEqual("eq");
+
+ var op2 = docGroup.getOperand(1);
+ expect(op2.type()).toEqual("date");
+ expect(op2.key()).toEqual("pubDate");
+ expect(op2.value()).toEqual("2014-12-05");
+ expect(op2.matchop()).toEqual("eq");
+
+ // Append empty group
+ var newGroup = docGroup.append(KorAP.DocGroup.create());
+ newGroup.operation('or');
+ newGroup.append(docFactory.create());
+ newGroup.append(docFactory.create({
+ "type" : "type:regex",
+ "key" : "title",
+ "value" : "^e.+?$",
+ "match" : "match:ne"
+ }));
+
+ expect(docGroup.operation()).toEqual("and");
+ expect(docGroup.operands().length).toEqual(3);
+
+ var op1 = docGroup.getOperand(0);
+ expect(op1.ldType()).toEqual("doc");
+ expect(op1.type()).toEqual("string");
+ expect(op1.key()).toEqual("author");
+ expect(op1.value()).toEqual("Max Birkendale");
+ expect(op1.matchop()).toEqual("eq");
+
+ var op2 = docGroup.getOperand(1);
+ expect(op2.ldType()).toEqual("doc");
+ expect(op2.type()).toEqual("date");
+ expect(op2.key()).toEqual("pubDate");
+ expect(op2.value()).toEqual("2014-12-05");
+ expect(op2.matchop()).toEqual("eq");
+
+ var op3 = docGroup.getOperand(2);
+ expect(op3.ldType()).toEqual("docGroup");
+ expect(op3.operation()).toEqual("or");
+
+ var op4 = op3.getOperand(0);
+ expect(op4.ldType()).toEqual("doc");
+ expect(op4.type()).toEqual("string");
+ expect(op4.key()).toEqual("author");
+ expect(op4.value()).toEqual("Max Birkendale");
+ expect(op4.matchop()).toEqual("eq");
+
+ var op5 = op3.getOperand(1);
+ expect(op5.ldType()).toEqual("doc");
+ expect(op5.type()).toEqual("regex");
+ expect(op5.key()).toEqual("title");
+ expect(op5.value()).toEqual("^e.+?$");
+ expect(op5.matchop()).toEqual("ne");
+ });
+
+ it('should be serializable to JSON', function () {
+ var docGroup = docGroupFactory.create();
+
+ expect(docGroup.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ ]
+ }));
+ });
+
+ it('should be serializable to String', function () {
+ var docGroup = docGroupFactory.create();
+ expect(docGroup.toQuery()).toEqual('author = "Max Birkendale" & pubDate in 2014-12-05');
+
+ docGroup = docGroupFactory.create({
+ "@type" : "koral:docGroup",
+ "operation" : "operation:or",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:geq',
+ "value": '2014-05-12',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:leq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'foo',
+ "match": 'match:ne',
+ "value": '[a]?bar',
+ "type": 'type:regex'
+ }
+ ]
+ }
+ ]
+ });
+ expect(docGroup.toQuery()).toEqual(
+ 'author = "Max Birkendale" | (pubDate since 2014-05-12 & pubDate until 2014-12-05 & foo != /[a]?bar/)'
+ );
+ });
+});
+
+describe('KorAP.UnspecifiedDoc', function () {
+ it('should be initializable', function () {
+ var doc = KorAP.UnspecifiedDoc.create();
+ var docElement = doc.element();
+ expect(docElement.getAttribute('class')).toEqual('doc unspecified');
+ expect(docElement.firstChild.firstChild.data).toEqual('⋯');
+ expect(docElement.lastChild.lastChild.data).toEqual('⋯');
+ expect(doc.toQuery()).toEqual('');
+
+ // Only removable
+ expect(docElement.lastChild.children.length).toEqual(0);
+ });
+
+ it('should be removable, when no root', function () {
+ var docGroup = KorAP.DocGroup.create();
+ docGroup.operation('or');
+ expect(docGroup.operation()).toEqual('or');
+
+ docGroup.append({
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ });
+
+ // Add unspecified object
+ docGroup.append();
+
+ expect(docGroup.element().getAttribute('class')).toEqual('docGroup');
+ expect(docGroup.element().children[0].getAttribute('class')).toEqual('doc');
+
+ var unspec = docGroup.element().children[1];
+ expect(unspec.getAttribute('class')).toEqual('doc unspecified');
+
+ // Removable
+ expect(unspec.lastChild.children.length).toEqual(1);
+ expect(unspec.lastChild.children[0].getAttribute('class')).toEqual('delete');
+ });
+
+ it('should be replaceable by a doc', function () {
+ var doc = KorAP.UnspecifiedDoc.create();
+ expect(doc.ldType()).toEqual("non");
+ // No parent, therefor not updateable
+ expect(doc.key("baum")).toBeNull();
+
+ var docGroup = KorAP.DocGroup.create();
+ docGroup.operation('or');
+ expect(docGroup.operation()).toEqual('or');
+
+ docGroup.append({
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ });
+
+ expect(docGroup.toQuery()).toEqual("pubDate in 2014-12-05");
+ docGroup.append();
+
+ expect(docGroup.getOperand(0).ldType()).toEqual("doc");
+ expect(docGroup.getOperand(1).ldType()).toEqual("non");
+
+ var op = docGroup.getOperand(1).element().lastChild;
+ expect(op.getAttribute('class')).toEqual('operators');
+ expect(op.children[0].getAttribute('class')).toEqual('delete');
+ expect(op.children.length).toEqual(1);
+
+ // Replace unspecified doc
+ expect(docGroup.getOperand(1).key("name")).not.toBeNull();
+ expect(docGroup.getOperand(1).ldType()).toEqual("doc");
+ expect(docGroup.getOperand(1).key()).toEqual("name");
+ expect(docGroup.getOperand(1).value()).toEqual("");
+
+ op = docGroup.getOperand(1).element().lastChild;
+ expect(op.getAttribute('class')).toEqual('operators');
+ expect(op.children[0].getAttribute('class')).toEqual('and');
+ expect(op.children[1].getAttribute('class')).toEqual('or');
+ expect(op.children[2].getAttribute('class')).toEqual('delete');
+ expect(op.children.length).toEqual(3);
+
+ docGroup.getOperand(1).value("Pachelbel");
+ expect(docGroup.getOperand(1).value()).toEqual("Pachelbel");
+ expect(docGroup.getOperand(1).type()).toEqual("string");
+ expect(docGroup.getOperand(1).matchop()).toEqual("eq");
+
+ // Specified!
+ expect(docGroup.toQuery()).toEqual('pubDate in 2014-12-05 | name = "Pachelbel"');
+ });
+
+ it('should be replaceable on root', function () {
+ var vc = KorAP.VirtualCollection.render();
+ expect(vc.toQuery()).toEqual("");
+
+ expect(vc.root().ldType()).toEqual("non");
+
+ // No operators on root
+ op = vc.root().element().lastChild;
+ expect(op.lastChild.textContent).toEqual('⋯');
+
+ // Replace
+ expect(vc.root().key("baum")).not.toBeNull();
+ expect(vc.root().ldType()).toEqual("doc");
+
+ op = vc.root().element().lastChild;
+ expect(op.getAttribute('class')).toEqual('operators');
+ expect(op.children[0].getAttribute('class')).toEqual('and');
+ expect(op.children[1].getAttribute('class')).toEqual('or');
+ expect(op.children[2].getAttribute('class')).toEqual('delete');
+ expect(op.children.length).toEqual(3);
+ });
+});
+
+describe('KorAP.Doc element', function () {
+ it('should be initializable', function () {
+ var docElement = KorAP.Doc.create(undefined, {
+ "@type" : "koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ });
+ expect(docElement.key()).toEqual('Titel');
+ expect(docElement.matchop()).toEqual('eq');
+ expect(docElement.value()).toEqual('Baum');
+
+ var docE = docElement.element();
+ expect(docE.children[0].firstChild.data).toEqual('Titel');
+ expect(docE.children[1].firstChild.data).toEqual('eq');
+ expect(docE.children[1].getAttribute('data-type')).toEqual('string');
+ expect(docE.children[2].firstChild.data).toEqual('Baum');
+ expect(docE.children[2].getAttribute('data-type')).toEqual('string');
+
+ expect(docElement.toJson()).toEqual(jasmine.objectContaining({
+ "@type" : "koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ }));
+ });
+});
+
+describe('KorAP.DocGroup element', function () {
+ it('should be initializable', function () {
+
+ var docGroup = KorAP.DocGroup.create(undefined, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ ]
+ });
+
+ expect(docGroup.operation()).toEqual('and');
+ var e = docGroup.element();
+ expect(e.getAttribute('class')).toEqual('docGroup');
+ expect(e.getAttribute('data-operation')).toEqual('and');
+
+ var first = e.children[0];
+ expect(first.getAttribute('class')).toEqual('doc');
+ expect(first.children[0].getAttribute('class')).toEqual('key');
+ expect(first.children[1].getAttribute('class')).toEqual('match');
+ expect(first.children[2].getAttribute('class')).toEqual('value');
+ expect(first.children[2].getAttribute('data-type')).toEqual('string');
+ expect(first.children[0].firstChild.data).toEqual('author');
+ expect(first.children[1].firstChild.data).toEqual('eq');
+ expect(first.children[2].firstChild.data).toEqual('Max Birkendale');
+
+ var second = e.children[1];
+ expect(second.getAttribute('class')).toEqual('doc');
+ expect(second.children[0].getAttribute('class')).toEqual('key');
+ expect(second.children[1].getAttribute('class')).toEqual('match');
+ expect(second.children[2].getAttribute('class')).toEqual('value');
+ expect(second.children[2].getAttribute('data-type')).toEqual('date');
+ expect(second.children[0].firstChild.data).toEqual('pubDate');
+ expect(second.children[1].firstChild.data).toEqual('eq');
+ expect(second.children[2].firstChild.data).toEqual('2014-12-05');
+
+ });
+
+ it('should be deserializable with nested groups', function () {
+ var docGroup = KorAP.DocGroup.create(undefined, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:or",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:geq',
+ "value": '2014-05-12',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:leq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ ]
+ }
+ ]
+ });
+
+ expect(docGroup.operation()).toEqual('or');
+ var e = docGroup.element();
+ expect(e.getAttribute('class')).toEqual('docGroup');
+ expect(e.getAttribute('data-operation')).toEqual('or');
+
+ expect(e.children[0].getAttribute('class')).toEqual('doc');
+ var docop = e.children[0].lastChild;
+ expect(docop.getAttribute('class')).toEqual('operators');
+ expect(docop.children[0].getAttribute('class')).toEqual('and');
+ expect(docop.children[1].getAttribute('class')).toEqual('or');
+ expect(docop.children[2].getAttribute('class')).toEqual('delete');
+
+ expect(e.children[1].getAttribute('class')).toEqual('docGroup');
+ expect(e.children[1].getAttribute('data-operation')).toEqual('and');
+
+ // This and-operation can be "or"ed or "delete"d
+ var secop = e.children[1].children[2];
+ expect(secop.getAttribute('class')).toEqual('operators');
+ expect(secop.children[0].getAttribute('class')).toEqual('or');
+ expect(secop.children[1].getAttribute('class')).toEqual('delete');
+
+ // This or-operation can be "and"ed or "delete"d
+ expect(e.children[2].getAttribute('class')).toEqual('operators');
+ expect(e.lastChild.getAttribute('class')).toEqual('operators');
+ expect(e.lastChild.children[0].getAttribute('class')).toEqual('and');
+ expect(e.lastChild.children[1].getAttribute('class')).toEqual('delete');
+
+ });
+});
+
+describe('KorAP.VirtualCollection', function () {
+
+ var simpleGroupFactory = buildFactory(KorAP.DocGroup, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ ]
+ });
+
+ var nestedGroupFactory = buildFactory(KorAP.VirtualCollection, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:or",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key" : 'author',
+ "match": 'match:eq',
+ "value": 'Max Birkendale',
+ "type": 'type:string'
+ },
+ {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:geq',
+ "value": '2014-05-12',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:leq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ ]
+ }
+ ]
+ });
+
+ var flatGroupFactory = buildFactory(KorAP.VirtualCollection, {
+ "@type" : "koral:docGroup",
+ "operation" : "operation:and",
+ "operands" : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:geq',
+ "value": '2014-05-12',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:leq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type": 'koral:doc',
+ "key": 'foo',
+ "match": 'match:eq',
+ "value": 'bar',
+ "type": 'type:string'
+ }
+ ]
+ });
+
+ it('should be initializable', function () {
+ var vc = KorAP.VirtualCollection.render();
+ expect(vc.element().getAttribute('class')).toEqual('vc');
+ expect(vc.root().element().getAttribute('class')).toEqual('doc unspecified');
+
+ // Not removable
+ expect(vc.root().element().lastChild.children.length).toEqual(0);
+ });
+
+ it('should be based on a doc', function () {
+ var vc = KorAP.VirtualCollection.render({
+ "@type" : "koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ });
+
+ expect(vc.element().getAttribute('class')).toEqual('vc');
+ expect(vc.root().element().getAttribute('class')).toEqual('doc');
+ expect(vc.root().key()).toEqual('Titel');
+ expect(vc.root().value()).toEqual('Baum');
+ expect(vc.root().matchop()).toEqual('eq');
+
+ var docE = vc.root().element();
+ expect(docE.children[0].firstChild.data).toEqual('Titel');
+ expect(docE.children[1].firstChild.data).toEqual('eq');
+ expect(docE.children[1].getAttribute('data-type')).toEqual('string');
+ expect(docE.children[2].firstChild.data).toEqual('Baum');
+ expect(docE.children[2].getAttribute('data-type')).toEqual('string');
+ });
+
+ it('should be based on a docGroup', function () {
+ var vc = KorAP.VirtualCollection.render(simpleGroupFactory.create().toJson());
+
+ expect(vc.element().getAttribute('class')).toEqual('vc');
+ expect(vc.root().element().getAttribute('class')).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('and');
+
+ var docGroup = vc.root();
+
+ var first = docGroup.getOperand(0);
+ expect(first.key()).toEqual('author');
+ expect(first.value()).toEqual('Max Birkendale');
+ expect(first.matchop()).toEqual('eq');
+
+ var second = docGroup.getOperand(1);
+ expect(second.key()).toEqual('pubDate');
+ expect(second.value()).toEqual('2014-12-05');
+ expect(second.matchop()).toEqual('eq');
+ });
+
+
+ it('should be based on a nested docGroup', function () {
+ var vc = nestedGroupFactory.create();
+
+ expect(vc.element().getAttribute('class')).toEqual('vc');
+ expect(vc.element().firstChild.getAttribute('class')).toEqual('docGroup');
+ expect(vc.element().firstChild.children[0].getAttribute('class')).toEqual('doc');
+ var dg = vc.element().firstChild.children[1];
+ expect(dg.getAttribute('class')).toEqual('docGroup');
+ expect(dg.children[0].getAttribute('class')).toEqual('doc');
+ expect(dg.children[1].getAttribute('class')).toEqual('doc');
+ expect(dg.children[2].getAttribute('class')).toEqual('operators');
+ expect(vc.element().firstChild.children[2].getAttribute('class')).toEqual('operators');
+ });
+
+ it('should be modifiable by deletion in flat docGroups', function () {
+ var vc = flatGroupFactory.create();
+ var docGroup = vc.root();
+
+ expect(docGroup.element().getAttribute('class')).toEqual('docGroup');
+
+ var doc = docGroup.getOperand(1);
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.value()).toEqual("2014-12-05");
+
+ // Remove operand 1
+ expect(docGroup.delOperand(doc).update()).not.toBeUndefined();
+ expect(doc._element).toEqual(undefined);
+
+ doc = docGroup.getOperand(1);
+ expect(doc.key()).toEqual("foo");
+
+ // Remove operand 1
+ expect(docGroup.delOperand(doc).update()).not.toBeUndefined();
+ expect(doc._element).toEqual(undefined);
+
+ // Only one operand left ...
+ expect(docGroup.getOperand(1)).toBeUndefined();
+ // ... but there shouldn't be a group anymore at all!
+ expect(docGroup.getOperand(0)).toBeUndefined();
+
+ var obj = vc.root();
+ expect(obj.ldType()).toEqual("doc");
+ expect(obj.key()).toEqual("pubDate");
+ expect(obj.value()).toEqual("2014-05-12");
+
+ expect(obj.element().getAttribute('class')).toEqual('doc');
+ });
+
+
+ it('should be modifiable by deletion in nested docGroups (root case)', function () {
+ var vc = nestedGroupFactory.create();
+
+ expect(vc.toQuery()).toEqual(
+ 'author = "Max Birkendale" | (pubDate since 2014-05-12 & pubDate until 2014-12-05)'
+ );
+
+ var docGroup = vc.root();
+ expect(docGroup.ldType()).toEqual("docGroup");
+ expect(docGroup.operation()).toEqual("or");
+
+ var doc = docGroup.getOperand(0);
+ expect(doc.key()).toEqual("author");
+ expect(doc.value()).toEqual("Max Birkendale");
+
+ docGroup = docGroup.getOperand(1);
+ expect(docGroup.operation()).toEqual("and");
+
+ doc = docGroup.getOperand(0);
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.matchop()).toEqual("geq");
+ expect(doc.value()).toEqual("2014-05-12");
+ expect(doc.type()).toEqual("date");
+
+ doc = docGroup.getOperand(1);
+ expect(doc.key()).toEqual("pubDate");
+ expect(doc.matchop()).toEqual("leq");
+ expect(doc.value()).toEqual("2014-12-05");
+ expect(doc.type()).toEqual("date");
+
+ // Remove first operand so everything becomes root
+ expect(
+ vc.root().delOperand(
+ vc.root().getOperand(0)
+ ).update().ldType()
+ ).toEqual("docGroup");
+
+ expect(vc.root().ldType()).toEqual("docGroup");
+ expect(vc.root().operation()).toEqual("and");
+ expect(vc.root().getOperand(0).ldType()).toEqual("doc");
+
+ expect(vc.toQuery()).toEqual(
+ 'pubDate since 2014-05-12 & pubDate until 2014-12-05'
+ );
+ });
+
+ it('should be modifiable by deletion in nested docGroups (resolve group case)', function () {
+ var vc = nestedGroupFactory.create();
+
+ // Get nested group
+ var firstGroup = vc.root().getOperand(1);
+ firstGroup.append(simpleGroupFactory.create({ "operation" : "operation:or" }));
+
+ // Structur is now:
+ // or(doc, and(doc, doc, or(doc, doc)))
+
+ // Get nested or in and
+ var orGroup = vc.root().getOperand(1).getOperand(2);
+ expect(orGroup.ldType()).toEqual("docGroup");
+ expect(orGroup.operation()).toEqual("or");
+
+ // Remove
+ // Structur is now:
+ // or(doc, and(doc, doc, doc)))
+ expect(orGroup.delOperand(orGroup.getOperand(0)).update().operation()).toEqual("and");
+ expect(vc.root().getOperand(1).operands().length).toEqual(3);
+ });
+
+ it('should be modifiable by deletion in nested docGroups (identical group case)', function () {
+ var vc = nestedGroupFactory.create();
+
+ // Get nested group
+ var firstGroup = vc.root().getOperand(1);
+ firstGroup.append(simpleGroupFactory.create({
+ "operation" : "operation:or"
+ }));
+ var oldAuthor = firstGroup.getOperand(2).getOperand(0);
+ oldAuthor.key("title");
+ oldAuthor.value("Der Birnbaum");
+
+ // Structur is now:
+ // or(doc, and(doc, doc, or(doc, doc)))
+ expect(vc.toQuery()).toEqual(
+ 'author = "Max Birkendale" | (pubDate since 2014-05-12 & pubDate until 2014-12-05 & (title = "Der Birnbaum" | pubDate in 2014-12-05))'
+ );
+
+ var andGroup = vc.root().getOperand(1);
+
+ // Get leading docs in and
+ var doc1 = andGroup.getOperand(0);
+ expect(doc1.ldType()).toEqual("doc");
+ expect(doc1.value()).toEqual("2014-05-12");
+ var doc2 = andGroup.getOperand(1);
+ expect(doc2.ldType()).toEqual("doc");
+ expect(doc2.value()).toEqual("2014-12-05");
+
+ // Remove 2
+ expect(andGroup.delOperand(doc2).update().operation()).toEqual("and");
+ // Structur is now:
+ // or(doc, and(doc, or(doc, doc)))
+
+ expect(vc.toQuery()).toEqual(
+ 'author = "Max Birkendale"' +
+ ' | (pubDate since 2014-05-12 & ' +
+ '(title = "Der Birnbaum" | pubDate in 2014-12-05))'
+ );
+
+
+ // Remove 1
+ expect(andGroup.delOperand(doc1).update().operation()).toEqual("or");
+ // Structur is now:
+ // or(doc, doc, doc)
+
+ expect(vc.toQuery()).toEqual(
+ 'author = "Max Birkendale" | title = "Der Birnbaum" | pubDate in 2014-12-05'
+ );
+ });
+
+ it('should be reducible to unspecification', function () {
+ var vc = demoFactory.create();
+
+ expect(vc.toQuery()).toEqual(vc.root().toQuery());
+
+ expect(vc.toQuery()).toEqual(
+ '(Titel = "Baum" & Veröffentlichungsort = "hihi" & ' +
+ '(Titel = "Baum" | Veröffentlichungsort = "hihi")) ' +
+ '| Untertitel = "huhu"');
+
+ expect(vc.root().element().lastChild.children[0].firstChild.nodeValue).toEqual('and');
+ expect(vc.root().element().lastChild.children[1].firstChild.nodeValue).toEqual('×');
+ expect(vc.root().delOperand(vc.root().getOperand(0)).update()).not.toBeUndefined();
+ expect(vc.toQuery()).toEqual('Untertitel = "huhu"');
+
+ var lc = vc.root().element().lastChild;
+ expect(lc.children[0].firstChild.nodeValue).toEqual('and');
+ expect(lc.children[1].firstChild.nodeValue).toEqual('or');
+ expect(lc.children[2].firstChild.nodeValue).toEqual('×');
+
+ // Clean everything
+ vc.clean();
+ expect(vc.toQuery()).toEqual('');
+ });
+
+ it('should flatten on import', function () {
+ var vc = KorAP.VirtualCollection.create().render({
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "@type":"koral:doc",
+ "key":"Untertitel",
+ "value":"huhu",
+ "match":"match:eq"
+ }
+ ]
+ });
+
+ expect(vc.toQuery()).toEqual(
+ 'Titel = "Baum" | Veröffentlichungsort = "hihi" | Untertitel = "huhu"'
+ );
+
+
+ });
+});
+
+describe('KorAP.Operators', function () {
+ it('should be initializable', function () {
+ var op = KorAP.Operators.create(true, false, false);
+ expect(op.and()).toBeTruthy();
+ expect(op.or()).not.toBeTruthy();
+ expect(op.del()).not.toBeTruthy();
+
+ op.and(false);
+ expect(op.and()).not.toBeTruthy();
+ expect(op.or()).not.toBeTruthy();
+ expect(op.del()).not.toBeTruthy();
+
+ op.or(true);
+ op.del(true);
+ expect(op.and()).not.toBeTruthy();
+ expect(op.or()).toBeTruthy();
+ expect(op.del()).toBeTruthy();
+
+ var e = op.element();
+ expect(e.getAttribute('class')).toEqual('operators');
+ expect(e.children[0].getAttribute('class')).toEqual('or');
+ expect(e.children[0].firstChild.data).toEqual('or');
+ expect(e.children[1].getAttribute('class')).toEqual('delete');
+ expect(e.children[1].firstChild.data).toEqual('×');
+
+ op.and(true);
+ op.del(false);
+ op.update();
+
+ e = op.element();
+ expect(e.getAttribute('class')).toEqual('operators');
+ expect(e.children[0].getAttribute('class')).toEqual('and');
+ expect(e.children[0].firstChild.data).toEqual('and');
+ expect(e.children[1].getAttribute('class')).toEqual('or');
+ expect(e.children[1].firstChild.data).toEqual('or');
+ });
+});
+
+describe('KorAP._delete (event)', function () {
+ var complexVCFactory = buildFactory(KorAP.VirtualCollection,{
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'value' : 'Hello World!'
+ },
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ ]
+ });
+
+ it('should clean on root docs', function () {
+ var vc = KorAP.VirtualCollection.render({
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ });
+ expect(vc.root().toQuery()).toEqual('pubDate in 2014-12-05');
+ expect(vc.root().element().lastChild.getAttribute('class')).toEqual('operators');
+
+ // Clean with delete from root
+ expect(vc.root().element().lastChild.lastChild.getAttribute('class')).toEqual('delete');
+ _delOn(vc.root());
+ expect(vc.root().toQuery()).toEqual('');
+ expect(vc.root().element().lastChild.lastChild.data).toEqual('⋯');
+ });
+
+ it ('should remove on nested docs', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ );
+
+ // Delete with direct element access
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+ _delOn(vc.root().getOperand(0));
+
+ expect(vc.toQuery()).toEqual('foo = "bar"');
+ expect(vc.root().ldType()).toEqual('doc');
+ });
+
+ it ('should clean on doc groups', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ );
+
+ // Cleanwith direct element access
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+ _delOn(vc.root());
+ expect(vc.toQuery()).toEqual('');
+ expect(vc.root().ldType()).toEqual('non');
+ });
+
+ it ('should remove on nested doc groups (case of ungrouping 1)', function () {
+ var vc = complexVCFactory.create();
+
+ // Delete with direct element access
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 & (title = "Hello World!" | foo = "bar")'
+ );
+
+ // Remove hello world:
+ _delOn(vc.root().getOperand(1).getOperand(0));
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+ expect(vc.root().ldType()).toEqual('docGroup');
+ });
+
+ it ('should remove on nested doc groups (case of ungrouping 2)', function () {
+ var vc = complexVCFactory.create();
+
+ // Delete with direct element access
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 & (title = "Hello World!" | foo = "bar")'
+ );
+
+ // Remove bar
+ _delOn(vc.root().getOperand(1).getOperand(1));
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & title = "Hello World!"');
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('and');
+ });
+
+ it ('should remove on nested doc groups (case of root changing)', function () {
+ var vc = complexVCFactory.create();
+
+ // Delete with direct element access
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 & (title = "Hello World!" | foo = "bar")'
+ );
+
+ // Remove bar
+ _delOn(vc.root().getOperand(0));
+ expect(vc.toQuery()).toEqual('title = "Hello World!" | foo = "bar"');
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('or');
+ });
+
+ it ('should remove on nested doc groups (list flattening)', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ },
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'value' : 'Hello World!'
+ },
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'yeah',
+ 'value' : 'juhu'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ );
+
+ // Delete with direct element access
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 | foo = "bar" | ' +
+ '(pubDate in 2014-12-05 & ' +
+ '(title = "Hello World!" | yeah = "juhu"))'
+ );
+
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('or');
+
+ // Operands and operators
+ expect(vc.element().firstChild.children.length).toEqual(4);
+ expect(vc.element().firstChild.lastChild.getAttribute('class')).toEqual('operators');
+
+ // Remove inner group and flatten
+ _delOn(vc.root().getOperand(2).getOperand(0));
+
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 | foo = "bar" | title = "Hello World!" | yeah = "juhu"'
+ );
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('or');
+
+ // Operands and operators
+ expect(vc.element().firstChild.children.length).toEqual(5);
+ expect(vc.element().firstChild.lastChild.getAttribute('class')).toEqual('operators');
+ });
+});
+
+describe('KorAP._add (event)', function () {
+ var complexVCFactory = buildFactory(KorAP.VirtualCollection,{
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'title',
+ 'value' : 'Hello World!'
+ },
+ {
+ '@type' : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ ]
+ });
+
+ it ('should add new unspecified doc with "and"', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ );
+
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+
+ var fc = vc.element().firstChild;
+ expect(fc.getAttribute('data-operation')).toEqual('and');
+ expect(fc.children.length).toEqual(3);
+ expect(fc.lastChild.getAttribute('class')).toEqual('operators');
+ expect(fc.children[0].getAttribute('class')).toEqual('doc');
+ expect(fc.children[1].getAttribute('class')).toEqual('doc');
+
+ // add with 'and' in the middle
+ _andOn(vc.root().getOperand(0));
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+
+ fc = vc.element().firstChild;
+ expect(fc.getAttribute('data-operation')).toEqual('and');
+ expect(fc.children.length).toEqual(4);
+ expect(fc.lastChild.getAttribute('class')).toEqual('operators');
+
+ expect(fc.children[0].getAttribute('class')).toEqual('doc');
+ expect(fc.children[1].getAttribute('class')).toEqual('doc unspecified');
+ expect(fc.children[2].getAttribute('class')).toEqual('doc');
+ });
+
+ it ('should add new unspecified doc with "or"', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ );
+
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+
+ var fc = vc.element().firstChild;
+ expect(fc.children.length).toEqual(3);
+ expect(fc.lastChild.getAttribute('class')).toEqual('operators');
+ expect(fc.children[0].getAttribute('class')).toEqual('doc');
+ expect(fc.children[1].getAttribute('class')).toEqual('doc');
+
+ // add with 'or' in the middle
+ _orOn(vc.root().getOperand(0));
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+ fc = vc.element().firstChild;
+
+ expect(fc.getAttribute('data-operation')).toEqual('and');
+ expect(fc.children.length).toEqual(3);
+ expect(fc.children[0].getAttribute('class')).toEqual('docGroup');
+ expect(fc.children[0].getAttribute('data-operation')).toEqual('or');
+ expect(fc.children[1].getAttribute('class')).toEqual('doc');
+ expect(fc.children[2].getAttribute('class')).toEqual('operators');
+ expect(fc.lastChild.getAttribute('class')).toEqual('operators');
+
+ fc = vc.element().firstChild.firstChild;
+ expect(fc.children.length).toEqual(3);
+ expect(fc.children[0].getAttribute('class')).toEqual('doc');
+ expect(fc.children[1].getAttribute('class')).toEqual('doc unspecified');
+ expect(fc.children[2].getAttribute('class')).toEqual('operators');
+ expect(fc.lastChild.getAttribute('class')).toEqual('operators');
+ });
+
+ it ('should add new unspecified doc with "and" before group', function () {
+ var vc = demoFactory.create();
+
+ // Wrap with direct element access
+ expect(vc.toQuery()).toEqual(
+ '(Titel = "Baum" & Veröffentlichungsort = "hihi" & (Titel = "Baum" | Veröffentlichungsort = "hihi")) | Untertitel = "huhu"'
+ );
+
+ expect(vc.root().getOperand(0).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(0).operation()).toEqual('and');
+ expect(vc.root().getOperand(0).operands().length).toEqual(3);
+
+ // Add unspecified on the second doc
+ var secDoc = vc.root().getOperand(0).getOperand(1);
+ expect(secDoc.value()).toEqual('hihi');
+
+ // Add
+ _andOn(secDoc);
+
+ var fo = vc.root().getOperand(0);
+
+ expect(fo.ldType()).toEqual('docGroup');
+ expect(fo.operation()).toEqual('and');
+ expect(fo.operands().length).toEqual(4);
+
+ expect(fo.getOperand(0).ldType()).toEqual('doc');
+ expect(fo.getOperand(1).ldType()).toEqual('doc');
+ expect(fo.getOperand(2).ldType()).toEqual('non');
+ expect(fo.getOperand(3).ldType()).toEqual('docGroup');
+ });
+
+
+ it ('should remove a doc with an unspecified doc in a nested group', function () {
+ var vc = demoFactory.create();
+
+ // Wrap with direct element access
+ expect(vc.toQuery()).toEqual(
+ '(Titel = "Baum" & Veröffentlichungsort = "hihi" & (Titel = "Baum" | Veröffentlichungsort = "hihi")) | Untertitel = "huhu"'
+ );
+
+ var fo = vc.root().getOperand(0).getOperand(0);
+ expect(fo.key()).toEqual('Titel');
+ expect(fo.value()).toEqual('Baum');
+
+ // Add unspecified on the root group
+ _orOn(fo);
+
+ fo = vc.root().getOperand(0).getOperand(0);
+
+ expect(fo.operation()).toEqual('or');
+ expect(fo.getOperand(0).ldType()).toEqual('doc');
+ expect(fo.getOperand(1).ldType()).toEqual('non');
+
+ // Delete document
+ _delOn(fo.getOperand(0));
+
+ // The operand is now non
+ expect(vc.root().getOperand(0).getOperand(0).ldType()).toEqual('non');
+ expect(vc.root().getOperand(0).getOperand(1).ldType()).toEqual('doc');
+ expect(vc.root().getOperand(0).getOperand(2).ldType()).toEqual('docGroup');
+ });
+
+ it ('should remove an unspecified doc with an doc in a nested group', function () {
+ var vc = demoFactory.create();
+
+ // Wrap with direct element access
+ expect(vc.toQuery()).toEqual(
+ '(Titel = "Baum" & Veröffentlichungsort = "hihi" & (Titel = "Baum" | Veröffentlichungsort = "hihi")) | Untertitel = "huhu"'
+ );
+
+ var fo = vc.root().getOperand(0).getOperand(0);
+ expect(fo.key()).toEqual('Titel');
+ expect(fo.value()).toEqual('Baum');
+
+ // Add unspecified on the root group
+ _orOn(fo);
+
+ fo = vc.root().getOperand(0).getOperand(0);
+
+ expect(fo.operation()).toEqual('or');
+ expect(fo.getOperand(0).ldType()).toEqual('doc');
+ expect(fo.getOperand(1).ldType()).toEqual('non');
+
+ // Delete unspecified doc
+ _delOn(fo.getOperand(1));
+
+ // The operand is now non
+ fo = vc.root().getOperand(0);
+ expect(fo.getOperand(0).ldType()).toEqual('doc');
+ expect(fo.getOperand(0).key()).toEqual('Titel');
+ expect(fo.getOperand(0).value()).toEqual('Baum');
+ expect(fo.getOperand(1).ldType()).toEqual('doc');
+ expect(fo.getOperand(2).ldType()).toEqual('docGroup');
+ });
+
+
+ it ('should add on parent group (case "and")', function () {
+ var vc = complexVCFactory.create();
+
+ // Wrap with direct element access
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 & (title = "Hello World!" | foo = "bar")'
+ );
+
+ expect(vc.root().operands().length).toEqual(2);
+
+ // Add unspecified on the root group
+ _andOn(vc.root().getOperand(1));
+ expect(vc.toQuery()).toEqual(
+ 'pubDate in 2014-12-05 & (title = "Hello World!" | foo = "bar")'
+ );
+
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operands().length).toEqual(3);
+ expect(vc.root().getOperand(0).ldType()).toEqual('doc');
+ expect(vc.root().getOperand(1).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(1).operation()).toEqual('or');
+ expect(vc.root().getOperand(2).ldType()).toEqual('non');
+
+ // Add another unspecified on the root group
+ _andOn(vc.root().getOperand(1));
+
+ expect(vc.root().operands().length).toEqual(4);
+ expect(vc.root().getOperand(0).ldType()).toEqual('doc');
+ expect(vc.root().getOperand(1).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(2).ldType()).toEqual('non');
+ expect(vc.root().getOperand(3).ldType()).toEqual('non');
+
+ // Add another unspecified after the first doc
+ _andOn(vc.root().getOperand(0));
+
+ expect(vc.root().operands().length).toEqual(5);
+ expect(vc.root().getOperand(0).ldType()).toEqual('doc');
+ expect(vc.root().getOperand(1).ldType()).toEqual('non');
+ expect(vc.root().getOperand(2).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(3).ldType()).toEqual('non');
+ expect(vc.root().getOperand(4).ldType()).toEqual('non');
+ });
+
+ it ('should wrap on root', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'foo',
+ 'value' : 'bar'
+ }
+ ]
+ }
+ );
+
+ // Wrap on root
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('and');
+ _orOn(vc.root());
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('or');
+
+ expect(vc.root().getOperand(0).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(0).operation()).toEqual('and');
+ });
+
+ it ('should add on root (case "and")', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ );
+
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05');
+ expect(vc.root().ldType()).toEqual('doc');
+ expect(vc.root().key()).toEqual('pubDate');
+ expect(vc.root().value()).toEqual('2014-12-05');
+
+ // Wrap on root
+ _andOn(vc.root());
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('and');
+ });
+
+ it ('should add on root (case "or")', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:doc',
+ "key": 'pubDate',
+ "match": 'match:eq',
+ "value": '2014-12-05',
+ "type": 'type:date'
+ }
+ );
+
+ expect(vc.toQuery()).toEqual('pubDate in 2014-12-05');
+ expect(vc.root().key()).toEqual('pubDate');
+ expect(vc.root().value()).toEqual('2014-12-05');
+
+ // Wrap on root
+ _orOn(vc.root());
+ expect(vc.root().ldType()).toEqual('docGroup');
+ expect(vc.root().operation()).toEqual('or');
+ });
+
+ it ('should support multiple sub groups per group', function () {
+ var vc = KorAP.VirtualCollection.render(
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:or',
+ 'operands' : [
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'title',
+ "value": 't1',
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'title',
+ 'value' : 't2'
+ }
+ ]
+ },
+ {
+ "@type": 'koral:docGroup',
+ 'operation' : 'operation:and',
+ 'operands' : [
+ {
+ "@type": 'koral:doc',
+ "key": 'title',
+ "value": 't3',
+ },
+ {
+ "@type" : 'koral:doc',
+ 'key' : 'title',
+ 'value' : 't4'
+ }
+ ]
+ }
+ ]
+ }
+ );
+ expect(vc.toQuery()).toEqual(
+ '(title = "t1" & title = "t2") | (title = "t3" & title = "t4")'
+ );
+ expect(vc.root().operation()).toEqual('or');
+ expect(vc.root().getOperand(0).toQuery()).toEqual('title = "t1" & title = "t2"');
+ expect(vc.root().getOperand(1).toQuery()).toEqual('title = "t3" & title = "t4"');
+
+ _andOn(vc.root());
+
+ expect(vc.root().operation()).toEqual('and');
+ expect(vc.root().getOperand(0).ldType()).toEqual('docGroup');
+ expect(vc.root().getOperand(1).ldType()).toEqual('non');
+ });
+});
+
+describe('KorAP.Rewrite', function () {
+
+ it('should be initializable', function () {
+ var rewrite = KorAP.Rewrite.create({
+ "@type" : "koral:rewrite",
+ "operation" : "operation:modification",
+ "src" : "querySerializer",
+ "scope" : "tree"
+ });
+ expect(rewrite.toString()).toEqual('Modification of "tree" by "querySerializer"');
+ });
+
+ it('should be deserialized by docs', function () {
+ var doc = KorAP.Doc.create(undefined,
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ });
+
+ expect(doc.element().classList.contains('doc')).toBeTruthy();
+ expect(doc.element().classList.contains('rewritten')).toBe(false);
+
+ doc = KorAP.Doc.create(undefined,
+ {
+ "@type":"koral:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq",
+ "rewrites" : [
+ {
+ "@type" : "koral:rewrite",
+ "operation" : "operation:modification",
+ "src" : "querySerializer",
+ "scope" : "tree"
+ }
+ ]
+ });
+
+ expect(doc.element().classList.contains('doc')).toBeTruthy();
+ expect(doc.element().classList.contains('rewritten')).toBeTruthy();
+ });
+/*
+ it('should be deserialized by docGroups', function () {
+ });
+*/
+});
+/*
+describe('KorAP.DocKey', function () {
+ it('should be initializable', function () {
+ var docKey = KorAP.DocKey.create();
+ expect(docKey.toString()).toEqual('...');
+ });
+});
+*/
diff --git a/dev/js/src/api.js b/dev/js/src/api.js
new file mode 100644
index 0000000..ed33cc2
--- /dev/null
+++ b/dev/js/src/api.js
@@ -0,0 +1,80 @@
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+ "use strict";
+
+ // Default log message
+ KorAP.log = KorAP.log || function (type, msg) {
+ console.log(type + ": " + msg);
+ };
+
+ KorAP.URL = KorAP.URL || 'http://korap.ids-mannheim.de/kalamar';
+
+ // TODO: https://github.com/honza/140medley/blob/master/140medley.js
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
+ // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
+ // r.addEventListener("progress", updateProgress, false);
+ // http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml
+ // http://stackoverflow.com/questions/6112744/load-javascript-on-demand
+
+ KorAP.API = {
+ getMatchInfo : function (match, param, cb) {
+ // match is a KorAP.Match object
+
+ var url = KorAP.URL;
+ url += '/corpus';
+ url += '/' + match.corpusID;
+ url += '/' + match.docID + '.' + match.textID; // TODO
+ url += '/' + match.matchID;
+
+ // { spans: true, layer:x, foundry : y}
+ if (param['spans'] == true) {
+ url += '?spans=true';
+ if (param['foundry'] !== undefined)
+ url += '&foundry=' + param['foundry'];
+ if (param['layer'] !== undefined)
+ url += '&layer=' + param['layer'];
+ }
+
+ // { spans : false, layer: [Array of KorAP.InfoLayer] }
+ else {
+ // TODO
+ url += '?spans=false';
+ }
+
+ this.getJSON(url, cb);
+ },
+ getJSON : function (url, onload) {
+ var req = new XMLHttpRequest();
+
+ console.log('Request url: ' + url);
+
+ req.open("GET", url, true);
+
+
+ req.setRequestHeader("Accept", "application/json");
+ req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ req.onreadystatechange = function () {
+ /*
+ States:
+ 0 - unsent (prior to open)
+ 1 - opened (prior to send)
+ 2 - headers received
+ 3 - loading (responseText has partial data)
+ 4 - done
+ */
+ if (this.readyState == 4) {
+ if (this.status === 200)
+ onload(JSON.parse(this.responseText));
+ else
+ KorAP.log(this.status, this.statusText);
+ }
+ };
+ req.ontimeout = function () {
+ KorAP.log(0, 'Request Timeout');
+ };
+ req.send();
+ }
+ };
+
+}(this.KorAP));
diff --git a/dev/js/src/hint.js b/dev/js/src/hint.js
new file mode 100644
index 0000000..80631ec
--- /dev/null
+++ b/dev/js/src/hint.js
@@ -0,0 +1,451 @@
+/**
+ * Hint menu for Kalamar.
+ *
+ * @author Nils Diewald
+ */
+
+// requires menu.js
+
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+ "use strict";
+
+ // Default log message
+ KorAP.log = KorAP.log || function (type, msg) {
+ console.log(type + ": " + msg);
+ };
+
+ /**
+ * @define {regex} Regular expression for context
+ */
+ KorAP.context =
+ "(?:^|[^-_a-zA-Z0-9])" + // Anchor
+ "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
+ "(?:" +
+ "(?:[-_a-zA-Z0-9]+?)=" + // Layer
+ "(?:(?:[^:=\/ ]+?):)?" + // Key
+ ")?" +
+ ")$";
+
+ // Initialize hint array
+ KorAP.hintArray = KorAP.hintArray || {};
+
+ // Input field for queries
+ KorAP.InputField = {
+ create : function (element) {
+ return Object.create(KorAP.InputField)._init(element);
+ },
+
+ _init : function (element) {
+ this._element = element;
+
+ // Create mirror for searchField
+ if ((this._mirror = document.getElementById("searchMirror")) === null) {
+ this._mirror = document.createElement("div");
+ this._mirror.setAttribute("id", "searchMirror");
+ this._mirror.appendChild(document.createElement("span"));
+ this._container = this._mirror.appendChild(document.createElement("div"));
+ this._mirror.style.height = "1px";
+ document.getElementsByTagName("body")[0].appendChild(this._mirror);
+ };
+
+ // Update position of the mirror
+ var that = this;
+ window.onresize = function () {
+ that.reposition();
+ };
+
+ that.reposition();
+
+ return this;
+ },
+
+ rightPos : function () {
+ var box = this._mirror.getBoundingClientRect();
+ return box.right - box.left;
+ },
+
+ mirror : function () {
+ return this._mirror;
+ },
+
+ container : function () {
+ return this._container;
+ },
+
+ element : function () {
+ return this._element;
+ },
+
+ value : function () {
+ return this._element.value;
+ },
+
+ update : function () {
+ this._mirror.firstChild.textContent = this.split()[0];
+ },
+
+ insert : function (text) {
+ var splittedText = this.split();
+ var s = this._element;
+ s.value = splittedText[0] + text + splittedText[1];
+ s.selectionStart = (splittedText[0] + text).length;
+ s.selectionEnd = s.selectionStart;
+ this._mirror.firstChild.textContent = splittedText[0] + text;
+ },
+
+ // Return two substrings, splitted at current cursor position
+ split : function () {
+ var s = this._element;
+ var value = s.value;
+ var start = s.selectionStart;
+ return new Array(
+ value.substring(0, start),
+ value.substring(start, value.length)
+ );
+ },
+
+ // Position the input mirror directly below the input box
+ reposition : function () {
+ var inputClientRect = this._element.getBoundingClientRect();
+ var inputStyle = window.getComputedStyle(this._element, null);
+
+ // Reset position
+ var mirrorStyle = this._mirror.style;
+ mirrorStyle.left = inputClientRect.left + "px";
+ mirrorStyle.top = inputClientRect.bottom + "px";
+
+ // These may be relevant in case of media depending css
+ mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
+ mirrorStyle.marginLeft = inputStyle.getPropertyValue("margin-left");
+ mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
+ mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
+ mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
+ mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
+ },
+ context : function () {
+ return this.split()[0];
+ }
+ };
+
+
+ /**
+ * Regex object for checking the context of the hint
+ */
+ KorAP.ContextAnalyzer = {
+ create : function (regex) {
+ return Object.create(KorAP.ContextAnalyzer)._init(regex);
+ },
+ _init : function (regex) {
+ try {
+ this._regex = new RegExp(regex);
+ }
+ catch (e) {
+ KorAP.log("error", e);
+ return;
+ };
+ return this;
+ },
+ test : function (text) {
+ if (!this._regex.exec(text))
+ return;
+ return RegExp.$1;
+ }
+ };
+
+
+ /**
+ * Hint menu item based on MenuItem
+ */
+ KorAP.HintMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.HintMenuItem)
+ ._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+ _init : function (params) {
+ if (params[0] === undefined ||
+ params[1] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._action = params[1];
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ if (params.length > 2) {
+ this._desc = params[2];
+ this._lcField += " " + this._desc.toLowerCase();
+ };
+
+ return this;
+ },
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
+
+ h.inputField().insert(this._action);
+ h.active = false;
+
+ h.show(true);
+ },
+ name : function () {
+ return this._name;
+ },
+ action : function () {
+ return this._action;
+ },
+ desc : function () {
+ return this._desc;
+ },
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+
+ if (this.onclick !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
+
+ // Create title
+ var name = document.createElement("span");
+ name.appendChild(document.createTextNode(this._name));
+
+ li.appendChild(name);
+
+ // Create description
+ if (this._desc !== undefined) {
+ var desc = document.createElement("span");
+ desc.classList.add('desc');
+ desc.appendChild(document.createTextNode(this._desc));
+ li.appendChild(desc);
+ };
+ return this._element = li;
+ }
+ };
+
+ KorAP.HintMenuPrefix = {
+ create : function (params) {
+ return Object.create(KorAP.MenuPrefix).upgradeTo(KorAP.HintMenuPrefix)._init(params);
+ },
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
+
+ h.inputField().insert(this.value());
+ h.active = false;
+ }
+ };
+
+ KorAP.HintMenu = {
+ create : function (hint, context, params) {
+ var obj = Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.HintMenu)
+ ._init(KorAP.HintMenuItem, KorAP.HintMenuPrefix, params);
+ obj._context = context;
+ obj._element.classList.add('hint');
+ obj._hint = hint;
+
+ // This is only domspecific
+ obj.element().addEventListener('blur', function (e) {
+ this.menu.hide();
+ });
+
+ // Focus on input field on hide
+ obj.onHide = function () {
+ var input = this._hint.inputField();
+ input.element().focus();
+ };
+
+ return obj;
+ },
+ // Todo: Is this necessary?
+ context : function () {
+ return this._context;
+ },
+ hint : function () {
+ return this._hint;
+ }
+ };
+
+
+ /**
+ * KorAP.Hint.create({
+ * inputField : node,
+ * context : context regex
+ * });
+ */
+ KorAP.Hint = {
+
+ // Some variables
+ // _firstTry : true,
+ active : false,
+
+ create : function (param) {
+ return Object.create(KorAP.Hint)._init(param);
+ },
+
+ _init : function (param) {
+ param = param || {};
+
+ // Holds all menus per prefix context
+ this._menu = {};
+
+ // Get input field
+ this._inputField = KorAP.InputField.create(
+ param["inputField"] || document.getElementById("q-field")
+ );
+
+ var inputFieldElement = this._inputField.element();
+
+ var that = this;
+
+
+ // Add event listener for key pressed down
+ inputFieldElement.addEventListener(
+ "keypress", function (e) {
+ var code = _codeFromEvent(e);
+ if (code === 40) {
+ that.show(false);
+ e.halt();
+ };
+ }, false
+ );
+
+ // Move infobox
+ inputFieldElement.addEventListener(
+ "keyup", function (e) {
+ var input = that._inputField;
+ input.update();
+ input.container().style.left = input.rightPos() + 'px';
+ }
+ );
+
+ // Set Analyzer for context
+ this._analyzer = KorAP.ContextAnalyzer.create(
+ param["context"] || KorAP.context
+ );
+ return this;
+ },
+
+ inputField : function () {
+ return this._inputField;
+ },
+
+ /**
+ * A new update by keypress
+ */
+ /*
+updateKeyPress : function (e) {
+ if (!this._active)
+ return;
+
+ var character = String.fromCharCode(_codeFromEvent(e));
+
+ e.halt(); // No event propagation
+
+ // Only relevant for key down
+ console.log("TODO: filter view");
+ },
+ */
+
+ // updateKeyDown : function (e) {},
+
+ /**
+ * Return hint menu and probably init based on an action
+ */
+ menu : function (action) {
+
+ if (this._menu[action] === undefined) {
+
+ // No matching hint menu
+ if (KorAP.hintArray[action] === undefined)
+ return;
+
+ // Create matching hint menu
+ this._menu[action] = KorAP.HintMenu.create(
+ this, action, KorAP.hintArray[action]
+ );
+
+ };
+
+ // Return matching hint menu
+ return this._menu[action];
+ },
+
+ /**
+ * Get the correct menu based on the context
+ */
+ contextMenu : function (ifContext) {
+ var context = this._inputField.context();
+ if (context === undefined || context.length == 0)
+ return ifContext ? undefined : this.menu("-");
+
+ context = this._analyzer.test(context);
+ if (context === undefined || context.length == 0)
+ return ifContext ? undefined : this.menu("-");
+
+ return this.menu(context);
+ },
+
+
+ /**
+ * Show the menu
+ */
+ show : function (ifContext) {
+
+ // Menu is already active
+ if (this.active)
+ return;
+
+ // Initialize the menus position
+ /*
+ if (this._firstTry) {
+ this._inputField.reposition();
+ this._firstTry = false;
+ };
+ */
+
+ // update
+
+ // Get the menu
+ var menu;
+ if (menu = this.contextMenu(ifContext)) {
+ this._inputField.container().appendChild(menu.element());
+ menu.show('');
+ menu.focus();
+// Update bounding box
+/*
+ }
+ else if (!ifContext) {
+ // this.hide();
+ };
+*/
+ // Focus on input field
+ // this.inputField.element.focus();
+ };
+ }
+ };
+
+
+ /**
+ * Return keycode based on event
+ */
+ function _codeFromEvent (e) {
+ if ((e.charCode) && (e.keyCode==0))
+ return e.charCode
+ return e.keyCode;
+ };
+
+}(this.KorAP));
diff --git a/dev/js/src/match.js b/dev/js/src/match.js
new file mode 100644
index 0000000..4e3786b
--- /dev/null
+++ b/dev/js/src/match.js
@@ -0,0 +1,1062 @@
+/**
+ * Get information on matches,
+ * generate annotation tables and trees.
+ *
+ * @author Nils Diewald
+ */
+// require menu.js, dagre
+/*
+ * - Highlight (at least mark as bold) the match
+ * - Scroll to match vertically per default
+ */
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+ "use strict";
+
+ var svgXmlns = "http://www.w3.org/2000/svg";
+
+ // Default log message
+ KorAP.log = KorAP.log || function (type, msg) {
+ console.log(type + ": " + msg);
+ };
+
+ // Localization values
+ var loc = (KorAP.Locale = KorAP.Locale || {} );
+ loc.ADDTREE = loc.ADDTREE || 'Add tree view';
+ loc.SHOWINFO = loc.SHOWINFO || 'Show information';
+ loc.CLOSE = loc.CLOSE || 'Close';
+
+ KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+ KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+ KorAP._matchTerms = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
+
+ // API requests
+ KorAP.API = KorAP.API || {};
+
+ // TODO: Make this async
+ KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
+ KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
+ return {};
+ };
+
+ /**
+ * Match object
+ */
+ KorAP.Match = {
+
+ /**
+ * Create a new annotation object.
+ * Expects an array of available foundry/layer=type terms.
+ * Supported types are 'spans', 'tokens' and 'rels'.
+ */
+ create : function (match) {
+ return Object.create(KorAP.Match)._init(match);
+ },
+
+ /**
+ * Initialize match.
+ */
+ _init : function (match) {
+ this._element = null;
+
+ // No match defined
+ if (arguments.length < 1 ||
+ match === null ||
+ match === undefined) {
+ throw new Error('Missing parameters');
+ }
+
+ // Match defined as a node
+ else if (match instanceof Node) {
+ this._element = match;
+
+ // Circular reference !!
+ match["_match"] = this;
+
+ this.corpusID = match.getAttribute('data-corpus-id'),
+ this.docID = match.getAttribute('data-doc-id'),
+ this.textID = match.getAttribute('data-text-id'),
+ this.matchID = match.getAttribute('data-match-id')
+
+ // List of available annotations
+ this.available = match.getAttribute('data-available-info').split(' ');
+ }
+
+ // Match as an object
+ else {
+
+ // Iterate over allowed match terms
+ for (var i in KorAP._matchTerms) {
+ var term = KorAP._matchTerms[i];
+ if (match[term] !== undefined) {
+ this[term] = match[term];
+ }
+ else {
+ this[term] = undefined;
+ }
+ };
+ };
+
+ this._available = {
+ tokens : [],
+ spans : [],
+ rels : []
+ };
+
+ // Iterate over info layers
+ for (var i = 0; i < this.available.length; i++) {
+ var term = this.available[i];
+
+ // Create info layer objects
+ try {
+ var layer = KorAP.InfoLayer.create(term);
+ this._available[layer.type].push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
+
+ return this;
+ },
+
+
+ /**
+ * Return a list of parseable tree annotations.
+ */
+ getSpans : function () {
+ return this._available.spans;
+ },
+
+
+ /**
+ * Return a list of parseable token annotations.
+ */
+ getTokens : function () {
+ return this._available.tokens;
+ },
+
+
+ /**
+ * Return a list of parseable relation annotations.
+ */
+ getRels : function () {
+ return this._available.rels;
+ },
+
+ /**
+ * Open match
+ */
+ open : function () {
+
+ // Add actions unless it's already activated
+ var element = this._element;
+
+ // There is an element to open
+ if (this._element === undefined || this._element === null)
+ return false;
+
+ // The element is already opened
+ if (element.classList.contains('active'))
+ return false;
+
+ // Add active class to element
+ element.classList.add('active');
+
+ // Create action buttons
+ var ul = document.createElement('ul');
+ ul.classList.add('action', 'right');
+ element.appendChild(ul);
+
+ // Use localization
+ var loc = KorAP.Locale;
+
+ // Add close button
+ var close = document.createElement('li');
+ close.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(loc.CLOSE));
+ close.classList.add('close');
+ close.setAttribute('title', loc.CLOSE);
+
+ // Add info button
+ var info = document.createElement('li');
+ info.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(loc.SHOWINFO));
+ info.classList.add('info');
+ info.setAttribute('title', loc.SHOWINFO);
+
+ var that = this;
+
+ // Close match
+ close.addEventListener('click', function (e) {
+ e.halt();
+ that.close()
+ });
+
+ // Add information, unless it already exists
+ info.addEventListener('click', function (e) {
+ e.halt();
+ that.info().toggle();
+ });
+
+ ul.appendChild(close);
+ ul.appendChild(info);
+
+ return true;
+ },
+
+ /**
+ * Close info view
+ */
+ close : function () {
+ this._element.classList.remove('active');
+
+/*
+ if (this._info !== undefined) {
+ this._info.destroy();
+ };
+*/
+ },
+
+
+ /**
+ * Get and open associated match info.
+ */
+ info : function () {
+
+ // Create match info
+ if (this._info === undefined)
+ this._info = KorAP.MatchInfo.create(this);
+
+ // There is an element to append
+ if (this._element === undefined ||
+ this._element === null)
+ return this._info;
+
+ // Info is already activated
+ if (this._info._element !== undefined)
+ return this._info;
+
+ return this._info;
+ },
+
+
+ /**
+ * Get match element.
+ */
+ element : function () {
+
+ // May be null
+ return this._element;
+ }
+ };
+
+
+
+ /**
+ * Information about a match.
+ */
+ KorAP.MatchInfo = {
+
+ /**
+ * Create new object
+ */
+ create : function (match) {
+ return Object.create(KorAP.MatchInfo)._init(match);
+ },
+
+ /**
+ * Initialize object
+ */
+ _init : function (match) {
+ this._match = match;
+ this.opened = false;
+ return this;
+ },
+
+ /**
+ * Get match object
+ */
+ match : function () {
+ return this._match;
+ },
+
+ toggle : function () {
+ if (this.opened == true) {
+ this._match.element().children[0].removeChild(
+ this.element()
+ );
+ this.opened = false;
+ }
+ else {
+ // Append element to match
+ this._match.element().children[0].appendChild(
+ this.element()
+ );
+ this.opened = true;
+ };
+
+ return this.opened;
+ },
+
+
+ /**
+ * Retrieve and parse snippet for table representation
+ */
+ getTable : function (tokens, cb) {
+ var focus = [];
+
+ // Get all tokens
+ if (tokens === undefined) {
+ focus = this._match.getTokens();
+ }
+
+ // Get only some tokens
+ else {
+
+ // Push newly to focus array
+ for (var i = 0; i < tokens.length; i++) {
+ var term = tokens[i];
+ try {
+ // Create info layer objects
+ var layer = KorAP.InfoLayer.create(term);
+ layer.type = "tokens";
+ focus.push(layer);
+ }
+ catch (e) {
+ continue;
+ };
+ };
+ };
+
+ // No tokens chosen
+ if (focus.length == 0)
+ cb(null);
+
+ // Get info (may be cached)
+ // TODO: Async
+ KorAP.API.getMatchInfo(
+ this._match,
+ { 'spans' : false, 'layer' : focus },
+
+ // Callback for retrieval
+ function (matchResponse) {
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
+ cb(this._table);
+ };
+ }.bind(this)
+ );
+
+/*
+ // Todo: Store the table as a hash of the focus
+ return null;
+*/
+ },
+
+
+ /**
+ * Retrieve and parse snippet for tree representation
+ */
+ getTree : function (foundry, layer, cb) {
+ var focus = [];
+
+ // TODO: Support and cache multiple trees
+
+ KorAP.API.getMatchInfo(
+ this._match, {
+ 'spans' : true,
+ 'foundry' : foundry,
+ 'layer' : layer
+ },
+ function (matchResponse) {
+ // Get snippet from match info
+ if (matchResponse["snippet"] !== undefined) {
+ // Todo: This should be cached somehow
+ cb(KorAP.MatchTree.create(matchResponse["snippet"]));
+ }
+ else {
+ cb(null);
+ };
+ }.bind(this)
+ );
+ },
+
+ /**
+ * Destroy this match information view.
+ */
+ destroy : function () {
+
+ // Remove circular reference
+ if (this._treeMenu !== undefined)
+ delete this._treeMenu["info"];
+
+ this._treeMenu.destroy();
+ this._treeMenu = undefined;
+ this._match = undefined;
+
+ // Element destroy
+ },
+
+
+ /**
+ * Add a new tree view to the list
+ */
+ addTree : function (foundry, layer, cb) {
+ var matchtree = document.createElement('div');
+ matchtree.classList.add('matchtree');
+
+ var h6 = matchtree.appendChild(document.createElement('h6'));
+ h6.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(foundry));
+ h6.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(layer));
+
+ var tree = matchtree.appendChild(
+ document.createElement('div')
+ );
+
+ this._element.insertBefore(matchtree, this._element.lastChild);
+
+ var close = tree.appendChild(document.createElement('em'));
+ close.addEventListener(
+ 'click', function (e) {
+ matchtree.parentNode.removeChild(matchtree);
+ e.halt();
+ }
+ );
+
+ // Get tree data async
+ this.getTree(foundry, layer, function (treeObj) {
+ // Something went wrong - probably log!!!
+ if (treeObj === null) {
+ tree.appendChild(document.createTextNode('No data available.'));
+ }
+ else {
+ tree.appendChild(treeObj.element());
+ // Reposition the view to the center
+ // (This may in a future release be a reposition
+ // to move the root into the center or the actual
+ // match)
+ treeObj.center();
+ }
+
+ if (cb !== undefined)
+ cb(treeObj);
+ });
+ },
+
+ /**
+ * Create match information view.
+ */
+ element : function () {
+
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create info table
+ var info = document.createElement('div');
+ info.classList.add('matchinfo');
+
+ // Append default table
+ var matchtable = document.createElement('div');
+ matchtable.classList.add('matchtable');
+ info.appendChild(matchtable);
+
+ // Create the table asynchronous
+ this.getTable(undefined, function (table) {
+ if (table !== null) {
+ matchtable.appendChild(table.element());
+ };
+ });
+
+ // Get spans
+ var spanLayers = this._match.getSpans().sort(
+ function (a, b) {
+ if (a.foundry < b.foundry) {
+ return -1;
+ }
+ else if (a.foundry > b.foundry) {
+ return 1;
+ }
+ else if (a.layer < b.layer) {
+ return -1;
+ }
+ else if (a.layer > b.layer) {
+ return 1;
+ };
+ return 0;
+ });
+
+ var menuList = [];
+
+ // Show tree views
+ for (var i = 0; i < spanLayers.length; i++) {
+ var span = spanLayers[i];
+
+ // Add foundry/layer to menu list
+ menuList.push([
+ span.foundry + '/' + span.layer,
+ span.foundry,
+ span.layer
+ ]);
+ };
+
+ // Create tree menu
+ var treemenu = this.treeMenu(menuList);
+ var span = info.appendChild(document.createElement('p'));
+ span.classList.add('addtree');
+ span.appendChild(document.createTextNode(loc.ADDTREE));
+
+ var treeElement = treemenu.element();
+ span.appendChild(treeElement);
+
+ span.addEventListener('click', function (e) {
+ treemenu.show('');
+ treemenu.focus();
+ });
+
+ this._element = info;
+
+ return info;
+
+ },
+
+
+ /**
+ * Get tree menu.
+ * There is only one menu rendered
+ * - no matter how many trees exist
+ */
+ treeMenu : function (list) {
+ if (this._treeMenu !== undefined)
+ return this._treeMenu;
+
+ return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
+ }
+ };
+
+
+
+ /**
+ *
+ * Alternatively pass a string as <tt>base/s=span</tt>
+ *
+ * @param foundry
+ */
+ KorAP.InfoLayer = {
+ create : function (foundry, layer, type) {
+ return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
+ },
+ _init : function (foundry, layer, type) {
+ if (foundry === undefined)
+ throw new Error("Missing parameters");
+
+ if (layer === undefined) {
+ if (KorAP._AvailableRE.exec(foundry)) {
+ this.foundry = RegExp.$1;
+ this.layer = RegExp.$2;
+ this.type = RegExp.$3;
+ }
+ else {
+ throw new Error("Missing parameters");
+ };
+ }
+ else {
+ this.foundry = foundry;
+ this.layer = layer;
+ this.type = type;
+ };
+
+ if (this.type === undefined)
+ this.type = 'tokens';
+
+ return this;
+ }
+ };
+
+
+ KorAP.MatchTable = {
+ create : function (snippet) {
+ return Object.create(KorAP.MatchTable)._init(snippet);
+ },
+ _init : function (snippet) {
+ // Create html for traversal
+ var html = document.createElement("div");
+ html.innerHTML = snippet;
+
+ this._pos = 0;
+ this._token = [];
+ this._info = [];
+ this._foundry = {};
+ this._layer = {};
+
+ // Parse the snippet
+ this._parse(html.childNodes);
+
+ html.innerHTML = '';
+ return this;
+ },
+
+ length : function () {
+ return this._pos;
+ },
+
+ getToken : function (pos) {
+ if (pos === undefined)
+ return this._token;
+ return this._token[pos];
+ },
+
+ getValue : function (pos, foundry, layer) {
+ return this._info[pos][foundry + '/' + layer]
+ },
+
+ getLayerPerFoundry : function (foundry) {
+ return this._foundry[foundry]
+ },
+
+ getFoundryPerLayer : function (layer) {
+ return this._layer[layer];
+ },
+
+ // Parse the snippet
+ _parse : function (children) {
+
+ // Get all children
+ for (var i in children) {
+ var c = children[i];
+
+ // Create object on position unless it exists
+ if (this._info[this._pos] === undefined)
+ this._info[this._pos] = {};
+
+ // Store at position in foundry/layer as array
+ var found = this._info[this._pos];
+
+ // Element with title
+ if (c.nodeType === 1) {
+ if (c.getAttribute("title") &&
+ KorAP._TermRE.exec(c.getAttribute("title"))) {
+
+ // Fill position with info
+ var foundry, layer, value;
+ if (RegExp.$2) {
+ foundry = RegExp.$1;
+ layer = RegExp.$2;
+ }
+ else {
+ foundry = "base";
+ layer = RegExp.$1
+ };
+
+ value = RegExp.$3;
+
+ if (found[foundry + "/" + layer] === undefined)
+ found[foundry + "/" + layer] = [];
+
+ // Push value to foundry/layer at correct position
+ found[foundry + "/" + layer].push(RegExp.$3);
+
+ // Set foundry
+ if (this._foundry[foundry] === undefined)
+ this._foundry[foundry] = {};
+ this._foundry[foundry][layer] = 1;
+
+ // Set layer
+ if (this._layer[layer] === undefined)
+ this._layer[layer] = {};
+ this._layer[layer][foundry] = 1;
+ };
+
+ // depth search
+ if (c.hasChildNodes())
+ this._parse(c.childNodes);
+ }
+
+ // Leaf node
+ // store string on position and go to next string
+ else if (c.nodeType === 3) {
+ if (c.nodeValue.match(/[a-z0-9]/i))
+ this._token[this._pos++] = c.nodeValue;
+ };
+ };
+
+ delete this._info[this._pos];
+ },
+
+
+ /**
+ * Get HTML table view of annotations.
+ */
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ // First the legend table
+ var d = document;
+ var table = d.createElement('table');
+
+ // Single row in head
+ var tr = table.appendChild(d.createElement('thead'))
+ .appendChild(d.createElement('tr'));
+
+ // Add cell to row
+ var addCell = function (type, name) {
+ var c = this.appendChild(d.createElement(type))
+ if (name === undefined)
+ return c;
+
+ if (name instanceof Array) {
+ for (var n = 0; n < name.length; n++) {
+ c.appendChild(d.createTextNode(name[n]));
+ if (n !== name.length - 1) {
+ c.appendChild(d.createElement('br'));
+ };
+ };
+ }
+ else {
+ c.appendChild(d.createTextNode(name));
+ };
+ };
+
+ tr.addCell = addCell;
+
+ // Add header information
+ tr.addCell('th', 'Foundry');
+ tr.addCell('th', 'Layer');
+
+ // Add tokens
+ for (var i in this._token) {
+ tr.addCell('th', this.getToken(i));
+ };
+
+ var tbody = table.appendChild(
+ d.createElement('tbody')
+ );
+
+ var foundryList = Object.keys(this._foundry).sort();
+
+ for (var f = 0; f < foundryList.length; f++) {
+ var foundry = foundryList[f];
+ var layerList =
+ Object.keys(this._foundry[foundry]).sort();
+
+ for (var l = 0; l < layerList.length; l++) {
+ var layer = layerList[l];
+ tr = tbody.appendChild(
+ d.createElement('tr')
+ );
+ tr.setAttribute('tabindex', 0);
+ tr.addCell = addCell;
+
+ tr.addCell('th', foundry);
+ tr.addCell('th', layer);
+
+ for (var v = 0; v < this.length(); v++) {
+ tr.addCell(
+ 'td',
+ this.getValue(v, foundry, layer)
+ );
+ };
+ };
+ };
+
+ return this._element = table;
+ }
+ };
+
+
+ /**
+ * Visualize span annotations as a tree using Dagre.
+ */
+ KorAP.MatchTree = {
+
+ create : function (snippet) {
+ return Object.create(KorAP.MatchTree)._init(snippet);
+ },
+
+ nodes : function () {
+ return this._next;
+ },
+
+ _addNode : function (id, obj) {
+ obj["width"] = 55;
+ obj["height"] = 20;
+ this._graph.setNode(id, obj)
+ },
+
+ _addEdge : function (src, target) {
+ this._graph.setEdge(src, target);
+ },
+
+ _init : function (snippet) {
+ this._next = new Number(0);
+
+ // Create html for traversal
+ var html = document.createElement("div");
+ html.innerHTML = snippet;
+ var g = new dagre.graphlib.Graph({
+ "directed" : true
+ });
+ g.setGraph({
+ "nodesep" : 35,
+ "ranksep" : 15,
+ "marginx" : 40,
+ "marginy" : 10
+ });
+ g.setDefaultEdgeLabel({});
+
+ this._graph = g;
+
+ // This is a new root
+ this._addNode(
+ this._next++,
+ { "class" : "root" }
+ );
+
+ // Parse nodes from root
+ this._parse(0, html.childNodes);
+
+ // Root node has only one child - remove
+ if (g.outEdges(0).length === 1)
+ g.removeNode(0);
+
+ html = undefined;
+ return this;
+ },
+
+ // Remove foundry and layer for labels
+ _clean : function (title) {
+ return title.replace(KorAP._TermRE, "$3");
+ },
+
+ // Parse the snippet
+ _parse : function (parent, children) {
+ for (var i in children) {
+ var c = children[i];
+
+ // Element node
+ if (c.nodeType == 1) {
+
+ // Get title from html
+ if (c.getAttribute("title")) {
+ var title = this._clean(c.getAttribute("title"));
+
+ // Add child node
+ var id = this._next++;
+
+ this._addNode(id, {
+ "class" : "middle",
+ "label" : title
+ });
+ this._addEdge(parent, id);
+
+ // Check for next level
+ if (c.hasChildNodes())
+ this._parse(id, c.childNodes);
+ }
+
+ // Step further
+ else if (c.hasChildNodes())
+ this._parse(parent, c.childNodes);
+ }
+
+ // Text node
+ else if (c.nodeType == 3)
+
+ if (c.nodeValue.match(/[-a-z0-9]/i)) {
+
+ // Add child node
+ var id = this._next++;
+ this._addNode(id, {
+ "class" : "leaf",
+ "label" : c.nodeValue
+ });
+
+ this._addEdge(parent, id);
+ };
+ };
+ return this;
+ },
+
+ /**
+ * Center the viewport of the canvas
+ */
+ center : function () {
+ if (this._element === undefined)
+ return;
+
+ var treeDiv = this._element.parentNode;
+
+ var cWidth = parseFloat(window.getComputedStyle(this._element).width);
+ var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
+ // Reposition:
+ if (cWidth > treeWidth) {
+ var scrollValue = (cWidth - treeWidth) / 2;
+ treeDiv.scrollLeft = scrollValue;
+ };
+ },
+
+ // Get element
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ var g = this._graph;
+
+ dagre.layout(g);
+
+ var canvas = document.createElementNS(svgXmlns, 'svg');
+ this._element = canvas;
+
+ canvas.setAttribute('height', g.graph().height);
+ canvas.setAttribute('width', g.graph().width);
+
+ // Create edges
+ g.edges().forEach(
+ function (e) {
+ var src = g.node(e.v);
+ var target = g.node(e.w);
+ var p = document.createElementNS(svgXmlns, 'path');
+ p.setAttributeNS(null, "d", _line(src, target));
+ p.classList.add('edge');
+ canvas.appendChild(p);
+ });
+
+ // Create nodes
+ g.nodes().forEach(
+ function (v) {
+ v = g.node(v);
+ var group = document.createElementNS(svgXmlns, 'g');
+ group.classList.add(v.class);
+
+ // Add node box
+ var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
+ rect.setAttributeNS(null, 'x', v.x - v.width / 2);
+ rect.setAttributeNS(null, 'y', v.y - v.height / 2);
+ rect.setAttributeNS(null, 'rx', 5);
+ rect.setAttributeNS(null, 'ry', 5);
+ rect.setAttributeNS(null, 'width', v.width);
+ rect.setAttributeNS(null, 'height', v.height);
+
+ if (v.class === 'root' && v.label === undefined) {
+ rect.setAttributeNS(null, 'width', v.height);
+ rect.setAttributeNS(null, 'x', v.x - v.height / 2);
+ rect.setAttributeNS(null, 'class', 'empty');
+ };
+
+ // Add label
+ if (v.label !== undefined) {
+ var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
+ text.setAttributeNS(null, 'x', v.x - v.width / 2);
+ text.setAttributeNS(null, 'y', v.y - v.height / 2);
+ text.setAttributeNS(
+ null,
+ 'transform',
+ 'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
+ );
+
+ var tspan = document.createElementNS(svgXmlns, 'tspan');
+ tspan.appendChild(document.createTextNode(v.label));
+ text.appendChild(tspan);
+ };
+
+ canvas.appendChild(group);
+ }
+ );
+
+ return this._element;
+ }
+ };
+
+ /**
+ * Menu item for tree view choice.
+ */
+ KorAP.MatchTreeItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.MatchTreeItem)._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+
+ // The foundry attribute
+ foundry : function () {
+ return this._foundry;
+ },
+
+ // The layer attribute
+ layer : function () {
+ return this._layer;
+ },
+
+ // enter or click
+ onclick : function (e) {
+ var menu = this.menu();
+ menu.hide();
+ e.halt();
+ if (menu.info() !== undefined)
+ menu.info().addTree(this._foundry, this._layer);
+ },
+
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._foundry = params[1];
+ this._layer = params[2];
+ this._content = document.createTextNode(this._name);
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+ return this;
+ }
+ };
+
+
+ /**
+ * Menu to choose from for tree views.
+ */
+ KorAP.MatchTreeMenu = {
+ create : function (info, params) {
+ var obj = Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.MatchTreeMenu)
+ ._init(KorAP.MatchTreeItem, undefined, params);
+ obj.limit(6);
+
+ obj._info = info;
+
+ // This is only domspecific
+ obj.element().addEventListener('blur', function (e) {
+ this.menu.hide();
+ });
+
+ return obj;
+ },
+ info :function () {
+ return this._info;
+ }
+ };
+
+
+ // Create path for node connections
+ function _line (src, target) {
+ var x1 = src.x,
+ y1 = src.y,
+ x2 = target.x,
+ y2 = target.y - target.height / 2;
+
+ // c 0,0 -10,0
+ return 'M ' + x1 + ',' + y1 + ' ' +
+ 'C ' + x1 + ',' + y1 + ' ' +
+ x2 + ',' + (y2 - (y2 - y1) / 2) + ' ' +
+ x2 + ',' + y2;
+ };
+
+}(this.KorAP));
diff --git a/dev/js/src/menu.js b/dev/js/src/menu.js
new file mode 100644
index 0000000..1cf3455
--- /dev/null
+++ b/dev/js/src/menu.js
@@ -0,0 +1,968 @@
+var KorAP = KorAP || {};
+
+/**
+ * Create scrollable drop-down menus.
+ *
+ * @author Nils Diewald
+ */
+
+(function (KorAP) {
+ "use strict";
+
+ // Don't let events bubble up
+ if (Event.halt === undefined) {
+ // Don't let events bubble up
+ Event.prototype.halt = function () {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+ };
+
+ // Default maximum number of menu items
+ KorAP.menuLimit = 8;
+
+ /**
+ * List of items for drop down menu (complete).
+ * Only a sublist of the menu is filtered (live).
+ * Only a sublist of the filtered menu is visible (shown).
+ */
+ KorAP.Menu = {
+ /**
+ * Create new Menu based on the action prefix
+ * and a list of menu items.
+ *
+ * @this {Menu}
+ * @constructor
+ * @param {string} Context prefix
+ * @param {Array.<Array.<string>>} List of menu items
+ */
+ create : function (params) {
+ return Object.create(KorAP.Menu)._init(params);
+ },
+
+ /**
+ * Destroy this menu
+ * (in case you don't trust the
+ * mark and sweep GC)!
+ */
+ destroy : function () {
+ if (this._element != undefined)
+ delete this._element["menu"];
+
+ for (var i = 0; i < this._items.length; i++) {
+ delete this._items[i]["_menu"];
+ };
+ delete this._prefix['_menu'];
+ },
+
+ focus : function () {
+ this._element.focus();
+ },
+
+ // mouse wheel treatment
+ _mousewheel : function (e) {
+ var delta = 0;
+
+ delta = e.deltaY / 120;
+ if (delta > 0)
+ this.next();
+ else if (delta < 0)
+ this.prev();
+ e.halt();
+ },
+
+ // Arrow key and prefix treatment
+ _keypress : function (e) {
+ var code = _codeFromEvent(e);
+
+ switch (code) {
+ case 27: // 'Esc'
+ e.halt();
+ this.hide();
+ break;
+
+ case 38: // 'Up'
+ e.halt();
+ this.prev();
+ break;
+ case 33: // 'Page up'
+ e.halt();
+ this.prev();
+ break;
+ case 40: // 'Down'
+ e.halt();
+ this.next();
+ break;
+ case 34: // 'Page down'
+ e.halt();
+ this.next();
+ break;
+ case 39: // 'Right'
+ if (this._prefix.active())
+ break;
+
+ var item = this.liveItem(this._position);
+ if (item["further"] !== undefined) {
+ item["further"].bind(item).apply();
+ e.halt();
+ };
+ break;
+ case 13: // 'Enter'
+
+ // Click on prefix
+ if (this._prefix.active())
+ this._prefix.onclick(e);
+
+ // Click on item
+ else
+ this.liveItem(this._position).onclick(e);
+ e.halt();
+ break;
+ case 8: // 'Backspace'
+ this._prefix.backspace();
+ this.show();
+ e.halt();
+ break;
+ default:
+ if (e.key !== undefined &&
+ e.key.length != 1)
+ return;
+
+ // Add prefix
+ this._prefix.add(e.key.toLowerCase());
+
+ if (!this.show()) {
+ this.prefix('').show();
+ };
+ };
+ },
+
+ // Initialize list
+ _init : function (itemClass, prefixClass, params) {
+ var that = this;
+ this._itemClass = itemClass;
+
+ if (prefixClass !== undefined)
+ this._prefix = prefixClass.create();
+ else
+ this._prefix = KorAP.MenuPrefix.create();
+
+ this._prefix._menu = this;
+
+ var e = document.createElement("ul");
+ e.style.opacity = 0;
+ e.style.outline = 0;
+ e.setAttribute('tabindex', 0);
+ e.classList.add('menu');
+ e.classList.add('roll');
+ e.appendChild(this._prefix.element());
+
+ // This has to be cleaned up later on
+ e["menu"] = this;
+
+ // Arrow keys
+ e.addEventListener(
+ 'keypress',
+ function (ev) {
+ that._keypress(ev)
+ },
+ false
+ );
+
+ // Mousewheel
+ e.addEventListener(
+ 'wheel',
+ function (ev) {
+ that._mousewheel(ev)
+ },
+ false
+ );
+
+ this._element = e;
+ this.active = false;
+ this._items = new Array();
+ var i;
+
+ // Initialize item list based on parameters
+ for (i in params) {
+ var obj = itemClass.create(params[i]);
+
+ // This may become circular
+ obj["_menu"] = this;
+
+ this._items.push(obj);
+ };
+ this._limit = KorAP.menuLimit;
+ this._position = 0; // position in the active list
+ this._active = -1; // active item in the item list
+ this._reset();
+ return this;
+ },
+
+ /**
+ * Get the instantiated HTML element
+ */
+ element : function () {
+ return this._element;
+ },
+
+ /**
+ * Get the creator object for items
+ */
+ itemClass : function () {
+ return this._itemClass;
+ },
+
+ /**
+ * Get and set numerical value for limit,
+ * i.e. the number of items visible.
+ */
+ limit : function (limit) {
+ if (arguments.length === 1) {
+ this._limit = limit;
+ return this;
+ };
+ return this._limit;
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object} An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ // Reset chosen item and prefix
+ _reset : function () {
+ this._offset = 0;
+ this._pos = 0;
+ this._prefix.value('');
+ },
+
+ /**
+ * Filter the list and make it visible
+ *
+ * @param {string} Prefix for filtering the list
+ */
+ show : function () {
+
+ // Initialize the list
+ if (!this._initList())
+ return false;
+
+ // show based on initial offset
+ this._showItems(0);
+
+ // Set the first element to active
+ // Todo: Or the last element chosen
+ this.liveItem(0).active(true);
+ this._prefix.active(false);
+
+ this._active = this._list[0];
+ this._position = 0;
+ this._element.style.opacity = 1;
+
+ // Add classes for rolling menus
+ this._boundary(true);
+ return true;
+ },
+
+ hide : function () {
+ this.active = false;
+ this.delete();
+ this._element.style.opacity = 0;
+ this.onHide();
+ /* this._element.blur(); */
+ },
+
+ // To be override
+ onHide : function () {},
+
+ // Initialize the list
+ _initList : function () {
+
+ // Create a new list
+ if (this._list === undefined) {
+ this._list = [];
+ }
+ else if (this._list.length != 0) {
+ this._boundary(false);
+ this._list.length = 0;
+ };
+
+ // Offset is initially zero
+ this._offset = 0;
+
+ // There is no prefix set
+ if (this.prefix().length <= 0) {
+ var i = 0;
+ for (; i < this._items.length; i++)
+ this._list.push(i);
+ while (this._items[++i] !== undefined) {
+ this._items[i].lowlight();
+ console.log(this._item);
+ };
+ return true;
+ };
+
+ // There is a prefix set, so filter the list
+ var pos;
+ var paddedPrefix = " " + this.prefix();
+
+ // Iterate over all items and choose preferred matching items
+ // i.e. the matching happens at the word start
+ for (pos = 0; pos < this._items.length; pos++) {
+ if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
+ this._list.push(pos);
+ };
+
+ // The list is empty - so lower your expectations
+ // Iterate over all items and choose matching items
+ // i.e. the matching happens anywhere in the word
+ if (this._list.length == 0) {
+ for (pos = 0; pos < this._items.length; pos++) {
+ if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
+ this._list.push(pos);
+ };
+ };
+
+ // Filter was successful - yeah!
+ return this._list.length > 0 ? true : false;
+ },
+
+ // Set boundary for viewport
+ _boundary : function (bool) {
+ this.item(this._list[0]).noMore(bool);
+ this.item(this._list[this._list.length - 1]).noMore(bool);
+ },
+
+ /**
+ * Get the prefix for filtering,
+ * e.g. "ve" for "verb"
+ */
+ prefix : function (pref) {
+ if (arguments.length === 1) {
+ this._prefix.value(pref);
+ return this;
+ };
+ return this._prefix.value();
+ },
+
+ // Append Items that should be shown
+ _showItems : function (offset) {
+ this.delete();
+
+ // Use list
+ var shown = 0;
+ var i;
+ for (i in this._list) {
+
+ // Don't show - it's before offset
+ if (shown++ < offset)
+ continue;
+
+ this._append(this._list[i]);
+
+ if (shown >= (this.limit() + this._offset))
+ break;
+ };
+ },
+
+ /**
+ * Delete all visible items from the menu element
+ */
+ delete : function () {
+ var child;
+
+ /*
+ // Iterate over all visible items
+ for (var i = 0; i <= this.limit(); i++) {
+
+ // there is a visible element
+ // unhighlight!
+ if (child = this.shownItem(i)) {
+ child.lowlight();
+ child.active(false);
+ };
+ };
+ */
+
+ for (var i in this._list) {
+ var item = this._items[this._list[i]];
+ item.lowlight();
+ item.active(false);
+ };
+
+ // Remove all children
+ var children = this._element.childNodes;
+ for (var i = children.length - 1; i >= 1; i--) {
+ this._element.removeChild(
+ children[i]
+ );
+ };
+ },
+
+
+ // Append item to the shown list based on index
+ _append : function (i) {
+ var item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix().length > 0)
+ item.highlight(this.prefix());
+
+ // Append element
+ this.element().appendChild(item.element());
+ },
+
+
+ // Prepend item to the shown list based on index
+ _prepend : function (i) {
+ var item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix().length > 0)
+ item.highlight(this.prefix());
+
+ var e = this.element();
+ // Append element
+ e.insertBefore(
+ item.element(),
+ e.children[1]
+ );
+ },
+
+
+ /**
+ * Get a specific item from the complete list
+ *
+ * @param {number} index of the list item
+ */
+ item : function (index) {
+ return this._items[index]
+ },
+
+
+ /**
+ * Get a specific item from the filtered list
+ *
+ * @param {number} index of the list item
+ * in the filtered list
+ */
+ liveItem : function (index) {
+ if (this._list === undefined)
+ if (!this._initList())
+ return;
+
+ return this._items[this._list[index]];
+ },
+
+
+ /**
+ * Get a specific item from the visible list
+ *
+ * @param {number} index of the list item
+ * in the visible list
+ */
+ shownItem : function (index) {
+ if (index >= this.limit())
+ return;
+ return this.liveItem(this._offset + index);
+ },
+
+
+ /**
+ * Get the length of the full list
+ */
+ length : function () {
+ return this._items.length;
+ },
+
+
+ /**
+ * Make the next item in the filtered menu active
+ */
+ next : function () {
+
+ // No active element set
+ if (this._position === -1)
+ return;
+
+ var newItem;
+
+ // Set new live item
+ if (!this._prefix.active()) {
+ var oldItem = this.liveItem(this._position);
+ oldItem.active(false);
+ };
+
+ this._position++;
+
+ newItem = this.liveItem(this._position);
+
+ // The next element is undefined - roll to top or to prefix
+ if (newItem === undefined) {
+
+ // Activate prefix
+ var prefix = this._prefix;
+
+ // Mark prefix
+ if (prefix.isSet() && !prefix.active()) {
+ this._position--;
+ prefix.active(true);
+ return;
+ }
+ else {
+ this._offset = 0;
+ this._position = 0;
+ newItem = this.liveItem(0);
+ this._showItems(0);
+ };
+ }
+
+ // The next element is outside the view - roll down
+ else if (this._position >= (this.limit() + this._offset)) {
+ this._removeFirst();
+ this._offset++;
+ this._append(this._list[this._position]);
+ };
+
+ this._prefix.active(false);
+ newItem.active(true);
+ },
+
+ /*
+ * Page down to the first item on the next page
+ */
+ /*
+ nextPage : function () {
+
+ // Prefix is active
+ if (this._prefix.active()) {
+ this._prefix.active(false);
+ }
+
+ // Last item is chosen
+ else if (this._position >= this.limit() + this._offset) {
+
+ this._position = this.limit() + this._offset - 1;
+ newItem = this.liveItem(this._position);
+ var oldItem = this.liveItem(this._position--);
+ oldItem.active(false);
+ }
+
+ // Last item of page is chosen
+ else if (0) {
+
+ // Jump to last item
+ else {
+ var oldItem = this.liveItem(this._position);
+ oldItem.active(false);
+
+ this._position = this.limit() + this._offset - 1;
+ newItem = this.liveItem(this._position);
+ };
+
+ newItem.active(true);
+ },
+ */
+
+
+ /*
+ * Make the previous item in the menu active
+ */
+ prev : function () {
+
+ // No active element set
+ if (this._position === -1) {
+ return;
+ // TODO: Choose last item
+ };
+
+ var newItem;
+
+ // Set new live item
+ if (!this._prefix.active()) {
+ var oldItem = this.liveItem(this._position--);
+ oldItem.active(false);
+ };
+
+ newItem = this.liveItem(this._position);
+
+ // The previous element is undefined - roll to bottom
+ if (newItem === undefined) {
+
+ // Activate prefix
+ var prefix = this._prefix;
+ this._offset = this.liveLength() - this.limit();
+
+ // Normalize offset
+ this._offset = this._offset < 0 ? 0 : this._offset;
+
+ this._position = this.liveLength() - 1;
+
+ if (prefix.isSet() && !prefix.active()) {
+ this._position++;
+ prefix.active(true);
+ return;
+ }
+ else {
+ newItem = this.liveItem(this._position);
+ this._showItems(this._offset);
+ };
+ }
+
+ // The previous element is outside the view - roll up
+ else if (this._position < this._offset) {
+ this._removeLast();
+ this._offset--;
+ this._prepend(this._list[this._position]);
+ };
+
+ this._prefix.active(false);
+ newItem.active(true);
+ },
+
+
+ // Length of the filtered list
+ liveLength : function () {
+ if (this._list === undefined)
+ this._initList();
+ return this._list.length;
+ },
+
+
+ // Remove the HTML node from the first item
+ _removeFirst : function () {
+ this.item(this._list[this._offset]).lowlight();
+ this._element.removeChild(this._element.children[1]);
+ },
+
+
+ // Remove the HTML node from the last item
+ _removeLast : function () {
+ this.item(this._list[this._offset + this.limit() - 1]).lowlight();
+ this._element.removeChild(this._element.lastChild);
+ }
+ };
+
+
+ /**
+ * Item in the Dropdown menu
+ */
+ KorAP.MenuItem = {
+
+ /**
+ * Create a new MenuItem object.
+ *
+ * @constructor
+ * @this {MenuItem}
+ * @param {Array.<string>} An array object of name, action and
+ * optionally a description
+ */
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)._init(params);
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object] An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ content : function (content) {
+ if (arguments.length === 1)
+ this._content = document.createTextNode(content);
+ return this._content;
+ },
+
+ lcField : function () {
+ return this._lcField;
+ },
+
+ action : function (action) {
+ if (arguments.length === 1)
+ this._action = action;
+ return this._action;
+ },
+
+ /**
+ * Check or set if the item is active
+ *
+ * @param {boolean|null} State of activity
+ */
+ active : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("active");
+ else if (bool)
+ cl.add("active");
+ else
+ cl.remove("active");
+ },
+
+ /**
+ * Check or set if the item is
+ * at the boundary of the menu
+ * list
+ *
+ * @param {boolean|null} State of activity
+ */
+ noMore : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("no-more");
+ else if (bool)
+ cl.add("no-more");
+ else
+ cl.remove("no-more");
+ },
+
+ /**
+ * Get the document element of the menu item
+ */
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+
+ // Connect action
+ if (this["onclick"] !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
+
+ // Append template
+ li.appendChild(this.content());
+
+ return this._element = li;
+ },
+
+ /**
+ * Highlight parts of the item
+ *
+ * @param {string} Prefix string for highlights
+ */
+ highlight : function (prefix) {
+ var children = this.element().childNodes;
+ for (var i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
+ },
+
+ // Highlight a certain substring of the menu item
+ _highlight : function (elem, prefix) {
+
+ if (elem.nodeType === 3) {
+
+ var text = elem.nodeValue;
+ var textlc = text.toLowerCase();
+ var pos = textlc.indexOf(prefix);
+ if (pos >= 0) {
+
+ // First element
+ if (pos > 0) {
+ elem.parentNode.insertBefore(
+ document.createTextNode(text.substr(0, pos)),
+ elem
+ );
+ };
+
+ // Second element
+ var hl = document.createElement("mark");
+ hl.appendChild(
+ document.createTextNode(text.substr(pos, prefix.length))
+ );
+ elem.parentNode.insertBefore(hl, elem);
+
+ // Third element
+ var third = text.substr(pos + prefix.length);
+ if (third.length > 0) {
+ var thirdE = document.createTextNode(third);
+ elem.parentNode.insertBefore(
+ thirdE,
+ elem
+ );
+ this._highlight(thirdE, prefix);
+ };
+
+ var p = elem.parentNode;
+ p.removeChild(elem);
+ };
+ }
+ else {
+ var children = elem.childNodes;
+ for (var i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
+ };
+ },
+
+
+ /**
+ * Remove highlight of the menu item
+ */
+ lowlight : function () {
+ var e = this.element();
+
+ var marks = e.getElementsByTagName("mark");
+ for (var i = marks.length - 1; i >= 0; i--) {
+ // Create text node clone
+ var x = document.createTextNode(
+ marks[i].firstChild.nodeValue
+ );
+
+ // Replace with content
+ marks[i].parentNode.replaceChild(
+ x,
+ marks[i]
+ );
+ };
+
+ // Remove consecutive textnodes
+ e.normalize();
+ },
+
+ // Initialize menu item
+ _init : function (params) {
+
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this.content(params[0]);
+
+ if (params.length === 2)
+ this._action = params[1];
+
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ },
+
+ /**
+ * Return menu list.
+ */
+ menu : function () {
+ return this._menu;
+ }
+ };
+
+ KorAP.MenuPrefix = {
+ create : function (params) {
+ return Object.create(KorAP.MenuPrefix)._init();
+ },
+ _init : function () {
+ this._string = '';
+
+ // Add prefix span
+ this._element = document.createElement('span');
+ this._element.classList.add('pref');
+ // Connect action
+
+ if (this["onclick"] !== undefined)
+ this._element["onclick"] = this.onclick.bind(this);
+
+ return this;
+ },
+ _update : function () {
+ this._element.innerHTML
+ = this._string;
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object} An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ active : function (bool) {
+ var cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("active");
+ else if (bool)
+ cl.add("active");
+ else
+ cl.remove("active");
+ },
+
+ element : function () {
+ return this._element;
+ },
+
+ isSet : function () {
+ return this._string.length > 0 ?
+ true : false;
+ },
+
+ value : function (string) {
+ if (arguments.length === 1) {
+ this._string = string;
+ this._update();
+ };
+ return this._string;
+ },
+
+ add : function (string) {
+ this._string += string;
+ this._update();
+ },
+
+ onclick : function () {},
+
+ backspace : function () {
+ if (this._string.length > 1) {
+ this._string = this._string.substring(
+ 0, this._string.length - 1
+ );
+ }
+ else {
+ this._string = '';
+ };
+
+ this._update();
+ },
+
+ /**
+ * Return menu list.
+ */
+ menu : function () {
+ return this._menu;
+ }
+ };
+
+ function _codeFromEvent (e) {
+ if (e.charCode && (e.keyCode == 0))
+ return e.charCode
+ return e.keyCode;
+ };
+
+}(this.KorAP));
+
+/**
+ * MenuItems may define:
+ *
+ * onclick: action happen on click and enter.
+ * further: action happen on right arrow
+ */
diff --git a/dev/js/src/session.js b/dev/js/src/session.js
new file mode 100644
index 0000000..a10ce81
--- /dev/null
+++ b/dev/js/src/session.js
@@ -0,0 +1,80 @@
+/**
+ * Simple cookie based session library that stores
+ * values in JSON encoded cookies.
+ *
+ * @author Nils Diewald
+ */
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+ "use strict";
+
+
+ KorAP.Session = {
+
+ /**
+ * Create a new session.
+ * Expects a name or defaults to 'korap'
+ */
+ create : function (name) {
+ var obj = Object.create(KorAP.Session);
+ if (name === undefined)
+ name = 'korap';
+ obj._name = name.toLowerCase();
+ obj._hash = {};
+ obj._parse();
+ return obj;
+ },
+
+ /**
+ * Get a value based on a key.
+ * The value can be complex, as the value is stored as JSON.
+ */
+ get : function (key) {
+ return this._hash[key.toLowerCase()];
+ },
+
+ /**
+ * Set a value based on a key.
+ * The value can be complex, as the value is stored as JSON.
+ */
+ set : function (key, value) {
+ this._hash[key] = value;
+ this._store();
+ },
+
+ /**
+ * Clears the session by removing the cookie
+ */
+ clear : function () {
+ document.cookie = this._name + '=; expires=-1';
+ },
+
+ /* Store cookie */
+ _store : function () {
+ /*
+ var date = new Date();
+ date.setYear(date.getFullYear() + 1);
+ */
+ document.cookie =
+ this._name + '=' + encodeURIComponent(JSON.stringify(this._hash)) + ';';
+ },
+
+ /* Parse cookie */
+ _parse : function () {
+ var c = document.cookie;
+ var part = document.cookie.split(';');
+ for(var i = 0; i < part.length; i++) {
+ var pair = part[i].split('=');
+ var name = pair[0].trim().toLowerCase();
+ if (name === this._name) {
+ if (pair.length === 1 || pair[1].length === 0)
+ return;
+ this._hash = JSON.parse(decodeURIComponent(pair[1]));
+ return;
+ };
+ };
+ }
+ }
+
+}(this.KorAP));
diff --git a/dev/js/src/tutorial.js b/dev/js/src/tutorial.js
new file mode 100644
index 0000000..03cf903
--- /dev/null
+++ b/dev/js/src/tutorial.js
@@ -0,0 +1,133 @@
+/**
+ * Open and close a tutorial page.
+ * The current page is stored and retrieved in a session cookie.
+ */
+// Requires session.js
+var KorAP = KorAP || {};
+
+// Todo: add query mechanism!
+
+(function (KorAP) {
+ "use strict";
+
+ // Localization values
+ var loc = (KorAP.Locale = KorAP.Locale || {} );
+ loc.CLOSE = loc.CLOSE || 'Close';
+
+ KorAP.Tutorial = {
+
+ /**
+ * Create new tutorial object.
+ * Accepts an element to bind the tutorial window to.
+ */
+ create : function (obj) {
+ return Object.create(KorAP.Tutorial)._init(obj);
+ },
+
+ // Initialize Tutorial object
+ _init : function (obj) {
+ this._session = KorAP.Session.create();
+ this._show = obj;
+ this.start = obj.getAttribute('href');
+ obj.removeAttribute('href');
+ var that = this;
+ obj.onclick = function () {
+ that.show();
+ };
+
+ // Injects a tutorial div to the body
+ var div = document.createElement('div');
+ div.setAttribute('id', 'tutorial');
+ div.style.display = 'none';
+ document.getElementsByTagName('body')[0].appendChild(div);
+ this._iframe = null;
+
+ this._element = div;
+ return this;
+ },
+
+ show : function () {
+ var element = this._element;
+ if (element.style.display === 'block')
+ return;
+
+ if (this._iframe === null) {
+ this._iframe = document.createElement('iframe');
+ this._iframe.setAttribute('src', this.getPage() || this.start);
+
+ var ul = document.createElement('ul');
+ ul.classList.add('action', 'right');
+
+ // Use localization
+ var loc = KorAP.Locale;
+
+ // Add close button
+ var close = document.createElement('li');
+ close.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(loc.CLOSE));
+ close.classList.add('close');
+ close.setAttribute('title', loc.CLOSE);
+ close.onclick = function () {
+ element.style.display = 'none';
+ };
+
+ // Add open in new window button
+ // Add scroll to top button
+ /*
+ var info = document.createElement('li');
+ info.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(loc.SHOWINFO));
+ info.classList.add('info');
+ info.setAttribute('title', loc.SHOWINFO);
+ */
+
+ ul.appendChild(close);
+
+ element.appendChild(ul);
+ element.appendChild(this._iframe);
+ };
+
+ element.style.display = 'block';
+ },
+
+ /**
+ * Close tutorial window.
+ */
+ hide : function () {
+ this._element.display.style = 'none';
+ },
+
+ /**
+ * Set a page to be the current tutorial page.
+ * Expects either a string or an element.
+ */
+ setPage : function (obj) {
+ var page = obj;
+ if (typeof page != 'string') {
+ page = window.location.pathname + window.location.search;
+ for (i = 1; i < 5; i++) {
+ if (obj.nodeName === 'SECTION') {
+ if (obj.hasAttribute('id'))
+ page += '#' + obj.getAttribute('id');
+ break;
+ }
+ else if (obj.nodeName === 'PRE' && obj.hasAttribute('id')) {
+ page += '#' + obj.getAttribute('id');
+ break;
+ }
+ else {
+ obj = obj.parentNode;
+ };
+ };
+ };
+ this._session.set('tutpage', page);
+ },
+
+ /**
+ * Get the current tutorial URL
+ */
+ getPage : function () {
+ this._session.get('tutpage');
+ },
+ }
+}(this.KorAP));
diff --git a/dev/js/src/util.js b/dev/js/src/util.js
new file mode 100644
index 0000000..ecaad6f
--- /dev/null
+++ b/dev/js/src/util.js
@@ -0,0 +1,124 @@
+/**
+ * These are utility functions for the frontend
+ */
+
+// Add toggleClass method similar to jquery
+HTMLElement.prototype.toggleClass = function (c1, c2) {
+ var cl = this.classList;
+ if (cl.contains(c1)) {
+ cl.add(c2);
+ cl.remove(c1);
+ }
+ else {
+ cl.remove(c2);
+ cl.add(c1);
+ };
+};
+
+
+// Don't let events bubble up
+if (Event.halt === undefined) {
+ // Don't let events bubble up
+ Event.prototype.halt = function () {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+};
+
+var KorAP = KorAP || {};
+
+
+(function (KorAP) {
+ "use strict";
+
+
+ /**
+ * Initialize user interface elements
+ */
+ KorAP.init = function () {
+ var obj = Object.create(KorAP.init);
+
+ /**
+ * Add actions to match entries
+ */
+ var inactiveLi = document.querySelectorAll(
+ '#search > ol > li:not(.active)'
+ );
+ var i = 0;
+ for (i = 0; i < inactiveLi.length; i++) {
+ inactiveLi[i].addEventListener('click', function (e) {
+ if (this._match !== undefined)
+ this._match.open();
+ else
+ KorAP.Match.create(this).open();
+ e.halt();
+ });
+ };
+
+
+ /**
+ * Toggle the alignment (left <=> right)
+ */
+ if (i > 0) {
+ var br = document.getElementById('button-right');
+ if (br !== null) {
+ var toggle = document.createElement('a');
+ toggle.setAttribute('title', 'toggle Alignment');
+ // Todo: Reuse old alignment from cookie!
+ var cl = toggle.classList;
+ cl.add('align');
+ cl.add('right');
+ toggle.addEventListener(
+ 'click',
+ function (e) {
+ var ol = document.querySelector('#search > ol');
+ ol.toggleClass("align-left", "align-right");
+ this.toggleClass("left", "right");
+ });
+ toggle.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode('Alignment'));
+ br.appendChild(toggle);
+ };
+ };
+
+ /**
+ * Init vc
+ */
+ var input = document.getElementById('vc');
+ if (input) {
+ input.style.display = 'none';
+ var vcname = document.createElement('span');
+ vcname.setAttribute('id', 'vc-choose');
+ vcname.appendChild(
+ document.createTextNode(
+ document.getElementById('vc-name').value
+ )
+ );
+ input.parentNode.insertBefore(vcname, input);
+
+ vcname.onclick = function () {
+ var vc = KorAP.VirtualCollection.render(vcExample);
+ var view = document.getElementById('vc-view');
+ view.appendChild(vc.element());
+ };
+ };
+
+ /**
+ * Init Tutorial view
+ */
+ obj.tutorial = KorAP.Tutorial.create(
+ document.getElementById('view-tutorial')
+ );
+
+ /**
+ * Init hint helper
+ * has to be final because of
+ * reposition
+ */
+// Todo: Pass an element, so this works with
+// tutorial pages as well!
+ obj.hint = KorAP.Hint.create();
+ return obj;
+ };
+
+}(this.KorAP));
diff --git a/dev/js/src/vc.js b/dev/js/src/vc.js
new file mode 100644
index 0000000..2c8ad01
--- /dev/null
+++ b/dev/js/src/vc.js
@@ -0,0 +1,1398 @@
+/**
+ * Create virtual collections with a visual user interface.
+ *
+ * @author Nils Diewald
+ */
+/*
+ * Replaces a previous version written by Mengfei Zhou
+ */
+var KorAP = KorAP || {};
+
+// Requires menu.js
+
+/*
+ TODO: Implement a working localization solution!
+ TODO: Disable "and" or "or" in case it's followed
+ by an unspecified document
+ TODO: Implement "persistance"-Option,
+ injecting the current creation date stamp
+
+ Error codes:
+ 701: "JSON-LD group has no @type attribute"
+ 704: "Operation needs operand list"
+ 802: "Match type is not supported by value type"
+ 804: "Unknown value type"
+ 805: "Value is invalid"
+ 806: "Value is not a valid date string"
+ 807: "Value is not a valid regular expression"
+ 810: "Unknown document group operation" (like 711)
+ 811: "Document group expects operation" (like 703)
+ 812: "Operand not supported in document group" (like 744)
+ 813: "Collection type is not supported" (like 713)
+ 814: "Unknown rewrite operation"
+ 815: "Rewrite expects source"
+*/
+
+(function (KorAP) {
+ "use strict";
+
+ // Default log message
+ KorAP.log = KorAP.log || function (type, msg) {
+ console.log(type + ": " + msg);
+ };
+
+ KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains|excludes)$");
+ KorAP._validRegexMatchRE = new RegExp("^(?:eq|ne)$");
+ KorAP._validDateMatchRE = new RegExp("^[lg]?eq$");
+ KorAP._validDateRE = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
+ KorAP._validGroupOpRE = new RegExp("^(?:and|or)$");
+ KorAP._validRewriteOpRE = new RegExp("^(?:injec|modifica)tion$");
+ KorAP._quote = new RegExp("([\"\\\\])", 'g');
+
+ // Localization values
+ var loc = (KorAP.Locale = KorAP.Locale || {} );
+ loc.AND = loc.AND || 'and';
+ loc.OR = loc.OR || 'or';
+ loc.DEL = loc.DEL || '×';
+ loc.EMPTY = loc.EMPTY || '⋯'
+
+ // Utility for analysing boolean values
+ function _bool (bool) {
+ return (bool === undefined || bool === null || bool === false) ? false : true;
+ };
+
+
+ // Utility for removing all children of a node
+ function _removeChildren (node) {
+ // Remove everything underneath
+ while (node.firstChild)
+ node.removeChild(node.firstChild);
+ };
+
+
+ // Add new unspecified document
+ KorAP._add = function (obj, type) {
+ var ref = obj.parentNode.refTo;
+ var parent = ref.parent();
+
+ if (ref.ldType() === 'docGroup') {
+
+ // Check that the action differs from the type
+ if (ref.operation() === type)
+ return;
+
+ if (parent.ldType() !== null) {
+ return parent.newAfter(ref);
+ }
+ else {
+ // The group is on root - wrap
+ return ref.wrapOnRoot();
+ };
+ }
+ else if (ref.ldType() === 'doc') {
+
+ if (parent.ldType() === null) {
+ return ref.wrapOnRoot(type);
+ }
+ else if (parent.operation() === type) {
+ return parent.newAfter(ref);
+ }
+ else {
+ return ref.wrap(type);
+ };
+ };
+ };
+
+
+ // Add doc with 'and' relation
+ KorAP._and = function () {
+ return KorAP._add(this, 'and');
+ };
+
+
+ // Add doc with 'or' relation
+ KorAP._or = function () {
+ return KorAP._add(this, 'or');
+ };
+
+
+ // Remove doc or docGroup
+ KorAP._delete = function () {
+ var ref = this.parentNode.refTo;
+ if (ref.parent().ldType() !== null) {
+ return ref.parent().delOperand(ref).update();
+ }
+ else {
+ ref.parent().clean();
+ };
+ };
+
+
+ /**
+ * Virtual Collection
+ */
+ KorAP.VirtualCollection = {
+ ldType : function () {
+ return null;
+ },
+
+ create : function () {
+ return Object.create(KorAP.VirtualCollection);
+ },
+
+ clean : function () {
+ if (this._root.ldType() !== "non") {
+ this._root.destroy();
+ this.root(KorAP.UnspecifiedDoc.create(this));
+ };
+ return this;
+ },
+
+ render : function (json) {
+ var obj = Object.create(KorAP.VirtualCollection);
+
+ if (json !== undefined) {
+ // Root object
+ if (json['@type'] == 'koral:doc') {
+ obj._root = KorAP.Doc.create(obj, json);
+ }
+ else if (json['@type'] == 'koral:docGroup') {
+ obj._root = KorAP.DocGroup.create(obj, json);
+ }
+ else {
+ KorAP.log(813, "Collection type is not supported");
+ return;
+ };
+ }
+
+ else {
+ // Add unspecified object
+ obj._root = KorAP.UnspecifiedDoc.create(obj);
+ };
+
+ // Init element and update
+ obj.update();
+
+ return obj;
+ },
+
+ root : function (obj) {
+ if (arguments.length === 1) {
+ var e = this.element();
+ if (e.firstChild !== null) {
+ if (e.firstChild !== obj.element()) {
+ e.replaceChild(obj.element(), e.firstChild);
+ };
+ }
+
+ // Append root element
+ else {
+ e.appendChild(obj.element());
+ };
+
+ // Update parent child relations
+ this._root = obj;
+ obj.parent(this);
+
+ this.update();
+ };
+ return this._root;
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'vc');
+
+ // Initialize root
+ this._element.appendChild(this._root.element());
+
+ return this._element;
+ },
+
+ update : function () {
+ this._root.update();
+ return this;
+ },
+
+ toJson : function () {
+ return this._root.toJson();
+ },
+
+ toQuery : function () {
+ return this._root.toQuery();
+ }
+ };
+
+
+ /**
+ * Operators for criteria
+ */
+ KorAP.Operators = {
+ create : function (and, or, del) {
+ var op = Object.create(KorAP.Operators);
+ op.and(and);
+ op.or(or);
+ op.del(del);
+ return op;
+ },
+
+ update : function () {
+ // Init the element
+ if (this._element === undefined)
+ return this.element();
+
+ var op = this._element;
+
+ op.refTo = this.parent();
+
+ // Remove everything underneath
+ _removeChildren(op);
+
+ // Add and button
+ if (this._and === true) {
+ var andE = document.createElement('span');
+ andE.setAttribute('class', 'and');
+ andE.addEventListener('click', KorAP._and, false);
+ andE.appendChild(
+ document.createTextNode(KorAP.Locale.AND)
+ );
+ op.appendChild(andE);
+ };
+
+ // Add or button
+ if (this._or === true) {
+ var orE = document.createElement('span');
+ orE.setAttribute('class', 'or');
+ orE.addEventListener('click', KorAP._or, false);
+ orE.appendChild(document.createTextNode(KorAP.Locale.OR));
+ op.appendChild(orE);
+ };
+
+ // Add delete button
+ if (this._del === true) {
+ var delE = document.createElement('span');
+ delE.setAttribute('class', 'delete');
+ delE.appendChild(document.createTextNode(KorAP.Locale.DEL));
+ delE.addEventListener('click', KorAP._delete, false);
+ op.appendChild(delE);
+ };
+
+ return op;
+ },
+
+ // Be aware! This may be cyclic
+ parent : function (obj) {
+ if (arguments.length === 1)
+ this._parent = obj;
+ return this._parent;
+ },
+
+ element : function () {
+
+ // Return existing element
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'operators');
+
+ // Init elements
+ this.update();
+ return this._element;
+ },
+
+ and : function (bool) {
+ if (arguments.length === 1)
+ this._and = _bool(bool);
+ return this._and;
+ },
+
+ or : function (bool) {
+ if (arguments.length === 1)
+ this._or = _bool(bool);
+ return this._or;
+ },
+
+ del : function (bool) {
+ if (arguments.length === 1)
+ this._del = _bool(bool);
+ return this._del;
+ }
+ };
+
+
+ /**
+ * Unspecified criterion
+ */
+ KorAP.UnspecifiedDoc = {
+ _ldType : "non",
+ create : function (parent) {
+ var obj = Object.create(KorAP.JsonLD).
+ upgradeTo(KorAP.UnspecifiedDoc);
+
+ if (parent !== undefined)
+ obj._parent = parent;
+
+ return obj;
+ },
+
+ // Set key - replace
+ key : function (v) {
+
+ // Not replaceable
+ if (this._parent === undefined)
+ return null;
+
+ // Set JSON-LD type
+ var newDoc = KorAP.Doc.create(this._parent, {
+ "@type" : "koral:doc",
+ "value" : "",
+ "key" : v
+ });
+
+ // Unspecified document on root
+ if (this._parent.ldType() === null) {
+ this._parent.root(newDoc);
+ this.destroy();
+ }
+
+ // Unspecified document in group
+ else {
+ this._parent.replaceOperand(this, newDoc);
+ };
+ this._parent.update();
+ return newDoc;
+ },
+
+ update : function () {
+
+ if (this._element === undefined)
+ return this.element();
+
+ // Remove element content
+ _removeChildren(this._element);
+
+ var ellipsis = document.createElement('span');
+ ellipsis.appendChild(document.createTextNode(loc.EMPTY));
+ this._element.appendChild(ellipsis);
+
+ // Set ref - TODO: Cleanup!
+ this._element.refTo = this;
+
+ // Set operators
+ if (this._parent !== undefined && this.parent().ldType() !== null) {
+ var op = this.operators(
+ false,
+ false,
+ true
+ );
+
+ this._element.appendChild(
+ op.element()
+ );
+ };
+
+ return this.element();
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'doc unspecified');
+ this.update();
+ return this._element;
+ },
+
+
+ };
+
+
+ /**
+ * Document criterion
+ */
+ KorAP.Doc = {
+ _ldType : "doc",
+ _obj : function () { return KorAP.Doc },
+
+ create : function (parent, json) {
+ var obj = Object(KorAP.JsonLD).
+ create().
+ upgradeTo(KorAP.Doc).
+ fromJson(json);
+
+ if (parent !== undefined)
+ obj._parent = parent;
+
+ obj.__changed = true;
+ return obj;
+ },
+
+ update : function () {
+ if (this._element === undefined)
+ return this.element();
+
+ // Get element
+ var e = this._element;
+
+ // Set ref - TODO: Cleanup!
+ e.refTo = this;
+
+ // Check if there is a change
+ if (this.__changed) {
+
+ // Was rewritten
+ if (this.rewrites() !== undefined) {
+ e.classList.add("rewritten");
+ };
+
+ // Added key
+ var key = document.createElement('span');
+ key.setAttribute('class', 'key');
+
+ // Change key
+ key.addEventListener('click', KorAP._changeKey, false);
+
+ if (this.key())
+ key.appendChild(document.createTextNode(this.key()));
+
+ // Added match operator
+ var matchop = document.createElement('span');
+ matchop.setAttribute('data-type', this.type());
+ matchop.setAttribute('class', 'match');
+ matchop.appendChild(
+ document.createTextNode(this.matchop())
+ );
+
+ // Added match operator
+ var value = document.createElement('span');
+ value.setAttribute('data-type', this.type());
+ value.setAttribute('class', 'value');
+ if (this.value())
+ value.appendChild(
+ document.createTextNode(this.value())
+ );
+
+ // Remove all element children
+ _removeChildren(e);
+
+ // Add spans
+ e.appendChild(key);
+ e.appendChild(matchop);
+ e.appendChild(value);
+
+ this.__changed = false;
+ };
+
+ if (this._rewrites !== undefined) {
+ e.appendChild(this._rewrites.element());
+ };
+
+ if (this._parent !== undefined) {
+ // Set operators
+ var op = this.operators(
+ true,
+ true,
+ true
+ );
+
+ // Append new operators
+ e.appendChild(op.element());
+ };
+
+ return e;
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'doc');
+
+ this.update();
+ return this._element;
+ },
+
+ // Wrap a new operation around the doc element
+ wrap : function (op) {
+ var parent = this.parent();
+ var group = KorAP.DocGroup.create(parent);
+ group.operation(op);
+ group.append(this);
+ group.append();
+ return parent.replaceOperand(this, group).update();
+ },
+
+ // Deserialize from json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ if (json["value"] === undefined ||
+ typeof json["value"] != 'string') {
+ KorAP.log(805, "Value is invalid");
+ return;
+ };
+
+ // There is a defined key
+ if (json["key"] !== undefined &&
+ typeof json["key"] === 'string') {
+
+ // Set key
+ this.key(json["key"]);
+
+ // Set match operation
+ if (json["match"] !== undefined) {
+ if (typeof json["match"] === 'string') {
+ this.matchop(json["match"]);
+ }
+ else {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+ };
+
+ // Key is a string
+ if (json["type"] === undefined ||
+ json["type"] == "type:string") {
+ this.type("string");
+
+ // Check match type
+ if (!KorAP._validStringMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ // Set string value
+ this.value(json["value"]);
+ }
+
+ // Key is a date
+ else if (json["type"] === "type:date") {
+ this.type("date");
+
+ if (json["value"] !== undefined &&
+ KorAP._validDateRE.test(json["value"])) {
+
+ if (!KorAP._validDateMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ // Set value
+ this.value(json["value"]);
+ }
+ else {
+ KorAP.log(806, "Value is not a valid date string");
+ return;
+ };
+ }
+
+ // Key is a regular expression
+ else if (json["type"] === "type:regex") {
+ this.type("regex");
+
+ try {
+
+ // Try to create a regular expression
+ var check = new RegExp(json["value"]);
+
+ if (!KorAP._validRegexMatchRE.test(this.matchop())) {
+ KorAP.log(802, "Match type is not supported by value type");
+ return;
+ };
+
+ this.value(json["value"]);
+ }
+
+ catch (e) {
+ KorAP.log(807, "Value is not a valid regular expression");
+ return;
+ };
+ this.type("regex");
+ }
+
+ else {
+ KorAP.log(804, "Unknown value type");
+ return;
+ };
+
+ };
+
+ if (json["rewrites"] !== undefined) {
+ this._rewrites = KorAP.RewriteList.create(json["rewrites"]);
+ };
+
+ return this;
+ },
+
+ key : function (value) {
+ if (arguments.length === 1) {
+ this._key = value;
+ this._changed();
+ return this;
+ };
+ return this._key;
+ },
+
+ matchop : function (match) {
+ if (arguments.length === 1) {
+ this._matchop = match.replace(/^match:/, '');
+ this._changed();
+ return this;
+ };
+ return this._matchop || "eq";
+ },
+
+ type : function (type) {
+ if (arguments.length === 1) {
+ this._type = type;
+ this._changed();
+ return this;
+ };
+ return this._type || "string";
+ },
+
+ value : function (value) {
+ if (arguments.length === 1) {
+ this._value = value;
+ this._changed();
+ return this;
+ };
+ return this._value;
+ },
+
+ rewrites : function () {
+ return this._rewrites;
+ },
+
+ _changed : function () {
+ this.__changed = true;
+
+ if (this._rewrites === undefined)
+ return;
+ delete this["_rewrites"];
+ if (this._element === undefined)
+ return;
+ this._element.classList.remove("rewritten");
+ },
+
+ toJson : function () {
+ if (!this.matchop() || !this.key())
+ return {};
+
+ return {
+ "@type" : "koral:" + this.ldType(),
+ "key" : this.key(),
+ "match" : "match:" + this.matchop(),
+ "value" : this.value() || '',
+ "type" : "type:" + this.type()
+ };
+ },
+
+ toQuery : function () {
+ if (!this.matchop() || !this.key())
+ return "";
+
+ // Build doc string based on key
+ var string = this.key() + ' ';
+
+ // Add match operator
+ switch (this.matchop()) {
+ case "ne":
+ string += '!=';
+ break;
+ case "contains":
+ string += '~';
+ break;
+ case "excludes":
+ string += '!~';
+ break;
+ case "geq":
+ string += 'since';
+ break;
+ case "leq":
+ string += 'until';
+ break;
+ default:
+ string += (this.type() == 'date') ? 'in' : '=';
+ break;
+ };
+
+ string += ' ';
+
+ // Add value
+ switch (this.type()) {
+ case "date":
+ return string + this.value();
+ break;
+ case "regex":
+ return string + '/' + this.value() + '/';
+ break;
+ case "string":
+ return string + '"' + this.value().replace(KorAP._quote, '\\$1') + '"';
+ break;
+ };
+
+ return "";
+ }
+ };
+
+
+ /**
+ * Document group criterion
+ */
+ KorAP.DocGroup = {
+ _ldType : "docGroup",
+
+ create : function (parent, json) {
+ var obj = Object.create(KorAP.JsonLD).upgradeTo(KorAP.DocGroup);
+ obj._operands = [];
+ obj.fromJson(json);
+ if (parent !== undefined)
+ obj._parent = parent;
+ return obj;
+ },
+
+ newAfter : function (obj) {
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === obj) {
+ var operand = KorAP.UnspecifiedDoc.create(this);
+ this._operands.splice(i + 1, 0, operand);
+ return this.update();
+ };
+ };
+ },
+
+ // The doc is already set in the group
+ _duplicate : function (operand) {
+ if (operand.ldType() !== 'doc')
+ return null;
+
+ for (var i = 0; i < this._operands.length; i++) {
+ var op = this.getOperand(i);
+ if (op.ldType() === 'doc'
+ && operand.key() === op.key()
+ && operand.matchop() === op.matchop()
+ && operand.value() === op.value()) {
+ return op;
+ };
+ };
+ return null;
+ },
+
+ append : function (operand) {
+
+ // Append unspecified object
+ if (operand === undefined) {
+
+ // Be aware of cyclic structures!
+ operand = KorAP.UnspecifiedDoc.create(this);
+ this._operands.push(operand);
+ return operand;
+ };
+
+ switch (operand["@type"]) {
+
+ case undefined:
+ // No @type defined
+ if (operand["ldType"] !== undefined) {
+ if (operand.ldType() !== 'doc' &&
+ operand.ldType() !== 'docGroup') {
+ KorAP.log(812, "Operand not supported in document group");
+ return;
+ };
+ // Be aware of cyclic structures!
+ operand.parent(this);
+
+ var dupl = this._duplicate(operand);
+ if (dupl === null) {
+ this._operands.push(operand);
+ return operand;
+ };
+ return dupl;
+ };
+
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+
+ case "koral:doc":
+ // Be aware of cyclic structures!
+ var doc = KorAP.Doc.create(this, operand);
+ if (doc === undefined)
+ return;
+ var dupl = this._duplicate(doc);
+ if (dupl === null) {
+ this._operands.push(doc);
+ return doc;
+ };
+ return dupl;
+
+ case "koral:docGroup":
+ // Be aware of cyclic structures!
+ var docGroup = KorAP.DocGroup.create(this, operand);
+ if (docGroup === undefined)
+ return;
+
+ // Flatten group
+ if (docGroup.operation() === this.operation()) {
+ for (var op in docGroup.operands()) {
+ op = docGroup.getOperand(op);
+ var dupl = this._duplicate(op);
+ if (dupl === null) {
+ this._operands.push(op);
+ op.parent(this);
+ };
+ };
+ docGroup._operands = [];
+ docGroup.destroy();
+ return this;
+ };
+ this._operands.push(docGroup);
+ return docGroup;
+
+ default:
+ KorAP.log(812, "Operand not supported in document group");
+ return;
+ };
+ },
+
+ update : function () {
+ // There is only one operand in group
+
+ if (this._operands.length === 1) {
+
+ var parent = this.parent();
+ var op = this.getOperand(0);
+
+ // This will prevent destruction of
+ // the operand
+ this._operands = [];
+
+ // Parent is a group
+ if (parent.ldType() !== null)
+ return parent.replaceOperand(this, op).update();
+
+ // Parent is vc
+ else {
+ this.destroy();
+ // Cyclic madness
+ parent.root(op);
+ op.parent(parent);
+ return parent.root();
+ };
+ };
+
+ if (this._element === undefined)
+ return this;
+
+ var group = this._element;
+ group.setAttribute('data-operation', this.operation());
+
+ _removeChildren(group);
+
+ // Append operands
+ for (var i = 0; i < this._operands.length; i++) {
+ group.appendChild(
+ this.getOperand(i).element()
+ );
+ };
+
+ // Set operators
+ var op = this.operators(
+ this.operation() == 'and' ? false : true,
+ this.operation() == 'or' ? false : true,
+ true
+ );
+
+ group.appendChild(
+ op.element()
+ );
+
+ return this;
+ },
+
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'docGroup');
+
+ // Update the object - including optimization
+ this.update();
+
+ return this._element;
+ },
+
+ operation : function (op) {
+ if (arguments.length === 1) {
+ if (KorAP._validGroupOpRE.test(op)) {
+ this._op = op;
+ }
+ else {
+ KorAP.log(810, "Unknown operation type");
+ return;
+ };
+ };
+ return this._op || 'and';
+ },
+
+ operands : function () {
+ return this._operands;
+ },
+
+ getOperand : function (index) {
+ return this._operands[index];
+ },
+
+ // Replace operand
+ replaceOperand : function (oldOp, newOp) {
+
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === oldOp) {
+
+ // Just insert a doc or ...
+ if (newOp.ldType() === "doc" ||
+ newOp.ldType() === "non" ||
+ // ... insert a group of a different operation
+ // (i.e. "and" in "or"/"or" in "and")
+ newOp.operation() != this.operation()) {
+ this._operands[i] = newOp;
+ newOp.parent(this);
+ }
+
+ // Flatten group
+ else {
+ // Remove old group
+ this._operands.splice(i, 1);
+
+ // Inject new operands
+ for (var op in newOp.operands().reverse()) {
+ op = newOp.getOperand(op);
+ this._operands.splice(i, 0, op);
+ op.parent(this);
+ };
+ // Prevent destruction of operands
+ newOp._operands = [];
+ newOp.destroy();
+ };
+ oldOp.destroy();
+ return this;
+ }
+ };
+ return false;
+ },
+
+ // Delete operand from group
+ delOperand : function (obj) {
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i] === obj) {
+
+ // Delete identified operand
+ this._operands.splice(i,1);
+
+ // Destroy object for cyclic references
+ obj.destroy();
+
+ return this;
+ };
+ };
+
+ // Operand not found
+ return undefined;
+ },
+
+ // Deserialize from json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ if (json["operation"] === undefined ||
+ typeof json["operation"] !== 'string') {
+ KorAP.log(811, "Document group expects operation");
+ return;
+ };
+
+ var operation = json["operation"];
+
+ this.operation(operation.replace(/^operation:/,''));
+
+ if (json["operands"] === undefined ||
+ !(json["operands"] instanceof Array)) {
+ KorAP.log(704, "Operation needs operand list")
+ return;
+ };
+
+ // Add all documents
+ for (var i in json["operands"]) {
+ var operand = json["operands"][i];
+ this.append(operand);
+ };
+
+ return this;
+ },
+
+ toJson : function () {
+ var opArray = new Array();
+ for (var i = 0; i < this._operands.length; i++) {
+ if (this._operands[i].ldType() !== 'non')
+ opArray.push(this._operands[i].toJson());
+ };
+ return {
+ "@type" : "koral:" + this.ldType(),
+ "operation" : "operation:" + this.operation(),
+ "operands" : opArray
+ };
+ },
+
+ toQuery : function (brackets) {
+ var list = this._operands
+ .filter(function (op) {
+ return op.ldType() !== 'non';
+ })
+ .map(function (op) {
+ return (op.ldType() === 'docGroup') ?
+ op.toQuery(true) :
+ op.toQuery();
+ });
+
+ if (list.length === 1)
+ return list.join('');
+ else {
+ var str = list.join(this.operation() === 'or' ? ' | ' : ' & ');
+ return brackets ? '(' + str + ')' : str;
+ };
+ }
+ };
+
+
+ KorAP.RewriteList = {
+ // Construction method
+ create : function (json) {
+ var obj = Object(KorAP.JsonLD).
+ create().
+ upgradeTo(KorAP.RewriteList).
+ fromJson(json);
+ return obj;
+ },
+ fromJson : function (json) {
+ this._list = new Array();
+ for (var i = 0; i < json.length; i++) {
+ this._list.push(
+ KorAP.Rewrite.create(json[i])
+ );
+ };
+ return this;
+ },
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'rewrite');
+ for (var x in this._list) {
+ var rewrite = this._list[x];
+ var span = document.createElement('span');
+
+ // Set class attribute
+ span.setAttribute('class', rewrite.operation());
+
+ // Append source information
+ span.appendChild(document.createTextNode(rewrite.src()));
+
+ // Append scope information
+ if (rewrite.scope() !== undefined) {
+ span.appendChild(
+ document.createTextNode(
+ ': ' + rewrite.scope()
+ )
+ );
+ };
+ this._element.appendChild(span);
+ };
+ return this._element;
+ }
+ };
+
+
+ /**
+ * Implementation of rewrite objects.
+ */
+ KorAP.Rewrite = {
+
+ // Construction method
+ create : function (json) {
+ var obj = Object(KorAP.JsonLD).
+ create().
+ upgradeTo(KorAP.Rewrite).
+ fromJson(json);
+ return obj;
+ },
+
+ // Get or set source
+ src : function (string) {
+ if (arguments.length === 1)
+ this._src = string;
+ return this._src;
+ },
+
+ // Get or set operation
+ operation : function (op) {
+ if (arguments.length === 1) {
+ if (KorAP._validRewriteOpRE.test(op)) {
+ this._op = op;
+ }
+ else {
+ KorAP.log(814, "Unknown rewrite operation");
+ return;
+ };
+ };
+ return this._op || 'injection';
+ },
+
+ // Get or set scope
+ scope : function (attr) {
+ if (arguments.length === 1)
+ this._scope = attr;
+ return this._scope;
+ },
+
+ // Serialize from Json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ // Missing @type
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ // Missing source
+ if (json["src"] === undefined ||
+ typeof json["src"] !== 'string') {
+ KorAP.log(815, "Rewrite expects source");
+ return;
+ };
+
+ // Set source
+ this.src(json["src"]);
+
+ // Set operation
+ if (json["operation"] !== undefined) {
+ var operation = json["operation"];
+ this.operation(operation.replace(/^operation:/,''));
+ };
+
+ // Set scope
+ if (json["scope"] !== undefined &&
+ typeof json["scope"] === 'string')
+ this.scope(json["scope"]);
+
+ return this;
+ },
+
+ toString : function () {
+ var str = '';
+ var op = this.operation();
+ str += op.charAt(0).toUpperCase() + op.slice(1);
+ str += ' of ' + (
+ this._scope === null ?
+ 'object' :
+ '"' +
+ this.scope().replace(KorAP._quote, '\\$1') +
+ '"'
+ );
+ str += ' by ' +
+ '"' +
+ this.src().replace(KorAP._quote, '\\$1') +
+ '"';
+ return str;
+ }
+ };
+
+
+ /**
+ * Abstract JsonLD criterion object
+ */
+ KorAP.JsonLD = {
+ __changed : false,
+
+ create : function () {
+ return Object.create(KorAP.JsonLD);
+ },
+
+ /**
+ * Upgrade this object to another object
+ * while private data stays intact
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ ldType : function (type) {
+ if (arguments.length === 1)
+ this._ldType = type;
+ return this._ldType;
+ },
+
+ parent : function (obj) {
+ if (arguments.length === 1) {
+ this._parent = obj;
+ this.__changed = true;
+ };
+ return this._parent;
+ },
+
+ // Destroy object - especially for
+ // acyclic structures!
+ // I'm paranoid!
+ destroy : function () {
+ if (this._ops != undefined) {
+ this._ops._parent = undefined;
+ if (this._ops._element !== undefined)
+ this._ops._element.refTo = undefined;
+ this._ops = undefined;
+ };
+ if (this._element !== undefined)
+ this._element = undefined;
+
+ // In case of a group, destroy all operands
+ if (this._operands !== undefined) {
+ for (var i = 0; i < this._operands.length; i++)
+ this.getOperand(i).destroy();
+ this._operands = [];
+ };
+ },
+
+ // Wrap a new operation around the root group element
+ wrapOnRoot : function (op) {
+ var parent = this.parent();
+
+ var group = KorAP.DocGroup.create(parent);
+ if (arguments.length === 1)
+ group.operation(op);
+ else
+ group.operation(
+ this.operation() === 'and' ? 'or' : 'and'
+ );
+ group.append(this);
+ this.parent(group);
+ group.append();
+ group.element(); // Init (seems to be necessary)
+ parent.root(group);
+ return this.parent();
+ },
+
+ // Be aware! This may be cyclic
+ operators : function (and, or, del) {
+ if (arguments === 0)
+ return this._ops;
+ this._ops = KorAP.Operators.create(
+ and, or, del
+ );
+ this._ops.parent(this);
+ return this._ops;
+ },
+
+ toJson : function () {
+ return {
+ // Unspecified object
+ "@type" : "koral:" + this.ldType()
+ };
+ },
+
+ toQuery : function () {
+ return '';
+ }
+ };
+
+
+ /**
+ * Criterion in a KorAP.Doc
+ */
+ KorAP._changeKey = function () {
+ var doc = this.parentNode.refTo;
+ var key = doc.element().firstChild;
+ key.appendChild(KorAP.FieldChooser.element());
+ KorAP.FieldChooser.show();
+ KorAP.FieldChooser.focus();
+ // key, matchop, type, value
+ };
+
+ // Field menu
+ KorAP.FieldMenu = {
+ create : function (params) {
+ return Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.FieldMenu)
+ ._init(KorAP.FieldMenuItem, undefined, params)
+ }
+ };
+
+
+ // Field menu item
+ KorAP.FieldMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.FieldMenuItem)
+ ._init(params);
+ },
+ _init : function (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._value = params[1];
+ this._type = params[2];
+
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ return this;
+ },
+ name : function () {
+ return this._name;
+ },
+ type : function () {
+ return this._type;
+ },
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+ li.setAttribute("data-type", this._type);
+ li.setAttribute("data-value", this._value);
+ li.appendChild(document.createTextNode(this._name));
+ return this._element = li;
+ }
+ };
+
+ KorAP.FieldChooser = KorAP.FieldMenu.create([
+ ['Titel', 'title', 'string'],
+ ['Untertitel', 'subTitle', 'string'],
+ ['Veröffentlichungsdatum', 'pubDate', 'date'],
+ ['Autor', 'author', 'string']
+ ]);
+ KorAP.FieldChooser.limit(5);
+
+}(this.KorAP));