Use parseUri() for req.uri. Update docs.

This commit is contained in:
Ryan 2009-05-18 19:33:05 +02:00
Родитель 9c70bf356b
Коммит edc38b4134
6 изменённых файлов: 832 добавлений и 201 удалений

Просмотреть файл

@ -38,6 +38,56 @@ node.http.STATUS_CODES = { 100 : 'Continue'
, 505 : 'HTTP Version not supported'
};
/*
parseUri 1.2.1
(c) 2007 Steven Levithan <stevenlevithan.com>
MIT License
*/
function parseUri (str) {
var o = parseUri.options,
m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
uri = {},
i = 14;
while (i--) uri[o.key[i]] = m[i] || "";
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) uri[o.q.name][$1] = $2;
});
uri.toString = function () { return str; };
return uri;
};
parseUri.options = {
strictMode: false,
key: [ "source"
, "protocol"
, "authority"
, "userInfo"
, "user"
, "password"
, "host"
, "port"
, "relative"
, "path"
, "directory"
, "file"
, "query"
, "anchor"
],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
};
var connection_expression = /Connection/i;
var transfer_encoding_expression = /Transfer-Encoding/i;
var close_expression = /close/i;
@ -51,6 +101,148 @@ function toRaw(string) {
return a;
}
node.http.ServerResponse = function (connection, responses) {
responses.push(this);
this.connection = connection;
var output = [];
// The send method appends data onto the output array. The deal is,
// the data is either an array of integer, representing binary or it
// is a string in which case it's UTF8 encoded.
// Two things to considered:
// - we should be able to send mixed encodings.
// - we don't want to call connection.send("smallstring") because that
// is wasteful. *I think* its rather faster to concat inside of JS
// Thus I attempt to concat as much as possible.
function send (data) {
if (connection.readyState === "closed" || connection.readyState === "readOnly")
{
responses = [];
return;
}
if (output.length == 0) {
output.push(data);
return;
}
var li = output.length-1;
if (data.constructor == String && output[li].constructor == String) {
output[li] += data;
return;
}
if (data.constructor == Array && output[li].constructor == Array) {
output[li] = output[li].concat(data);
return;
}
// If the string is small enough, just convert it to binary
if (data.constructor == String
&& data.length < 128
&& output[li].constructor == Array)
{
output[li] = output[li].concat(toRaw(data));
return;
}
output.push(data);
};
this.flush = function () {
if (responses.length > 0 && responses[0] === this)
while (output.length > 0)
connection.send(output.shift());
};
var chunked_encoding = false;
var connection_close = false;
this.sendHeader = function (status_code, headers) {
var sent_connection_header = false;
var sent_transfer_encoding_header = false;
var sent_content_length_header = false;
var reason = node.http.STATUS_CODES[status_code] || "unknown";
var header = "HTTP/1.1 "
+ status_code.toString()
+ " "
+ reason
+ "\r\n"
;
for (var i = 0; i < headers.length; i++) {
var field = headers[i][0];
var value = headers[i][1];
header += field + ": " + value + "\r\n";
if (connection_expression.exec(field)) {
sent_connection_header = true;
if (close_expression.exec(value))
connection_close = true;
} else if (transfer_encoding_expression.exec(field)) {
sent_transfer_encoding_header = true;
if (chunk_expression.exec(value))
chunked_encoding = true;
} else if (content_length_expression.exec(field)) {
sent_content_length_header = true;
}
}
// keep-alive logic
if (sent_connection_header == false) {
if (this.should_keep_alive) {
header += "Connection: keep-alive\r\n";
} else {
connection_close = true;
header += "Connection: close\r\n";
}
}
if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
header += "Transfer-Encoding: chunked\r\n";
chunked_encoding = true;
}
header += "\r\n";
send(header);
};
this.sendBody = function (chunk) {
if (chunked_encoding) {
send(chunk.length.toString(16));
send("\r\n");
send(chunk);
send("\r\n");
} else {
send(chunk);
}
this.flush();
};
this.finished = false;
this.finish = function () {
if (chunked_encoding)
send("0\r\n\r\n"); // last chunk
this.finished = true;
while (responses.length > 0 && responses[0].finished) {
var res = responses[0];
res.flush();
responses.shift();
}
if (responses.length == 0 && connection_close) {
connection.fullClose();
}
};
};
/* This is a wrapper around the LowLevelServer interface. It provides
* connection handling, overflow checking, and some data buffering.
*/
@ -58,148 +250,6 @@ node.http.Server = function (RequestHandler, options) {
if (!(this instanceof node.http.Server))
throw Error("Constructor called as a function");
function Response (connection, responses) {
responses.push(this);
this.connection = connection;
var output = [];
// The send method appends data onto the output array. The deal is,
// the data is either an array of integer, representing binary or it
// is a string in which case it's UTF8 encoded.
// Two things to considered:
// - we should be able to send mixed encodings.
// - we don't want to call connection.send("smallstring") because that
// is wasteful. *I think* its rather faster to concat inside of JS
// Thus I attempt to concat as much as possible.
function send (data) {
if (connection.readyState === "closed" || connection.readyState === "readOnly")
{
responses = [];
return;
}
if (output.length == 0) {
output.push(data);
return;
}
var li = output.length-1;
if (data.constructor == String && output[li].constructor == String) {
output[li] += data;
return;
}
if (data.constructor == Array && output[li].constructor == Array) {
output[li] = output[li].concat(data);
return;
}
// If the string is small enough, just convert it to binary
if (data.constructor == String
&& data.length < 128
&& output[li].constructor == Array)
{
output[li] = output[li].concat(toRaw(data));
return;
}
output.push(data);
};
this.flush = function () {
if (responses.length > 0 && responses[0] === this)
while (output.length > 0)
connection.send(output.shift());
};
var chunked_encoding = false;
var connection_close = false;
this.sendHeader = function (status_code, headers) {
var sent_connection_header = false;
var sent_transfer_encoding_header = false;
var sent_content_length_header = false;
var reason = node.http.STATUS_CODES[status_code] || "unknown";
var header = "HTTP/1.1 "
+ status_code.toString()
+ " "
+ reason
+ "\r\n"
;
for (var i = 0; i < headers.length; i++) {
var field = headers[i][0];
var value = headers[i][1];
header += field + ": " + value + "\r\n";
if (connection_expression.exec(field)) {
sent_connection_header = true;
if (close_expression.exec(value))
connection_close = true;
} else if (transfer_encoding_expression.exec(field)) {
sent_transfer_encoding_header = true;
if (chunk_expression.exec(value))
chunked_encoding = true;
} else if (content_length_expression.exec(field)) {
sent_content_length_header = true;
}
}
// keep-alive logic
if (sent_connection_header == false) {
if (this.should_keep_alive) {
header += "Connection: keep-alive\r\n";
} else {
connection_close = true;
header += "Connection: close\r\n";
}
}
if (sent_content_length_header == false && sent_transfer_encoding_header == false) {
header += "Transfer-Encoding: chunked\r\n";
chunked_encoding = true;
}
header += "\r\n";
send(header);
};
this.sendBody = function (chunk) {
if (chunked_encoding) {
send(chunk.length.toString(16));
send("\r\n");
send(chunk);
send("\r\n");
} else {
send(chunk);
}
this.flush();
};
this.finished = false;
this.finish = function () {
if (chunked_encoding)
send("0\r\n\r\n"); // last chunk
this.finished = true;
while (responses.length > 0 && responses[0].finished) {
var res = responses[0];
res.flush();
responses.shift();
}
if (responses.length == 0 && connection_close) {
connection.fullClose();
}
};
}
function ConnectionHandler (connection) {
// An array of responses for each connection. In pipelined connections
// we need to keep track of the order they were sent.
@ -214,7 +264,7 @@ node.http.Server = function (RequestHandler, options) {
, onBody : null // by user
, onBodyComplete : null // by user
}
var res = new Response(connection, responses);
var res = new node.http.ServerResponse(connection, responses);
this.onURI = function (data) {
req.uri += data;
@ -246,6 +296,7 @@ node.http.Server = function (RequestHandler, options) {
this.onHeadersComplete = function () {
req.http_version = this.http_version;
req.method = this.method;
req.uri = parseUri(req.uri);
res.should_keep_alive = this.should_keep_alive;

Просмотреть файл

@ -10,7 +10,7 @@ new node.http.Server(function (req, res) {
var arg = commands[2];
var status = 200;
//p(req.headers);
p(req.headers);
if (command == "bytes") {
var n = parseInt(arg, 10)

Просмотреть файл

@ -1,7 +1,7 @@
new node.http.Server(function (req, res) {
setTimeout(function () {
res.sendHeader(200, [["Content-Type", "text/plain"]]);
res.sendBody("Hello World");
res.sendBody(JSON.stringify(req.uri));
res.finish();
}, 1000);
}, 1);
}).listen(8000, "localhost");

Просмотреть файл

@ -68,7 +68,7 @@ a:hover { text-decoration: underline; }
}
</style>
<script type="text/javascript" src="sh_main.min.js"></script>
<script type="text/javascript" src="sh_main.js"></script>
<script type="text/javascript" src="sh_javascript.min.js"></script>
<link type="text/css" rel="stylesheet" href="sh_vim-dark.css">
@ -84,10 +84,12 @@ a:hover { text-decoration: underline; }
<ol>
<li><a href="#timers">Timers</a>
<li><a href="#files">File System</a>
<li><a href="#tcp">TCP</a>
<li><a href="#http">HTTP</a>
<li><a href="#tcp">tcp</a>
<li><a href="#http">http</a>
<ol>
<li><a href="#http_server">Server</a>
<li><a href="#http_server_request">ServerRequest</a>
<li><a href="#http_server_response">ServerResponse</a>
<li><a href="#http_client">Client</a>
</ol>
<li><a href="#modules">Modules</a>
@ -99,11 +101,12 @@ a:hover { text-decoration: underline; }
<h1><a href="http://tinyclouds.org/node">Node</a></h1>
<p id="introduction"> Node is a purely asynchronous I/O framework for <a
<p id="introduction"> Purely asynchronous I/O for <a
href="http://code.google.com/p/v8/">V8 javascript</a>.
<p>This is an example of a web server written with Node which listens on
port 8000 and responds with "Hello World" after waiting two seconds:
<p>This is an example of a web server written with Node which responds with
"Hello World" after waiting two seconds:
<pre class="sh_javascript">new node.http.Server(function (req, res) {
setTimeout(function () {
res.sendHeader(200, [["Content-Type", "text/plain"]]);
@ -111,7 +114,20 @@ port 8000 and responds with "Hello World" after waiting two seconds:
res.finish();
}, 2000);
}).listen(8000);
</pre>
puts("Server running at http://127.0.0.1:8000/");</pre>
<p> Execution does not block on <code
class="sh_javascript">setTimeout()</code>
nor
<code
class="sh_javascript">listen(8000)</code>.
File I/O is also preformed without blocking.
In fact, not a single function in Node blocks execution.
There isn't even a call to run the event loop.
<p> Programmers using this environment will find it difficult to design
their systems ineffiencely. It's impossible to make a database call from a
web server and block other requests.
<p> Check out <a href="#api">the API documentation</a> for more examples.
@ -224,26 +240,35 @@ See <a
<h3 id="files">File System</h3>
<h3 id="tcp">TCP</h3>
<h3 id="tcp"><code>node.tcp</code></h3>
<h3 id="http">HTTP (<code class="sh_javascript">node.http</code>)</h3>
<h3 id="http"><code>node.http</code></h3>
<p> Node provides a web server and client interface. The interface is rather
low-level but complete. For example, it does not parse
<code class="sh_javascript">application/x-www-form-urlencoded</code> message bodies. The interface
does abstract the Transfer-Encoding (i.e. chuncked or identity), Message
boundarys, and Keep-Alive connections.
boundaries, and Keep-Alive connections.
<h4 id="http_server"><code class="sh_javascript">node.http.Server</code></h4>
<h4 id="http_server">HTTP Server (<code class="sh_javascript">node.http.Server</code>)</h4>
<dl>
<dt><code class="sh_javascript">new Server(request_handler, options)</code></dt>
<dt><code class="sh_javascript">var server = new node.http.Server(request_handler, options);</code></dt>
<dd>
<p>Creates a new web server. The <code class="sh_javascript">options</code> argument accepts
the same values as the options argument for
<code class="sh_javascript">node.tcp.Server</code> does. The options argument is optional.
<p>Creates a new web server.
<p>
The <code>options</code> argument is optional.
The <code
class="sh_javascript">options</code> argument accepts the same values
as the options argument for <code
class="sh_javascript">node.tcp.Server</code> does.
<p>The <code class="sh_javascript">request_handler</code> is a
callback which is made on each request with a
<code>ServerRequest</code> and
<code>ServerResponse</code> arguments.
<p>The <code class="sh_javascript">request_handler</code> is a function which is called on
each request with a <code class="sh_javascript">Message</code> object argument.
</dd>
<dt><code class="sh_javascript">server.listen(port, hostname)</code>
@ -261,26 +286,37 @@ boundarys, and Keep-Alive connections.
</dl>
<h4>HTTP Request Message (<code class="sh_javascript">node.http.Message</code>)</h4>
<h4 id="http_server_request"><code class="sh_javascript">node.http.ServerRequest</code></h4>
<p> This object is only created internally&mdash;not by the user. It is passed
as an argument to the <code class="sh_javascript">request_handler</code> callback in a web server.
<p> This object is created internally by a HTTP server&mdash;not by the user.
It is passed as the first argument to the <code
class="sh_javascript">request_handler</code> callback.
<p> This object, unlike in other HTTP APIs, is used as an interface for both
the request and response. Members and callbacks reference request data, like
<code class="sh_javascript">msg.method</code> and <code class="sh_javascript">msg.onBody</code>. The methods are for
sending a response to this message. Like <code class="sh_javascript">msg.sendHeader()</code> and
<code class="sh_javascript">msg.sendBody()</code>.
<dl>
<dt><code class="sh_javascript">msg.method</code>
<dt><code class="sh_javascript">req.method</code>
<dd>The request method as a string. Read only. Example: <code class="sh_javascript">"GET"</code>,
<code class="sh_javascript">"DELETE"</code>.</dd>
<dt><code class="sh_javascript">msg.uri</code>
<dd>The request URI as a string. Read only.
Example: <code class="sh_javascript">"/index.html?hello=world"</code>.</dd>
<dt><code class="sh_javascript">req.uri</code>
<dd> URI object. Has many fields.
<dt><code>req.uri.toString()</code>
<dd> The original URI found in the status line.
<dt><code>req.uri.anchor</code>
<dt><code>req.uri.query</code>
<dt><code>req.uri.file</code>
<dt><code>req.uri.directory</code>
<dt><code>req.uri.path</code>
<dt><code>req.uri.relative</code>
<dt><code>req.uri.port</code>
<dt><code>req.uri.host</code>
<dt><code>req.uri.password</code>
<dt><code>req.uri.user</code>
<dt><code>req.uri.authority</code>
<dt><code>req.uri.protocol</code>
<dt><code>req.uri.source</code>
<dt><code>req.uri.queryKey</code>
<dt><code class="sh_javascript">msg.headers</code>
<dt><code class="sh_javascript">req.headers</code>
<dd>The request headers expressed as an array of 2-element arrays. Read only.
Example:
<pre class="sh_javascript">
@ -291,40 +327,41 @@ sending a response to this message. Like <code class="sh_javascript">msg.sendHea
]
</pre>
<dt><code class="sh_javascript">msg.http_version</code></dt>
<dt><code class="sh_javascript">req.http_version</code></dt>
<dd>The HTTP protocol version as a string. Read only. Examples: <code class="sh_javascript">"1.1"</code>,
<code class="sh_javascript">"1.0"</code>
<dt><code class="sh_javascript">msg.connection</code></dt>
<dd> A reference to the <code class="sh_javascript">node.tcp.Connection</code> object. Read
only. Note that multiple messages can be sent on a single connection.
</dd>
<dt><code class="sh_javascript">msg.onBody</code></dt>
<dt><code class="sh_javascript">req.onBody</code></dt>
<dd>Callback. Should be set by the user to be informed of when a piece
of the message body is received. Example:
<pre class="sh_javascript">
msg.onBody = function (chunk) {
req.onBody = function (chunk) {
puts("part of the body: " + chunk);
}
};
</pre>
A chunk of the body is given as the single argument. The transfer-encoding
has been removed.
<p>The body chunk is either a String in the case of utf8 encoding or an
array of numbers in the case of raw encoding.
<dt><code class="sh_javascript">msg.onBodyComplete</code></dt>
<p>The body chunk is either a String in the case of UTF-8 encoding or an
array of numbers in the case of raw encoding. The body encoding is set with
<code class="sh_javascript">req.setBodyEncoding()</code>.
<dt><code class="sh_javascript">req.onBodyComplete</code></dt>
<dd>Callback. Made exactly once for each message. No arguments. After
<code class="sh_javascript">onBodyComplete</code> is executed <code class="sh_javascript">onBody</code> will no longer be called.
</dd>
<dt><code class="sh_javascript">msg.setBodyEncoding(encoding)</code></dt>
<dt><code class="sh_javascript">req.setBodyEncoding(encoding)</code></dt>
<dd>
Set the encoding for the request body. Either <code class="sh_javascript">"utf8"</code> or
<code class="sh_javascript">"raw"</code>. Defaults to raw.
<big>TODO</big>
</dl>
<dt><code class="sh_javascript">msg.sendHeader(status_code, headers)</code></dt>
<h4 id="http_server_response"><code class="sh_javascript">node.http.ServerResponse</code></h4>
<dl>
<dt><code class="sh_javascript">res.sendHeader(status_code, headers)</code></dt>
<dd>
Sends a response header to the request. The status code is a 3-digit
HTTP status code, like <code class="sh_javascript">404</code>. The second argument,
@ -334,28 +371,26 @@ msg.onBody = function (chunk) {
<p>Example:
<pre class="sh_javascript">
var body = "hello world";
msg.sendHeader( 200
, [ ["Content-Length", body.length]
, ["Content-Type", "text/plain"]
]
);
res.sendHeader(200, [ ["Content-Length", body.length]
, ["Content-Type", "text/plain"]
]);
</pre>
This method must only be called once on a message and it must be called
before <code class="sh_javascript">msg.finish()</code> is called.
before <code class="sh_javascript">res.finish()</code> is called.
</dd>
<dt><code class="sh_javascript">msg.sendBody(chunk)</code></dt>
<dt><code class="sh_javascript">res.sendBody(chunk)</code></dt>
<dd>
This method must be called after <code class="sh_javascript">sendHeader</code> was called. It
sends a chunk of the response body. This method may be called multiple
times to provide successive parts of the body.
</dd>
<dt><code class="sh_javascript">msg.finish()</code></dt>
<dt><code class="sh_javascript">res.finish()</code></dt>
<dd>
This method signals that all of the response headers and body has been
sent; that server should consider this message complete.
The method, <code class="sh_javascript">msg.finish()</code>, MUST be called on each response.
The method, <code class="sh_javascript">res.finish()</code>, MUST be called on each response.
</dl>

545
website/sh_main.js Normal file
Просмотреть файл

@ -0,0 +1,545 @@
/*
SHJS - Syntax Highlighting in JavaScript
Copyright (C) 2007, 2008 gnombat@users.sourceforge.net
License: http://shjs.sourceforge.net/doc/gplv3.html
*/
if (! this.sh_languages) {
this.sh_languages = {};
}
var sh_requests = {};
function sh_isEmailAddress(url) {
if (/^mailto:/.test(url)) {
return false;
}
return url.indexOf('@') !== -1;
}
function sh_setHref(tags, numTags, inputString) {
var url = inputString.substring(tags[numTags - 2].pos, tags[numTags - 1].pos);
if (url.length >= 2 && url.charAt(0) === '<' && url.charAt(url.length - 1) === '>') {
url = url.substr(1, url.length - 2);
}
if (sh_isEmailAddress(url)) {
url = 'mailto:' + url;
}
tags[numTags - 2].node.href = url;
}
/*
Konqueror has a bug where the regular expression /$/g will not match at the end
of a line more than once:
var regex = /$/g;
var match;
var line = '1234567890';
regex.lastIndex = 10;
match = regex.exec(line);
var line2 = 'abcde';
regex.lastIndex = 5;
match = regex.exec(line2); // fails
*/
function sh_konquerorExec(s) {
var result = [''];
result.index = s.length;
result.input = s;
return result;
}
/**
Highlights all elements containing source code in a text string. The return
value is an array of objects, each representing an HTML start or end tag. Each
object has a property named pos, which is an integer representing the text
offset of the tag. Every start tag also has a property named node, which is the
DOM element started by the tag. End tags do not have this property.
@param inputString a text string
@param language a language definition object
@return an array of tag objects
*/
function sh_highlightString(inputString, language) {
if (/Konqueror/.test(navigator.userAgent)) {
if (! language.konquered) {
for (var s = 0; s < language.length; s++) {
for (var p = 0; p < language[s].length; p++) {
var r = language[s][p][0];
if (r.source === '$') {
r.exec = sh_konquerorExec;
}
}
}
language.konquered = true;
}
}
var a = document.createElement('a');
var span = document.createElement('span');
// the result
var tags = [];
var numTags = 0;
// each element is a pattern object from language
var patternStack = [];
// the current position within inputString
var pos = 0;
// the name of the current style, or null if there is no current style
var currentStyle = null;
var output = function(s, style) {
var length = s.length;
// this is more than just an optimization - we don't want to output empty <span></span> elements
if (length === 0) {
return;
}
if (! style) {
var stackLength = patternStack.length;
if (stackLength !== 0) {
var pattern = patternStack[stackLength - 1];
// check whether this is a state or an environment
if (! pattern[3]) {
// it's not a state - it's an environment; use the style for this environment
style = pattern[1];
}
}
}
if (currentStyle !== style) {
if (currentStyle) {
tags[numTags++] = {pos: pos};
if (currentStyle === 'sh_url') {
sh_setHref(tags, numTags, inputString);
}
}
if (style) {
var clone;
if (style === 'sh_url') {
clone = a.cloneNode(false);
}
else {
clone = span.cloneNode(false);
}
clone.className = style;
tags[numTags++] = {node: clone, pos: pos};
}
}
pos += length;
currentStyle = style;
};
var endOfLinePattern = /\r\n|\r|\n/g;
endOfLinePattern.lastIndex = 0;
var inputStringLength = inputString.length;
while (pos < inputStringLength) {
var start = pos;
var end;
var startOfNextLine;
var endOfLineMatch = endOfLinePattern.exec(inputString);
if (endOfLineMatch === null) {
end = inputStringLength;
startOfNextLine = inputStringLength;
}
else {
end = endOfLineMatch.index;
startOfNextLine = endOfLinePattern.lastIndex;
}
var line = inputString.substring(start, end);
var matchCache = [];
for (;;) {
var posWithinLine = pos - start;
var stateIndex;
var stackLength = patternStack.length;
if (stackLength === 0) {
stateIndex = 0;
}
else {
// get the next state
stateIndex = patternStack[stackLength - 1][2];
}
var state = language[stateIndex];
var numPatterns = state.length;
var mc = matchCache[stateIndex];
if (! mc) {
mc = matchCache[stateIndex] = [];
}
var bestMatch = null;
var bestPatternIndex = -1;
for (var i = 0; i < numPatterns; i++) {
var match;
if (i < mc.length && (mc[i] === null || posWithinLine <= mc[i].index)) {
match = mc[i];
}
else {
var regex = state[i][0];
regex.lastIndex = posWithinLine;
match = regex.exec(line);
mc[i] = match;
}
if (match !== null && (bestMatch === null || match.index < bestMatch.index)) {
bestMatch = match;
bestPatternIndex = i;
if (match.index === posWithinLine) {
break;
}
}
}
if (bestMatch === null) {
output(line.substring(posWithinLine), null);
break;
}
else {
// got a match
if (bestMatch.index > posWithinLine) {
output(line.substring(posWithinLine, bestMatch.index), null);
}
var pattern = state[bestPatternIndex];
var newStyle = pattern[1];
var matchedString;
if (newStyle instanceof Array) {
for (var subexpression = 0; subexpression < newStyle.length; subexpression++) {
matchedString = bestMatch[subexpression + 1];
output(matchedString, newStyle[subexpression]);
}
}
else {
matchedString = bestMatch[0];
output(matchedString, newStyle);
}
switch (pattern[2]) {
case -1:
// do nothing
break;
case -2:
// exit
patternStack.pop();
break;
case -3:
// exitall
patternStack.length = 0;
break;
default:
// this was the start of a delimited pattern or a state/environment
patternStack.push(pattern);
break;
}
}
}
// end of the line
if (currentStyle) {
tags[numTags++] = {pos: pos};
if (currentStyle === 'sh_url') {
sh_setHref(tags, numTags, inputString);
}
currentStyle = null;
}
pos = startOfNextLine;
}
return tags;
}
////////////////////////////////////////////////////////////////////////////////
// DOM-dependent functions
function sh_getClasses(element) {
var result = [];
var htmlClass = element.className;
if (htmlClass && htmlClass.length > 0) {
var htmlClasses = htmlClass.split(' ');
for (var i = 0; i < htmlClasses.length; i++) {
if (htmlClasses[i].length > 0) {
result.push(htmlClasses[i]);
}
}
}
return result;
}
function sh_addClass(element, name) {
var htmlClasses = sh_getClasses(element);
for (var i = 0; i < htmlClasses.length; i++) {
if (name.toLowerCase() === htmlClasses[i].toLowerCase()) {
return;
}
}
htmlClasses.push(name);
element.className = htmlClasses.join(' ');
}
/**
Extracts the tags from an HTML DOM NodeList.
@param nodeList a DOM NodeList
@param result an object with text, tags and pos properties
*/
function sh_extractTagsFromNodeList(nodeList, result) {
var length = nodeList.length;
for (var i = 0; i < length; i++) {
var node = nodeList.item(i);
switch (node.nodeType) {
case 1:
if (node.nodeName.toLowerCase() === 'br') {
var terminator;
if (/MSIE/.test(navigator.userAgent)) {
terminator = '\r';
}
else {
terminator = '\n';
}
result.text.push(terminator);
result.pos++;
}
else {
result.tags.push({node: node.cloneNode(false), pos: result.pos});
sh_extractTagsFromNodeList(node.childNodes, result);
result.tags.push({pos: result.pos});
}
break;
case 3:
case 4:
result.text.push(node.data);
result.pos += node.length;
break;
}
}
}
/**
Extracts the tags from the text of an HTML element. The extracted tags will be
returned as an array of tag objects. See sh_highlightString for the format of
the tag objects.
@param element a DOM element
@param tags an empty array; the extracted tag objects will be returned in it
@return the text of the element
@see sh_highlightString
*/
function sh_extractTags(element, tags) {
var result = {};
result.text = [];
result.tags = tags;
result.pos = 0;
sh_extractTagsFromNodeList(element.childNodes, result);
return result.text.join('');
}
/**
Merges the original tags from an element with the tags produced by highlighting.
@param originalTags an array containing the original tags
@param highlightTags an array containing the highlighting tags - these must not overlap
@result an array containing the merged tags
*/
function sh_mergeTags(originalTags, highlightTags) {
var numOriginalTags = originalTags.length;
if (numOriginalTags === 0) {
return highlightTags;
}
var numHighlightTags = highlightTags.length;
if (numHighlightTags === 0) {
return originalTags;
}
var result = [];
var originalIndex = 0;
var highlightIndex = 0;
while (originalIndex < numOriginalTags && highlightIndex < numHighlightTags) {
var originalTag = originalTags[originalIndex];
var highlightTag = highlightTags[highlightIndex];
if (originalTag.pos <= highlightTag.pos) {
result.push(originalTag);
originalIndex++;
}
else {
result.push(highlightTag);
if (highlightTags[highlightIndex + 1].pos <= originalTag.pos) {
highlightIndex++;
result.push(highlightTags[highlightIndex]);
highlightIndex++;
}
else {
// new end tag
result.push({pos: originalTag.pos});
// new start tag
highlightTags[highlightIndex] = {node: highlightTag.node.cloneNode(false), pos: originalTag.pos};
}
}
}
while (originalIndex < numOriginalTags) {
result.push(originalTags[originalIndex]);
originalIndex++;
}
while (highlightIndex < numHighlightTags) {
result.push(highlightTags[highlightIndex]);
highlightIndex++;
}
return result;
}
/**
Inserts tags into text.
@param tags an array of tag objects
@param text a string representing the text
@return a DOM DocumentFragment representing the resulting HTML
*/
function sh_insertTags(tags, text) {
var doc = document;
var result = document.createDocumentFragment();
var tagIndex = 0;
var numTags = tags.length;
var textPos = 0;
var textLength = text.length;
var currentNode = result;
// output one tag or text node every iteration
while (textPos < textLength || tagIndex < numTags) {
var tag;
var tagPos;
if (tagIndex < numTags) {
tag = tags[tagIndex];
tagPos = tag.pos;
}
else {
tagPos = textLength;
}
if (tagPos <= textPos) {
// output the tag
if (tag.node) {
// start tag
var newNode = tag.node;
currentNode.appendChild(newNode);
currentNode = newNode;
}
else {
// end tag
currentNode = currentNode.parentNode;
}
tagIndex++;
}
else {
// output text
currentNode.appendChild(doc.createTextNode(text.substring(textPos, tagPos)));
textPos = tagPos;
}
}
return result;
}
/**
Highlights an element containing source code. Upon completion of this function,
the element will have been placed in the "sh_sourceCode" class.
@param element a DOM <pre> element containing the source code to be highlighted
@param language a language definition object
*/
function sh_highlightElement(element, language) {
sh_addClass(element, 'sh_sourceCode');
var originalTags = [];
var inputString = sh_extractTags(element, originalTags);
var highlightTags = sh_highlightString(inputString, language);
var tags = sh_mergeTags(originalTags, highlightTags);
var documentFragment = sh_insertTags(tags, inputString);
while (element.hasChildNodes()) {
element.removeChild(element.firstChild);
}
element.appendChild(documentFragment);
}
function sh_getXMLHttpRequest() {
if (window.ActiveXObject) {
return new ActiveXObject('Msxml2.XMLHTTP');
}
else if (window.XMLHttpRequest) {
return new XMLHttpRequest();
}
throw 'No XMLHttpRequest implementation available';
}
function sh_load(language, element, prefix, suffix) {
if (language in sh_requests) {
sh_requests[language].push(element);
return;
}
sh_requests[language] = [element];
var request = sh_getXMLHttpRequest();
var url = prefix + 'sh_' + language + suffix;
request.open('GET', url, true);
request.onreadystatechange = function () {
if (request.readyState === 4) {
try {
if (! request.status || request.status === 200) {
eval(request.responseText);
var elements = sh_requests[language];
for (var i = 0; i < elements.length; i++) {
sh_highlightElement(elements[i], sh_languages[language]);
}
}
else {
throw 'HTTP error: status ' + request.status;
}
}
finally {
request = null;
}
}
};
request.send(null);
}
/**
Highlights all elements containing source code on the current page. Elements
containing source code must be "pre" elements with a "class" attribute of
"sh_LANGUAGE", where LANGUAGE is a valid language identifier; e.g., "sh_java"
identifies the element as containing "java" language source code.
*/
function highlight(prefix, suffix, tag) {
var nodeList = document.getElementsByTagName(tag);
for (var i = 0; i < nodeList.length; i++) {
var element = nodeList.item(i);
var htmlClasses = sh_getClasses(element);
for (var j = 0; j < htmlClasses.length; j++) {
var htmlClass = htmlClasses[j].toLowerCase();
if (htmlClass === 'sh_sourcecode') {
continue;
}
if (htmlClass.substr(0, 3) === 'sh_') {
var language = htmlClass.substring(3);
if (language in sh_languages) {
sh_highlightElement(element, sh_languages[language]);
}
else if (typeof(prefix) === 'string' && typeof(suffix) === 'string') {
sh_load(language, element, prefix, suffix);
}
else {
throw 'Found <' + tag + '> element with class="' + htmlClass + '", but no such language exists';
}
break;
}
}
}
}
function sh_highlightDocument(prefix, suffix) {
highlight(prefix, suffix, 'code');
highlight(prefix, suffix, 'pre');
}

Просмотреть файл

@ -4,7 +4,7 @@
font-style: normal;
}
.sh_sourceCode .sh_symbol , pre.sh_sourceCode .sh_cbracket {
.sh_sourceCode .sh_symbol , .sh_sourceCode .sh_cbracket {
color: #bbd;
}
@ -12,7 +12,7 @@
font-style: italic;
}
.sh_sourceCode .sh_string, pre.sh_sourceCode .sh_regexp, pre.sh_sourceCode .sh_number
.sh_sourceCode .sh_string, .sh_sourceCode .sh_regexp, .sh_sourceCode .sh_number
{
color: #daa;
}