├── slurp
├── src
│ └── main
│ │ ├── resources
│ │ └── reference.conf
│ │ ├── scala
│ │ ├── morph
│ │ │ ├── extractor
│ │ │ │ ├── Extractor.scala
│ │ │ │ ├── AstTransformer.scala
│ │ │ │ └── Interface.scala
│ │ │ ├── parser
│ │ │ │ ├── ParsingException.scala
│ │ │ │ ├── AstBuilder.scala
│ │ │ │ ├── WhiteSpaceExpansion.scala
│ │ │ │ ├── XmlParser.scala
│ │ │ │ ├── Interface.scala
│ │ │ │ ├── BaseParser.scala
│ │ │ │ ├── CsvParser.scala
│ │ │ │ └── JsonParser.scala
│ │ │ ├── utils
│ │ │ │ └── Utils.scala
│ │ │ └── ast
│ │ │ │ ├── Ast.scala
│ │ │ │ └── DSL.scala
│ │ ├── slurp
│ │ │ ├── QueueDumper.scala
│ │ │ ├── Slurp.scala
│ │ │ └── GitHubStream.scala
│ │ └── Counter.scala
│ │ └── java
│ │ ├── org
│ │ └── json
│ │ │ ├── JSONString.java
│ │ │ ├── JSONException.java
│ │ │ ├── HTTPTokener.java
│ │ │ ├── Property.java
│ │ │ ├── JSONStringer.java
│ │ │ ├── CookieList.java
│ │ │ ├── HTTP.java
│ │ │ ├── Cookie.java
│ │ │ ├── CDL.java
│ │ │ ├── JSONWriter.java
│ │ │ ├── XMLTokener.java
│ │ │ ├── Kim.java
│ │ │ ├── JSONTokener.java
│ │ │ ├── JSONML.java
│ │ │ └── XML.java
│ │ └── DeliveryBoy.java
├── .gitignore
├── application.example.conf
└── build.sbt
├── cartographer
├── public
│ ├── .gitignore
│ ├── favicon.ico
│ ├── media
│ │ └── tooltip.png
│ ├── index.html
│ ├── css
│ │ └── gitlive.css
│ └── gitlive.js
├── cache
│ └── .gitignore
├── .gitignore
├── README.md
├── readyComponents
├── bower.json
├── package.json
└── app.js
├── runCartographer
├── runSlurp
├── README.md
└── LICENSE.md
/slurp/src/main/resources/reference.conf:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cartographer/public/.gitignore:
--------------------------------------------------------------------------------
1 | components/
2 |
--------------------------------------------------------------------------------
/cartographer/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/slurp/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | application.conf
3 |
--------------------------------------------------------------------------------
/cartographer/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 |
--------------------------------------------------------------------------------
/cartographer/README.md:
--------------------------------------------------------------------------------
1 | # Cartographer
2 |
3 | Real-time GitHub Data Visualization
4 |
--------------------------------------------------------------------------------
/cartographer/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anishathalye/gitlive/HEAD/cartographer/public/favicon.ico
--------------------------------------------------------------------------------
/cartographer/public/media/tooltip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anishathalye/gitlive/HEAD/cartographer/public/media/tooltip.png
--------------------------------------------------------------------------------
/slurp/application.example.conf:
--------------------------------------------------------------------------------
1 | github {
2 | clientId = xxxxxxxxxxxxxxxxxxxx
3 | clientSecret = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4 | }
5 |
--------------------------------------------------------------------------------
/runCartographer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6 |
7 | cd $BASEDIR/cartographer
8 |
9 | forever --minUptime 500ms --spinSleepTime 1000ms app.js
10 |
--------------------------------------------------------------------------------
/cartographer/readyComponents:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6 |
7 | cd $BASEDIR
8 |
9 | mkdir -p public/components
10 |
11 | cp bower_components/datamaps/dist/datamaps.world.min.js public/components/datamaps.world.min.js
12 | cp bower_components/d3-tip/index.js public/components/d3-tip.js
13 |
--------------------------------------------------------------------------------
/cartographer/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cartographer",
3 | "homepage": "https://github.com/anishathalye/gh-vis-challenge",
4 | "private": true,
5 | "ignore": [
6 | "**/.*",
7 | "node_modules",
8 | "public/components",
9 | "test",
10 | "tests"
11 | ],
12 | "dependencies": {
13 | "datamaps": "~0.3.3",
14 | "d3-tip": "~0.6.4"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/runSlurp:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -eu
4 |
5 | BASEDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6 |
7 | slurp() {
8 | (
9 | cd $BASEDIR/slurp
10 | sbt 'runMain slurp.Slurp'
11 | )
12 | }
13 |
14 | while [ 1 ]; do
15 | echo 'Running slurp...'
16 | slurp
17 | if [ $? == 130 ]; then
18 | exit 130
19 | fi
20 | done
21 |
--------------------------------------------------------------------------------
/cartographer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Cartographer",
3 | "description": "Real-time GitHub Data Visualization",
4 | "repository": "https://github.com/anishathalye/gh-vis-challenge",
5 | "version": "1.0.0",
6 | "dependencies": {
7 | "compression": "^1.0.11",
8 | "express": "^4.8.4",
9 | "express-minify": "0.0.11",
10 | "rabbit.js": "^0.4.1"
11 | },
12 | "engine": "node >= 0.10"
13 | }
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GitHub Data Visualization Challenge 2014
2 |
3 | This is no longer hosted on the internet. For more info, see my [blog][blog].
4 |
5 | Made with ♥ by Anish Athalye ([anishathalye][anishathalye]).
6 |
7 | The code is a bit of a mess. Use it at your own risk.
8 |
9 | License
10 | -------
11 |
12 | Copyright (c) 2014 Anish Athalye. Released under the MIT License. See
13 | [LICENSE.md][license] for details.
14 |
15 | [blog]: http://www.anishathalye.com/2015/07/07/bye-git-live/
16 | [anishathalye]: https://github.com/anishathalye
17 | [license]: LICENSE.md
18 |
--------------------------------------------------------------------------------
/slurp/build.sbt:
--------------------------------------------------------------------------------
1 | name := "slurp"
2 |
3 | scalaVersion := "2.11.2"
4 |
5 | scalacOptions := Seq(
6 | "-unchecked", "-deprecation", "-feature", "-Xfatal-warnings"
7 | )
8 |
9 | unmanagedClasspath in Runtime += baseDirectory.value
10 |
11 | libraryDependencies ++= Seq(
12 | "org.scala-lang.modules" %% "scala-async" % "0.9.2",
13 | "com.typesafe" % "config" % "1.2.1",
14 | "org.parboiled" %% "parboiled-scala" % "1.1.6",
15 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.2",
16 | "com.rabbitmq" % "amqp-client" % "3.3.5",
17 | "com.twitter" %% "util-collection" % "6.20.0"
18 | )
19 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/extractor/Extractor.scala:
--------------------------------------------------------------------------------
1 | package morph.extractor
2 |
3 | import morph.ast._
4 |
5 | /**
6 | * The base class that all extractors should extend.
7 | *
8 | * This class mixes in the DSL and AST-related implicit conversions
9 | * so that subclasses can omit those imports.
10 | *
11 | * Subclasses must define the extract method (inherited from AstTransformer),
12 | * which takes a ValueNode and performs extractions / transformations on it.
13 | *
14 | * @author Anish Athalye
15 | */
16 | abstract class Extractor extends AstTransformer with DSL with Implicits
17 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/ParsingException.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | /**
4 | * An exception that parsers throw if they encounter an error while parsing.
5 | *
6 | * @author Anish Athalye
7 | */
8 | class ParsingException(message: String = null, cause: Throwable = null)
9 | extends RuntimeException(message, cause)
10 |
11 | object ParsingException {
12 |
13 | def apply() = new ParsingException(null, null)
14 |
15 | def apply(msg: String) = new ParsingException(msg, null)
16 |
17 | def apply(msg: String, cause: Throwable) = new ParsingException(msg, cause)
18 |
19 | def apply(cause: Throwable) = new ParsingException(null, cause)
20 | }
21 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/JSONString.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 | /**
3 | * The JSONString interface allows a toJSONString()
4 | * method so that a class can change the behavior of
5 | * JSONObject.toString(), JSONArray.toString(),
6 | * and JSONWriter.value(Object). The
7 | * toJSONString method will be used instead of the default behavior
8 | * of using the Object's toString() method and quoting the result.
9 | */
10 | public interface JSONString {
11 | /**
12 | * The toJSONString method allows a class to produce its own JSON
13 | * serialization.
14 | *
15 | * @return A strictly syntactically correct JSON text.
16 | */
17 | public String toJSONString();
18 | }
19 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/extractor/AstTransformer.scala:
--------------------------------------------------------------------------------
1 | package morph.extractor
2 |
3 | import morph.ast._
4 |
5 | /**
6 | * A trait describing methods that extractors must implement.
7 | *
8 | * The rest of the framework expects extractors to implement this trait.
9 | *
10 | * @author Anish Athalye
11 | */
12 | trait AstTransformer {
13 |
14 | /**
15 | * The main extraction / transformation method.
16 | *
17 | * @param node The node to transform.
18 | *
19 | * @return A transformed node.
20 | */
21 | final def apply(node: ValueNode): ValueNode = extract(node)
22 |
23 | /**
24 | * The main extraction / transformation method.
25 | *
26 | * @param node The node to transform.
27 | *
28 | * @return A transformed node.
29 | */
30 | def extract(node: ValueNode): ValueNode
31 | }
32 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/AstBuilder.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | /**
6 | * A trait describing methods that AST builders must implement.
7 | *
8 | * The rest of the framework expects parsers to implement this trait,
9 | * so all custom parsers should implement this trait.
10 | *
11 | * @author Anish Athalye
12 | */
13 | trait AstBuilder {
14 |
15 | /**
16 | * The main parsing method.
17 | *
18 | * @param input The content to parse.
19 | *
20 | * @return The root of the generated AST.
21 | */
22 | def apply(input: String): ValueNode
23 |
24 | /**
25 | * The main parsing method.
26 | *
27 | * @param input The content to parse.
28 | *
29 | * @return The root of the generated AST.
30 | */
31 | def apply(input: Array[Char]): ValueNode
32 | }
33 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/slurp/QueueDumper.scala:
--------------------------------------------------------------------------------
1 | package slurp
2 |
3 | import java.util.concurrent.BlockingQueue
4 |
5 | import com.rabbitmq.client._
6 |
7 | class QueueDumper(val queue: BlockingQueue[String]) {
8 |
9 | def createConnection() {
10 | val factory = new ConnectionFactory();
11 | factory.setHost("localhost");
12 | val connection = factory.newConnection();
13 | channel = connection.createChannel();
14 | channel.exchangeDeclare(EXCHANGE_NAME, "fanout")
15 | }
16 |
17 | def start() {
18 | createConnection()
19 | val thread = new Thread(new Runnable {
20 | override def run {
21 | loop()
22 | }
23 | })
24 | thread.start()
25 | }
26 |
27 | def loop() {
28 | while (true) {
29 | val msg = queue.take()
30 | channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes)
31 | }
32 | }
33 |
34 | private var channel: Channel = null
35 |
36 | private val EXCHANGE_NAME = "gh-events"
37 |
38 | private val HOST = "localhost"
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | **Copyright (c) 2014 Anish Athalye (me@anishathalye.com)**
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10 | of the Software, and to permit persons to whom the Software is furnished to do
11 | so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/JSONException.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /**
4 | * The JSONException is thrown by the JSON.org classes when things are amiss.
5 | *
6 | * @author JSON.org
7 | * @version 2013-02-10
8 | */
9 | public class JSONException extends RuntimeException {
10 | private static final long serialVersionUID = 0;
11 | private Throwable cause;
12 |
13 | /**
14 | * Constructs a JSONException with an explanatory message.
15 | *
16 | * @param message
17 | * Detail about the reason for the exception.
18 | */
19 | public JSONException(String message) {
20 | super(message);
21 | }
22 |
23 | /**
24 | * Constructs a new JSONException with the specified cause.
25 | */
26 | public JSONException(Throwable cause) {
27 | super(cause.getMessage());
28 | this.cause = cause;
29 | }
30 |
31 | /**
32 | * Returns the cause of this exception or null if the cause is nonexistent
33 | * or unknown.
34 | *
35 | * @returns the cause of this exception or null if the cause is nonexistent
36 | * or unknown.
37 | */
38 | public Throwable getCause() {
39 | return this.cause;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/Counter.scala:
--------------------------------------------------------------------------------
1 | import collection.mutable.{ Set => MSet }
2 |
3 | import com.rabbitmq.client._
4 |
5 | import morph.ast._, DSL._, Implicits._
6 | import morph.parser._
7 |
8 | object Counter {
9 |
10 | def main(args: Array[String]) {
11 | val factory = new ConnectionFactory()
12 | factory.setHost("localhost")
13 | val connection = factory.newConnection()
14 | val channel = connection.createChannel()
15 |
16 | channel.exchangeDeclare(EXCHANGE_NAME, "fanout")
17 | val queueName = channel.queueDeclare().getQueue()
18 | channel.queueBind(queueName, EXCHANGE_NAME, "")
19 |
20 | val consumer = new QueueingConsumer(channel)
21 | channel.basicConsume(queueName, true, consumer)
22 |
23 | val locs = MSet[String]()
24 |
25 | val startTime = System.nanoTime
26 | var messages = 0
27 |
28 | while (true) {
29 | val delivery = consumer.nextDelivery()
30 | val message = new String(delivery.getBody())
31 | val json = JsonParser(message)
32 | val loc = (json ~> "location") foreach { l =>
33 | locs += l.asString
34 | messages += 1
35 | val time = (System.nanoTime - startTime) / 1000000000
36 | println(s"${locs.size} unique, ${messages} total in ${time} seconds")
37 | }
38 | }
39 | }
40 |
41 | val EXCHANGE_NAME = "gh-events"
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/extractor/Interface.scala:
--------------------------------------------------------------------------------
1 | package morph.extractor
2 |
3 | import morph.ast._
4 |
5 | import io.Source
6 |
7 | /**
8 | * An interface to make using extractors easy.
9 | *
10 | * This interface makes calling extractors look like part of
11 | * the DSL.
12 | *
13 | * @example
14 | * {{{
15 | * scala> val transformed = transform node dataNode using DataNodeExtractor
16 | * transformed: morph.ast.ValueNode = ...
17 | * }}}
18 | *
19 | * @author Anish Athalye
20 | */
21 | object Interface {
22 |
23 | /**
24 | * An object to help transform various data sources.
25 | */
26 | object transform {
27 |
28 | /**
29 | * Transform a node.
30 | *
31 | * @param node The node to transform.
32 | *
33 | * @return A Transformable instance that can be transformed using an Extractor.
34 | */
35 | def node(node: ValueNode): Transformable = new Transformable(node)
36 | }
37 |
38 | /**
39 | * A class that holds ready-to-transform data.
40 | */
41 | class Transformable(node: ValueNode) {
42 |
43 | /**
44 | * Transform data using a specific extractor.
45 | *
46 | * @param extractor The extractor to use.
47 | *
48 | * @return The root of the transformed AST.
49 | */
50 | def using(extractor: AstTransformer): ValueNode = extractor(node)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/slurp/Slurp.scala:
--------------------------------------------------------------------------------
1 | package slurp
2 |
3 | import java.util.concurrent.{ BlockingQueue, LinkedBlockingQueue }
4 |
5 | import com.typesafe.config.ConfigFactory
6 |
7 | import dispatch._, Defaults._
8 |
9 | import morph.ast._, DSL._, Implicits._
10 |
11 | object Slurp {
12 |
13 | def main(args: Array[String]) {
14 | val config = ConfigFactory.load()
15 | val clientId = config.getString("github.clientId")
16 | val clientSecret = config.getString("github.clientSecret")
17 | val ghs = new GitHubStream(clientId, clientSecret)
18 | val queue = new LinkedBlockingQueue[String]()
19 | val qd = new QueueDumper(queue)
20 | qd.start()
21 | mainLoop(ghs, queue)
22 | }
23 |
24 | def mainLoop(ghs: GitHubStream, queue: BlockingQueue[String]) {
25 | while (true) {
26 | try {
27 | val (events, pollInterval) = ghs.getEvents()
28 | val timeStep = pollInterval * 1000
29 | if (events.nonEmpty) {
30 | events foreach { event =>
31 | queue.offer(event)
32 | Thread.sleep(timeStep)
33 | }
34 | } else {
35 | Thread.sleep(EMPTY_SLEEP)
36 | }
37 | } catch {
38 | case e: Exception => {
39 | e.printStackTrace()
40 | Thread.sleep(EMPTY_SLEEP)
41 | }
42 | }
43 | }
44 | }
45 |
46 | private val EMPTY_SLEEP = 5000
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/slurp/src/main/java/DeliveryBoy.java:
--------------------------------------------------------------------------------
1 | import java.io.IOException;
2 | import com.rabbitmq.client.ConnectionFactory;
3 | import com.rabbitmq.client.Connection;
4 | import com.rabbitmq.client.Channel;
5 | import com.rabbitmq.client.QueueingConsumer;
6 |
7 | public class DeliveryBoy {
8 |
9 | private static final String EXCHANGE_NAME = "gh-events";
10 |
11 | public static void main(String[] argv)
12 | throws java.io.IOException,
13 | java.lang.InterruptedException {
14 |
15 | ConnectionFactory factory = new ConnectionFactory();
16 | factory.setHost("localhost");
17 | Connection connection = factory.newConnection();
18 | Channel channel = connection.createChannel();
19 |
20 | channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
21 | String queueName = channel.queueDeclare().getQueue();
22 | channel.queueBind(queueName, EXCHANGE_NAME, "");
23 |
24 | System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
25 |
26 | QueueingConsumer consumer = new QueueingConsumer(channel);
27 | channel.basicConsume(queueName, true, consumer);
28 |
29 | while (true) {
30 | QueueingConsumer.Delivery delivery = consumer.nextDelivery();
31 | String message = new String(delivery.getBody());
32 |
33 | System.out.println(" [x] Received '" + message + "'");
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/WhiteSpaceExpansion.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import scala.language.implicitConversions
4 |
5 | import org.parboiled.scala._
6 |
7 | /**
8 | * A mixin for Parsers that makes matching whitespace easier.
9 | *
10 | * When this trait is mixed in, all strings of the form `"text "`
11 | * (note the trailing space) are automatically converted to a
12 | * rule that matches all trailing whitespace characters as well.
13 | * Whitespace characters are defined as any of:
14 | * `' '`, `'\n'`, `'\r'`, `'\t'`, `'\f'`, but this behavior can
15 | * be overridden.
16 | *
17 | * @author Anish Athalye
18 | */
19 | trait WhiteSpaceExpansion {
20 |
21 | // self type, class mixing this in must be a Parser
22 | this: Parser =>
23 |
24 | /**
25 | * Rule that matches all whitespace.
26 | *
27 | * Matches all whitespace including `' '` (space), `'\n'` (newline),
28 | * `'\r'` (carriage return), `'\t'` (tab), and `'\f'` (form feed).
29 | */
30 | def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf(" \n\r\t\f")) }
31 |
32 | /**
33 | * An implicit conversion to make writing whitespace matching rules
34 | * less tedious.
35 | *
36 | * When converting a string to a rule, if the string ends with a space,
37 | * this automatically converts it to a rule that matches any whitespace
38 | * after the string.
39 | */
40 | override implicit def toRule(string: String) = {
41 | if (string.endsWith(" ")) {
42 | str(string.trim) ~ WhiteSpace
43 | } else {
44 | str(string)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/XmlParser.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | import org.json.{ XML, JSONException }
6 |
7 | /**
8 | * An XML parser that constructs an AST.
9 | *
10 | * Instead of implementing an XML parser from scratch, this
11 | * parser uses an already existing implementation of an XML
12 | * parser to convert XML to JSON, and then uses the
13 | * [[morph.parser.JsonParser]] to convert that to a native AST.
14 | *
15 | * This parser follows the guidelines available at
16 | * www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html
17 | * to convert XML to the native AST format.
18 | *
19 | * XML attributes are prepended with an `@`, and content can be found
20 | * in `#text`.
21 | *
22 | * Arrays will be transformed to JSON arrays.
23 | *
24 | * @author Anish Athalye
25 | */
26 | object XmlParser extends AstBuilder {
27 |
28 | /**
29 | * The main parsing method.
30 | *
31 | * @param input The content to parse.
32 | *
33 | * @return The root of the generated AST.
34 | */
35 | def apply(input: String) = {
36 | try {
37 | val json = XML toJSONObject input toString 0
38 | JsonParser(json) // to get the XML in native AST form
39 | } catch {
40 | case ex: JSONException => throw ParsingException(ex.getMessage)
41 | }
42 | }
43 |
44 | /**
45 | * The main parsing method.
46 | *
47 | * @param input The content to parse.
48 | *
49 | * @return The root of the generated AST.
50 | */
51 | def apply(input: Array[Char]) = apply(input.mkString)
52 | }
53 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/Interface.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | import io.Source
6 |
7 | /**
8 | * An interface to make using parsers easy.
9 | *
10 | * This interface makes calling parsers look like part of
11 | * the DSL.
12 | *
13 | * @example
14 | * {{{
15 | * scala> val root = parse file "data.xml" using XmlParser
16 | * root: morph.ast.ValueNode = ...
17 | * }}}
18 | *
19 | * @author Anish Athalye
20 | */
21 | object Interface {
22 |
23 | /**
24 | * An object to help parse various data sources.
25 | */
26 | object parse {
27 |
28 | /**
29 | * Parse a string.
30 | *
31 | * @param str The string to parse.
32 | *
33 | * @return A Parsable instance that can be parsed using an AstBuilder.
34 | */
35 | def string(str: String): Parsable = new Parsable(str)
36 |
37 | /**
38 | * Parse a file specified by a given path.
39 | *
40 | * @param path The path of the file to parse.
41 | *
42 | * @throws FileNotFoundException If the file cannot be accessed.
43 | *
44 | * @return A Parsable instance that can be parsed using an AstBuilder.
45 | */
46 | def file(path: String): Parsable = {
47 | val source = Source fromFile path
48 | val data = source.getLines mkString "\n"
49 | source.close()
50 | new Parsable(data)
51 | }
52 | }
53 |
54 | /**
55 | * A class that holds ready-to-parse data.
56 | */
57 | class Parsable(data: String) {
58 |
59 | /**
60 | * Parse data using a specific parser.
61 | *
62 | * @param parser The parser to use.
63 | *
64 | * @throws ParsingException If an error occurs during parsing.
65 | *
66 | * @return The root of the AST generated by parsing the data.
67 | */
68 | def using(parser: AstBuilder): ValueNode = parser(data)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/BaseParser.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | import org.parboiled.scala._
6 | import org.parboiled.errors.ErrorUtils
7 |
8 | /**
9 | * A base class for parsers.
10 | *
11 | * This class provides the boilerplate code that all Parboiled
12 | * parsers will need. Subclasses only need to provide the
13 | * parboiled rules for transforming input into an AST.
14 | *
15 | * @author Anish Athalye
16 | */
17 | abstract class BaseParser extends Parser with AstBuilder {
18 |
19 | /**
20 | * The root parsing rule.
21 | *
22 | * This is the rule that will be run when there is input
23 | * to be parsed. It should return the root of the generated AST.
24 | */
25 | def RootRule: Rule1[ValueNode]
26 |
27 | /**
28 | * The main parsing method.
29 | *
30 | * If an error is encountered when parsing, this method does not
31 | * make any attempt to continue.
32 | *
33 | * @param input The content to parse.
34 | *
35 | * @throws ParsingException If the parser encounters an error while parsing.
36 | *
37 | * @return The root of the generated AST.
38 | */
39 | def apply(input: String): ValueNode = apply(input.toCharArray)
40 |
41 | /**
42 | * The main parsing method.
43 | *
44 | * If an error is encountered when parsing, this method does not
45 | * make any attempt to continue.
46 | *
47 | * @param input The content to parse.
48 | *
49 | * @throws ParsingException If the parser encounters an error while parsing.
50 | *
51 | * @return The root of the generated AST.
52 | */
53 | def apply(input: Array[Char]): ValueNode = {
54 | val parsingResult = ReportingParseRunner(RootRule).run(input)
55 | parsingResult.result getOrElse {
56 | throw ParsingException("Invalid source:\n" +
57 | ErrorUtils.printParseErrors(parsingResult))
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/CsvParser.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | import org.parboiled.scala._
6 | import org.parboiled.Context
7 |
8 | import scala.collection.mutable.Builder
9 |
10 | /**
11 | * A CSV parser that constructs an AST.
12 | *
13 | * This parser can parse CSV files that conform to the RFC 4180 spec.
14 | * This parser was implemented almost directly from the ABNF grammar
15 | * found in the RFC. Note that input to this parser must be valid
16 | * according to the spec to be able to be correctly parsed.
17 | *
18 | * @author Anish Athalye
19 | */
20 | object CsvParser extends BaseParser {
21 |
22 | def RootRule = Csv
23 |
24 | lazy val Csv = rule { File ~ optional(CRLF) ~ EOI }
25 |
26 | def File = rule { FileUnwrapped ~~> { ArrayNode(_) } }
27 |
28 | def FileUnwrapped = rule { Records ~~> { _.result } }
29 |
30 | def Records = rule {
31 | push(Vector.newBuilder[ArrayNode]) ~ oneOrMore(rule {
32 | Record ~~% { withContext(appendToVb(_: ArrayNode, _)) }
33 | }, separator = CRLF)
34 | }
35 |
36 | def Record = rule { RecordUnwrapped ~~> { ArrayNode(_) } }
37 |
38 | def RecordUnwrapped = rule { Fields ~~> { _.result } }
39 |
40 | def Fields = rule {
41 | push(Vector.newBuilder[StringNode]) ~ oneOrMore(rule {
42 | Field ~~% { withContext(appendToVb(_: StringNode, _)) }
43 | }, separator = COMMA)
44 | }
45 |
46 | def Field = rule { Escaped | NonEscaped }
47 |
48 | def Escaped = rule {
49 | DQUOTE ~
50 | zeroOrMore(TEXTDATA | COMMA | CR | LF | DDQUOTE) ~>
51 | { s => StringNode(unDDQUOTE(s)) } ~
52 | DQUOTE
53 | }
54 |
55 | def NonEscaped = rule {
56 | zeroOrMore(TEXTDATA) ~> { s => StringNode(s) }
57 | }
58 |
59 | def unDDQUOTE(s: String) = s.replace(DDQUOTE, DQUOTE)
60 |
61 | def COMMA = ","
62 |
63 | def CR = "\r"
64 |
65 | def LF = "\n"
66 |
67 | def CRLF = rule { LF | CR + LF }
68 |
69 | def DQUOTE = "\""
70 |
71 | def DDQUOTE = "\"\""
72 |
73 | def TEXTDATA = rule { " " - "!" | "#" - "+" | "-" - "~" }
74 |
75 | def appendToVb[T](e: T, ctx: Context[_]) {
76 | ctx.getValueStack.peek.asInstanceOf[Builder[T, Vector[T]]] += e
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/utils/Utils.scala:
--------------------------------------------------------------------------------
1 | package morph.utils
2 |
3 | /**
4 | * A collection of various utilities.
5 | *
6 | * Most utilities are in the form of implicit classes contained
7 | * inside the `Utils` object.
8 | *
9 | * @author Anish Athalye
10 | */
11 | object Utils {
12 |
13 | /**
14 | * An implicit class to provide additional methods on `String`.
15 | */
16 | implicit class StringUtils(val str: String) extends AnyVal {
17 |
18 | /**
19 | * Indent every line in the by two spaces.
20 | *
21 | * @note This works as expected with both `"\n"` and `"\r\n"` newlines.
22 | *
23 | * @return The indented string.
24 | */
25 | def indent: String = str indent 2
26 |
27 | /**
28 | * Indent every line in a string by a specified number of spaces.
29 | *
30 | * @note This works as expected with both `"\n"` and `"\r\n"` newlines.
31 | *
32 | * @param num The number of spaces to indent every line by.
33 | *
34 | * @return The indented string.
35 | */
36 | def indent(num: Int): String =
37 | (" " * num) + str.replace("\n", "\n" + " " * num)
38 |
39 | /**
40 | * Escape special characters with backslash escapes.
41 | *
42 | * This escapes the special charactesr in the string so that the string
43 | * will be suitable for being displayed as a double quoted string.
44 | *
45 | * The escape codes specified in JLS 3.10.6 are implemented, with the
46 | * exception of the single quote character (which does not need to be
47 | * escaped in a double quoted string).
48 | *
49 | * @example
50 | * {{{
51 | * scala> val str = """hello\
52 | * | world"""
53 | * str: String =
54 | * hello\
55 | * world
56 | *
57 | * scala> val escaped = str.escaped
58 | * escaped: String = hello\\\nworld
59 | * }}}
60 | *
61 | * @return The string with escapes visibly escaped.
62 | */
63 | def escaped: String = str flatMap {
64 | case '\b' => "\\b"
65 | case '\f' => "\\f"
66 | case '\n' => "\\n"
67 | case '\r' => "\\r"
68 | case '\t' => "\\t"
69 | case '\\' => "\\\\"
70 | case '"' => "\\\""
71 | case c => c.toString
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/cartographer/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var compression = require('compression');
3 | var minify = require('express-minify');
4 | var rabbit = require('rabbit.js');
5 | var connections = [];
6 |
7 | var context = rabbit.createContext();
8 | context.on('ready', function() {
9 | console.log('Connection ready');
10 | var sub = context.socket('SUB');
11 | sub.setEncoding('utf-8');
12 | sub.connect('gh-events', '', function() {
13 | console.log('Subscribed')
14 | sub.on('data', function(data) {
15 | consume(data);
16 | });
17 | });
18 | });
19 |
20 | app = express();
21 | app.all(/.*/, function(req, res, next) {
22 | var host = req.header("host");
23 | if (host.match(/^www\..*/i)) {
24 | next();
25 | } else {
26 | res.redirect(301, "http://www." + host);
27 | }
28 | });
29 | app.use(compression());
30 | app.use(minify({cache: __dirname + '/cache'}));
31 | app.use(express.static(__dirname + '/public'));
32 |
33 | app.get('/events', function(req, res) {
34 | if (req.headers.accept == 'text/event-stream') {
35 | res.writeHead(200, {
36 | 'content-type': 'text/event-stream',
37 | 'cache-control': 'no-cache',
38 | 'connection': 'keep-alive'
39 | });
40 | connections.push(res);
41 | console.log('Connection added for ' + req.ip);
42 |
43 | req.on('close', function () {
44 | removeConnection(res);
45 | });
46 | } else {
47 | res.status(500).send('This path for EventSource subscription only...');
48 | }
49 | });
50 |
51 | app.listen(8000);
52 |
53 | function consume(message) {
54 | broadcast(JSON.stringify(message));
55 | }
56 |
57 | function broadcast(data) {
58 | var id = (new Date()).toLocaleTimeString();
59 | connections.forEach(function (res) {
60 | writeEvent(res, id, data);
61 | });
62 | }
63 |
64 | function writeEvent(res, id, data) {
65 | res.write('id: ' + id + '\n');
66 | res.write('data: ' + data + '\n\n');
67 | res.flush();
68 | }
69 |
70 | function removeConnection(res) {
71 | var i = connections.indexOf(res);
72 | if (i !== -1) {
73 | connections.splice(i, 1);
74 | console.log('Connection closed.');
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/HTTPTokener.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * The HTTPTokener extends the JSONTokener to provide additional methods
29 | * for the parsing of HTTP headers.
30 | * @author JSON.org
31 | * @version 2012-11-13
32 | */
33 | public class HTTPTokener extends JSONTokener {
34 |
35 | /**
36 | * Construct an HTTPTokener from a string.
37 | * @param string A source string.
38 | */
39 | public HTTPTokener(String string) {
40 | super(string);
41 | }
42 |
43 |
44 | /**
45 | * Get the next token or string. This is used in parsing HTTP headers.
46 | * @throws JSONException
47 | * @return A String.
48 | */
49 | public String nextToken() throws JSONException {
50 | char c;
51 | char q;
52 | StringBuffer sb = new StringBuffer();
53 | do {
54 | c = next();
55 | } while (Character.isWhitespace(c));
56 | if (c == '"' || c == '\'') {
57 | q = c;
58 | for (;;) {
59 | c = next();
60 | if (c < ' ') {
61 | throw syntaxError("Unterminated string.");
62 | }
63 | if (c == q) {
64 | return sb.toString();
65 | }
66 | sb.append(c);
67 | }
68 | }
69 | for (;;) {
70 | if (c == 0 || Character.isWhitespace(c)) {
71 | return sb.toString();
72 | }
73 | sb.append(c);
74 | c = next();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/cartographer/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Git Live is a visualization of collaboration and interaction on GitHub. It uses the 27 | GitHub Events API and the 28 | Google Maps API 29 | to map interactions between people around the globe in real-time.
30 | 31 |Git Live tracks several different types of activities: 32 | issues, pull requests, 33 | forks, and stars.
34 | 35 |To see the details of what people are up to, you can move your mouse over the circles, 36 | and you'll get something that looks like this:
37 | 38 |
39 |
35 | * A JSONStringer instance provides a value method for appending
36 | * values to the
37 | * text, and a key
38 | * method for adding keys before values in objects. There are array
39 | * and endArray methods that make and bound array values, and
40 | * object and endObject methods which make and bound
41 | * object values. All of these methods return the JSONWriter instance,
42 | * permitting cascade style. For example,
43 | * myString = new JSONStringer()
44 | * .object()
45 | * .key("JSON")
46 | * .value("Hello, World!")
47 | * .endObject()
48 | * .toString(); which produces the string
49 | * {"JSON":"Hello, World!"}
50 | *
51 | * The first method called must be array or object.
52 | * There are no methods for adding commas or colons. JSONStringer adds them for
53 | * you. Objects and arrays can be nested up to 20 levels deep.
54 | *
55 | * This can sometimes be easier than using a JSONObject to build a string.
56 | * @author JSON.org
57 | * @version 2008-09-18
58 | */
59 | public class JSONStringer extends JSONWriter {
60 | /**
61 | * Make a fresh JSONStringer. It can be used to build one JSON text.
62 | */
63 | public JSONStringer() {
64 | super(new StringWriter());
65 | }
66 |
67 | /**
68 | * Return the JSON text. This method is used to obtain the product of the
69 | * JSONStringer instance. It will return null if there was a
70 | * problem in the construction of the JSON text (such as the calls to
71 | * array were not properly balanced with calls to
72 | * endArray).
73 | * @return The JSON text.
74 | */
75 | public String toString() {
76 | return this.mode == 'd' ? this.writer.toString() : null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/CookieList.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.util.Iterator;
28 |
29 | /**
30 | * Convert a web browser cookie list string to a JSONObject and back.
31 | * @author JSON.org
32 | * @version 2010-12-24
33 | */
34 | public class CookieList {
35 |
36 | /**
37 | * Convert a cookie list into a JSONObject. A cookie list is a sequence
38 | * of name/value pairs. The names are separated from the values by '='.
39 | * The pairs are separated by ';'. The names and the values
40 | * will be unescaped, possibly converting '+' and '%' sequences.
41 | *
42 | * To add a cookie to a cooklist,
43 | * cookielistJSONObject.put(cookieJSONObject.getString("name"),
44 | * cookieJSONObject.getString("value"));
45 | * @param string A cookie list string
46 | * @return A JSONObject
47 | * @throws JSONException
48 | */
49 | public static JSONObject toJSONObject(String string) throws JSONException {
50 | JSONObject jo = new JSONObject();
51 | JSONTokener x = new JSONTokener(string);
52 | while (x.more()) {
53 | String name = Cookie.unescape(x.nextTo('='));
54 | x.next('=');
55 | jo.put(name, Cookie.unescape(x.nextTo(';')));
56 | x.next();
57 | }
58 | return jo;
59 | }
60 |
61 |
62 | /**
63 | * Convert a JSONObject into a cookie list. A cookie list is a sequence
64 | * of name/value pairs. The names are separated from the values by '='.
65 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';'
66 | * in the names and values are replaced by "%hh".
67 | * @param jo A JSONObject
68 | * @return A cookie list string
69 | * @throws JSONException
70 | */
71 | public static String toString(JSONObject jo) throws JSONException {
72 | boolean b = false;
73 | Iterator keys = jo.keys();
74 | String string;
75 | StringBuffer sb = new StringBuffer();
76 | while (keys.hasNext()) {
77 | string = keys.next().toString();
78 | if (!jo.isNull(string)) {
79 | if (b) {
80 | sb.append(';');
81 | }
82 | sb.append(Cookie.escape(string));
83 | sb.append("=");
84 | sb.append(Cookie.escape(jo.getString(string)));
85 | b = true;
86 | }
87 | }
88 | return sb.toString();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/parser/JsonParser.scala:
--------------------------------------------------------------------------------
1 | package morph.parser
2 |
3 | import morph.ast._
4 |
5 | import org.parboiled.scala._
6 | import org.parboiled.Context
7 |
8 | import scala.collection.mutable.Builder
9 | import java.lang.StringBuilder
10 |
11 | /**
12 | * A JSON parser that constructs an AST.
13 | *
14 | * It can parse JSON files that conform to the specification available
15 | * at www.json.org. This implimentation almost directly follows the
16 | * grammar specified there.
17 | *
18 | * @author Anish Athalye
19 | */
20 | object JsonParser extends BaseParser with WhiteSpaceExpansion {
21 |
22 | def RootRule = Json
23 |
24 | lazy val Json = rule { WhiteSpace ~ Value ~ EOI }
25 |
26 | def JsonObject = rule {
27 | "{ " ~ JsonObjectUnwrapped ~ "} " ~~> { ObjectNode(_) }
28 | }
29 |
30 | def JsonObjectUnwrapped = rule { Pairs ~~> { _.result } }
31 |
32 | def Pairs = rule {
33 | push(Map.newBuilder[String, ValueNode]) ~ zeroOrMore(rule {
34 | Pair ~~% { withContext[String, ValueNode, Unit](appendToMb) }
35 | }, separator = ", ")
36 | }
37 |
38 | def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value }
39 |
40 | def Value: Rule1[ValueNode] = rule {
41 | JsonString | JsonNumber | JsonObject | JsonArray |
42 | JsonTrue | JsonFalse | JsonNull
43 | }
44 |
45 | def JsonString = rule { JsonStringUnwrapped ~~> { StringNode(_) } }
46 |
47 | def JsonStringUnwrapped = rule {
48 | "\"" ~ Characters ~ "\" " ~~> { _.toString }
49 | }
50 |
51 | def JsonNumber = rule {
52 | group(Integer ~ optional(Frac) ~ optional(Exp)) ~>
53 | { NumberNode(_) } ~ WhiteSpace
54 | }
55 |
56 | def JsonArray = rule {
57 | "[ " ~ JsonArrayUnwrapped ~ "] " ~~> { ArrayNode(_) }
58 | }
59 |
60 | def JsonArrayUnwrapped = rule { Values ~~> { _.result } }
61 |
62 | def Values = rule {
63 | push(Vector.newBuilder[ValueNode]) ~ zeroOrMore(rule {
64 | Value ~~% { withContext(appendToVb(_: ValueNode, _)) }
65 | }, separator = ", ")
66 | }
67 |
68 | def Characters = rule {
69 | push(new StringBuilder) ~ zeroOrMore("\\" ~ EscapedChar | NormalChar)
70 | }
71 |
72 | def EscapedChar = {
73 | def unicode(code: Int, ctx: Context[_]) {
74 | appendToSb(code.asInstanceOf[Char], ctx)
75 | }
76 | def escaped(c: Char, ctx: Context[_]) {
77 | appendToSb('\\', ctx)
78 | appendToSb(c, ctx)
79 | }
80 | rule {
81 | anyOf("\"\\/") ~:% withContext(appendToSb) |
82 | anyOf("bfnrt") ~:% withContext(escaped) |
83 | Unicode ~~% {
84 | withContext(unicode)
85 | }
86 | }
87 | }
88 |
89 | def NormalChar = rule {
90 | !anyOf("\"\\") ~ ANY ~:% { withContext(appendToSb) }
91 | }
92 |
93 | def Unicode = rule {
94 | "u" ~ group(HexDigit ~ HexDigit ~ HexDigit ~ HexDigit) ~>
95 | { java.lang.Integer.parseInt(_, 16) }
96 | }
97 |
98 | def Integer = rule { optional("-") ~ (("1" - "9") ~ Digits | Digit) }
99 |
100 | def Digits = rule { oneOrMore(Digit) }
101 |
102 | def Digit = rule { "0" - "9" }
103 |
104 | def HexDigit = rule { Digit | "a" - "f" | "A" - "F" }
105 |
106 | def Frac = rule { "." ~ Digits }
107 |
108 | def Exp = rule { ignoreCase("e") ~ optional(anyOf("+-")) ~ Digits }
109 |
110 | def JsonTrue = rule { "true " ~ push(TrueNode) }
111 |
112 | def JsonFalse = rule { "false " ~ push(FalseNode) }
113 |
114 | def JsonNull = rule { "null " ~ push(NullNode) }
115 |
116 | def appendToSb(c: Char, ctx: Context[_]) {
117 | ctx.getValueStack.peek.asInstanceOf[StringBuilder].append(c)
118 | }
119 |
120 | def appendToMb(k: String, v: ValueNode, ctx: Context[_]) {
121 | ctx.getValueStack.peek.asInstanceOf[Builder[(String, ValueNode), Map[String, ValueNode]]] += ((k, v))
122 | }
123 |
124 | def appendToVb[T](e: T, ctx: Context[_]) {
125 | ctx.getValueStack.peek.asInstanceOf[Builder[T, Vector[T]]] += e
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/morph/ast/Ast.scala:
--------------------------------------------------------------------------------
1 | package morph.ast
2 |
3 | import morph.utils.Utils._
4 |
5 | import collection.immutable.ListMap
6 | import scala.language.implicitConversions
7 |
8 | /**
9 | * The abstract superclass for all AST node types.
10 | *
11 | * @author Anish Athalye
12 | */
13 | sealed abstract class ValueNode
14 |
15 | /**
16 | * An object.
17 | *
18 | * @author Anish Athalye
19 | */
20 | case class ObjectNode(fields: Map[String, ValueNode]) extends ValueNode {
21 |
22 | override def toString = {
23 | val mapStr = fields map {
24 | case (k, v) => "\"" + k + "\": " + v
25 | } mkString ",\n"
26 | if (fields.isEmpty) {
27 | "{}"
28 | } else {
29 | "{\n" + mapStr.indent + "\n}"
30 | }
31 | }
32 | }
33 |
34 | object ObjectNode {
35 |
36 | def apply(fields: (String, ValueNode)*) =
37 | new ObjectNode(ListMap(fields: _*))
38 |
39 | def apply(fields: List[(String, ValueNode)]) =
40 | new ObjectNode(ListMap(fields: _*))
41 | }
42 |
43 | /**
44 | * An array.
45 | *
46 | * @author Anish Athalye
47 | */
48 | case class ArrayNode(elements: IndexedSeq[ValueNode]) extends ValueNode {
49 |
50 | override def toString = if (elements.isEmpty) {
51 | "[]"
52 | } else {
53 | "[\n" + elements.mkString(",\n").indent + "\n]"
54 | }
55 | }
56 |
57 | object ArrayNode {
58 |
59 | def apply(elements: List[ValueNode]) = new ArrayNode(elements.toVector)
60 |
61 | def apply(elements: ValueNode*) = new ArrayNode(elements.toVector)
62 | }
63 |
64 | /**
65 | * A string.
66 | *
67 | * @author Anish Athalye
68 | */
69 | case class StringNode(value: String) extends ValueNode {
70 |
71 | override def toString = "\"" + value.escaped + "\""
72 | }
73 |
74 | object StringNode {
75 |
76 | def apply(sym: Symbol) = new StringNode(sym.name)
77 | }
78 |
79 | /**
80 | * A number.
81 | *
82 | * A generic number type internally represented as a BigDecimal.
83 | *
84 | * @author Anish Athalye
85 | */
86 | case class NumberNode(value: BigDecimal) extends ValueNode {
87 |
88 | override def toString = value.toString
89 | }
90 |
91 | object NumberNode {
92 |
93 | def apply(n: Int) = new NumberNode(BigDecimal(n))
94 |
95 | def apply(n: Long) = new NumberNode(BigDecimal(n))
96 |
97 | def apply(n: Double) = n match {
98 | case n if n.isNaN => NullNode
99 | case n if n.isInfinity => NullNode
100 | case _ => new NumberNode(BigDecimal(n))
101 | }
102 |
103 | def apply(n: BigInt) = new NumberNode(BigDecimal(n))
104 |
105 | def apply(n: String) = new NumberNode(BigDecimal(n))
106 | }
107 |
108 | sealed abstract class BooleanNode extends ValueNode {
109 |
110 | def value: Boolean
111 |
112 | override def toString = value.toString
113 | }
114 |
115 | /**
116 | * A boolean.
117 | *
118 | * @author Anish Athalye
119 | */
120 | object BooleanNode {
121 |
122 | def apply(x: Boolean): BooleanNode =
123 | if (x) TrueNode else FalseNode
124 |
125 | def unapply(x: BooleanNode): Option[Boolean] = Some(x.value)
126 | }
127 |
128 | case object TrueNode extends BooleanNode {
129 |
130 | def value = true
131 | }
132 |
133 | case object FalseNode extends BooleanNode {
134 |
135 | def value = false
136 | }
137 |
138 | /**
139 | * A null value.
140 | *
141 | * @author Anish Athalye
142 | */
143 | case object NullNode extends ValueNode {
144 |
145 | override def toString = "null"
146 | }
147 |
148 | /**
149 | * Implicit conversions from several scala built in data types to
150 | * their corresponding AST representations.
151 | *
152 | * @author Anish Athalye
153 | */
154 | trait Implicits {
155 |
156 | implicit def String2StringNode(s: String): StringNode = StringNode(s)
157 |
158 | implicit def Boolean2BooleanNode(b: Boolean): BooleanNode = BooleanNode(b)
159 |
160 | implicit def Int2NumberNode(n: Int): NumberNode = NumberNode(n)
161 |
162 | implicit def Long2NumberNode(n: Long): NumberNode = NumberNode(n)
163 |
164 | implicit def Double2NumberNode(n: Double): NumberNode = NumberNode(n)
165 |
166 | implicit def BigDecimal2NumberNode(n: BigDecimal): NumberNode = NumberNode(n)
167 |
168 | implicit def StringValueNodeViewable2StringValueNode[T <% ValueNode](
169 | ss: (String, T)): (String, ValueNode) = (ss._1, ss._2)
170 | }
171 |
172 | /**
173 | * A companion object to make it possible to either
174 | * mix in the trait or import the companion object's methods.
175 | */
176 | object Implicits extends Implicits
177 |
--------------------------------------------------------------------------------
/slurp/src/main/scala/slurp/GitHubStream.scala:
--------------------------------------------------------------------------------
1 | package slurp
2 |
3 | import scala.async.Async.{ async, await }
4 | import scala.concurrent.Await
5 | import scala.concurrent.duration._
6 |
7 | import java.util.concurrent.{ ExecutionException, TimeoutException }
8 |
9 | import dispatch._, Defaults._
10 |
11 | import com.twitter.util.LruMap
12 |
13 | import morph.ast._, DSL._, Implicits._
14 | import morph.parser._
15 |
16 | final class GitHubStream(clientId: String, clientSecret: String) {
17 |
18 | private var lastPollMillis: Long = 0
19 | private var pollIntervalMillis: Long = 0
20 | private var eTag: String = ""
21 | private var lastId = BigInt("-1")
22 |
23 | def getEvents(): (List[String], Long) = {
24 | val (events, pollInterval) = getRawEvents()
25 | val filtered = events filter { event =>
26 | val fln = event lift "fromLocation" match {
27 | case Some(loc) => loc != ""
28 | case None => false
29 | }
30 | val tln = event lift "toLocation" match {
31 | case Some(loc) => loc != ""
32 | case None => false
33 | }
34 | fln && tln
35 | } map { event =>
36 | ObjectNode(event mapValues { value =>
37 | StringNode(value)
38 | }).toString
39 | }
40 | (filtered, pollInterval)
41 | }
42 |
43 | def getRawEvents(): (List[Map[String, String]], Long) = {
44 | try {
45 | val headers = Map("If-None-Match" -> eTag)
46 | val url = apiBase / "events" < keys <:< (headers ++ userAgent)
47 | val req = Http(url OK identity)
48 |
49 | val sleepTimeMillis = lastPollMillis + pollIntervalMillis - System.currentTimeMillis
50 | if (sleepTimeMillis > 0) {
51 | Thread.sleep(sleepTimeMillis)
52 | }
53 |
54 | val resp = Await.result(req, DEFAULT_TIMEOUT)
55 |
56 | lastPollMillis = System.currentTimeMillis
57 | val pollInterval = (resp getHeader "X-Poll-Interval").toLong / 16 // speed up
58 | pollIntervalMillis = pollInterval * 1000
59 | eTag = resp getHeader "ETag" // update for next request
60 |
61 | val json = JsonParser(resp.getResponseBody).asList
62 | val nextId = if (json.nonEmpty) {
63 | BigInt((json.head ~> "id").asString)
64 | } else {
65 | lastId
66 | }
67 | val latest = json filter { event =>
68 | BigInt((event ~> "id").asString) > lastId
69 | }
70 | lastId = nextId
71 |
72 | val filtered = latest filter { event =>
73 | (event ~> "type").asString match {
74 | case "ForkEvent" => true
75 | case "WatchEvent" => true
76 | case "PullRequestEvent" => (event ~> "payload" ~> "action").asString == "opened"
77 | case "IssuesEvent" => (event ~> "payload" ~> "action").asString == "opened"
78 | case _ => false
79 | }
80 | }
81 |
82 | val events = filtered map { event =>
83 | val eventType = (event ~> "type").asString
84 | val login = (event ~> "actor" ~> "login").asString
85 | val (targetLogin, targetRest) = (event ~> "repo" ~> "name").asString span { _ != '/' }
86 | val targetRepo = targetRest.tail
87 | val url = (event ~> "type").asString match {
88 | case "ForkEvent" => (event ~> "payload" ~> "forkee" ~> "html_url").asString
89 | case "WatchEvent" => s"https://github.com/${(event ~> "repo" ~> "name").asString}"
90 | case "PullRequestEvent" => (event ~> "payload" ~> "pull_request" ~> "html_url").asString
91 | case "IssuesEvent" => (event ~> "payload" ~> "issue" ~> "html_url").asString
92 | case _ => "https://github.com/"
93 | }
94 | async {
95 | val fromLocation = await(getUserLocation(login))
96 | val toLocation = await(getUserLocation(targetLogin))
97 | Map(
98 | "type" -> eventType,
99 | "fromLogin" -> login,
100 | "fromLocation" -> fromLocation,
101 | "toRepo" -> targetRepo,
102 | "toLogin" -> targetLogin,
103 | "toLocation" -> toLocation,
104 | "url" -> url
105 | )
106 | }
107 | }
108 | (Await.result(Future.sequence(events), pollInterval.seconds), pollInterval)
109 | } catch {
110 | case e @ (_: ExecutionException | _: TimeoutException) => {
111 | e.printStackTrace()
112 | (List(Map()), 0)
113 | }
114 | }
115 | }
116 |
117 | private val LRU_MAP_SIZE = 20000
118 | private val locations: LruMap[String, String] = new LruMap[String, String](LRU_MAP_SIZE)
119 |
120 | def getUserLocation(user: String): Future[String] = {
121 | val location = locations.synchronized { locations lift user }
122 | location match {
123 | case Some(loc) => Future(loc)
124 | case None => {
125 | val url = apiBase / "users" / user < keys <:< userAgent
126 | val req = Http(url OK as.String)
127 | req map { res =>
128 | val loc = JsonParser(res) ~> "location" collect {
129 | case StringNode(l) => l
130 | } getOrElse ""
131 | locations.synchronized {
132 | locations(user) = loc
133 | }
134 | loc
135 | }
136 | }
137 | }
138 | }
139 |
140 | private val apiBase = url("https://api.github.com")
141 | private val keys = Map("client_id" -> clientId, "client_secret" -> clientSecret)
142 | private val userAgent = Map("User-Agent" -> "anishathalye")
143 |
144 | private val DEFAULT_TIMEOUT = 10.seconds
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/HTTP.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.util.Iterator;
28 |
29 | /**
30 | * Convert an HTTP header to a JSONObject and back.
31 | * @author JSON.org
32 | * @version 2010-12-24
33 | */
34 | public class HTTP {
35 |
36 | /** Carriage return/line feed. */
37 | public static final String CRLF = "\r\n";
38 |
39 | /**
40 | * Convert an HTTP header string into a JSONObject. It can be a request
41 | * header or a response header. A request header will contain
42 | *
{
43 | * Method: "POST" (for example),
44 | * "Request-URI": "/" (for example),
45 | * "HTTP-Version": "HTTP/1.1" (for example)
46 | * }
47 | * A response header will contain
48 | * {
49 | * "HTTP-Version": "HTTP/1.1" (for example),
50 | * "Status-Code": "200" (for example),
51 | * "Reason-Phrase": "OK" (for example)
52 | * }
53 | * In addition, the other parameters in the header will be captured, using
54 | * the HTTP field names as JSON names, so that 55 | * Date: Sun, 26 May 2002 18:06:04 GMT 56 | * Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s 57 | * Cache-Control: no-cache58 | * become 59 | *
{...
60 | * Date: "Sun, 26 May 2002 18:06:04 GMT",
61 | * Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
62 | * "Cache-Control": "no-cache",
63 | * ...}
64 | * It does no further checking or conversion. It does not parse dates.
65 | * It does not do '%' transforms on URLs.
66 | * @param string An HTTP header string.
67 | * @return A JSONObject containing the elements and attributes
68 | * of the XML string.
69 | * @throws JSONException
70 | */
71 | public static JSONObject toJSONObject(String string) throws JSONException {
72 | JSONObject jo = new JSONObject();
73 | HTTPTokener x = new HTTPTokener(string);
74 | String token;
75 |
76 | token = x.nextToken();
77 | if (token.toUpperCase().startsWith("HTTP")) {
78 |
79 | // Response
80 |
81 | jo.put("HTTP-Version", token);
82 | jo.put("Status-Code", x.nextToken());
83 | jo.put("Reason-Phrase", x.nextTo('\0'));
84 | x.next();
85 |
86 | } else {
87 |
88 | // Request
89 |
90 | jo.put("Method", token);
91 | jo.put("Request-URI", x.nextToken());
92 | jo.put("HTTP-Version", x.nextToken());
93 | }
94 |
95 | // Fields
96 |
97 | while (x.more()) {
98 | String name = x.nextTo(':');
99 | x.next(':');
100 | jo.put(name, x.nextTo('\0'));
101 | x.next();
102 | }
103 | return jo;
104 | }
105 |
106 |
107 | /**
108 | * Convert a JSONObject into an HTTP header. A request header must contain
109 | * {
110 | * Method: "POST" (for example),
111 | * "Request-URI": "/" (for example),
112 | * "HTTP-Version": "HTTP/1.1" (for example)
113 | * }
114 | * A response header must contain
115 | * {
116 | * "HTTP-Version": "HTTP/1.1" (for example),
117 | * "Status-Code": "200" (for example),
118 | * "Reason-Phrase": "OK" (for example)
119 | * }
120 | * Any other members of the JSONObject will be output as HTTP fields.
121 | * The result will end with two CRLF pairs.
122 | * @param jo A JSONObject
123 | * @return An HTTP header string.
124 | * @throws JSONException if the object does not contain enough
125 | * information.
126 | */
127 | public static String toString(JSONObject jo) throws JSONException {
128 | Iterator keys = jo.keys();
129 | String string;
130 | StringBuffer sb = new StringBuffer();
131 | if (jo.has("Status-Code") && jo.has("Reason-Phrase")) {
132 | sb.append(jo.getString("HTTP-Version"));
133 | sb.append(' ');
134 | sb.append(jo.getString("Status-Code"));
135 | sb.append(' ');
136 | sb.append(jo.getString("Reason-Phrase"));
137 | } else if (jo.has("Method") && jo.has("Request-URI")) {
138 | sb.append(jo.getString("Method"));
139 | sb.append(' ');
140 | sb.append('"');
141 | sb.append(jo.getString("Request-URI"));
142 | sb.append('"');
143 | sb.append(' ');
144 | sb.append(jo.getString("HTTP-Version"));
145 | } else {
146 | throw new JSONException("Not enough material for an HTTP header.");
147 | }
148 | sb.append(CRLF);
149 | while (keys.hasNext()) {
150 | string = keys.next().toString();
151 | if (!"HTTP-Version".equals(string) && !"Status-Code".equals(string) &&
152 | !"Reason-Phrase".equals(string) && !"Method".equals(string) &&
153 | !"Request-URI".equals(string) && !jo.isNull(string)) {
154 | sb.append(string);
155 | sb.append(": ");
156 | sb.append(jo.getString(string));
157 | sb.append(CRLF);
158 | }
159 | }
160 | sb.append(CRLF);
161 | return sb.toString();
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/Cookie.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * Convert a web browser cookie specification to a JSONObject and back.
29 | * JSON and Cookies are both notations for name/value pairs.
30 | * @author JSON.org
31 | * @version 2010-12-24
32 | */
33 | public class Cookie {
34 |
35 | /**
36 | * Produce a copy of a string in which the characters '+', '%', '=', ';'
37 | * and control characters are replaced with "%hh". This is a gentle form
38 | * of URL encoding, attempting to cause as little distortion to the
39 | * string as possible. The characters '=' and ';' are meta characters in
40 | * cookies. By convention, they are escaped using the URL-encoding. This is
41 | * only a convention, not a standard. Often, cookies are expected to have
42 | * encoded values. We encode '=' and ';' because we must. We encode '%' and
43 | * '+' because they are meta characters in URL encoding.
44 | * @param string The source string.
45 | * @return The escaped result.
46 | */
47 | public static String escape(String string) {
48 | char c;
49 | String s = string.trim();
50 | StringBuffer sb = new StringBuffer();
51 | int length = s.length();
52 | for (int i = 0; i < length; i += 1) {
53 | c = s.charAt(i);
54 | if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') {
55 | sb.append('%');
56 | sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16));
57 | sb.append(Character.forDigit((char)(c & 0x0f), 16));
58 | } else {
59 | sb.append(c);
60 | }
61 | }
62 | return sb.toString();
63 | }
64 |
65 |
66 | /**
67 | * Convert a cookie specification string into a JSONObject. The string
68 | * will contain a name value pair separated by '='. The name and the value
69 | * will be unescaped, possibly converting '+' and '%' sequences. The
70 | * cookie properties may follow, separated by ';', also represented as
71 | * name=value (except the secure property, which does not have a value).
72 | * The name will be stored under the key "name", and the value will be
73 | * stored under the key "value". This method does not do checking or
74 | * validation of the parameters. It only converts the cookie string into
75 | * a JSONObject.
76 | * @param string The cookie specification string.
77 | * @return A JSONObject containing "name", "value", and possibly other
78 | * members.
79 | * @throws JSONException
80 | */
81 | public static JSONObject toJSONObject(String string) throws JSONException {
82 | String name;
83 | JSONObject jo = new JSONObject();
84 | Object value;
85 | JSONTokener x = new JSONTokener(string);
86 | jo.put("name", x.nextTo('='));
87 | x.next('=');
88 | jo.put("value", x.nextTo(';'));
89 | x.next();
90 | while (x.more()) {
91 | name = unescape(x.nextTo("=;"));
92 | if (x.next() != '=') {
93 | if (name.equals("secure")) {
94 | value = Boolean.TRUE;
95 | } else {
96 | throw x.syntaxError("Missing '=' in cookie parameter.");
97 | }
98 | } else {
99 | value = unescape(x.nextTo(';'));
100 | x.next();
101 | }
102 | jo.put(name, value);
103 | }
104 | return jo;
105 | }
106 |
107 |
108 | /**
109 | * Convert a JSONObject into a cookie specification string. The JSONObject
110 | * must contain "name" and "value" members.
111 | * If the JSONObject contains "expires", "domain", "path", or "secure"
112 | * members, they will be appended to the cookie specification string.
113 | * All other members are ignored.
114 | * @param jo A JSONObject
115 | * @return A cookie specification string
116 | * @throws JSONException
117 | */
118 | public static String toString(JSONObject jo) throws JSONException {
119 | StringBuffer sb = new StringBuffer();
120 |
121 | sb.append(escape(jo.getString("name")));
122 | sb.append("=");
123 | sb.append(escape(jo.getString("value")));
124 | if (jo.has("expires")) {
125 | sb.append(";expires=");
126 | sb.append(jo.getString("expires"));
127 | }
128 | if (jo.has("domain")) {
129 | sb.append(";domain=");
130 | sb.append(escape(jo.getString("domain")));
131 | }
132 | if (jo.has("path")) {
133 | sb.append(";path=");
134 | sb.append(escape(jo.getString("path")));
135 | }
136 | if (jo.optBoolean("secure")) {
137 | sb.append(";secure");
138 | }
139 | return sb.toString();
140 | }
141 |
142 | /**
143 | * Convert %hh sequences to single characters, and
144 | * convert plus to space.
145 | * @param string A string that may contain
146 | * + (plus) and
147 | * %hh sequences.
148 | * @return The unescaped string.
149 | */
150 | public static String unescape(String string) {
151 | int length = string.length();
152 | StringBuffer sb = new StringBuffer();
153 | for (int i = 0; i < length; ++i) {
154 | char c = string.charAt(i);
155 | if (c == '+') {
156 | c = ' ';
157 | } else if (c == '%' && i + 2 < length) {
158 | int d = JSONTokener.dehexchar(string.charAt(i + 1));
159 | int e = JSONTokener.dehexchar(string.charAt(i + 2));
160 | if (d >= 0 && e >= 0) {
161 | c = (char)(d * 16 + e);
162 | i += 2;
163 | }
164 | }
165 | sb.append(c);
166 | }
167 | return sb.toString();
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/cartographer/public/css/gitlive.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | overflow: hidden;
3 | height: 100%;
4 | width: 100%;
5 | margin: 0;
6 | padding: 0;
7 | background: #22262e;
8 | font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | }
10 |
11 | #footer {
12 | height: 50px;
13 | width: 100%;
14 | position: fixed;
15 | bottom: 5px;
16 | }
17 |
18 | #footer span {
19 | position: absolute;
20 | bottom: 0;
21 | width: 100%;
22 | text-align: center;
23 | font-weight: 300;
24 | font-size: 10px;
25 | color: #424a59;
26 | }
27 |
28 | #footer a {
29 | color: #4b5466;
30 | text-decoration: none;
31 | -webkit-transition: all 0.15s linear;
32 | -moz-transition: all 0.15s linear;
33 | -o-transition: all 0.15s linear;
34 | transition: all 0.15s linear;
35 | }
36 |
37 | #footer a:hover {
38 | color: rgba(255, 255, 255, 0.5);
39 | text-shadow: rgba(255, 255, 255, 0.5) 0 0 3px;
40 | }
41 |
42 | #map {
43 | height: 100%;
44 | width: 100%;
45 | margin: 0 auto;
46 | }
47 |
48 | .shade {
49 | background: rgba(0,0,0,.7);
50 | }
51 |
52 | .lightbox-target {
53 | position: fixed;
54 | top: -100%;
55 | width: 100%;
56 | opacity: 0;
57 | -webkit-transition: opacity .5s ease-in-out;
58 | -moz-transition: opacity .5s ease-in-out;
59 | -o-transition: opacity .5s ease-in-out;
60 | transition: opacity .5s ease-in-out;
61 | overflow: hidden;
62 | }
63 |
64 | :target div.lightbox-target {
65 | opacity: 1;
66 | top: 0;
67 | bottom: 0;
68 | }
69 |
70 | .lightbox-close-target {
71 | position: fixed;
72 | top: -100%;
73 | width: 50px;
74 | height: 50px;
75 | right: 0;
76 | overflow: hidden;
77 | }
78 |
79 | :target div.lightbox-close-target {
80 | top: 0;
81 | }
82 |
83 | div.lightbox-content {
84 | margin: auto;
85 | overflow-y: auto;
86 | padding: 15px 15px 15px 15px;
87 | position: absolute;
88 | top: 0;
89 | left: 0;
90 | right: 0;
91 | bottom: 0;
92 | height: 100%;
93 | width: 100%;
94 | background: #1a1c23;
95 | border-radius: 5px;
96 | color: #8d9ebf;
97 | }
98 |
99 | @media (min-width: 900px) {
100 | div.lightbox-content {
101 | height: 75%;
102 | width: 768px;
103 | }
104 | }
105 |
106 | .lightbox-content h1 {
107 | text-align: center;
108 | }
109 |
110 | .lightbox-content hr {
111 | border: 1px solid #8d9ebf;
112 | width: 50%;
113 | }
114 |
115 | .lightbox-content img {
116 | margin: auto;
117 | display: block;
118 | max-width: 50%;
119 | border: 1px solid #8d9ebf;
120 | }
121 |
122 | .lightbox-content a {
123 | color: #bcd3ff;
124 | text-decoration: none;
125 | -webkit-transition: all 0.15s linear;
126 | -moz-transition: all 0.15s linear;
127 | -o-transition: all 0.15s linear;
128 | transition: all 0.15s linear;
129 | }
130 |
131 | .lightbox-content a:hover {
132 | text-shadow: #bcd3ff 0 0 3px;
133 | }
134 |
135 | .lightbox-content span.red {
136 | color: #ea5b89;
137 | }
138 |
139 | .lightbox-content span.green {
140 | color: #4fcb7d;
141 | }
142 |
143 | .lightbox-content span.blue {
144 | color: #46b3b3;
145 | }
146 |
147 | .lightbox-content span.yellow {
148 | color: #ffd564;
149 | }
150 |
151 | a.lightbox-close {
152 | display: block;
153 | width:50px;
154 | height:50px;
155 | box-sizing: border-box;
156 | background: transparent;
157 | color: black;
158 | text-decoration: none;
159 | position: absolute;
160 | top: 0px;
161 | right: 0;
162 | }
163 |
164 | a.plus {
165 | display: block;
166 | width:50px;
167 | height:50px;
168 | box-sizing: border-box;
169 | background: #555f73;
170 | color: #8d9ebf;
171 | text-decoration: none;
172 | position: absolute;
173 | top: 0px;
174 | right: 0;
175 | -webkit-transition: transform .5s ease-in-out;
176 | -moz-transition: transform .5s ease-in-out;
177 | -o-transition: transform .5s ease-in-out;
178 | transition: transform .5s ease-in-out;
179 | -webkit-transition: background-color .5s ease-in-out;
180 | -moz-transition: background-color .5s ease-in-out;
181 | -o-transition: background-color .5s ease-in-out;
182 | transition: background-color .5s ease-in-out;
183 | }
184 |
185 | :target a.plus {
186 | background: transparent;
187 | }
188 |
189 | a.plus:before {
190 | content: "";
191 | display: block;
192 | height: 30px;
193 | width: 1px;
194 | background: #8d9ebf;
195 | position: absolute;
196 | left: 26px;
197 | top:10px;
198 | -webkit-transform:rotate(0deg);
199 | -moz-transform:rotate(0deg);
200 | -o-transform:rotate(0deg);
201 | transform:rotate(0deg);
202 | -webkit-transition: transform .5s ease-in-out;
203 | -moz-transition: transform .5s ease-in-out;
204 | -o-transition: transform .5s ease-in-out;
205 | transition: transform .5s ease-in-out;
206 | }
207 |
208 | :target a.plus:before {
209 | -webkit-transform:rotate(-135deg);
210 | -moz-transform:rotate(-135deg);
211 | -o-transform:rotate(-135deg);
212 | transform:rotate(-135deg);
213 | -webkit-transition: transform .5s ease-in-out;
214 | -moz-transition: transform .5s ease-in-out;
215 | -o-transition: transform .5s ease-in-out;
216 | transition: transform .5s ease-in-out;
217 | }
218 |
219 | a.plus:after {
220 | content: "";
221 | display: block;
222 | height: 30px;
223 | width: 1px;
224 | background: #8d9ebf;
225 | position: absolute;
226 | left: 26px;
227 | top:10px;
228 | -webkit-transform:rotate(90deg);
229 | -moz-transform:rotate(90deg);
230 | -o-transform:rotate(90deg);
231 | transform:rotate(90deg);
232 | -webkit-transition: transform .5s ease-in-out;
233 | -moz-transition: transform .5s ease-in-out;
234 | -o-transition: transform .5s ease-in-out;
235 | transition: transform .5s ease-in-out;
236 | }
237 |
238 | :target a.plus:after {
239 | -webkit-transform:rotate(-45deg);
240 | -moz-transform:rotate(-45deg);
241 | -o-transform:rotate(-45deg);
242 | transform:rotate(-45deg);
243 | -webkit-transition: transform .5s ease-in-out;
244 | -moz-transition: transform .5s ease-in-out;
245 | -o-transition: transform .5s ease-in-out;
246 | transition: transform .5s ease-in-out;
247 | }
248 |
249 | circle.githublive-linez-circle {
250 | stroke-width: 2;
251 | -webkit-transition: all 0.15s linear;
252 | -moz-transition: all 0.15s linear;
253 | -o-transition: all 0.15s linear;
254 | transition: all 0.15s linear;
255 | }
256 |
257 | circle.githublive-linez-circle:hover {
258 | stroke-width: 3;
259 | }
260 |
261 | circle.red {
262 | fill: #b00038;
263 | stroke: #ea5b89;
264 | }
265 |
266 | circle.green {
267 | fill: #008f35;
268 | stroke: #4fcb7d;
269 | }
270 |
271 | circle.blue {
272 | fill: #007777;
273 | stroke: #46b3b3;
274 | }
275 |
276 | circle.yellow {
277 | fill: #c69000;
278 | stroke: #ffd564;
279 | }
280 |
281 | circle.default {
282 | fill: #111111;
283 | stroke: #555555;
284 | }
285 |
286 | path.githublive-linez-path {
287 | fill: none !important;
288 | stroke-width: 2px !important;
289 | stroke-linecap: round !important;
290 | }
291 |
292 | path.red {
293 | stroke: #e20048 !important;
294 | }
295 |
296 | path.green {
297 | stroke: #00b945 !important;
298 | }
299 |
300 | path.blue {
301 | stroke: #009999 !important;
302 | }
303 |
304 | path.yellow {
305 | stroke: #ffba00 !important;
306 | }
307 |
308 | path.default {
309 | stroke: #111111 !important;
310 | }
311 |
312 | .d3-tip {
313 | line-height: 150%;
314 | padding: 12px;
315 | font-size: 12px;
316 | max-width: 250px;
317 | background: rgba(0, 0, 0, 0.8);
318 | color: #ffffff;
319 | border-radius: 2px;
320 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.8);
321 | }
322 |
323 | .d3-tip:after {
324 | box-sizing: border-box;
325 | display: inline;
326 | font-size: 10px;
327 | width: 100%;
328 | line-height: 1;
329 | color: rgba(0, 0, 0, 0.8);
330 | content: "\25BC";
331 | position: absolute;
332 | text-align: center;
333 | }
334 |
335 | .d3-tip.n:after {
336 | margin: -1px 0 0 0;
337 | top: 100%;
338 | left: 0;
339 | }
340 |
341 | .d3-tip em {
342 | color: #999999;
343 | }
344 |
345 | .d3-tip span {
346 | font-weight: bold;
347 | }
348 |
349 | .d3-tip span.red {
350 | color: #e20048;
351 | }
352 |
353 | .d3-tip span.green {
354 | color: #00b945;
355 | }
356 |
357 | .d3-tip span.blue {
358 | color: #009999;
359 | }
360 |
361 | .d3-tip span.yellow {
362 | color: #ffba00;
363 | }
364 |
--------------------------------------------------------------------------------
/cartographer/public/gitlive.js:
--------------------------------------------------------------------------------
1 | var map = new Datamap({
2 | element: document.getElementById('map'),
3 | geographyConfig: {
4 | highlightOnHover: false,
5 | popupOnHover: false,
6 | borderColor: '#555555',
7 | borderWidth: 1
8 | },
9 | fills: {
10 | defaultFill: '#2e2e2e'
11 | },
12 | });
13 |
14 | // have map intercept clicks / taps
15 | document.getElementById('map').addEventListener('click', function() {});
16 |
17 | var geocoder = new google.maps.Geocoder();
18 |
19 | var geocode = function(location, success, retries) {
20 | if (typeof(retries) === 'undefined') retries = 3;
21 | geocoder.geocode({'address': location}, function(results, status) {
22 | if (status == google.maps.GeocoderStatus.OK) {
23 | success(results);
24 | } else if (status == google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
25 | if (retries > 0) {
26 | setTimeout(function() {
27 | geocode(location, success, retries - 1);
28 | }, 2000);
29 | } else {
30 | window.location.reload();
31 | }
32 | } else {
33 | // ignore
34 | }
35 | });
36 | }
37 |
38 | var id = 0;
39 |
40 | var locs = [];
41 |
42 | var removeLoc = function(data) {
43 | var index;
44 | for (var i = 0, len = locs.length; i < len; i++) {
45 | if (locs[i].id === data.id) {
46 | index = i;
47 | break;
48 | }
49 | }
50 | locs.splice(index, 1);
51 | }
52 |
53 | var TIMEOUT = 60000;
54 |
55 | var processEvent = function(data) {
56 | var flip = function(data) {
57 | var tmp = data.origin;
58 | data.origin = data.destination;
59 | data.destination = tmp;
60 | }
61 | switch(data.type) {
62 | case 'WatchEvent':
63 | data.options = { color: 'yellow' };
64 | break;
65 | case 'ForkEvent':
66 | data.options = { color: 'blue' };
67 | break;
68 | case 'PullRequestEvent':
69 | data.options = { color: 'green' };
70 | break;
71 | case 'IssuesEvent':
72 | data.options = { color: 'red' };
73 | break;
74 | }
75 | data.id = id++;
76 | data.timeoutTime = TIMEOUT;
77 | data.timeoutFunc = function() {
78 | removeLoc(data);
79 | map.linez(locs);
80 | }
81 | data.timeout = setTimeout(data.timeoutFunc, data.timeoutTime);
82 | locs.push(data);
83 | map.linez(locs);
84 | };
85 |
86 | var processRawEvent = function(event) {
87 | geocode(event.fromLocation, function(from) {
88 | geocode(event.toLocation, function(to) {
89 | var fromLoc = {
90 | latitude: from[0].geometry.location.lat(),
91 | longitude: from[0].geometry.location.lng()
92 | };
93 | var toLoc = {
94 | latitude: to[0].geometry.location.lat(),
95 | longitude: to[0].geometry.location.lng()
96 | }
97 | event.origin = fromLoc;
98 | event.destination = toLoc;
99 | processEvent(event);
100 | });
101 | });
102 | };
103 |
104 | if (typeof(EventSource) !== 'undefined') {
105 | var source = new EventSource('/events');
106 | source.onmessage = function(e) {
107 | var event = JSON.parse(JSON.parse(e.data));
108 | processRawEvent(event);
109 | };
110 | } else {
111 | alert('Your browser is not supported. You must use a browser that supports the EventSource API.');
112 | }
113 |
114 | var escapeHtml = function(str) {
115 | var div = document.createElement('div');
116 | div.appendChild(document.createTextNode(str));
117 | return div.innerHTML;
118 | }
119 |
120 | var linezOptions = {
121 | arcSharpness: 1,
122 | highlightBorderWidth: 3,
123 | popupTemplate: function(data) {
124 | // remember to escape data
125 | var user = escapeHtml(data.fromLogin);
126 | var from = escapeHtml(data.fromLocation);
127 | var repo = escapeHtml(data.toLogin) + '/' + escapeHtml(data.toRepo);
128 | var to = escapeHtml(data.toLocation);
129 | var message;
130 | switch (data.type) {
131 | case 'WatchEvent':
132 | message = 'starred';
133 | break;
134 | case 'ForkEvent':
135 | message = 'forked';
136 | break;
137 | case 'PullRequestEvent':
138 | message = 'sent a pull request to';
139 | break;
140 | case 'IssuesEvent':
141 | message = 'opened an issue at';
142 | break;
143 | }
144 | return user + ' (' + from + ') ' + message + ' ' + repo + ' (' + to + ')';
145 | }
146 | };
147 |
148 | var mobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
149 |
150 | var handleLinez = function(layer, data) {
151 | var self = this;
152 | var svg = this.svg;
153 |
154 | var tip = d3.tip().attr('class', 'd3-tip').offset([-10, 0]).html(linezOptions.popupTemplate);
155 | layer.call(tip);
156 |
157 | var linez = layer.selectAll('.githublive-linez').data(data, JSON.stringify);
158 |
159 | var container = linez.enter().append('g').attr('class', 'githublive-linez');
160 |
161 | var back = container.append('g');
162 | var front = container.append('g');
163 |
164 | front.append('svg:circle')
165 | .attr('class', 'githublive-linez-circle')
166 | .attr('cx', function(datum) {
167 | var latLng = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
168 | if (latLng) {
169 | return latLng[0];
170 | }
171 | })
172 | .attr('cy', function(datum) {
173 | var latLng = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
174 | if (latLng) {
175 | return latLng[1];
176 | }
177 | })
178 | .attr('r', 0)
179 | .attr('data-info', function(d) {
180 | return JSON.stringify(d);
181 | })
182 | .attr('class', function(datum) {
183 | var previous = d3.select(this).attr('class');
184 | if (datum.options && datum.options.color) {
185 | var ret = previous + ' ' + datum.options.color;
186 | return ret;
187 | }
188 | return previous + ' default';
189 | })
190 | .on('mouseover', function(datum) {
191 | clearTimeout(datum.timeout);
192 |
193 | tip.show(datum);
194 | })
195 | .on('mouseout', function(datum) {
196 | datum.timeout = setTimeout(datum.timeoutFunc, datum.timeoutTime);
197 |
198 | tip.hide(datum);
199 | })
200 | .on('click', function(datum) {
201 | if (!mobileDevice) {
202 | window.open(datum.url, '_blank');
203 | }
204 | })
205 | .transition().delay(0).duration(400)
206 | .attr('r', 10)
207 | .transition().delay(400).duration(200)
208 | .attr('r', 5);
209 |
210 | back.append('svg:path')
211 | .attr('class', 'githublive-linez-path')
212 | .attr('class', function(datum) {
213 | var previous = d3.select(this).attr('class');
214 | if (datum.options && datum.options.color) {
215 | var ret = previous + ' ' + datum.options.color;
216 | return ret;
217 | }
218 | return previous + ' default';
219 | })
220 | .attr('d', function(datum) {
221 | var originXY = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
222 | var destXY = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
223 | var midXY = [ (originXY[0] + destXY[0]) / 2, (originXY[1] + destXY[1]) / 2];
224 | return "M" + originXY[0] + ',' + originXY[1] + "S" + (midXY[0] + (50 * linezOptions.arcSharpness)) + "," + (midXY[1] - (75 * linezOptions.arcSharpness)) + "," + destXY[0] + "," + destXY[1];
225 | })
226 | .attr('stroke-dasharray', function() {
227 | var length = this.getTotalLength();
228 | return length + ' ' + length;
229 | })
230 | .attr('stroke-dashoffset', function() {
231 | var length = this.getTotalLength();
232 | return length;
233 | })
234 | .transition()
235 | .delay(400)
236 | .duration(600)
237 | .ease('linear')
238 | .attr('stroke-dashoffset', 0);
239 |
240 | front.append('svg:circle')
241 | .attr('class', 'githublive-linez-circle')
242 | .attr('cx', function(datum) {
243 | var latLng = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
244 | if (latLng) {
245 | return latLng[0];
246 | }
247 | })
248 | .attr('cy', function(datum) {
249 | var latLng = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
250 | if (latLng) {
251 | return latLng[1];
252 | }
253 | })
254 | .attr('r', 0)
255 | .attr('data-info', function(d) {
256 | return JSON.stringify(d);
257 | })
258 | .attr('class', function(datum) {
259 | var previous = d3.select(this).attr('class');
260 | if (datum.options && datum.options.color) {
261 | var ret = previous + ' ' + datum.options.color;
262 | return ret;
263 | }
264 | return previous + ' default';
265 | })
266 | .on('mouseover', function(datum) {
267 | clearTimeout(datum.timeout);
268 |
269 | tip.show(datum);
270 | })
271 | .on('mouseout', function(datum) {
272 | datum.timeout = setTimeout(datum.timeoutFunc, datum.timeoutTime);
273 |
274 | tip.hide(datum);
275 | })
276 | .on('click', function(datum) {
277 | if (!mobileDevice) {
278 | window.open(datum.url, '_blank');
279 | }
280 | })
281 | .transition().delay(1000).duration(400)
282 | .attr('r', 10)
283 | .transition().delay(1400).duration(200)
284 | .attr('r', 5);
285 |
286 | linez.exit()
287 | .transition()
288 | .style('opacity', 0)
289 | .remove();
290 | };
291 |
292 | map.addPlugin('linez', handleLinez);
293 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/CDL.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * This provides static methods to convert comma delimited text into a
29 | * JSONArray, and to covert a JSONArray into comma delimited text. Comma
30 | * delimited text is a very popular format for data interchange. It is
31 | * understood by most database, spreadsheet, and organizer programs.
32 | * 33 | * Each row of text represents a row in a table or a data record. Each row 34 | * ends with a NEWLINE character. Each row contains one or more values. 35 | * Values are separated by commas. A value can contain any character except 36 | * for comma, unless is is wrapped in single quotes or double quotes. 37 | *
38 | * The first row usually contains the names of the columns. 39 | *
40 | * A comma delimited list can be converted into a JSONArray of JSONObjects. 41 | * The names for the elements in the JSONObjects can be taken from the names 42 | * in the first row. 43 | * @author JSON.org 44 | * @version 2012-11-13 45 | */ 46 | public class CDL { 47 | 48 | /** 49 | * Get the next value. The value can be wrapped in quotes. The value can 50 | * be empty. 51 | * @param x A JSONTokener of the source text. 52 | * @return The value string, or null if empty. 53 | * @throws JSONException if the quoted string is badly formed. 54 | */ 55 | private static String getValue(JSONTokener x) throws JSONException { 56 | char c; 57 | char q; 58 | StringBuffer sb; 59 | do { 60 | c = x.next(); 61 | } while (c == ' ' || c == '\t'); 62 | switch (c) { 63 | case 0: 64 | return null; 65 | case '"': 66 | case '\'': 67 | q = c; 68 | sb = new StringBuffer(); 69 | for (;;) { 70 | c = x.next(); 71 | if (c == q) { 72 | break; 73 | } 74 | if (c == 0 || c == '\n' || c == '\r') { 75 | throw x.syntaxError("Missing close quote '" + q + "'."); 76 | } 77 | sb.append(c); 78 | } 79 | return sb.toString(); 80 | case ',': 81 | x.back(); 82 | return ""; 83 | default: 84 | x.back(); 85 | return x.nextTo(','); 86 | } 87 | } 88 | 89 | /** 90 | * Produce a JSONArray of strings from a row of comma delimited values. 91 | * @param x A JSONTokener of the source text. 92 | * @return A JSONArray of strings. 93 | * @throws JSONException 94 | */ 95 | public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { 96 | JSONArray ja = new JSONArray(); 97 | for (;;) { 98 | String value = getValue(x); 99 | char c = x.next(); 100 | if (value == null || 101 | (ja.length() == 0 && value.length() == 0 && c != ',')) { 102 | return null; 103 | } 104 | ja.put(value); 105 | for (;;) { 106 | if (c == ',') { 107 | break; 108 | } 109 | if (c != ' ') { 110 | if (c == '\n' || c == '\r' || c == 0) { 111 | return ja; 112 | } 113 | throw x.syntaxError("Bad character '" + c + "' (" + 114 | (int)c + ")."); 115 | } 116 | c = x.next(); 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Produce a JSONObject from a row of comma delimited text, using a 123 | * parallel JSONArray of strings to provides the names of the elements. 124 | * @param names A JSONArray of names. This is commonly obtained from the 125 | * first row of a comma delimited text file using the rowToJSONArray 126 | * method. 127 | * @param x A JSONTokener of the source text. 128 | * @return A JSONObject combining the names and values. 129 | * @throws JSONException 130 | */ 131 | public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) 132 | throws JSONException { 133 | JSONArray ja = rowToJSONArray(x); 134 | return ja != null ? ja.toJSONObject(names) : null; 135 | } 136 | 137 | /** 138 | * Produce a comma delimited text row from a JSONArray. Values containing 139 | * the comma character will be quoted. Troublesome characters may be 140 | * removed. 141 | * @param ja A JSONArray of strings. 142 | * @return A string ending in NEWLINE. 143 | */ 144 | public static String rowToString(JSONArray ja) { 145 | StringBuffer sb = new StringBuffer(); 146 | for (int i = 0; i < ja.length(); i += 1) { 147 | if (i > 0) { 148 | sb.append(','); 149 | } 150 | Object object = ja.opt(i); 151 | if (object != null) { 152 | String string = object.toString(); 153 | if (string.length() > 0 && (string.indexOf(',') >= 0 || 154 | string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || 155 | string.indexOf(0) >= 0 || string.charAt(0) == '"')) { 156 | sb.append('"'); 157 | int length = string.length(); 158 | for (int j = 0; j < length; j += 1) { 159 | char c = string.charAt(j); 160 | if (c >= ' ' && c != '"') { 161 | sb.append(c); 162 | } 163 | } 164 | sb.append('"'); 165 | } else { 166 | sb.append(string); 167 | } 168 | } 169 | } 170 | sb.append('\n'); 171 | return sb.toString(); 172 | } 173 | 174 | /** 175 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 176 | * using the first row as a source of names. 177 | * @param string The comma delimited text. 178 | * @return A JSONArray of JSONObjects. 179 | * @throws JSONException 180 | */ 181 | public static JSONArray toJSONArray(String string) throws JSONException { 182 | return toJSONArray(new JSONTokener(string)); 183 | } 184 | 185 | /** 186 | * Produce a JSONArray of JSONObjects from a comma delimited text string, 187 | * using the first row as a source of names. 188 | * @param x The JSONTokener containing the comma delimited text. 189 | * @return A JSONArray of JSONObjects. 190 | * @throws JSONException 191 | */ 192 | public static JSONArray toJSONArray(JSONTokener x) throws JSONException { 193 | return toJSONArray(rowToJSONArray(x), x); 194 | } 195 | 196 | /** 197 | * Produce a JSONArray of JSONObjects from a comma delimited text string 198 | * using a supplied JSONArray as the source of element names. 199 | * @param names A JSONArray of strings. 200 | * @param string The comma delimited text. 201 | * @return A JSONArray of JSONObjects. 202 | * @throws JSONException 203 | */ 204 | public static JSONArray toJSONArray(JSONArray names, String string) 205 | throws JSONException { 206 | return toJSONArray(names, new JSONTokener(string)); 207 | } 208 | 209 | /** 210 | * Produce a JSONArray of JSONObjects from a comma delimited text string 211 | * using a supplied JSONArray as the source of element names. 212 | * @param names A JSONArray of strings. 213 | * @param x A JSONTokener of the source text. 214 | * @return A JSONArray of JSONObjects. 215 | * @throws JSONException 216 | */ 217 | public static JSONArray toJSONArray(JSONArray names, JSONTokener x) 218 | throws JSONException { 219 | if (names == null || names.length() == 0) { 220 | return null; 221 | } 222 | JSONArray ja = new JSONArray(); 223 | for (;;) { 224 | JSONObject jo = rowToJSONObject(names, x); 225 | if (jo == null) { 226 | break; 227 | } 228 | ja.put(jo); 229 | } 230 | if (ja.length() == 0) { 231 | return null; 232 | } 233 | return ja; 234 | } 235 | 236 | 237 | /** 238 | * Produce a comma delimited text from a JSONArray of JSONObjects. The 239 | * first row will be a list of names obtained by inspecting the first 240 | * JSONObject. 241 | * @param ja A JSONArray of JSONObjects. 242 | * @return A comma delimited text. 243 | * @throws JSONException 244 | */ 245 | public static String toString(JSONArray ja) throws JSONException { 246 | JSONObject jo = ja.optJSONObject(0); 247 | if (jo != null) { 248 | JSONArray names = jo.names(); 249 | if (names != null) { 250 | return rowToString(names) + toString(names, ja); 251 | } 252 | } 253 | return null; 254 | } 255 | 256 | /** 257 | * Produce a comma delimited text from a JSONArray of JSONObjects using 258 | * a provided list of names. The list of names is not included in the 259 | * output. 260 | * @param names A JSONArray of strings. 261 | * @param ja A JSONArray of JSONObjects. 262 | * @return A comma delimited text. 263 | * @throws JSONException 264 | */ 265 | public static String toString(JSONArray names, JSONArray ja) 266 | throws JSONException { 267 | if (names == null || names.length() == 0) { 268 | return null; 269 | } 270 | StringBuffer sb = new StringBuffer(); 271 | for (int i = 0; i < ja.length(); i += 1) { 272 | JSONObject jo = ja.optJSONObject(i); 273 | if (jo != null) { 274 | sb.append(rowToString(jo.toJSONArray(names))); 275 | } 276 | } 277 | return sb.toString(); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /slurp/src/main/java/org/json/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | /* 7 | Copyright (c) 2006 JSON.org 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | The Software shall be used for Good, not Evil. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | /** 31 | * JSONWriter provides a quick and convenient way of producing JSON text. 32 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 33 | * added, so the results are ready for transmission or storage. Each instance of 34 | * JSONWriter can produce one JSON text. 35 | *
36 | * A JSONWriter instance provides a value method for appending
37 | * values to the
38 | * text, and a key
39 | * method for adding keys before values in objects. There are array
40 | * and endArray methods that make and bound array values, and
41 | * object and endObject methods which make and bound
42 | * object values. All of these methods return the JSONWriter instance,
43 | * permitting a cascade style. For example,
44 | * new JSONWriter(myWriter)
45 | * .object()
46 | * .key("JSON")
47 | * .value("Hello, World!")
48 | * .endObject(); which writes
49 | * {"JSON":"Hello, World!"}
50 | *
51 | * The first method called must be array or object.
52 | * There are no methods for adding commas or colons. JSONWriter adds them for
53 | * you. Objects and arrays can be nested up to 20 levels deep.
54 | *
55 | * This can sometimes be easier than using a JSONObject to build a string.
56 | * @author JSON.org
57 | * @version 2011-11-24
58 | */
59 | public class JSONWriter {
60 | private static final int maxdepth = 200;
61 |
62 | /**
63 | * The comma flag determines if a comma should be output before the next
64 | * value.
65 | */
66 | private boolean comma;
67 |
68 | /**
69 | * The current mode. Values:
70 | * 'a' (array),
71 | * 'd' (done),
72 | * 'i' (initial),
73 | * 'k' (key),
74 | * 'o' (object).
75 | */
76 | protected char mode;
77 |
78 | /**
79 | * The object/array stack.
80 | */
81 | private final JSONObject stack[];
82 |
83 | /**
84 | * The stack top index. A value of 0 indicates that the stack is empty.
85 | */
86 | private int top;
87 |
88 | /**
89 | * The writer that will receive the output.
90 | */
91 | protected Writer writer;
92 |
93 | /**
94 | * Make a fresh JSONWriter. It can be used to build one JSON text.
95 | */
96 | public JSONWriter(Writer w) {
97 | this.comma = false;
98 | this.mode = 'i';
99 | this.stack = new JSONObject[maxdepth];
100 | this.top = 0;
101 | this.writer = w;
102 | }
103 |
104 | /**
105 | * Append a value.
106 | * @param string A string value.
107 | * @return this
108 | * @throws JSONException If the value is out of sequence.
109 | */
110 | private JSONWriter append(String string) throws JSONException {
111 | if (string == null) {
112 | throw new JSONException("Null pointer");
113 | }
114 | if (this.mode == 'o' || this.mode == 'a') {
115 | try {
116 | if (this.comma && this.mode == 'a') {
117 | this.writer.write(',');
118 | }
119 | this.writer.write(string);
120 | } catch (IOException e) {
121 | throw new JSONException(e);
122 | }
123 | if (this.mode == 'o') {
124 | this.mode = 'k';
125 | }
126 | this.comma = true;
127 | return this;
128 | }
129 | throw new JSONException("Value out of sequence.");
130 | }
131 |
132 | /**
133 | * Begin appending a new array. All values until the balancing
134 | * endArray will be appended to this array. The
135 | * endArray method must be called to mark the array's end.
136 | * @return this
137 | * @throws JSONException If the nesting is too deep, or if the object is
138 | * started in the wrong place (for example as a key or after the end of the
139 | * outermost array or object).
140 | */
141 | public JSONWriter array() throws JSONException {
142 | if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
143 | this.push(null);
144 | this.append("[");
145 | this.comma = false;
146 | return this;
147 | }
148 | throw new JSONException("Misplaced array.");
149 | }
150 |
151 | /**
152 | * End something.
153 | * @param mode Mode
154 | * @param c Closing character
155 | * @return this
156 | * @throws JSONException If unbalanced.
157 | */
158 | private JSONWriter end(char mode, char c) throws JSONException {
159 | if (this.mode != mode) {
160 | throw new JSONException(mode == 'a'
161 | ? "Misplaced endArray."
162 | : "Misplaced endObject.");
163 | }
164 | this.pop(mode);
165 | try {
166 | this.writer.write(c);
167 | } catch (IOException e) {
168 | throw new JSONException(e);
169 | }
170 | this.comma = true;
171 | return this;
172 | }
173 |
174 | /**
175 | * End an array. This method most be called to balance calls to
176 | * array.
177 | * @return this
178 | * @throws JSONException If incorrectly nested.
179 | */
180 | public JSONWriter endArray() throws JSONException {
181 | return this.end('a', ']');
182 | }
183 |
184 | /**
185 | * End an object. This method most be called to balance calls to
186 | * object.
187 | * @return this
188 | * @throws JSONException If incorrectly nested.
189 | */
190 | public JSONWriter endObject() throws JSONException {
191 | return this.end('k', '}');
192 | }
193 |
194 | /**
195 | * Append a key. The key will be associated with the next value. In an
196 | * object, every value must be preceded by a key.
197 | * @param string A key string.
198 | * @return this
199 | * @throws JSONException If the key is out of place. For example, keys
200 | * do not belong in arrays or if the key is null.
201 | */
202 | public JSONWriter key(String string) throws JSONException {
203 | if (string == null) {
204 | throw new JSONException("Null key.");
205 | }
206 | if (this.mode == 'k') {
207 | try {
208 | this.stack[this.top - 1].putOnce(string, Boolean.TRUE);
209 | if (this.comma) {
210 | this.writer.write(',');
211 | }
212 | this.writer.write(JSONObject.quote(string));
213 | this.writer.write(':');
214 | this.comma = false;
215 | this.mode = 'o';
216 | return this;
217 | } catch (IOException e) {
218 | throw new JSONException(e);
219 | }
220 | }
221 | throw new JSONException("Misplaced key.");
222 | }
223 |
224 |
225 | /**
226 | * Begin appending a new object. All keys and values until the balancing
227 | * endObject will be appended to this object. The
228 | * endObject method must be called to mark the object's end.
229 | * @return this
230 | * @throws JSONException If the nesting is too deep, or if the object is
231 | * started in the wrong place (for example as a key or after the end of the
232 | * outermost array or object).
233 | */
234 | public JSONWriter object() throws JSONException {
235 | if (this.mode == 'i') {
236 | this.mode = 'o';
237 | }
238 | if (this.mode == 'o' || this.mode == 'a') {
239 | this.append("{");
240 | this.push(new JSONObject());
241 | this.comma = false;
242 | return this;
243 | }
244 | throw new JSONException("Misplaced object.");
245 |
246 | }
247 |
248 |
249 | /**
250 | * Pop an array or object scope.
251 | * @param c The scope to close.
252 | * @throws JSONException If nesting is wrong.
253 | */
254 | private void pop(char c) throws JSONException {
255 | if (this.top <= 0) {
256 | throw new JSONException("Nesting error.");
257 | }
258 | char m = this.stack[this.top - 1] == null ? 'a' : 'k';
259 | if (m != c) {
260 | throw new JSONException("Nesting error.");
261 | }
262 | this.top -= 1;
263 | this.mode = this.top == 0
264 | ? 'd'
265 | : this.stack[this.top - 1] == null
266 | ? 'a'
267 | : 'k';
268 | }
269 |
270 | /**
271 | * Push an array or object scope.
272 | * @param c The scope to open.
273 | * @throws JSONException If nesting is too deep.
274 | */
275 | private void push(JSONObject jo) throws JSONException {
276 | if (this.top >= maxdepth) {
277 | throw new JSONException("Nesting too deep.");
278 | }
279 | this.stack[this.top] = jo;
280 | this.mode = jo == null ? 'a' : 'k';
281 | this.top += 1;
282 | }
283 |
284 |
285 | /**
286 | * Append either the value true or the value
287 | * false.
288 | * @param b A boolean.
289 | * @return this
290 | * @throws JSONException
291 | */
292 | public JSONWriter value(boolean b) throws JSONException {
293 | return this.append(b ? "true" : "false");
294 | }
295 |
296 | /**
297 | * Append a double value.
298 | * @param d A double.
299 | * @return this
300 | * @throws JSONException If the number is not finite.
301 | */
302 | public JSONWriter value(double d) throws JSONException {
303 | return this.value(new Double(d));
304 | }
305 |
306 | /**
307 | * Append a long value.
308 | * @param l A long.
309 | * @return this
310 | * @throws JSONException
311 | */
312 | public JSONWriter value(long l) throws JSONException {
313 | return this.append(Long.toString(l));
314 | }
315 |
316 |
317 | /**
318 | * Append an object value.
319 | * @param object The object to append. It can be null, or a Boolean, Number,
320 | * String, JSONObject, or JSONArray, or an object that implements JSONString.
321 | * @return this
322 | * @throws JSONException If the value is out of sequence.
323 | */
324 | public JSONWriter value(Object object) throws JSONException {
325 | return this.append(JSONObject.valueToString(object));
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/XMLTokener.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2002 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | /**
28 | * The XMLTokener extends the JSONTokener to provide additional methods
29 | * for the parsing of XML texts.
30 | * @author JSON.org
31 | * @version 2012-11-13
32 | */
33 | public class XMLTokener extends JSONTokener {
34 |
35 |
36 | /** The table of entity values. It initially contains Character values for
37 | * amp, apos, gt, lt, quot.
38 | */
39 | public static final java.util.HashMap]]>.
61 | * @throws JSONException If the ]]> is not found.
62 | */
63 | public String nextCDATA() throws JSONException {
64 | char c;
65 | int i;
66 | StringBuffer sb = new StringBuffer();
67 | for (;;) {
68 | c = next();
69 | if (end()) {
70 | throw syntaxError("Unclosed CDATA");
71 | }
72 | sb.append(c);
73 | i = sb.length() - 3;
74 | if (i >= 0 && sb.charAt(i) == ']' &&
75 | sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
76 | sb.setLength(i);
77 | return sb.toString();
78 | }
79 | }
80 | }
81 |
82 |
83 | /**
84 | * Get the next XML outer token, trimming whitespace. There are two kinds
85 | * of tokens: the '<' character which begins a markup tag, and the content
86 | * text between markup tags.
87 | *
88 | * @return A string, or a '<' Character, or null if there is no more
89 | * source text.
90 | * @throws JSONException
91 | */
92 | public Object nextContent() throws JSONException {
93 | char c;
94 | StringBuffer sb;
95 | do {
96 | c = next();
97 | } while (Character.isWhitespace(c));
98 | if (c == 0) {
99 | return null;
100 | }
101 | if (c == '<') {
102 | return XML.LT;
103 | }
104 | sb = new StringBuffer();
105 | for (;;) {
106 | if (c == '<' || c == 0) {
107 | back();
108 | return sb.toString().trim();
109 | }
110 | if (c == '&') {
111 | sb.append(nextEntity(c));
112 | } else {
113 | sb.append(c);
114 | }
115 | c = next();
116 | }
117 | }
118 |
119 |
120 | /**
121 | * Return the next entity. These entities are translated to Characters:
122 | * & ' > < ".
123 | * @param ampersand An ampersand character.
124 | * @return A Character or an entity String if the entity is not recognized.
125 | * @throws JSONException If missing ';' in XML entity.
126 | */
127 | public Object nextEntity(char ampersand) throws JSONException {
128 | StringBuffer sb = new StringBuffer();
129 | for (;;) {
130 | char c = next();
131 | if (Character.isLetterOrDigit(c) || c == '#') {
132 | sb.append(Character.toLowerCase(c));
133 | } else if (c == ';') {
134 | break;
135 | } else {
136 | throw syntaxError("Missing ';' in XML entity: &" + sb);
137 | }
138 | }
139 | String string = sb.toString();
140 | Object object = entity.get(string);
141 | return object != null ? object : ampersand + string + ";";
142 | }
143 |
144 |
145 | /**
146 | * Returns the next XML meta token. This is used for skipping over
147 | * and ...?> structures.
148 | * @return Syntax characters (< > / = ! ?) are returned as
149 | * Character, and strings and names are returned as Boolean. We don't care
150 | * what the values actually are.
151 | * @throws JSONException If a string is not properly closed or if the XML
152 | * is badly structured.
153 | */
154 | public Object nextMeta() throws JSONException {
155 | char c;
156 | char q;
157 | do {
158 | c = next();
159 | } while (Character.isWhitespace(c));
160 | switch (c) {
161 | case 0:
162 | throw syntaxError("Misshaped meta tag");
163 | case '<':
164 | return XML.LT;
165 | case '>':
166 | return XML.GT;
167 | case '/':
168 | return XML.SLASH;
169 | case '=':
170 | return XML.EQ;
171 | case '!':
172 | return XML.BANG;
173 | case '?':
174 | return XML.QUEST;
175 | case '"':
176 | case '\'':
177 | q = c;
178 | for (;;) {
179 | c = next();
180 | if (c == 0) {
181 | throw syntaxError("Unterminated string");
182 | }
183 | if (c == q) {
184 | return Boolean.TRUE;
185 | }
186 | }
187 | default:
188 | for (;;) {
189 | c = next();
190 | if (Character.isWhitespace(c)) {
191 | return Boolean.TRUE;
192 | }
193 | switch (c) {
194 | case 0:
195 | case '<':
196 | case '>':
197 | case '/':
198 | case '=':
199 | case '!':
200 | case '?':
201 | case '"':
202 | case '\'':
203 | back();
204 | return Boolean.TRUE;
205 | }
206 | }
207 | }
208 | }
209 |
210 |
211 | /**
212 | * Get the next XML Token. These tokens are found inside of angle
213 | * brackets. It may be one of these characters: / > = ! ? or it
214 | * may be a string wrapped in single quotes or double quotes, or it may be a
215 | * name.
216 | * @return a String or a Character.
217 | * @throws JSONException If the XML is not well formed.
218 | */
219 | public Object nextToken() throws JSONException {
220 | char c;
221 | char q;
222 | StringBuffer sb;
223 | do {
224 | c = next();
225 | } while (Character.isWhitespace(c));
226 | switch (c) {
227 | case 0:
228 | throw syntaxError("Misshaped element");
229 | case '<':
230 | throw syntaxError("Misplaced '<'");
231 | case '>':
232 | return XML.GT;
233 | case '/':
234 | return XML.SLASH;
235 | case '=':
236 | return XML.EQ;
237 | case '!':
238 | return XML.BANG;
239 | case '?':
240 | return XML.QUEST;
241 |
242 | // Quoted string
243 |
244 | case '"':
245 | case '\'':
246 | q = c;
247 | sb = new StringBuffer();
248 | for (;;) {
249 | c = next();
250 | if (c == 0) {
251 | throw syntaxError("Unterminated string");
252 | }
253 | if (c == q) {
254 | return sb.toString();
255 | }
256 | if (c == '&') {
257 | sb.append(nextEntity(c));
258 | } else {
259 | sb.append(c);
260 | }
261 | }
262 | default:
263 |
264 | // Name
265 |
266 | sb = new StringBuffer();
267 | for (;;) {
268 | sb.append(c);
269 | c = next();
270 | if (Character.isWhitespace(c)) {
271 | return sb.toString();
272 | }
273 | switch (c) {
274 | case 0:
275 | return sb.toString();
276 | case '>':
277 | case '/':
278 | case '=':
279 | case '!':
280 | case '?':
281 | case '[':
282 | case ']':
283 | back();
284 | return sb.toString();
285 | case '<':
286 | case '"':
287 | case '\'':
288 | throw syntaxError("Bad character in a name");
289 | }
290 | }
291 | }
292 | }
293 |
294 |
295 | /**
296 | * Skip characters until past the requested string.
297 | * If it is not found, we are left at the end of the source with a result of false.
298 | * @param to A string to skip past.
299 | * @throws JSONException
300 | */
301 | public boolean skipPast(String to) throws JSONException {
302 | boolean b;
303 | char c;
304 | int i;
305 | int j;
306 | int offset = 0;
307 | int length = to.length();
308 | char[] circle = new char[length];
309 |
310 | /*
311 | * First fill the circle buffer with as many characters as are in the
312 | * to string. If we reach an early end, bail.
313 | */
314 |
315 | for (i = 0; i < length; i += 1) {
316 | c = next();
317 | if (c == 0) {
318 | return false;
319 | }
320 | circle[i] = c;
321 | }
322 |
323 | /* We will loop, possibly for all of the remaining characters. */
324 |
325 | for (;;) {
326 | j = offset;
327 | b = true;
328 |
329 | /* Compare the circle buffer with the to string. */
330 |
331 | for (i = 0; i < length; i += 1) {
332 | if (circle[j] != to.charAt(i)) {
333 | b = false;
334 | break;
335 | }
336 | j += 1;
337 | if (j >= length) {
338 | j -= length;
339 | }
340 | }
341 |
342 | /* If we exit the loop with b intact, then victory is ours. */
343 |
344 | if (b) {
345 | return true;
346 | }
347 |
348 | /* Get the next character. If there isn't one, then defeat is ours. */
349 |
350 | c = next();
351 | if (c == 0) {
352 | return false;
353 | }
354 | /*
355 | * Shove the character in the circle buffer and advance the
356 | * circle offset. The offset is mod n.
357 | */
358 | circle[offset] = c;
359 | offset += 1;
360 | if (offset >= length) {
361 | offset -= length;
362 | }
363 | }
364 | }
365 | }
366 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/Kim.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 |
4 | /*
5 | Copyright (c) 2013 JSON.org
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | The Software shall be used for Good, not Evil.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 | SOFTWARE.
26 | */
27 |
28 | /**
29 | * Kim makes immutable eight bit Unicode strings. If the MSB of a byte is set,
30 | * then the next byte is a continuation byte. The last byte of a character
31 | * never has the MSB reset. Every byte that is not the last byte has the MSB
32 | * set. Kim stands for "Keep it minimal". A Unicode character is never longer
33 | * than 3 bytes. Every byte contributes 7 bits to the character. ASCII is
34 | * unmodified.
35 | *
36 | * Kim UTF-8
37 | * one byte U+007F U+007F
38 | * two bytes U+3FFF U+07FF
39 | * three bytes U+10FFF U+FFFF
40 | * four bytes U+10FFFF
41 | *
42 | * Characters in the ranges U+0800..U+3FFF and U+10000..U+10FFFF will be one
43 | * byte smaller when encoded in Kim compared to UTF-8.
44 | *
45 | * Kim is beneficial when using scripts such as Old South Arabian, Aramaic,
46 | * Avestan, Balinese, Batak, Bopomofo, Buginese, Buhid, Carian, Cherokee,
47 | * Coptic, Cyrillic, Deseret, Egyptian Hieroglyphs, Ethiopic, Georgian,
48 | * Glagolitic, Gothic, Hangul Jamo, Hanunoo, Hiragana, Kanbun, Kaithi,
49 | * Kannada, Katakana, Kharoshthi, Khmer, Lao, Lepcha, Limbu, Lycian, Lydian,
50 | * Malayalam, Mandaic, Meroitic, Miao, Mongolian, Myanmar, New Tai Lue,
51 | * Ol Chiki, Old Turkic, Oriya, Osmanya, Pahlavi, Parthian, Phags-Pa,
52 | * Phoenician, Samaritan, Sharada, Sinhala, Sora Sompeng, Tagalog, Tagbanwa,
53 | * Takri, Tai Le, Tai Tham, Tamil, Telugu, Thai, Tibetan, Tifinagh, UCAS.
54 | *
55 | * A kim object can be constructed from an ordinary UTF-16 string, or from a
56 | * byte array. A kim object can produce a UTF-16 string.
57 | *
58 | * As with UTF-8, it is possible to detect character boundaries within a byte
59 | * sequence. UTF-8 is one of the world's great inventions. While Kim is more
60 | * efficient, it is not clear that it is worth the expense of transition.
61 | *
62 | * @version 2013-04-18
63 | */
64 | public class Kim {
65 |
66 | /**
67 | * The byte array containing the kim's content.
68 | */
69 | private byte[] bytes = null;
70 |
71 | /**
72 | * The kim's hashcode, conforming to Java's hashcode conventions.
73 | */
74 | private int hashcode = 0;
75 |
76 | /**
77 | * The number of bytes in the kim. The number of bytes can be as much as
78 | * three times the number of characters.
79 | */
80 | public int length = 0;
81 |
82 | /**
83 | * The memoization of toString().
84 | */
85 | private String string = null;
86 |
87 | /**
88 | * Make a kim from a portion of a byte array.
89 | *
90 | * @param bytes
91 | * A byte array.
92 | * @param from
93 | * The index of the first byte.
94 | * @param thru
95 | * The index of the last byte plus one.
96 | */
97 | public Kim(byte[] bytes, int from, int thru) {
98 |
99 | // As the bytes are copied into the new kim, a hashcode is computed using a
100 | // modified Fletcher code.
101 |
102 | int sum = 1;
103 | int value;
104 | this.hashcode = 0;
105 | this.length = thru - from;
106 | if (this.length > 0) {
107 | this.bytes = new byte[this.length];
108 | for (int at = 0; at < this.length; at += 1) {
109 | value = (int) bytes[at + from] & 0xFF;
110 | sum += value;
111 | this.hashcode += sum;
112 | this.bytes[at] = (byte) value;
113 | }
114 | this.hashcode += sum << 16;
115 | }
116 | }
117 |
118 | /**
119 | * Make a kim from a byte array.
120 | *
121 | * @param bytes
122 | * The byte array.
123 | * @param length
124 | * The number of bytes.
125 | */
126 | public Kim(byte[] bytes, int length) {
127 | this(bytes, 0, length);
128 | }
129 |
130 | /**
131 | * Make a new kim from a substring of an existing kim. The coordinates are
132 | * in byte units, not character units.
133 | *
134 | * @param kim
135 | * The source of bytes.
136 | * @param from
137 | * The point at which to take bytes.
138 | * @param thru
139 | * The point at which to stop taking bytes.
140 | * @return the substring
141 | */
142 | public Kim(Kim kim, int from, int thru) {
143 | this(kim.bytes, from, thru);
144 | }
145 |
146 | /**
147 | * Make a kim from a string.
148 | *
149 | * @param string
150 | * The string.
151 | * @throws JSONException
152 | * if surrogate pair mismatch.
153 | */
154 | public Kim(String string) throws JSONException {
155 | int stringLength = string.length();
156 | this.hashcode = 0;
157 | this.length = 0;
158 |
159 | // First pass: Determine the length of the kim, allowing for the UTF-16
160 | // to UTF-32 conversion, and then the UTF-32 to Kim conversion.
161 |
162 | if (stringLength > 0) {
163 | for (int i = 0; i < stringLength; i += 1) {
164 | int c = string.charAt(i);
165 | if (c <= 0x7F) {
166 | this.length += 1;
167 | } else if (c <= 0x3FFF) {
168 | this.length += 2;
169 | } else {
170 | if (c >= 0xD800 && c <= 0xDFFF) {
171 | i += 1;
172 | int d = string.charAt(i);
173 | if (c > 0xDBFF || d < 0xDC00 || d > 0xDFFF) {
174 | throw new JSONException("Bad UTF16");
175 | }
176 | }
177 | this.length += 3;
178 | }
179 | }
180 |
181 | // Second pass: Allocate a byte array and fill that array with the conversion
182 | // while computing the hashcode.
183 |
184 | this.bytes = new byte[length];
185 | int at = 0;
186 | int b;
187 | int sum = 1;
188 | for (int i = 0; i < stringLength; i += 1) {
189 | int character = string.charAt(i);
190 | if (character <= 0x7F) {
191 | bytes[at] = (byte) character;
192 | sum += character;
193 | this.hashcode += sum;
194 | at += 1;
195 | } else if (character <= 0x3FFF) {
196 | b = 0x80 | (character >>> 7);
197 | bytes[at] = (byte) b;
198 | sum += b;
199 | this.hashcode += sum;
200 | at += 1;
201 | b = character & 0x7F;
202 | bytes[at] = (byte) b;
203 | sum += b;
204 | this.hashcode += sum;
205 | at += 1;
206 | } else {
207 | if (character >= 0xD800 && character <= 0xDBFF) {
208 | i += 1;
209 | character = (((character & 0x3FF) << 10) | (string
210 | .charAt(i) & 0x3FF)) + 65536;
211 | }
212 | b = 0x80 | (character >>> 14);
213 | bytes[at] = (byte) b;
214 | sum += b;
215 | this.hashcode += sum;
216 | at += 1;
217 | b = 0x80 | ((character >>> 7) & 0xFF);
218 | bytes[at] = (byte) b;
219 | sum += b;
220 | this.hashcode += sum;
221 | at += 1;
222 | b = character & 0x7F;
223 | bytes[at] = (byte) b;
224 | sum += b;
225 | this.hashcode += sum;
226 | at += 1;
227 | }
228 | }
229 | this.hashcode += sum << 16;
230 | }
231 | }
232 |
233 | /**
234 | * Returns the character at the specified index. The index refers to byte
235 | * values and ranges from 0 to length - 1. The index of the next character
236 | * is at index + Kim.characterSize(kim.characterAt(index)).
237 | *
238 | * @param at
239 | * the index of the char value. The first character is at 0.
240 | * @returns a Unicode character between 0 and 0x10FFFF.
241 | * @throws JSONException
242 | * if at does not point to a valid character.
243 | */
244 | public int characterAt(int at) throws JSONException {
245 | int c = get(at);
246 | if ((c & 0x80) == 0) {
247 | return c;
248 | }
249 | int character;
250 | int c1 = get(at + 1);
251 | if ((c1 & 0x80) == 0) {
252 | character = ((c & 0x7F) << 7) | c1;
253 | if (character > 0x7F) {
254 | return character;
255 | }
256 | } else {
257 | int c2 = get(at + 2);
258 | character = ((c & 0x7F) << 14) | ((c1 & 0x7F) << 7) | c2;
259 | if ((c2 & 0x80) == 0 && character > 0x3FFF && character <= 0x10FFFF
260 | && (character < 0xD800 || character > 0xDFFF)) {
261 | return character;
262 | }
263 | }
264 | throw new JSONException("Bad character at " + at);
265 | }
266 |
267 | /**
268 | * Returns the number of bytes needed to contain the character in Kim
269 | * format.
270 | *
271 | * @param character
272 | * a Unicode character between 0 and 0x10FFFF.
273 | * @return 1, 2, or 3
274 | * @throws JSONException
275 | * if the character is not representable in a kim.
276 | */
277 | public static int characterSize(int character) throws JSONException {
278 | if (character < 0 || character > 0x10FFFF) {
279 | throw new JSONException("Bad character " + character);
280 | }
281 | return character <= 0x7F ? 1 : character <= 0x3FFF ? 2 : 3;
282 | }
283 |
284 | /**
285 | * Copy the contents of this kim to a byte array.
286 | *
287 | * @param bytes
288 | * A byte array of sufficient size.
289 | * @param at
290 | * The position within the byte array to take the byes.
291 | * @return The position immediately after the copy.
292 | */
293 | public int copy(byte[] bytes, int at) {
294 | System.arraycopy(this.bytes, 0, bytes, at, this.length);
295 | return at + this.length;
296 | }
297 |
298 | /**
299 | * Two kim objects containing exactly the same bytes in the same order are
300 | * equal to each other.
301 | *
302 | * @param obj
303 | * the other kim with which to compare.
304 | * @returns true if this and obj are both kim objects containing identical
305 | * byte sequences.
306 | */
307 | public boolean equals(Object obj) {
308 | if (!(obj instanceof Kim)) {
309 | return false;
310 | }
311 | Kim that = (Kim) obj;
312 | if (this == that) {
313 | return true;
314 | }
315 | if (this.hashcode != that.hashcode) {
316 | return false;
317 | }
318 | return java.util.Arrays.equals(this.bytes, that.bytes);
319 | }
320 |
321 | /**
322 | * Get a byte from a kim.
323 | * @param at
324 | * The position of the byte. The first byte is at 0.
325 | * @return The byte.
326 | * @throws JSONException
327 | * if there is no byte at that position.
328 | */
329 | public int get(int at) throws JSONException {
330 | if (at < 0 || at > this.length) {
331 | throw new JSONException("Bad character at " + at);
332 | }
333 | return ((int) this.bytes[at]) & 0xFF;
334 | }
335 |
336 | /**
337 | * Returns a hash code value for the kim.
338 | */
339 | public int hashCode() {
340 | return this.hashcode;
341 | }
342 |
343 | /**
344 | * Produce a UTF-16 String from this kim. The number of codepoints in the
345 | * string will not be greater than the number of bytes in the kim, although
346 | * it could be less.
347 | *
348 | * @return The string. A kim memoizes its string representation.
349 | * @throws JSONException
350 | * if the kim is not valid.
351 | */
352 | public String toString() throws JSONException {
353 | if (this.string == null) {
354 | int c;
355 | int length = 0;
356 | char chars[] = new char[this.length];
357 | for (int at = 0; at < this.length; at += characterSize(c)) {
358 | c = this.characterAt(at);
359 | if (c < 0x10000) {
360 | chars[length] = (char) c;
361 | length += 1;
362 | } else {
363 | chars[length] = (char) (0xD800 | ((c - 0x10000) >>> 10));
364 | length += 1;
365 | chars[length] = (char) (0xDC00 | (c & 0x03FF));
366 | length += 1;
367 | }
368 | }
369 | this.string = new String(chars, 0, length);
370 | }
371 | return this.string;
372 | }
373 | }
374 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/JSONTokener.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.Reader;
8 | import java.io.StringReader;
9 |
10 | /*
11 | Copyright (c) 2002 JSON.org
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy
14 | of this software and associated documentation files (the "Software"), to deal
15 | in the Software without restriction, including without limitation the rights
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | copies of the Software, and to permit persons to whom the Software is
18 | furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all
21 | copies or substantial portions of the Software.
22 |
23 | The Software shall be used for Good, not Evil.
24 |
25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | SOFTWARE.
32 | */
33 |
34 | /**
35 | * A JSONTokener takes a source string and extracts characters and tokens from
36 | * it. It is used by the JSONObject and JSONArray constructors to parse
37 | * JSON source strings.
38 | * @author JSON.org
39 | * @version 2012-02-16
40 | */
41 | public class JSONTokener {
42 |
43 | private long character;
44 | private boolean eof;
45 | private long index;
46 | private long line;
47 | private char previous;
48 | private Reader reader;
49 | private boolean usePrevious;
50 |
51 |
52 | /**
53 | * Construct a JSONTokener from a Reader.
54 | *
55 | * @param reader A reader.
56 | */
57 | public JSONTokener(Reader reader) {
58 | this.reader = reader.markSupported()
59 | ? reader
60 | : new BufferedReader(reader);
61 | this.eof = false;
62 | this.usePrevious = false;
63 | this.previous = 0;
64 | this.index = 0;
65 | this.character = 1;
66 | this.line = 1;
67 | }
68 |
69 |
70 | /**
71 | * Construct a JSONTokener from an InputStream.
72 | */
73 | public JSONTokener(InputStream inputStream) throws JSONException {
74 | this(new InputStreamReader(inputStream));
75 | }
76 |
77 |
78 | /**
79 | * Construct a JSONTokener from a string.
80 | *
81 | * @param s A source string.
82 | */
83 | public JSONTokener(String s) {
84 | this(new StringReader(s));
85 | }
86 |
87 |
88 | /**
89 | * Back up one character. This provides a sort of lookahead capability,
90 | * so that you can test for a digit or letter before attempting to parse
91 | * the next number or identifier.
92 | */
93 | public void back() throws JSONException {
94 | if (this.usePrevious || this.index <= 0) {
95 | throw new JSONException("Stepping back two steps is not supported");
96 | }
97 | this.index -= 1;
98 | this.character -= 1;
99 | this.usePrevious = true;
100 | this.eof = false;
101 | }
102 |
103 |
104 | /**
105 | * Get the hex value of a character (base16).
106 | * @param c A character between '0' and '9' or between 'A' and 'F' or
107 | * between 'a' and 'f'.
108 | * @return An int between 0 and 15, or -1 if c was not a hex digit.
109 | */
110 | public static int dehexchar(char c) {
111 | if (c >= '0' && c <= '9') {
112 | return c - '0';
113 | }
114 | if (c >= 'A' && c <= 'F') {
115 | return c - ('A' - 10);
116 | }
117 | if (c >= 'a' && c <= 'f') {
118 | return c - ('a' - 10);
119 | }
120 | return -1;
121 | }
122 |
123 | public boolean end() {
124 | return this.eof && !this.usePrevious;
125 | }
126 |
127 |
128 | /**
129 | * Determine if the source string still contains characters that next()
130 | * can consume.
131 | * @return true if not yet at the end of the source.
132 | */
133 | public boolean more() throws JSONException {
134 | this.next();
135 | if (this.end()) {
136 | return false;
137 | }
138 | this.back();
139 | return true;
140 | }
141 |
142 |
143 | /**
144 | * Get the next character in the source string.
145 | *
146 | * @return The next character, or 0 if past the end of the source string.
147 | */
148 | public char next() throws JSONException {
149 | int c;
150 | if (this.usePrevious) {
151 | this.usePrevious = false;
152 | c = this.previous;
153 | } else {
154 | try {
155 | c = this.reader.read();
156 | } catch (IOException exception) {
157 | throw new JSONException(exception);
158 | }
159 |
160 | if (c <= 0) { // End of stream
161 | this.eof = true;
162 | c = 0;
163 | }
164 | }
165 | this.index += 1;
166 | if (this.previous == '\r') {
167 | this.line += 1;
168 | this.character = c == '\n' ? 0 : 1;
169 | } else if (c == '\n') {
170 | this.line += 1;
171 | this.character = 0;
172 | } else {
173 | this.character += 1;
174 | }
175 | this.previous = (char) c;
176 | return this.previous;
177 | }
178 |
179 |
180 | /**
181 | * Consume the next character, and check that it matches a specified
182 | * character.
183 | * @param c The character to match.
184 | * @return The character.
185 | * @throws JSONException if the character does not match.
186 | */
187 | public char next(char c) throws JSONException {
188 | char n = this.next();
189 | if (n != c) {
190 | throw this.syntaxError("Expected '" + c + "' and instead saw '" +
191 | n + "'");
192 | }
193 | return n;
194 | }
195 |
196 |
197 | /**
198 | * Get the next n characters.
199 | *
200 | * @param n The number of characters to take.
201 | * @return A string of n characters.
202 | * @throws JSONException
203 | * Substring bounds error if there are not
204 | * n characters remaining in the source string.
205 | */
206 | public String next(int n) throws JSONException {
207 | if (n == 0) {
208 | return "";
209 | }
210 |
211 | char[] chars = new char[n];
212 | int pos = 0;
213 |
214 | while (pos < n) {
215 | chars[pos] = this.next();
216 | if (this.end()) {
217 | throw this.syntaxError("Substring bounds error");
218 | }
219 | pos += 1;
220 | }
221 | return new String(chars);
222 | }
223 |
224 |
225 | /**
226 | * Get the next char in the string, skipping whitespace.
227 | * @throws JSONException
228 | * @return A character, or 0 if there are no more characters.
229 | */
230 | public char nextClean() throws JSONException {
231 | for (;;) {
232 | char c = this.next();
233 | if (c == 0 || c > ' ') {
234 | return c;
235 | }
236 | }
237 | }
238 |
239 |
240 | /**
241 | * Return the characters up to the next close quote character.
242 | * Backslash processing is done. The formal JSON format does not
243 | * allow strings in single quotes, but an implementation is allowed to
244 | * accept them.
245 | * @param quote The quoting character, either
246 | * " (double quote) or
247 | * ' (single quote).
248 | * @return A String.
249 | * @throws JSONException Unterminated string.
250 | */
251 | public String nextString(char quote) throws JSONException {
252 | char c;
253 | StringBuffer sb = new StringBuffer();
254 | for (;;) {
255 | c = this.next();
256 | switch (c) {
257 | case 0:
258 | case '\n':
259 | case '\r':
260 | throw this.syntaxError("Unterminated string");
261 | case '\\':
262 | c = this.next();
263 | switch (c) {
264 | case 'b':
265 | sb.append('\b');
266 | break;
267 | case 't':
268 | sb.append('\t');
269 | break;
270 | case 'n':
271 | sb.append('\n');
272 | break;
273 | case 'f':
274 | sb.append('\f');
275 | break;
276 | case 'r':
277 | sb.append('\r');
278 | break;
279 | case 'u':
280 | sb.append((char)Integer.parseInt(this.next(4), 16));
281 | break;
282 | case '"':
283 | case '\'':
284 | case '\\':
285 | case '/':
286 | sb.append(c);
287 | break;
288 | default:
289 | throw this.syntaxError("Illegal escape.");
290 | }
291 | break;
292 | default:
293 | if (c == quote) {
294 | return sb.toString();
295 | }
296 | sb.append(c);
297 | }
298 | }
299 | }
300 |
301 |
302 | /**
303 | * Get the text up but not including the specified character or the
304 | * end of line, whichever comes first.
305 | * @param delimiter A delimiter character.
306 | * @return A string.
307 | */
308 | public String nextTo(char delimiter) throws JSONException {
309 | StringBuffer sb = new StringBuffer();
310 | for (;;) {
311 | char c = this.next();
312 | if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
313 | if (c != 0) {
314 | this.back();
315 | }
316 | return sb.toString().trim();
317 | }
318 | sb.append(c);
319 | }
320 | }
321 |
322 |
323 | /**
324 | * Get the text up but not including one of the specified delimiter
325 | * characters or the end of line, whichever comes first.
326 | * @param delimiters A set of delimiter characters.
327 | * @return A string, trimmed.
328 | */
329 | public String nextTo(String delimiters) throws JSONException {
330 | char c;
331 | StringBuffer sb = new StringBuffer();
332 | for (;;) {
333 | c = this.next();
334 | if (delimiters.indexOf(c) >= 0 || c == 0 ||
335 | c == '\n' || c == '\r') {
336 | if (c != 0) {
337 | this.back();
338 | }
339 | return sb.toString().trim();
340 | }
341 | sb.append(c);
342 | }
343 | }
344 |
345 |
346 | /**
347 | * Get the next value. The value can be a Boolean, Double, Integer,
348 | * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
349 | * @throws JSONException If syntax error.
350 | *
351 | * @return An object.
352 | */
353 | public Object nextValue() throws JSONException {
354 | char c = this.nextClean();
355 | String string;
356 |
357 | switch (c) {
358 | case '"':
359 | case '\'':
360 | return this.nextString(c);
361 | case '{':
362 | this.back();
363 | return new JSONObject(this);
364 | case '[':
365 | this.back();
366 | return new JSONArray(this);
367 | }
368 |
369 | /*
370 | * Handle unquoted text. This could be the values true, false, or
371 | * null, or it can be a number. An implementation (such as this one)
372 | * is allowed to also accept non-standard forms.
373 | *
374 | * Accumulate characters until we reach the end of the text or a
375 | * formatting character.
376 | */
377 |
378 | StringBuffer sb = new StringBuffer();
379 | while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
380 | sb.append(c);
381 | c = this.next();
382 | }
383 | this.back();
384 |
385 | string = sb.toString().trim();
386 | if ("".equals(string)) {
387 | throw this.syntaxError("Missing value");
388 | }
389 | return JSONObject.stringToValue(string);
390 | }
391 |
392 |
393 | /**
394 | * Skip characters until the next character is the requested character.
395 | * If the requested character is not found, no characters are skipped.
396 | * @param to A character to skip to.
397 | * @return The requested character, or zero if the requested character
398 | * is not found.
399 | */
400 | public char skipTo(char to) throws JSONException {
401 | char c;
402 | try {
403 | long startIndex = this.index;
404 | long startCharacter = this.character;
405 | long startLine = this.line;
406 | this.reader.mark(1000000);
407 | do {
408 | c = this.next();
409 | if (c == 0) {
410 | this.reader.reset();
411 | this.index = startIndex;
412 | this.character = startCharacter;
413 | this.line = startLine;
414 | return c;
415 | }
416 | } while (c != to);
417 | } catch (IOException exc) {
418 | throw new JSONException(exc);
419 | }
420 |
421 | this.back();
422 | return c;
423 | }
424 |
425 |
426 | /**
427 | * Make a JSONException to signal a syntax error.
428 | *
429 | * @param message The error message.
430 | * @return A JSONException object, suitable for throwing
431 | */
432 | public JSONException syntaxError(String message) {
433 | return new JSONException(message + this.toString());
434 | }
435 |
436 |
437 | /**
438 | * Make a printable string of this JSONTokener.
439 | *
440 | * @return " at {index} [character {character} line {line}]"
441 | */
442 | public String toString() {
443 | return " at " + this.index + " [character " + this.character + " line " +
444 | this.line + "]";
445 | }
446 | }
447 |
--------------------------------------------------------------------------------
/slurp/src/main/java/org/json/JSONML.java:
--------------------------------------------------------------------------------
1 | package org.json;
2 |
3 | /*
4 | Copyright (c) 2008 JSON.org
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | The Software shall be used for Good, not Evil.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | import java.util.Iterator;
28 |
29 |
30 | /**
31 | * This provides static methods to convert an XML text into a JSONArray or
32 | * JSONObject, and to covert a JSONArray or JSONObject into an XML text using
33 | * the JsonML transform.
34 | *
35 | * @author JSON.org
36 | * @version 2012-03-28
37 | */
38 | public class JSONML {
39 |
40 | /**
41 | * Parse XML values and store them in a JSONArray.
42 | * @param x The XMLTokener containing the source string.
43 | * @param arrayForm true if array form, false if object form.
44 | * @param ja The JSONArray that is containing the current tag or null
45 | * if we are at the outermost level.
46 | * @return A JSONArray if the value is the outermost tag, otherwise null.
47 | * @throws JSONException
48 | */
49 | private static Object parse(
50 | XMLTokener x,
51 | boolean arrayForm,
52 | JSONArray ja
53 | ) throws JSONException {
54 | String attribute;
55 | char c;
56 | String closeTag = null;
57 | int i;
58 | JSONArray newja = null;
59 | JSONObject newjo = null;
60 | Object token;
61 | String tagName = null;
62 |
63 | // Test for and skip past these forms:
64 | //
65 | //
66 | //
67 | // ... ?>
68 |
69 | while (true) {
70 | if (!x.more()) {
71 | throw x.syntaxError("Bad XML");
72 | }
73 | token = x.nextContent();
74 | if (token == XML.LT) {
75 | token = x.nextToken();
76 | if (token instanceof Character) {
77 | if (token == XML.SLASH) {
78 |
79 | // Close tag
80 |
81 | token = x.nextToken();
82 | if (!(token instanceof String)) {
83 | throw new JSONException(
84 | "Expected a closing name instead of '" +
85 | token + "'.");
86 | }
87 | if (x.nextToken() != XML.GT) {
88 | throw x.syntaxError("Misshaped close tag");
89 | }
90 | return token;
91 | } else if (token == XML.BANG) {
92 |
93 | // ");
99 | } else {
100 | x.back();
101 | }
102 | } else if (c == '[') {
103 | token = x.nextToken();
104 | if (token.equals("CDATA") && x.next() == '[') {
105 | if (ja != null) {
106 | ja.put(x.nextCDATA());
107 | }
108 | } else {
109 | throw x.syntaxError("Expected 'CDATA['");
110 | }
111 | } else {
112 | i = 1;
113 | do {
114 | token = x.nextMeta();
115 | if (token == null) {
116 | throw x.syntaxError("Missing '>' after ' 0);
123 | }
124 | } else if (token == XML.QUEST) {
125 |
126 | //
127 |
128 | x.skipPast("?>");
129 | } else {
130 | throw x.syntaxError("Misshaped tag");
131 | }
132 |
133 | // Open tag <
134 |
135 | } else {
136 | if (!(token instanceof String)) {
137 | throw x.syntaxError("Bad tagName '" + token + "'.");
138 | }
139 | tagName = (String)token;
140 | newja = new JSONArray();
141 | newjo = new JSONObject();
142 | if (arrayForm) {
143 | newja.put(tagName);
144 | if (ja != null) {
145 | ja.put(newja);
146 | }
147 | } else {
148 | newjo.put("tagName", tagName);
149 | if (ja != null) {
150 | ja.put(newjo);
151 | }
152 | }
153 | token = null;
154 | for (;;) {
155 | if (token == null) {
156 | token = x.nextToken();
157 | }
158 | if (token == null) {
159 | throw x.syntaxError("Misshaped tag");
160 | }
161 | if (!(token instanceof String)) {
162 | break;
163 | }
164 |
165 | // attribute = value
166 |
167 | attribute = (String)token;
168 | if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) {
169 | throw x.syntaxError("Reserved attribute.");
170 | }
171 | token = x.nextToken();
172 | if (token == XML.EQ) {
173 | token = x.nextToken();
174 | if (!(token instanceof String)) {
175 | throw x.syntaxError("Missing value");
176 | }
177 | newjo.accumulate(attribute, XML.stringToValue((String)token));
178 | token = null;
179 | } else {
180 | newjo.accumulate(attribute, "");
181 | }
182 | }
183 | if (arrayForm && newjo.length() > 0) {
184 | newja.put(newjo);
185 | }
186 |
187 | // Empty tag <.../>
188 |
189 | if (token == XML.SLASH) {
190 | if (x.nextToken() != XML.GT) {
191 | throw x.syntaxError("Misshaped tag");
192 | }
193 | if (ja == null) {
194 | if (arrayForm) {
195 | return newja;
196 | } else {
197 | return newjo;
198 | }
199 | }
200 |
201 | // Content, between <...> and
202 |
203 | } else {
204 | if (token != XML.GT) {
205 | throw x.syntaxError("Misshaped tag");
206 | }
207 | closeTag = (String)parse(x, arrayForm, newja);
208 | if (closeTag != null) {
209 | if (!closeTag.equals(tagName)) {
210 | throw x.syntaxError("Mismatched '" + tagName +
211 | "' and '" + closeTag + "'");
212 | }
213 | tagName = null;
214 | if (!arrayForm && newja.length() > 0) {
215 | newjo.put("childNodes", newja);
216 | }
217 | if (ja == null) {
218 | if (arrayForm) {
219 | return newja;
220 | } else {
221 | return newjo;
222 | }
223 | }
224 | }
225 | }
226 | }
227 | } else {
228 | if (ja != null) {
229 | ja.put(token instanceof String
230 | ? XML.stringToValue((String)token)
231 | : token);
232 | }
233 | }
234 | }
235 | }
236 |
237 |
238 | /**
239 | * Convert a well-formed (but not necessarily valid) XML string into a
240 | * JSONArray using the JsonML transform. Each XML tag is represented as
241 | * a JSONArray in which the first element is the tag name. If the tag has
242 | * attributes, then the second element will be JSONObject containing the
243 | * name/value pairs. If the tag contains children, then strings and
244 | * JSONArrays will represent the child tags.
245 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored.
246 | * @param string The source string.
247 | * @return A JSONArray containing the structured data from the XML string.
248 | * @throws JSONException
249 | */
250 | public static JSONArray toJSONArray(String string) throws JSONException {
251 | return toJSONArray(new XMLTokener(string));
252 | }
253 |
254 |
255 | /**
256 | * Convert a well-formed (but not necessarily valid) XML string into a
257 | * JSONArray using the JsonML transform. Each XML tag is represented as
258 | * a JSONArray in which the first element is the tag name. If the tag has
259 | * attributes, then the second element will be JSONObject containing the
260 | * name/value pairs. If the tag contains children, then strings and
261 | * JSONArrays will represent the child content and tags.
262 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored.
263 | * @param x An XMLTokener.
264 | * @return A JSONArray containing the structured data from the XML string.
265 | * @throws JSONException
266 | */
267 | public static JSONArray toJSONArray(XMLTokener x) throws JSONException {
268 | return (JSONArray)parse(x, true, null);
269 | }
270 |
271 |
272 | /**
273 | * Convert a well-formed (but not necessarily valid) XML string into a
274 | * JSONObject using the JsonML transform. Each XML tag is represented as
275 | * a JSONObject with a "tagName" property. If the tag has attributes, then
276 | * the attributes will be in the JSONObject as properties. If the tag
277 | * contains children, the object will have a "childNodes" property which
278 | * will be an array of strings and JsonML JSONObjects.
279 |
280 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored.
281 | * @param x An XMLTokener of the XML source text.
282 | * @return A JSONObject containing the structured data from the XML string.
283 | * @throws JSONException
284 | */
285 | public static JSONObject toJSONObject(XMLTokener x) throws JSONException {
286 | return (JSONObject)parse(x, false, null);
287 | }
288 |
289 |
290 | /**
291 | * Convert a well-formed (but not necessarily valid) XML string into a
292 | * JSONObject using the JsonML transform. Each XML tag is represented as
293 | * a JSONObject with a "tagName" property. If the tag has attributes, then
294 | * the attributes will be in the JSONObject as properties. If the tag
295 | * contains children, the object will have a "childNodes" property which
296 | * will be an array of strings and JsonML JSONObjects.
297 |
298 | * Comments, prologs, DTDs, and <[ [ ]]> are ignored.
299 | * @param string The XML source text.
300 | * @return A JSONObject containing the structured data from the XML string.
301 | * @throws JSONException
302 | */
303 | public static JSONObject toJSONObject(String string) throws JSONException {
304 | return toJSONObject(new XMLTokener(string));
305 | }
306 |
307 |
308 | /**
309 | * Reverse the JSONML transformation, making an XML text from a JSONArray.
310 | * @param ja A JSONArray.
311 | * @return An XML string.
312 | * @throws JSONException
313 | */
314 | public static String toString(JSONArray ja) throws JSONException {
315 | int i;
316 | JSONObject jo;
317 | String key;
318 | Iterator keys;
319 | int length;
320 | Object object;
321 | StringBuffer sb = new StringBuffer();
322 | String tagName;
323 | String value;
324 |
325 | // Emit
68 | * & (ampersand) is replaced by &
69 | * < (less than) is replaced by <
70 | * > (greater than) is replaced by >
71 | * " (double quote) is replaced by "
72 | *
73 | * @param string The string to be escaped.
74 | * @return The escaped string.
75 | */
76 | public static String escape(String string) {
77 | StringBuffer sb = new StringBuffer();
78 | for (int i = 0, length = string.length(); i < length; i++) {
79 | char c = string.charAt(i);
80 | switch (c) {
81 | case '&':
82 | sb.append("&");
83 | break;
84 | case '<':
85 | sb.append("<");
86 | break;
87 | case '>':
88 | sb.append(">");
89 | break;
90 | case '"':
91 | sb.append(""");
92 | break;
93 | case '\'':
94 | sb.append("'");
95 | break;
96 | default:
97 | sb.append(c);
98 | }
99 | }
100 | return sb.toString();
101 | }
102 |
103 | /**
104 | * Throw an exception if the string contains whitespace.
105 | * Whitespace is not allowed in tagNames and attributes.
106 | * @param string
107 | * @throws JSONException
108 | */
109 | public static void noSpace(String string) throws JSONException {
110 | int i, length = string.length();
111 | if (length == 0) {
112 | throw new JSONException("Empty string.");
113 | }
114 | for (i = 0; i < length; i += 1) {
115 | if (Character.isWhitespace(string.charAt(i))) {
116 | throw new JSONException("'" + string +
117 | "' contains a space character.");
118 | }
119 | }
120 | }
121 |
122 | /**
123 | * Scan the content following the named tag, attaching it to the context.
124 | * @param x The XMLTokener containing the source string.
125 | * @param context The JSONObject that will include the new material.
126 | * @param name The tag name.
127 | * @return true if the close tag is processed.
128 | * @throws JSONException
129 | */
130 | private static boolean parse(XMLTokener x, JSONObject context,
131 | String name) throws JSONException {
132 | char c;
133 | int i;
134 | JSONObject jsonobject = null;
135 | String string;
136 | String tagName;
137 | Object token;
138 |
139 | // Test for and skip past these forms:
140 | //
141 | //
142 | //
143 | // ... ?>
144 | // Report errors for these forms:
145 | // <>
146 | // <=
147 | // <<
148 |
149 | token = x.nextToken();
150 |
151 | // ");
158 | return false;
159 | }
160 | x.back();
161 | } else if (c == '[') {
162 | token = x.nextToken();
163 | if ("CDATA".equals(token)) {
164 | if (x.next() == '[') {
165 | string = x.nextCDATA();
166 | if (string.length() > 0) {
167 | context.accumulate("#text", string);
168 | }
169 | return false;
170 | }
171 | }
172 | throw x.syntaxError("Expected 'CDATA['");
173 | }
174 | i = 1;
175 | do {
176 | token = x.nextMeta();
177 | if (token == null) {
178 | throw x.syntaxError("Missing '>' after ' 0);
185 | return false;
186 | } else if (token == QUEST) {
187 |
188 | //
189 |
190 | x.skipPast("?>");
191 | return false;
192 | } else if (token == SLASH) {
193 |
194 | // Close tag
195 |
196 | token = x.nextToken();
197 | if (name == null) {
198 | throw x.syntaxError("Mismatched close tag " + token);
199 | }
200 | if (!token.equals(name)) {
201 | throw x.syntaxError("Mismatched " + name + " and " + token);
202 | }
203 | if (x.nextToken() != GT) {
204 | throw x.syntaxError("Misshaped close tag");
205 | }
206 | return true;
207 |
208 | } else if (token instanceof Character) {
209 | throw x.syntaxError("Misshaped tag");
210 |
211 | // Open tag <
212 |
213 | } else {
214 | tagName = (String)token;
215 | token = null;
216 | jsonobject = new JSONObject();
217 | for (;;) {
218 | if (token == null) {
219 | token = x.nextToken();
220 | }
221 |
222 | // attribute = value
223 |
224 | if (token instanceof String) {
225 | string = (String)token;
226 | token = x.nextToken();
227 | if (token == EQ) {
228 | token = x.nextToken();
229 | if (!(token instanceof String)) {
230 | throw x.syntaxError("Missing value");
231 | }
232 | jsonobject.accumulate("@" + string,
233 | XML.stringToValue((String)token));
234 | token = null;
235 | } else {
236 | jsonobject.accumulate("@" + string, "");
237 | }
238 |
239 | // Empty tag <.../>
240 |
241 | } else if (token == SLASH) {
242 | if (x.nextToken() != GT) {
243 | throw x.syntaxError("Misshaped tag");
244 | }
245 | if (jsonobject.length() > 0) {
246 | context.accumulate(tagName, jsonobject);
247 | } else {
248 | context.accumulate(tagName, JSONObject.NULL);
249 | }
250 | return false;
251 |
252 | // Content, between <...> and
253 |
254 | } else if (token == GT) {
255 | for (;;) {
256 | token = x.nextContent();
257 | if (token == null) {
258 | if (tagName != null) {
259 | throw x.syntaxError("Unclosed tag " + tagName);
260 | }
261 | return false;
262 | } else if (token instanceof String) {
263 | string = (String)token;
264 | if (string.length() > 0) {
265 | jsonobject.accumulate("#text",
266 | XML.stringToValue(string));
267 | }
268 |
269 | // Nested element
270 |
271 | } else if (token == LT) {
272 | if (parse(x, jsonobject, tagName)) {
273 | if (jsonobject.length() == 0) {
274 | context.accumulate(tagName, JSONObject.NULL);
275 | } else if (jsonobject.length() == 1 &&
276 | jsonobject.opt("#text") != null) {
277 | context.accumulate(tagName,
278 | jsonobject.opt("#text"));
279 | } else {
280 | context.accumulate(tagName, jsonobject);
281 | }
282 | return false;
283 | }
284 | }
285 | }
286 | } else {
287 | throw x.syntaxError("Misshaped tag");
288 | }
289 | }
290 | }
291 | }
292 |
293 |
294 | /**
295 | * Try to convert a string into a number, boolean, or null. If the string
296 | * can't be converted, return the string. This is much less ambitious than
297 | * JSONObject.stringToValue, especially because it does not attempt to
298 | * convert plus forms, octal forms, hex forms, or E forms lacking decimal
299 | * points.
300 | * @param string A String.
301 | * @return A simple JSON value.
302 | */
303 | public static Object stringToValue(String string) {
304 | if ("".equals(string)) {
305 | return string;
306 | }
307 | if ("true".equalsIgnoreCase(string)) {
308 | return Boolean.TRUE;
309 | }
310 | if ("false".equalsIgnoreCase(string)) {
311 | return Boolean.FALSE;
312 | }
313 | if ("null".equalsIgnoreCase(string)) {
314 | return JSONObject.NULL;
315 | }
316 | if ("0".equals(string)) {
317 | return new Integer(0);
318 | }
319 |
320 | // If it might be a number, try converting it. If that doesn't work,
321 | // return the string.
322 |
323 | try {
324 | char initial = string.charAt(0);
325 | boolean negative = false;
326 | if (initial == '-') {
327 | initial = string.charAt(1);
328 | negative = true;
329 | }
330 | if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') {
331 | return string;
332 | }
333 | if ((initial >= '0' && initial <= '9')) {
334 | if (string.indexOf('.') >= 0) {
335 | return Double.valueOf(string);
336 | } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) {
337 | Long myLong = new Long(string);
338 | if (myLong.longValue() == myLong.intValue()) {
339 | return new Integer(myLong.intValue());
340 | } else {
341 | return myLong;
342 | }
343 | }
344 | }
345 | } catch (Exception ignore) {
346 | }
347 | return string;
348 | }
349 |
350 |
351 | /**
352 | * Convert a well-formed (but not necessarily valid) XML string into a
353 | * JSONObject. Some information may be lost in this transformation
354 | * because JSON is a data format and XML is a document format. XML uses
355 | * elements, attributes, and content text, while JSON uses unordered
356 | * collections of name/value pairs and arrays of values. JSON does not
357 | * does not like to distinguish between elements and attributes.
358 | * Sequences of similar elements are represented as JSONArrays. Content
359 | * text may be placed in a "#text" member. Comments, prologs, DTDs, and
360 | * <[ [ ]]> are ignored.
361 | * @param string The source string.
362 | * @return A JSONObject containing the structured data from the XML string.
363 | * @throws JSONException
364 | */
365 | public static JSONObject toJSONObject(String string) throws JSONException {
366 | JSONObject jo = new JSONObject();
367 | XMLTokener x = new XMLTokener(string);
368 | while (x.more() && x.skipPast("<")) {
369 | parse(x, jo, null);
370 | }
371 | return jo;
372 | }
373 |
374 |
375 | /**
376 | * Convert a JSONObject into a well-formed, element-normal XML string.
377 | * @param object A JSONObject.
378 | * @return A string.
379 | * @throws JSONException
380 | */
381 | public static String toString(Object object) throws JSONException {
382 | return toString(object, null);
383 | }
384 |
385 |
386 | /**
387 | * Convert a JSONObject into a well-formed, element-normal XML string.
388 | * @param object A JSONObject.
389 | * @param tagName The optional name of the enclosing tag.
390 | * @return A string.
391 | * @throws JSONException
392 | */
393 | public static String toString(Object object, String tagName)
394 | throws JSONException {
395 | StringBuffer sb = new StringBuffer();
396 | int i;
397 | JSONArray ja;
398 | JSONObject jo;
399 | String key;
400 | Iterator keys;
401 | int length;
402 | String string;
403 | Object value;
404 | if (object instanceof JSONObject) {
405 |
406 | // Emit