1
0
Fork 0
mirror of https://github.com/futurepress/epub.js.git synced 2025-10-04 15:09:16 +02:00

updated polymer example, added urljs (no implemented)

This commit is contained in:
Fred Chasen 2013-07-19 16:24:31 -07:00
parent 8f06e86d9c
commit 150b6b6050
10 changed files with 2635 additions and 4 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
<script src="../../../build/epub.min.js"></script> <script src="../../../build/epub.min.js"></script>
<script type="text/javascript"> <!-- <script type="text/javascript">
//-- Setups //-- Setups
</script> </script> -->
<polymer-element name="epub-js" attributes="src chapter width height restore spreads"> <polymer-element name="epub-js" attributes="src chapter width height restore spreads">
<template> <template>

View file

@ -6,8 +6,10 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<script src="../../libs/polymer/polymer.min.js"></script> <script src="../../libs/polymer/polymer.min.js"></script>
<link rel="import" href="epub-reader/epub-reader.html"> <!-- // <script src="../../build/epub.min.js"></script> -->
<!-- <link rel="import" href="epub-reader/epub-reader.html"> -->
<link rel="import" href="epub-reader.html">
<link rel="stylesheet" href="../../demo/css/normalize.css"> <link rel="stylesheet" href="../../demo/css/normalize.css">
<style> <style>

2
examples/polymer/vulcanize/.gitignore vendored Executable file
View file

@ -0,0 +1,2 @@
output.html
node_modules

View file

@ -0,0 +1,86 @@
# Vulcan
### Concatenate a set of Web Components into one file
>Named for the [Vulcanization](http://en.wikipedia.org/wiki/Vulcanization) process that turns polymers into more durable
materials.
## Getting Started
- Install the node dependencies with `npm install`
- Depends on [cheerio](https://github.com/MatthewMueller/cheerio) and [nopt](https://github.com/isaacs/nopt)
- Give some input html files with the `--input` or `-i` flags
- Input html should have `<link rel="import">` tags
- Specify an output html file with `--output` or `-o`
- Defaults to `output.html` in the current directory
- URL paths are adjusted for the new output location automatically (execpt ones set in Javascript)
- Once finished, link the final output html into your app page with `<link rel="import">`.
## Example
Say we have three html files: `index.html`, `x-app.html`, and `x-dep.html`.
index.html:
```html
<!DOCTYPE html>
<link rel="import" href="app.html">
<x-app></x-app>
```
app.html:
```html
<link rel="import" href="path/to/x-dep.html">
<polymer-element name="x-app">
<template>
<x-dep></x-dep>
</template>
<script>Polymer('x-app')</script>
</polymer-element>
```
x-dep.html:
```html
<polymer-element name="x-dep">
<template>
<img src="x-dep-icon.jpg">
</template>
<script>
Polymer('x-dep');
</script>
</polymer-element>
```
Running vulcan on `index.html`, and specifying `build.html` as the output:
node vulcan.js -i index.html -o build.html
Will result in `build.html` that appears as so:
```html
<polymer-element name="x-dep" assetpath="path/to/">
<template>
<img src="path/to/x-dep-icon.jpg">
</template>
<script>
Polymer('x-dep');
</script>
</polymer-element>
<polymer-element name="x-app" assetpath="">
<template>
<x-dep></x-dep>
</template>
<script>
Polymer('x-app');
</script>
</polymer-element>
```
To use this, make `build.html` the only import in `index.html`:
```html
<!DOCTYPE html>
<link rel="import" href="build.html">
<x-app></x-app>
```

View file

@ -0,0 +1,18 @@
{
"name": "vulcanize",
"version": "0.0.0",
"description": "Concat all the components into one output file, with dependencies in the proper order",
"main": "vulcan.js",
"dependencies": {
"cheerio": "~0.11.0",
"nopt": "~2.1.1"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "",
"author": "",
"license": "BSD",
"readmeFilename": "README.md"
}

View file

@ -0,0 +1,183 @@
var fs = require('fs');
var path = require('path');
var cheerio = require('cheerio');
var nopt = require('nopt');
var options = nopt(
{
'output': path,
'input': [path, Array],
'verbose': Boolean
},
{
'o': ['--output'],
'i': ['--input'],
'v': ['--verbose']
}
);
if (!options.input) {
console.error('No input files given');
process.exit(1);
}
if (!options.output) {
console.warn('Default output to output.html');
options.output = path.resolve('output.html');
}
var outputDir = path.dirname(options.output);
var IMPORTS = 'link[rel="import"][href]';
var ELEMENTS = 'polymer-element';
var URL_ATTR = ['href', 'src', 'action', 'style'];
var URL_ATTR_SEL = '[' + URL_ATTR.join('],[') + ']';
var ABS_URL = /(^data:)|(^http[s]?:)|(^\/)/;
var URL = /url\([^)]*\)/g;
var URL_TEMPLATE = '{{.*}}';
function concatElement(dir, output, e) {
e = resolveElementPaths(dir, output, e);
buffer.push(e);
}
function resolveElementPaths(input, output, element) {
var $ = cheerio.load(element);
resolvePaths(input, output, $);
return $.html(ELEMENTS);
}
function resolvePaths(input, output, $) {
// resolve attributes
$(URL_ATTR_SEL).each(function() {
var val;
URL_ATTR.forEach(function(a) {
if (val = this.attr(a)) {
if (val.search(URL_TEMPLATE) < 0) {
if (a === 'style') {
this.attr(a, rewriteURL(input, output, val));
} else {
this.attr(a, rewriteRelPath(input, output, val));
}
}
}
}, this);
});
// resolve style elements
$('style').each(function() {
var val = this.html();
this.html(rewriteURL(input, output, val));
});
}
function rewriteRelPath(inputPath, outputPath, rel) {
if (ABS_URL.test(rel)) {
return rel;
}
var abs = path.resolve(inputPath, rel);
return path.relative(outputPath, abs);
}
function rewriteURL(inputPath, outputPath, cssText) {
return cssText.replace(URL, function(match) {
var path = match.replace(/["']/g, "").slice(4, -1);
path = rewriteRelPath(inputPath, outputPath, path);
return 'url(' + path + ')';
});
}
function readDocument(docname) {
if (options.verbose) {
console.log('Reading:', docname);
}
var content = fs.readFileSync(docname, 'utf8');
return cheerio.load(content);
}
function extractImports($, dir) {
var hrefs = $(IMPORTS).map(function(){ return this.attr('href') });
return hrefs.map(function(h) { return path.resolve(dir, h) });
}
function extractScripts($, assetPath) {
var scripts = $.root().children('script');
scripts.each(function(){
var src = this.attr('src');
if(src) {
this.attr('src', path.join(assetPath, src));
}
});
return scripts;
}
function extractElements($, assetPath) {
return $(ELEMENTS).map(function(i, e){ this.attr('assetpath', assetPath); return $.html(e) });
}
function concat(filename) {
if (!read[filename]) {
read[filename] = true;
var $ = readDocument(filename);
var dir = path.dirname(filename);
var assetPath = path.relative(outputDir, dir);
var links = extractImports($, dir);
resolve(filename, links);
var scripts = extractScripts($, assetPath);
scripts.each(function(){
buffer.push($.html(this));
});
var es = extractElements($, assetPath);
es.forEach(concatElement.bind(this, dir, outputDir));
} else {
if (options.verbose) {
console.log('Dependency deduplicated');
}
}
}
function resolve(inName, inDependencies) {
if (inDependencies.length > 0) {
if (options.verbose) {
console.log('Dependencies:', inDependencies);
}
inDependencies.forEach(concat);
}
}
// monkey patch addResolvePath for build
var monkeyPatch = function(proto, element) {
var assetPath = element.getAttribute('assetpath');
var url = HTMLImports.getDocumentUrl(element.ownerDocument) || '';
if (url) {
var parts = url.split('/');
parts.pop();
if (assetPath) {
parts.push(assetPath);
}
parts.push('');
url = parts.join('/');
}
proto.resolvePath = function(path) {
return url + path;
}
};
var buffer = [
'<!-- Monkey Patch addResolvePath to use assetpath -->',
'<script>Polymer.addResolvePath = ' + monkeyPatch + '</script>'
];
var read = {};
options.input.forEach(concat);
if (buffer.length) {
fs.writeFileSync(options.output, buffer.join('\n'), 'utf8');
}

140
libs/urljs/README.md Executable file
View file

@ -0,0 +1,140 @@
URL.js
======
**An API for working with URLs in JavaScript.**
URL.js can be used in both **server-side** and **client-side** JavaScript environments,
it has **no dependencies** on any other libraries, and is easy to use for common URL-related tasks.
Oh and since I built this to use on [TipTheWeb](http://tiptheweb.org/),
you should [**tip this project**](http://tiptheweb.org/tip/?link=https://github.com/ericf/urljs) if you find it useful! :-D
Design & Approach
-----------------
I had some very specific URL-related programming tasks I needed to do:
validate and normalize user input of URLs and URL-like strings within the browser,
and resolve URLs against each other on the server (in a YQL table to be specific).
Both of these tasks require a very good URL parser, so URL.js centers around parsing.
The design of the API and features of URL.js center around these four main concepts:
* Parsing
* Normalization
* Resolving
* Mutating/Building
**`URL` is both a namespace for utility methods and a constructor/factory to create instances of URL Objects.**
The static utility methods make it convenient when you just want to work with Strings since they return Strings.
When you want to retain a reference to a parsed URL you can easily create a URL instance;
these instances have useful methods, most serve as both an accessor/mutator to a specific part of the URL.
**Currently URL.js only works with HTTP URLs**, albiet the most popular type of URL;
I have plans to extend the functionality to include support [for all URL schemes](http://www.w3.org/Addressing/URL/url-spec.txt).
Internal to the library is the distiction between absolute and relative URLs.
Absolute URLs are ones which contain a scheme or are scheme-relative, and contain a host.
Relative URLs have slightly looser contraints but the relavence is maintained, either host- or path- relative.
Usage
-----
**`URL` is both a namespace for utility methods and a constructor/factory to create instances of URL Objects.**
### Using Static Utilites
There are two static methods: `normalize` and `resolve`
#### `URL.normalize`:
Takes in a dirty URL and makes it nice and clean.
URL.normalize('Http://Example.com'); // 'http://example.com/'
URL.normalize('Http://Example.com?foo=#bar'); // 'http://example.com/?foo#bar'
This should be suffient to serve the use-case of want to clean up URLs,
especially if were inputted by a user.
#### `URL.resolve`:
Given a base URL, this will resolve another URL against it; this method is inspired by what browsers do.
Normalizing is part of resolving, so a normalized and resolved URL `String` is returned.
URL.resolve('http://example.com/foo/bar', 'baz/index.html'); // 'http://example.com/foo/baz/index.html'
URL.resolve('https://example.com/foo/, '//example.com/bar.css'); // 'https://example.com/bar.css'
URL.resolve('http://example.com/foo/bar/zee/', '../../crazy#whoa'); // 'http://example.com/foo/crazy#whoa'
Resolving URLs is a pain in the ass, trust me, you dont want to have to do this by hand.
The implementation of `resolve` is using all parts of this librarys API to pull it off.
### Using URL Instances
The `URL` `Object` is also a constructor/factory for creating instances of `URL`s.
When creating an instance, **the `new` keyword is optional**.
var url = URL('http://www.example.com');
// Accessor/Mutator Methods
url.scheme(); // 'http'
url.userInfo(); // undefined
url.host(); // 'www.example.com'
url.port(); // undefined
url.path(); // '/'
url.query(); // undefined
url.queryString(); // ''
url.fragment(); // undefined
// Convenience Methods
url.original(); // 'http://www.example.com'
url.isValid(); // true
url.isAbsolute(); // true
url.isRelative(); // false
url.isHostRelative(); // false
url.type(); // 'absolute' === URL.ABSOLUTE
url.domain(); // 'example.com'
url.authority(); // 'www.example.com'
// Output Methods
url.toString(); // 'http://www.example.com'
url.resolve('/foo/').toString(); // 'http://www.example.com/foo/'
**Yeah, `URL` instances are packed full of useful URL-ly jazz!**
Here are a few more “complex” examples of what you can do with mutation, chaining, building, and resolving:
// switch the scheme, resolve a path with a fragment, and navigate to it
window.location = URL(window.location.toString()).scheme('https').resolve('/about/#people').toString();
// turn 'http://example.com' -> 'http://example.com/?foo=bar#baz'
URL('http://example.com').query([['foo', 'bar']]).fragment('baz');
// build up a URL to: http://tiptheweb.org/tip/?link=https://github.com/ericf/urljs
URL()
.scheme('http')
.host('tiptheweb.org')
.path('/tip/')
.query([['link', 'https://github.com/ericf/urljs']]);
License
-------
Copyright (c) 2011 Eric Ferraiuolo (http://eric.ferraiuolo.name/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

7
libs/urljs/url-min.js vendored Executable file

File diff suppressed because one or more lines are too long

666
libs/urljs/url.js Executable file
View file

@ -0,0 +1,666 @@
/*!
* URL.js
*
* Copyright 2011 Eric Ferraiuolo
* https://github.com/ericf/urljs
*/
/**
* URL constructor and utility.
* Provides support for validating whether something is a URL,
* formats and cleans up URL-like inputs into something nice and pretty,
* ability to resolve one URL against another and returned the formatted result,
* and is a convenient API for working with URL Objects and the various parts of URLs.
*
* @constructor URL
* @param {String | URL} url - the URL String to parse or URL instance to copy
* @return {URL} url - instance of a URL all nice and parsed
*/
var URL = function () {
var u = this;
if ( ! (u && u.hasOwnProperty && (u instanceof URL))) {
u = new URL();
}
return u._init.apply(u, arguments);
};
(function(){
var ABSOLUTE = 'absolute',
RELATIVE = 'relative',
HTTP = 'http',
HTTPS = 'https',
COLON = ':',
SLASH_SLASH = '//',
AT = '@',
DOT = '.',
SLASH = '/',
DOT_DOT = '..',
DOT_DOT_SLASH = '../',
QUESTION = '?',
EQUALS = '=',
AMP = '&',
HASH = '#',
EMPTY_STRING = '',
TYPE = 'type',
SCHEME = 'scheme',
USER_INFO = 'userInfo',
HOST = 'host',
PORT = 'port',
PATH = 'path',
QUERY = 'query',
FRAGMENT = 'fragment',
URL_TYPE_REGEX = /^(?:(https?:\/\/|\/\/)|(\/|\?|#)|[^;:@=\.\s])/i,
URL_ABSOLUTE_REGEX = /^(?:(https?):\/\/|\/\/)(?:([^:@\s]+:?[^:@\s]+?)@)?((?:[^;:@=\/\?\.\s]+\.)+[A-Za-z0-9\-]{2,})(?::(\d+))?(?=\/|\?|#|$)([^\?#]+)?(?:\?([^#]+))?(?:#(.+))?/i,
URL_RELATIVE_REGEX = /^([^\?#]+)?(?:\?([^#]+))?(?:#(.+))?/i,
OBJECT = 'object',
STRING = 'string',
TRIM_REGEX = /^\s+|\s+$/g,
trim, isObject, isString;
// *** Utilities *** //
trim = String.prototype.trim ? function (s) {
return ( s && s.trim ? s.trim() : s );
} : function (s) {
try {
return s.replace(TRIM_REGEX, EMPTY_STRING);
} catch (e) { return s; }
};
isObject = function (o) {
return ( o && typeof o === OBJECT );
};
isString = function (o) {
return typeof o === STRING;
};
// *** Static *** //
/**
*
*/
URL.ABSOLUTE = ABSOLUTE;
/**
*
*/
URL.RELATIVE = RELATIVE;
/**
*
*/
URL.normalize = function (url) {
return new URL(url).toString();
};
/**
* Returns a resolved URL String using the baseUrl to resolve the url against.
* This attempts to resolve URLs like a browser would on a web page.
*
* @static
* @method resolve
* @param {String | URL} baseUrl - the URL String, or URL instance as the resolving base
* @param {String | URL} url - the URL String, or URL instance to resolve
* @return {String} resolvedUrl - a resolved URL String
*/
URL.resolve = function (baseUrl, url) {
return new URL(baseUrl).resolve(url).toString();
};
// *** Prototype *** //
URL.prototype = {
// *** Lifecycle Methods *** //
/**
* Initializes a new URL instance, or re-initializes an existing one.
* The URL constructor delegates to this method to do the initializing,
* and the mutator instance methods call this to re-initialize when something changes.
*
* @protected
* @method _init
* @param {String | URL} url - the URL String, or URL instance
* @return {URL} url - instance of a URL all nice and parsed/re-parsed
*/
_init : function (url) {
this.constructor = URL;
url = isString(url) ? url : url instanceof URL ? url.toString() : null;
this._original = url;
this._url = {};
this._isValid = this._parse(url);
return this;
},
// *** Object Methods *** //
/**
* Returns the formatted URL String.
* Overridden Object toString method to do something useful.
*
* @public
* @method toString
* @return {String} url - formatted URL string
*/
toString : function () {
var url = this._url,
urlParts = [],
type = url[TYPE],
scheme = url[SCHEME],
path = url[PATH],
query = url[QUERY],
fragment = url[FRAGMENT];
if (type === ABSOLUTE) {
urlParts.push(
scheme ? (scheme + COLON + SLASH_SLASH) : SLASH_SLASH,
this.authority()
);
if (path && path.indexOf(SLASH) !== 0) { // this should maybe go in _set
path = SLASH + path;
}
}
urlParts.push(
path,
query ? (QUESTION + this.queryString()) : EMPTY_STRING,
fragment ? (HASH + fragment) : EMPTY_STRING
);
return urlParts.join(EMPTY_STRING);
},
// *** Accessor/Mutator Methods *** //
original : function () {
return this._original;
},
/**
* Whether parsing from initialization or re-initialization produced something valid.
*
* @public
* @method isValid
* @return {Boolean} valid - whether the URL is valid
*/
isValid : function () {
return this._isValid;
},
/**
* URL is absolute if it has a scheme or is scheme-relative (//).
*
* @public
* @method isAbsolute
* @return {Boolean} absolute - whether the URL is absolute
*/
isAbsolute : function () {
return this._url[TYPE] === ABSOLUTE;
},
/**
* URL is relative if it host or path relative, i.e. doesn't contain a host.
*
* @public
* @method isRelative
* @return {Boolean} relative - whether the URL is relative
*/
isRelative : function () {
return this._url[TYPE] === RELATIVE;
},
/**
* URL is host relative if it's relative and the path begins with '/'.
*
* @public
* @method isHostRelative
* @return {Boolean} hostRelative - whether the URL is host-relative
*/
isHostRelative : function () {
var path = this._url[PATH];
return ( this.isRelative() && path && path.indexOf(SLASH) === 0 );
},
/**
* Returns the type of the URL, either: URL.ABSOLUTE or URL.RELATIVE.
*
* @public
* @method type
* @return {String} type - the type of the URL: URL.ABSOLUTE or URL.RELATIVE
*/
type : function () {
return this._url[TYPE];
},
/**
* Returns or sets the scheme of the URL.
* If URL is determined to be absolute (i.e. contains a host) and no scheme is provided,
* the scheme will default to http.
*
* @public
* @method scheme
* @param {String} scheme - Optional scheme to set on the URL
* @return {String | URL} the URL scheme or the URL instance
*/
scheme : function (scheme) {
return ( arguments.length ? this._set(SCHEME, scheme) : this._url[SCHEME] );
},
/**
* Returns or set the user info of the URL.
* The user info can optionally contain a password and is only valid for absolute URLs.
*
* @public
* @method userInfo
* @param {String} userInfo - Optional userInfo to set on the URL
* @return {String | URL} the URL userInfo or the URL instance
*/
userInfo : function (userInfo) {
return ( arguments.length ? this._set(USER_INFO, userInfo) : this._url[USER_INFO] );
},
/**
* Returns or sets the host of the URL.
* The host name, if set, must be something valid otherwise the URL will become invalid.
*
* @public
* @method host
* @param {String} host - Optional host to set on the URL
* @return {String | URL} the URL host or the URL instance
*/
host : function (host) {
return ( arguments.length ? this._set(HOST, host) : this._url[HOST] );
},
/**
* Returns the URL's domain, where the domain is the TLD and SLD of the host.
* e.g. foo.example.com -> example.com
*
* @public
* @method domain
* @return {String} domain - the URL domain
*/
domain : function () {
var host = this._url[HOST];
return ( host ? host.split(DOT).slice(-2).join(DOT) : undefined );
},
/**
* Returns or sets the port of the URL.
*
* @public
* @method port
* @param {Number} port - Optional port to set on the URL
* @return {Number | URL} the URL port or the URL instance
*/
port : function (port) {
return ( arguments.length ? this._set(PORT, port) : this._url[PORT] );
},
/**
* Returns the URL's authority which is the userInfo, host, and port combined.
* This only makes sense for absolute URLs
*
* @public
* @method authority
* @return {String} authority - the URL's authority (userInfo, host, and port)
*/
authority : function () {
var url = this._url,
userInfo = url[USER_INFO],
host = url[HOST],
port = url[PORT];
return [
userInfo ? (userInfo + AT) : EMPTY_STRING,
host,
port ? (COLON + port) : EMPTY_STRING,
].join(EMPTY_STRING);
},
/**
* Returns or sets the path of the URL.
*
* @public
* @method path
* @param {String} path - Optional path to set on the URL
* @return {String | URL} the URL path or the URL instance
*/
path : function (path) {
return ( arguments.length ? this._set(PATH, path) : this._url[PATH] );
},
/**
* Returns or sets the query of the URL.
* This takes or returns the parsed query as an Array of Arrays.
*
* @public
* @method query
* @param {Array} query - Optional query to set on the URL
* @return {Array | URL} the URL query or the URL instance
*/
query : function (query) {
return ( arguments.length ? this._set(QUERY, query) : this._url[QUERY] );
},
/**
* Returns or sets the query of the URL.
* This takes or returns the query as a String; doesn't include the '?'
*
* @public
* @method queryString
* @param {String} queryString - Optional queryString to set on the URL
* @return {String | URL} the URL queryString or the URL instance
*/
queryString : function (queryString) {
// parse and set queryString
if (arguments.length) {
return this._set(QUERY, this._parseQuery(queryString));
}
queryString = EMPTY_STRING;
var query = this._url[QUERY],
i, len;
if (query) {
for (i = 0, len = query.length; i < len; i++) {
queryString += query[i].join(EQUALS);
if (i < len - 1) {
queryString += AMP;
}
}
}
return queryString;
},
/**
* Returns or sets the fragment on the URL.
* The fragment does not contain the '#'.
*
* @public
* @method fragment
* @param {String} fragment - Optional fragment to set on the URL
* @return {String | URL} the URL fragment or the URL instance
*/
fragment : function (fragment) {
return ( arguments.length ? this._set(FRAGMENT, fragment) : this._url[FRAGMENT] );
},
/**
* Returns a new, resolved URL instance using this as the baseUrl.
* The URL passed in will be resolved against the baseUrl.
*
* @public
* @method resolve
* @param {String | URL} url - the URL String, or URL instance to resolve
* @return {URL} url - a resolved URL instance
*/
resolve : function (url) {
url = (url instanceof URL) ? url : new URL(url);
var resolved, path;
if ( ! (this.isValid() && url.isValid())) { return this; } // not sure what to do???
// the easy way
if (url.isAbsolute()) {
return ( this.isAbsolute() ? url.scheme() ? url : new URL(url).scheme(this.scheme()) : url );
}
// the hard way
resolved = new URL(this.isAbsolute() ? this : null);
if (url.path()) {
if (url.isHostRelative() || ! this.path()) {
path = url.path();
} else {
path = this.path().substring(0, this.path().lastIndexOf(SLASH) + 1) + url.path();
}
resolved.path(this._normalizePath(path)).query(url.query()).fragment(url.fragment());
} else if (url.query()) {
resolved.query(url.query()).fragment(url.fragment());
} else if (url.fragment()) {
resolved.fragment(url.fragment());
}
return resolved;
},
/**
* Returns a new, reduced relative URL instance using this as the baseUrl.
* The URL passed in will be compared to the baseUrl with the goal of
* returning a reduced-down URL to one thats relative to the base (this).
* This method is basically the opposite of resolve.
*
* @public
* @method reduce
* @param {String | URL} url - the URL String, or URL instance to resolve
* @return {URL} url - the reduced URL instance
*/
reduce : function (url) {
url = (url instanceof URL) ? url : new URL(url);
var reduced = this.resolve(url);
if (this.isAbsolute() && reduced.isAbsolute()) {
if (reduced.scheme() === this.scheme() && reduced.authority() === this.authority()) {
reduced.scheme(null).userInfo(null).host(null).port(null);
}
}
return reduced;
},
// *** Private Methods *** //
/**
* Parses a URL into usable parts.
* Reasonable defaults are applied to parts of the URL which weren't present in the input,
* e.g. 'http://example.com' -> { type: 'absolute', scheme: 'http', host: 'example.com', path: '/' }
* If nothing or a falsy value is returned, the URL wasn't something valid.
*
* @private
* @method _parse
* @param {String} url - the URL string to parse
* @param {String} type - Optional type to seed parsing: URL.ABSOLUTE or URL.RELATIVE
* @return {Boolean} parsed - whether or not the URL string was parsed
*/
_parse : function (url, type) {
// make sure we have a good string
url = trim(url);
if ( ! (isString(url) && url.length > 0)) {
return false;
}
var urlParts, parsed;
// figure out type, absolute or relative, or quit
if ( ! type) {
type = url.match(URL_TYPE_REGEX);
type = type ? type[1] ? ABSOLUTE : type[2] ? RELATIVE : null : null;
}
switch (type) {
case ABSOLUTE:
urlParts = url.match(URL_ABSOLUTE_REGEX);
if (urlParts) {
parsed = {};
parsed[TYPE] = ABSOLUTE;
parsed[SCHEME] = urlParts[1] ? urlParts[1].toLowerCase() : undefined;
parsed[USER_INFO] = urlParts[2];
parsed[HOST] = urlParts[3].toLowerCase();
parsed[PORT] = urlParts[4] ? parseInt(urlParts[4], 10) : undefined;
parsed[PATH] = urlParts[5] || SLASH;
parsed[QUERY] = this._parseQuery(urlParts[6]);
parsed[FRAGMENT] = urlParts[7];
}
break;
case RELATIVE:
urlParts = url.match(URL_RELATIVE_REGEX);
if (urlParts) {
parsed = {};
parsed[TYPE] = RELATIVE;
parsed[PATH] = urlParts[1];
parsed[QUERY] = this._parseQuery(urlParts[2]);
parsed[FRAGMENT] = urlParts[3];
}
break;
// try to parse as absolute, if that fails then as relative
default:
return ( this._parse(url, ABSOLUTE) || this._parse(url, RELATIVE) );
break;
}
if (parsed) {
this._url = parsed;
return true;
} else {
return false;
}
},
/**
* Helper to parse a URL query string into an array of arrays.
* Order of the query paramerters is maintained, an example structure would be:
* queryString: 'foo=bar&baz' -> [['foo', 'bar'], ['baz']]
*
* @private
* @method _parseQuery
* @param {String} queryString - the query string to parse, should not include '?'
* @return {Array} parsedQuery - array of arrays representing the query parameters and values
*/
_parseQuery : function (queryString) {
if ( ! isString(queryString)) { return; }
queryString = trim(queryString);
var query = [],
queryParts = queryString.split(AMP),
queryPart, i, len;
for (i = 0, len = queryParts.length; i < len; i++) {
if (queryParts[i]) {
queryPart = queryParts[i].split(EQUALS);
query.push(queryPart[1] ? queryPart : [queryPart[0]]);
}
}
return query;
},
/**
* Helper for mutators to set a new URL-part value.
* After the URL-part is updated, the URL will be toString'd and re-parsed.
* This is a brute, but will make sure the URL stays in sync and is re-validated.
*
* @private
* @method _set
* @param {String} urlPart - the _url Object member String name
* @param {Object} val - the new value for the URL-part, mixed type
* @return {URL} this - returns this URL instance, chainable
*/
_set : function (urlPart, val) {
this._url[urlPart] = val;
if (val && (
urlPart === SCHEME ||
urlPart === USER_INFO ||
urlPart === HOST ||
urlPart === PORT )){
this._url[TYPE] = ABSOLUTE; // temp, set this to help clue parsing
}
if ( ! val && urlPart === HOST) {
this._url[TYPE] = RELATIVE; // temp, no host means relative
}
this._isValid = this._parse(this.toString());
return this;
},
/**
* Returns a normalized path String, by removing ../'s.
*
* @private
* @method _normalizePath
* @param {String} path the path String to normalize
* @return {String} normalizedPath the normalized path String
*/
_normalizePath : function (path) {
var pathParts, pathPart, pathStack, normalizedPath, i, len;
if (path.indexOf(DOT_DOT_SLASH) > -1) {
pathParts = path.split(SLASH);
pathStack = [];
for ( i = 0, len = pathParts.length; i < len; i++ ) {
pathPart = pathParts[i];
if (pathPart === DOT_DOT) {
pathStack.pop();
} else if (pathPart) {
pathStack.push(pathPart);
}
}
normalizedPath = pathStack.join(SLASH);
// prepend slash if needed
if (path[0] === SLASH) {
normalizedPath = SLASH + normalizedPath;
}
// append slash if needed
if (path[path.length - 1] === SLASH && normalizedPath.length > 1) {
normalizedPath += SLASH;
}
} else {
normalizedPath = path;
}
return normalizedPath;
}
};
}());