/** @license Hyphenator 4.0.0 - client side hyphenation for webbrowsers
 *  Copyright (C) 2011  Mathias Nater, Zürich (mathias at mnn dot ch)
 *  Project and Source hosted on http://code.google.com/p/hyphenator/
 * 
 *  This JavaScript code is free software: you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser
 *  General Public License (GNU LGPL) as published by the Free Software
 *  Foundation, either version 3 of the License, or (at your option)
 *  any later version.  The code is distributed WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
 *
 *  As additional permission under GNU GPL version 3 section 7, you
 *  may distribute non-source (e.g., minimized or compacted) forms of
 *  that code without the copy of the GNU GPL normally required by
 *  section 4, provided you include this license notice and a URL
 *  through which recipients can access the Corresponding Source.
 *
 * 
 *  Hyphenator.js contains code from Bram Steins hypher.js-Project:
 *  https://github.com/bramstein/Hypher
 *  
 *  Code from this project is marked in the source and belongs 
 *  to the following license:
 *  
 *  Copyright (c) 2011, Bram Stein
 *  All rights reserved.
 *  
 *  Redistribution and use in source and binary forms, with or without 
 *  modification, are permitted provided that the following conditions 
 *  are met:
 *   
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer. 
 *   2. Redistributions in binary form must reproduce the above copyright 
 *      notice, this list of conditions and the following disclaimer in the 
 *      documentation and/or other materials provided with the distribution. 
 *   3. The name of the author may not be used to endorse or promote products 
 *      derived from this software without specific prior written permission. 
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
 *  EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
 *  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *  
 */
 
/* 
 *  Comments are jsdoctoolkit formatted. See http://code.google.com/p/jsdoc-toolkit/
 */
 
/* The following comment is for JSLint: */
/*global window, ActiveXObject, unescape */
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, regexp: true, sub: true, newcap: true, immed: true, evil: true, eqeqeq: false */


/**
 * @constructor
 * @description Provides all functionality to do hyphenation, except the patterns that are loaded
 * externally.
 * @author Mathias Nater, <a href = "mailto:mathias@mnn.ch">mathias@mnn.ch</a>
 * @version X.Y.Z
 * @namespace Holds all methods and properties
 * @example
 * &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
 * &lt;script type = "text/javascript"&gt;
 *   Hyphenator.run();
 * &lt;/script&gt;
 */
var Hyphenator = (function (window) {

    var
    /**
     * @name Hyphenator-supportedLang
     * @description
     * A key-value object that stores supported languages.
     * The key is the bcp47 code of the language and the value
     * is the (abbreviated) filename of the pattern file.
     * @type {Object.<string, string>}
     * @private
     * @example
     * Check if language lang is supported:
     * if (supportedLang.hasOwnProperty(lang))
     */
    supportedLang = {
        'be': 'be.js',
        'ca': 'ca.js',
        'cs': 'cs.js',
        'da': 'da.js',
        'bn': 'bn.js',
        'de': 'de.js',
        'el': 'el-monoton.js',
        'el-monoton': 'el-monoton.js',
        'el-polyton': 'el-polyton.js',
        'en': 'en-us.js',
        'en-gb': 'en-gb.js',
        'en-us': 'en-us.js',
        'es': 'es.js',
        'fi': 'fi.js',
        'fr': 'fr.js',
        'grc': 'grc.js',
        'gu': 'gu.js',
        'hi': 'hi.js',
        'hu': 'hu.js',
        'hy': 'hy.js',
        'it': 'it.js',
        'kn': 'kn.js',
        'la': 'la.js',
        'lt': 'lt.js',
        'lv': 'lv.js',
        'ml': 'ml.js',
        'nb': 'nb-no.js',
        'no': 'nb-no.js',
        'nb-no': 'nb-no.js',
        'nl': 'nl.js',
        'or': 'or.js',
        'pa': 'pa.js',
        'pl': 'pl.js',
        'pt': 'pt.js',
        'ru': 'ru.js',
        'sk': 'sk.js',
        'sl': 'sl.js',
        'sv': 'sv.js',
        'ta': 'ta.js',
        'te': 'te.js',
        'tr': 'tr.js',
        'uk': 'uk.js'
    },

    /**
     * @name Hyphenator-languageHint
     * @description
     * An automatically generated string to be displayed in a prompt if the language can't be guessed.
     * The string is generated using the supportedLang-object.
     * @see Hyphenator-supportedLang
     * @type {string}
     * @private
     * @see Hyphenator-autoSetMainLanguage
     */

    languageHint = (function () {
        var k, r = '';
        for (k in supportedLang) {
            if (supportedLang.hasOwnProperty(k)) {
                r += k + ', ';
            }
        }
        r = r.substring(0, r.length - 2);
        return r;
    }()),
    
    /**
     * @name Hyphenator-prompterStrings
     * @description
     * A key-value object holding the strings to be displayed if the language can't be guessed
     * If you add hyphenation patterns change this string.
     * @type {Object.<string,string>}
     * @private
     * @see Hyphenator-autoSetMainLanguage
     */ 
    prompterStrings = {
        'be': 'Мова гэтага сайта не можа быць вызначаны аўтаматычна. Калі ласка пакажыце мову:',
        'cs': 'Jazyk této internetové stránky nebyl automaticky rozpoznán. Určete prosím její jazyk:',
        'da': 'Denne websides sprog kunne ikke bestemmes. Angiv venligst sprog:',
        'de': 'Die Sprache dieser Webseite konnte nicht automatisch bestimmt werden. Bitte Sprache angeben:',
        'en': 'The language of this website could not be determined automatically. Please indicate the main language:',
        'es': 'El idioma del sitio no pudo determinarse autom%E1ticamente. Por favor, indique el idioma principal:',
        'fi': 'Sivun kielt%E4 ei tunnistettu automaattisesti. M%E4%E4rit%E4 sivun p%E4%E4kieli:',
        'fr': 'La langue de ce site n%u2019a pas pu %EAtre d%E9termin%E9e automatiquement. Veuillez indiquer une langue, s.v.p.%A0:',
        'hu': 'A weboldal nyelvét nem sikerült automatikusan megállapítani. Kérem adja meg a nyelvet:',
        'hy': 'Չհաջողվեց հայտնաբերել այս կայքի լեզուն։ Խնդրում ենք նշեք հիմնական լեզուն՝',
        'it': 'Lingua del sito sconosciuta. Indicare una lingua, per favore:',
        'kn': 'ಜಾಲ ತಾಣದ ಭಾಷೆಯನ್ನು ನಿರ್ಧರಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ಮುಖ್ಯ ಭಾಷೆಯನ್ನು ಸೂಚಿಸಿ:',
        'lt': 'Nepavyko automatiškai nustatyti šios svetainės kalbos. Prašome įvesti kalbą:',
        'lv': 'Šīs lapas valodu nevarēja noteikt automātiski. Lūdzu norādiet pamata valodu:',
        'ml': 'ഈ വെ%u0D2C%u0D4D%u200Cസൈറ്റിന്റെ ഭാഷ കണ്ടുപിടിയ്ക്കാ%u0D28%u0D4D%u200D കഴിഞ്ഞില്ല. ഭാഷ ഏതാണെന്നു തിരഞ്ഞെടുക്കുക:',
        'nl': 'De taal van deze website kan niet automatisch worden bepaald. Geef de hoofdtaal op:',
        'no': 'Nettstedets språk kunne ikke finnes automatisk. Vennligst oppgi språk:',
        'pt': 'A língua deste site não pôde ser determinada automaticamente. Por favor indique a língua principal:',
        'ru': 'Язык этого сайта не может быть определен автоматически. Пожалуйста укажите язык:',
        'sl': 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:',
        'sv': 'Spr%E5ket p%E5 den h%E4r webbplatsen kunde inte avg%F6ras automatiskt. V%E4nligen ange:',
        'tr': 'Bu web sitesinin dili otomatik olarak tespit edilememiştir. Lütfen dökümanın dilini seçiniz%A0:',
        'uk': 'Мова цього веб-сайту не може бути визначена автоматично. Будь ласка, вкажіть головну мову:'
    },
    
    /**
     * @name Hyphenator-basePath
     * @description
     * A string storing the basepath from where Hyphenator.js was loaded.
     * This is used to load the patternfiles.
     * The basepath is determined dynamically by searching all script-tags for Hyphenator.js
     * If the path cannot be determined http://hyphenator.googlecode.com/svn/trunk/ is used as fallback.
     * @type {string}
     * @private
     * @see Hyphenator-loadPatterns
     */
    basePath = (function () {
        var s = document.getElementsByTagName('script'), i = 0, p, src, t;
        while (!!(t = s[i++])) {
            if (!t.src) {
                continue;
            }
            src = t.src;
            p = src.indexOf('Hyphenator.js');
            if (p !== -1) {
                return src.substring(0, p);
            }
        }
        return 'http://hyphenator.googlecode.com/svn/trunk/';
    }()),

    /**
     * @name Hyphenator-isLocal
     * @description
     * isLocal is true, if Hyphenator is loaded from the same domain, as the webpage, but false, if
     * it's loaded from an external source (i.e. directly from google.code)
     */
    isLocal = (function () {
        var re = false;
        if (window.location.href.indexOf(basePath) !== -1) {
            re = true;
        }
        return re;
    }()),
    
    /**
     * @name Hyphenator-documentLoaded
     * @description
     * documentLoaded is true, when the DOM has been loaded. This is set by runOnContentLoaded
     */
    documentLoaded = false,
    documentCount = 0,
    
    /**
     * @name Hyphenator-persistentConfig
     * @description
     * if persistentConfig is set to true (defaults to false), config options and the state of the 
     * toggleBox are stored in DOM-storage (according to the storage-setting). So they haven't to be
     * set for each page.
     */ 
    persistentConfig = false,   

    /**
     * @name Hyphenator-contextWindow
     * @description
     * contextWindow stores the window for the document to be hyphenated.
     * If there are frames this will change.
     * So use contextWindow instead of window!
     */
    contextWindow = window,

    /**
     * @name Hyphenator-doFrames
     * @description
     * switch to control if frames/iframes should be hyphenated, too
     * defaults to false (frames are a bag of hurt!)
     */
    doFrames = false,
    
    /**
     * @name Hyphenator-dontHyphenate
     * @description
     * A key-value object containing all html-tags whose content should not be hyphenated
     * @type {Object.<string,boolean>}
     * @private
     * @see Hyphenator-hyphenateElement
     */
    dontHyphenate = {'script': true, 'code': true, 'pre': true, 'img': true, 'br': true, 'samp': true, 'kbd': true, 'var': true, 'abbr': true, 'acronym': true, 'sub': true, 'sup': true, 'button': true, 'option': true, 'label': true, 'textarea': true, 'input': true, 'math': true, 'svg': true},

    /**
     * @name Hyphenator-enableCache
     * @description
     * A variable to set if caching is enabled or not
     * @type boolean
     * @default true
     * @private
     * @see Hyphenator.config
     * @see hyphenateWord
     */
    enableCache = true,

    /**
     * @name Hyphenator-storageType
     * @description
     * A variable to define what html5-DOM-Storage-Method is used ('none', 'local' or 'session')
     * @type {string}
     * @default 'none'
     * @private
     * @see Hyphenator.config
     */ 
    storageType = 'local',

    /**
     * @name Hyphenator-storage
     * @description
     * An alias to the storage-Method defined in storageType.
     * Set by Hyphenator.run()
     * @type {Object|undefined}
     * @default null
     * @private
     * @see Hyphenator.run
     */ 
    storage,
    
    /**
     * @name Hyphenator-enableReducedPatternSet
     * @description
     * A variable to set if storing the used patterns is set
     * @type boolean
     * @default false
     * @private
     * @see Hyphenator.config
     * @see hyphenateWord
     * @see Hyphenator.getRedPatternSet
     */ 
    enableReducedPatternSet = false,
    
    /**
     * @name Hyphenator-enableRemoteLoading
     * @description
     * A variable to set if pattern files should be loaded remotely or not
     * @type boolean
     * @default true
     * @private
     * @see Hyphenator.config
     * @see Hyphenator-loadPatterns
     */
    enableRemoteLoading = true,
    
    /**
     * @name Hyphenator-displayToggleBox
     * @description
     * A variable to set if the togglebox should be displayed or not
     * @type boolean
     * @default false
     * @private
     * @see Hyphenator.config
     * @see Hyphenator-toggleBox
     */
    displayToggleBox = false,

    /**
     * @name Hyphenator-css3
     * @description
     * A variable to set if css3 hyphenation should be used
     * @type boolean
     * @default false
     * @private
     * @see Hyphenator.config
     */
    css3 = false,
    /**
     * @name Hyphenator-css3_hsupport
     * @description
     * A generated object containing information for CSS3-hyphenation support
     * {
     *   support: boolean,
     *   property: <the property name to access hyphen-settings>,
     *   languages: <an object containing supported languages>
     * }
     * @type object
     * @default undefined
     * @private
     * @see Hyphenator-css3_gethsupport
     */
    css3_h9n,
    /**
     * @name Hyphenator-css3_gethsupport
     * @description
     * This function sets Hyphenator-css3_h9n for the current UA
     * @type function
     * @private
     * @see Hyphenator-css3_h9n
     */
    css3_gethsupport = function () {
        var s,
        ua = navigator.userAgent,
        r = {
            support: false,
            property: '',
            languages: {}
        };
        if (window.getComputedStyle) {
            s = window.getComputedStyle(window.document.getElementsByTagName('body')[0]);
        } else {
            //ancient Browser don't support CSS3 anyway
            css3_h9n = r;
            return;
        }
        if (ua.indexOf('Chrome') !== -1) {
            //Chrome actually knows -webkit-hyphens but does no hyphenation
            r.support = false;
        } else if ((ua.indexOf('Safari') !== -1) && (s['-webkit-hyphens'] !== undefined)) {
            r.support = true;
            r.property = '-webkit-hyphens';
            if (ua.indexOf('Mobile') !== -1) {
                //iOS only hyphenates in systemlanguage
                r.languages[navigator.language.split('-')[0]] = true;
            } else {
                //Desktop Safari only hyphenates some languages:
                r.languages = {
                    de: true,
                    en: true,
                    es: true,
                    fr: true,
                    it: true,
                    nl: true,
                    ru: true,
                    zh: true
                };
            }
        } else if ((ua.indexOf('Firefox') !== -1) && (s['MozHyphens'] !== undefined)) {
            r.support = true;
            r.property = 'MozHyphens';
            r.languages = {
                en: true
            };
        }
        css3_h9n = r;
    },
    
    /**
     * @name Hyphenator-hyphenateClass
     * @description
     * A string containing the css-class-name for the hyphenate class
     * @type {string}
     * @default 'hyphenate'
     * @private
     * @example
     * &lt;p class = "hyphenate"&gt;Text&lt;/p&gt;
     * @see Hyphenator.config
     */
    hyphenateClass = 'hyphenate',

    /**
     * @name Hyphenator-dontHyphenateClass
     * @description
     * A string containing the css-class-name for elements that should not be hyphenated
     * @type {string}
     * @default 'donthyphenate'
     * @private
     * @example
     * &lt;p class = "donthyphenate"&gt;Text&lt;/p&gt;
     * @see Hyphenator.config
     */
    dontHyphenateClass = 'donthyphenate',
    
    /**
     * @name Hyphenator-min
     * @description
     * A number wich indicates the minimal length of words to hyphenate.
     * @type {number}
     * @default 6
     * @private
     * @see Hyphenator.config
     */ 
    min = 6,
    
    /**
     * @name Hyphenator-orphanControl
     * @description
     * Control how the last words of a line are handled:
     * level 1 (default): last word is hyphenated
     * level 2: last word is not hyphenated
     * level 3: last word is not hyphenated and last space is non breaking
     * @type {number}
     * @default 1
     * @private
     */
    orphanControl = 1,
    
    /**
     * @name Hyphenator-isBookmarklet
     * @description
     * Indicates if Hyphanetor runs as bookmarklet or not.
     * @type boolean
     * @default false
     * @private
     */ 
    isBookmarklet = (function () {
        var loc = null, re = false, jsArray = document.getElementsByTagName('script'), i, l;
        for (i = 0, l = jsArray.length; i < l; i++) {
            if (!!jsArray[i].getAttribute('src')) {
                loc = jsArray[i].getAttribute('src');
            }
            if (!loc) {
                continue;
            } else if (loc.indexOf('Hyphenator.js?bm=true') !== -1) {
                re = true;
            }
        }
        return re;
    }()),
    
    /**
     * @name Hyphenator-mainLanguage
     * @description
     * The general language of the document. In contrast to {@link Hyphenator-defaultLanguage},
     * mainLanguage is defined by the client (i.e. by the html or by a prompt).
     * @type {string|null}
     * @private
     * @see Hyphenator-autoSetMainLanguage
     */ 
    mainLanguage = null,

    /**
     * @name Hyphenator-defaultLanguage
     * @description
     * The language defined by the developper. This language setting is defined by a config option.
     * It is overwritten by any html-lang-attribute and only taken in count, when no such attribute can
     * be found (i.e. just before the prompt).
     * @type {string|null}
     * @private
     * @see Hyphenator-autoSetMainLanguage
     */ 
    defaultLanguage = '',
    

    /**
     * @name Hyphenator-elements
     * @description
     * An array holding all elements that have to be hyphenated. This var is filled by
     * {@link Hyphenator-gatherDocumentInfos}
     * @type {Array}
     * @private
     */ 
    elements = (function () {
        var Element = function (element, data) {
            this.element = element;
            this.hyphenated = false;
            this.treated = false; //collected but not hyphenated (dohyphenation is off)
            this.data = data;
        },
        ElementCollection = function () {
            this.count = 0;
            this.hyCount = 0;
            this.list = {};
        };
        ElementCollection.prototype = {
            add: function (el, lang, data) {
                if (!this.list.hasOwnProperty(lang)) {
                    this.list[lang] = [];
                }
                this.list[lang].push(new Element(el, data));
                this.count += 1;
            },
            each: function (fn) {
                var k;
                for (k in this.list) {
                    if (this.list.hasOwnProperty(k)) {
                        fn(k, this.list[k]);
                    }
                }
            }
        };
        return new ElementCollection();
    }()),

    
    /**
     * @name Hyphenator-exceptions
     * @description
     * An object containing exceptions as comma separated strings for each language.
     * When the language-objects are loaded, their exceptions are processed, copied here and then deleted.
     * @see Hyphenator-prepareLanguagesObj
     * @type {Object}
     * @private
     */ 
    exceptions = {},
    
    /**
     * @name Hyphenator-docLanguages
     * @description
     * An object holding all languages used in the document. This is filled by
     * {@link Hyphenator-gatherDocumentInfos}
     * @type {Object}
     * @private
     */ 
    docLanguages = {},

    /**
     * @name Hyphenator-state
     * @description
     * A number that inidcates the current state of the script
     * 0: not initialized
     * 1: loading patterns
     * 2: ready
     * 3: hyphenation done
     * 4: hyphenation removed
     * @type {number}
     * @private
     */ 
    state = 0,

    /**
     * @name Hyphenator-url
     * @description
     * A string containing a RegularExpression to match URL's
     * @type {string}
     * @private
     */ 
    url = '(\\w*:\/\/)?((\\w*:)?(\\w*)@)?((([\\d]{1,3}\\.){3}([\\d]{1,3}))|((www\\.|[a-zA-Z]\\.)?[a-zA-Z0-9\\-\\.]+\\.([a-z]{2,4})))(:\\d*)?(\/[\\w#!:\\.?\\+=&%@!\\-]*)*',
    //      protocoll     usr     pwd                    ip               or                          host                 tld        port               path
    /**
     * @name Hyphenator-mail
     * @description
     * A string containing a RegularExpression to match mail-adresses
     * @type {string}
     * @private
     */ 
    mail = '[\\w-\\.]+@[\\w\\.]+',

    /**
     * @name Hyphenator-urlRE
     * @description
     * A RegularExpressions-Object for url- and mail adress matching
     * @type {RegExp}
     * @private
     */     
    urlOrMailRE = new RegExp('(' + url + ')|(' + mail + ')', 'i'),

    /**
     * @name Hyphenator-zeroWidthSpace
     * @description
     * A string that holds a char.
     * Depending on the browser, this is the zero with space or an empty string.
     * zeroWidthSpace is used to break URLs
     * @type {string}
     * @private
     */     
    zeroWidthSpace = (function () {
        var zws, ua = navigator.userAgent.toLowerCase();
        zws = String.fromCharCode(8203); //Unicode zero width space
        if (ua.indexOf('msie 6') !== -1) {
            zws = ''; //IE6 doesn't support zws
        }
        if (ua.indexOf('opera') !== -1 && ua.indexOf('version/10.00') !== -1) {
            zws = ''; //opera 10 on XP doesn't support zws
        }
        return zws;
    }()),
    
    /**
     * @name Hyphenator-createElem
     * @description
     * A function alias to document.createElementNS or document.createElement
     * @type {function(string, Object)}
     * @private
     */     
    createElem = function (tagname, context) {
        context = context || contextWindow;
        if (document.createElementNS) {
            return context.document.createElementNS('http://www.w3.org/1999/xhtml', tagname);
        } else if (document.createElement) {
            return context.document.createElement(tagname);
        }
    },
    
    /**
     * @name Hyphenator-onHyphenationDone
     * @description
     * A method to be called, when the last element has been hyphenated or the hyphenation has been
     * removed from the last element.
     * @see Hyphenator.config
     * @type {function()}
     * @private
     */     
    onHyphenationDone = function () {},

    /**
     * @name Hyphenator-onError
     * @description
     * A function that can be called upon an error.
     * @see Hyphenator.config
     * @type {function(Object)}
     * @private
     */     
    onError = function (e) {
        window.alert("Hyphenator.js says:\n\nAn Error ocurred:\n" + e.message);
    },

    /**
     * @name Hyphenator-selectorFunction
     * @description
     * A function that has to return a HTMLNodeList of Elements to be hyphenated.
     * By default it uses the classname ('hyphenate') to select the elements.
     * @see Hyphenator.config
     * @type {function()}
     * @private
     */     
    selectorFunction = function () {
        var tmp, el = [], i, l;
        if (document.getElementsByClassName) {
            el = contextWindow.document.getElementsByClassName(hyphenateClass);
        } else {
            tmp = contextWindow.document.getElementsByTagName('*');
            l = tmp.length;
            for (i = 0; i < l; i++)
            {
                if (tmp[i].className.indexOf(hyphenateClass) !== -1 && tmp[i].className.indexOf(dontHyphenateClass) === -1) {
                    el.push(tmp[i]);
                }
            }
        }
        return el;
    },

    /**
     * @name Hyphenator-intermediateState
     * @description
     * The value of style.visibility of the text while it is hyphenated.
     * @see Hyphenator.config
     * @type {string}
     * @private
     */     
    intermediateState = 'hidden',
    
    /**
     * @name Hyphenator-unhide
     * @description
     * How hidden elements unhide: either simultaneous (default: 'wait') or progressively.
     * 'wait' makes Hyphenator.js to wait until all elements are hyphenated (one redraw)
     * With 'progressiv' Hyphenator.js unhides elements as soon as they are hyphenated.
     * @see Hyphenator.config
     * @type {string}
     * @private
     */     
    unhide = 'wait',
    
    /**
     * @name Hyphenator-hyphen
     * @description
     * A string containing the character for in-word-hyphenation
     * @type {string}
     * @default the soft hyphen
     * @private
     * @see Hyphenator.config
     */
    hyphen = String.fromCharCode(173),
    
    /**
     * @name Hyphenator-urlhyphen
     * @description
     * A string containing the character for url/mail-hyphenation
     * @type {string}
     * @default the zero width space
     * @private
     * @see Hyphenator.config
     * @see Hyphenator-zeroWidthSpace
     */
    urlhyphen = zeroWidthSpace,

    /**
     * @name Hyphenator-safeCopy
     * @description
     * Defines wether work-around for copy issues is active or not
     * Not supported by Opera (no onCopy handler)
     * @type boolean
     * @default true
     * @private
     * @see Hyphenator.config
     * @see Hyphenator-registerOnCopy
     */
    safeCopy = true,
    
        
    /*
     * runOnContentLoaded is based od jQuery.bindReady()
     * see
     * jQuery JavaScript Library v1.3.2
     * http://jquery.com/
     *
     * Copyright (c) 2009 John Resig
     * Dual licensed under the MIT and GPL licenses.
     * http://docs.jquery.com/License
     *
     * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
     * Revision: 6246
     */
    /**
     * @name Hyphenator-runOnContentLoaded
     * @description
     * A crossbrowser solution for the DOMContentLoaded-Event based on jQuery
     * <a href = "http://jquery.com/</a>
     * I added some functionality: e.g. support for frames and iframes…
     * @param {Object} w the window-object
     * @param {function()} f the function to call onDOMContentLoaded
     * @private
     */
    runOnContentLoaded = function (w, f) {
        var DOMContentLoaded = function () {}, toplevel, hyphRunForThis = {};
        if (documentLoaded && !hyphRunForThis[w.location.href]) {
            f();
            hyphRunForThis[w.location.href] = true;
            return;
        }
        function init(context) {
            contextWindow = context || window;
            if (!hyphRunForThis[contextWindow.location.href] && (!documentLoaded || contextWindow != window.parent)) {
                documentLoaded = true;
                f();
                hyphRunForThis[contextWindow.location.href] = true;
            }
        }
        
        function doScrollCheck() {
            try {
                // If IE is used, use the trick by Diego Perini
                // http://javascript.nwbox.com/IEContentLoaded/
                document.documentElement.doScroll("left");
            } catch (error) {
                setTimeout(doScrollCheck, 1);
                return;
            }
        
            // and execute any waiting functions
            init(window);
        }

        function doOnLoad() {
            var i, haveAccess, fl = window.frames.length;
            if (doFrames && fl > 0) {
                for (i = 0; i < fl; i++) {
                    haveAccess = undefined;
                    //try catch isn't enough for webkit
                    try {
                        //opera throws only on document.toString-access
                        haveAccess = window.frames[i].document.toString();
                    } catch (e) {
                        haveAccess = undefined;
                    }
                    if (!!haveAccess) {
                        init(window.frames[i]);
                    }
                }
                contextWindow = window;
                f();
                hyphRunForThis[window.location.href] = true;
            } else {
                init(window);
            }
        }
        
        // Cleanup functions for the document ready method
        if (document.addEventListener) {
            DOMContentLoaded = function () {
                document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
                if (doFrames && window.frames.length > 0) {
                    //we are in a frameset, so do nothing but wait for onload to fire
                    return;
                } else {
                    init(window);
                }
            };
        
        } else if (document.attachEvent) {
            DOMContentLoaded = function () {
                // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
                if (document.readyState === "complete") {
                    document.detachEvent("onreadystatechange", DOMContentLoaded);
                    if (doFrames && window.frames.length > 0) {
                        //we are in a frameset, so do nothing but wait for onload to fire
                        return;
                    } else {
                        init(window);
                    }
                }
            };
        }

        // Mozilla, Opera and webkit nightlies currently support this event
        if (document.addEventListener) {
            // Use the handy event callback
            document.addEventListener("DOMContentLoaded", DOMContentLoaded, false);
            
            // A fallback to window.onload, that will always work
            window.addEventListener("load", doOnLoad, false);

        // If IE event model is used
        } else if (document.attachEvent) {
            // ensure firing before onload,
            // maybe late but safe also for iframes
            document.attachEvent("onreadystatechange", DOMContentLoaded);
            
            // A fallback to window.onload, that will always work
            window.attachEvent("onload", doOnLoad);

            // If IE and not a frame
            // continually check to see if the document is ready
            toplevel = false;
            try {
                toplevel = window.frameElement === null;
            } catch (e) {}

            if (document.documentElement.doScroll && toplevel) {
                doScrollCheck();
            }
        }

    },



    /**
     * @name Hyphenator-getLang
     * @description
     * Gets the language of an element. If no language is set, it may use the {@link Hyphenator-mainLanguage}.
     * @param {Object} el The first parameter is an DOM-Element-Object
     * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator-mainLanguage}
     * if there's no language found for the element.
     * @private
     */
    getLang = function (el, fallback) {
        if (!!el.getAttribute('lang')) {
            return el.getAttribute('lang').toLowerCase();
        }
        // The following doesn't work in IE due to a bug when getAttribute('xml:lang') in a table
        /*if (!!el.getAttribute('xml:lang')) {
            return el.getAttribute('xml:lang').substring(0, 2);
        }*/
        //instead, we have to do this (thanks to borgzor):
        try {
            if (!!el.getAttribute('xml:lang')) {
                return el.getAttribute('xml:lang').toLowerCase();
            }
        } catch (ex) {}
        if (el.tagName !== 'HTML') {
            return getLang(el.parentNode, true);
        }
        if (fallback) {
            return mainLanguage;
        }
        return null;
    },
    
    /**
     * @name Hyphenator-autoSetMainLanguage
     * @description
     * Retrieves the language of the document from the DOM.
     * The function looks in the following places:
     * <ul>
     * <li>lang-attribute in the html-tag</li>
     * <li>&lt;meta http-equiv = "content-language" content = "xy" /&gt;</li>
     * <li>&lt;meta name = "DC.Language" content = "xy" /&gt;</li>
     * <li>&lt;meta name = "language" content = "xy" /&gt;</li>
     * </li>
     * If nothing can be found a prompt using {@link Hyphenator-languageHint} and {@link Hyphenator-prompterStrings} is displayed.
     * If the retrieved language is in the object {@link Hyphenator-supportedLang} it is copied to {@link Hyphenator-mainLanguage}
     * @private
     */     
    autoSetMainLanguage = function (w) {
        w = w || contextWindow;
        var el = w.document.getElementsByTagName('html')[0],
            m = w.document.getElementsByTagName('meta'),
            i, text, e, ul;
        mainLanguage = getLang(el, false);
        if (!mainLanguage) {
            for (i = 0; i < m.length; i++) {
                //<meta http-equiv = "content-language" content="xy">   
                if (!!m[i].getAttribute('http-equiv') && (m[i].getAttribute('http-equiv').toLowerCase() === 'content-language')) {
                    mainLanguage = m[i].getAttribute('content').toLowerCase();
                }
                //<meta name = "DC.Language" content="xy">
                if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'dc.language')) {
                    mainLanguage = m[i].getAttribute('content').toLowerCase();
                }           
                //<meta name = "language" content = "xy">
                if (!!m[i].getAttribute('name') && (m[i].getAttribute('name').toLowerCase() === 'language')) {
                    mainLanguage = m[i].getAttribute('content').toLowerCase();
                }
            }
        }
        //get lang for frame from enclosing document
        if (!mainLanguage && doFrames && contextWindow != window.parent) {
            autoSetMainLanguage(window.parent);
        }
        //fallback to defaultLang if set
        if (!mainLanguage && defaultLanguage !== '') {
            mainLanguage = defaultLanguage;
        }
        //ask user for lang
        if (!mainLanguage) {
            text = '';
            ul = navigator.language ? navigator.language : navigator.userLanguage;
            ul = ul.substring(0, 2);
            if (prompterStrings.hasOwnProperty(ul)) {
                text = prompterStrings[ul];
            } else {
                text = prompterStrings.en;
            }
            text += ' (ISO 639-1)\n\n' + languageHint;
            mainLanguage = window.prompt(unescape(text), ul).toLowerCase();
        }
        if (!supportedLang.hasOwnProperty(mainLanguage)) {
            if (supportedLang.hasOwnProperty(mainLanguage.split('-')[0])) { //try subtag
                mainLanguage = mainLanguage.split('-')[0];
            } else {
                e = new Error('The language "' + mainLanguage + '" is not yet supported.');
                throw e;
            }
        }
    },
    
    /**
     * @name Hyphenator-gatherDocumentInfos
     * @description
     * This method runs through the DOM and executes the process()-function on:
     * - every node returned by the {@link Hyphenator-selectorFunction}.
     * The process()-function copies the element to the elements-variable, sets its visibility
     * to intermediateState, retrieves its language and recursivly descends the DOM-tree until
     * the child-Nodes aren't of type 1
     * @private
     */     
    gatherDocumentInfos = function () {
        var elToProcess, tmp, i = 0,
        process = function (el, hide, lang) {
            var n, i = 0, hyphenatorSettings = {};

            if (el.lang && typeof(el.lang) === 'string') {
                lang = el.lang.toLowerCase(); //copy attribute-lang to internal lang
            } else if (lang) {
                lang = lang.toLowerCase();
            } else {
                lang = getLang(el, true);
            }
            
            //if css3-hyphenation is supported: use it!
            if (css3 && css3_h9n.support && !!css3_h9n.languages[lang]) {
                el.style[css3_h9n.property] = "auto";
                el.style['-webkit-locale'] = "'" + lang + "'";
            } else {
                if (intermediateState === 'hidden') {
                    if (!!el.getAttribute('style')) {
                        hyphenatorSettings.hasOwnStyle = true;
                    } else {
                        hyphenatorSettings.hasOwnStyle = false;                 
                    }
                    hyphenatorSettings.isHidden = true;
                    el.style.visibility = 'hidden';
                }
                if (supportedLang[lang]) {
                    docLanguages[lang] = true;
                } else {
                    if (supportedLang.hasOwnProperty(lang.split('-')[0])) { //try subtag
                        lang = lang.split('-')[0];
                        hyphenatorSettings.language = lang;
                    } else if (!isBookmarklet) {
                        onError(new Error('Language ' + lang + ' is not yet supported.'));
                    }
                }               
                elements.add(el, lang, hyphenatorSettings);
            }
            while (!!(n = el.childNodes[i++])) {
                if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] &&
                    n.className.indexOf(dontHyphenateClass) === -1 && !(n in elToProcess)) {
                    process(n, false, lang);
                }
            }
        };
        if (css3) {
            css3_gethsupport();
        }
        if (isBookmarklet) {
            elToProcess = contextWindow.document.getElementsByTagName('body')[0];
            process(elToProcess, false, mainLanguage);
        } else {
            elToProcess = selectorFunction();
            while (!!(tmp = elToProcess[i++]))
            {
                process(tmp, true, '');
            }           
        }
        if (elements.count === 0) {
            //nothing to hyphenate or all hyphenated b css3
            state = 3;
            onHyphenationDone();
        }
    },
         
    
    /**
     * @name Hyphenator-createTrie
     * @description
     * converts patterns of the given language in a trie
     * @private
     * @param {string} lang the language whose patterns shall be converted
     */     
    convertPatterns = function (lang) {
        /** @license BSD licenced code
         * The following code is based on code from hypher.js and adapted for Hyphenator.js
         * Copyright (c) 2011, Bram Stein
         */
        var size = 0,
            tree = {
                tpoints: []
            },
            patterns, pattern, i, j, k,
            patternObject = Hyphenator.languages[lang].patterns,
            c, chars, points, t, p, codePoint,
            getPoints = (function () {
                //IE<9 doesn't act like other browsers
                if ('in3se'.split(/\D/).length === 1) {
                    return function (pattern) {
                        var chars = pattern.split(''), c, i, r = [],
                        numb3rs = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}, lastWasNum = false;
                        i = 0;
                        while (!!(c = chars[i])) {
                            if (numb3rs.hasOwnProperty(c)) {
                                r.push(c);
                                i += 2;
                                lastWasNum = true;
                            } else {
                                r.push('');
                                i += 1;
                                lastWasNum = false;
                            }
                        }
                        if (!lastWasNum) {
                            r.push('');
                        }
                        return r;
                    };
                } else {
                    return function (pattern) {
                        return pattern.split(/\D/);
                    };
                }
            }());
    
        for (size in patternObject) {
            if (patternObject.hasOwnProperty(size)) {
                patterns = patternObject[size].match(new RegExp('.{1,' + (+size) + '}', 'g'));
                i = 0;
                while (!!(pattern = patterns[i++])) {
                    chars = pattern.replace(/[\d]/g, '').split('');
                    points = getPoints(pattern);
                    t = tree;

                    j = 0;
                    while (!!(c = chars[j++])) {
                        codePoint = c.charCodeAt(0);
                        
                        if (!t[codePoint]) {
                            t[codePoint] = {};
                        }
                        t = t[codePoint];
                    }

                    t.tpoints = [];
                    for (k = 0; k < points.length; k++) {
                        p = points[k];
                        t.tpoints.push((p == "") ? 0 : p);
                    }
                }
            }
        }
        Hyphenator.languages[lang].patterns = tree;
        /**
         * end of BSD licenced code from hypher.js
         */
    },

    recreatePattern = function (pattern, nodePoints) {
        var r = [], c = pattern.split(''), i;
        for (i = 0; i < nodePoints.length; i++) {
            if (nodePoints[i] !== 0) {
                r.push(nodePoints[i]);
            }
            if (c[i]) {
                r.push(c[i]);
            }
        }
        return r.join('');
    },
    
    /**
     * @name Hyphenator-convertExceptionsToObject
     * @description
     * Converts a list of comma seprated exceptions to an object:
     * 'Fortran,Hy-phen-a-tion' -> {'Fortran':'Fortran','Hyphenation':'Hy-phen-a-tion'}
     * @private
     * @param {string} exc a comma separated string of exceptions (without spaces)
     */     
    convertExceptionsToObject = function (exc) {
        var w = exc.split(', '),
            r = {},
            i, l, key;
        for (i = 0, l = w.length; i < l; i++) {
            key = w[i].replace(/-/g, '');
            if (!r.hasOwnProperty(key)) {
                r[key] = w[i];
            }
        }
        return r;
    },
    
    /**
     * @name Hyphenator-loadPatterns
     * @description
     * Adds a &lt;script&gt;-Tag to the DOM to load an externeal .js-file containing patterns and settings for the given language.
     * If the given language is not in the {@link Hyphenator-supportedLang}-Object it returns.
     * One may ask why we are not using AJAX to load the patterns. The XMLHttpRequest-Object 
     * has a same-origin-policy. This makes the isBookmarklet-functionality impossible.
     * @param {string} lang The language to load the patterns for
     * @private
     * @see Hyphenator-basePath
     */
    loadPatterns = function (lang) {
        var url, xhr, head, script;
        if (supportedLang[lang] && !Hyphenator.languages[lang]) {
            url = basePath + 'patterns/' + supportedLang[lang];
        } else {
            return;
        }
        if (isLocal && !isBookmarklet) {
            //check if 'url' is available:
            xhr = null;
            if (typeof XMLHttpRequest !== 'undefined') {
                xhr = new XMLHttpRequest();
            }
            if (!xhr) {
                try {
                    xhr  = new ActiveXObject("Msxml2.XMLHTTP");
                } catch (e) {
                    xhr  = null;
                }
            }
            if (xhr) {
                xhr.open('HEAD', url, false);
                xhr.setRequestHeader('Cache-Control', 'no-cache');
                xhr.send(null);
                if (xhr.status === 404) {
                    onError(new Error('Could not load\n' + url));
                    delete docLanguages[lang];
                    return;
                }
            }
        }
        if (createElem) {
            head = window.document.getElementsByTagName('head').item(0);
            script = createElem('script', window);
            script.src = url;
            script.type = 'text/javascript';
            head.appendChild(script);
        }
    },
    
    /**
     * @name Hyphenator-prepareLanguagesObj
     * @description
     * Adds a cache to each language and converts the exceptions-list to an object.
     * If storage is active the object is stored there.
     * @private
     * @param {string} lang the language ob the lang-obj
     */     
    prepareLanguagesObj = function (lang) {
        var lo = Hyphenator.languages[lang], wrd;
        if (!lo.prepared) { 
            if (enableCache) {
                lo.cache = {};
                //Export
                lo['cache'] = lo.cache;
            }
            if (enableReducedPatternSet) {
                lo.redPatSet = {};
            }
            //add exceptions from the pattern file to the local 'exceptions'-obj
            if (lo.hasOwnProperty('exceptions')) {
                Hyphenator.addExceptions(lang, lo.exceptions);
                delete lo.exceptions;
            }
            //copy global exceptions to the language specific exceptions
            if (exceptions.hasOwnProperty('global')) {
                if (exceptions.hasOwnProperty(lang)) {
                    exceptions[lang] += ', ' + exceptions.global;
                } else {
                    exceptions[lang] = exceptions.global;
                }
            }
            //move exceptions from the the local 'exceptions'-obj to the 'language'-object
            if (exceptions.hasOwnProperty(lang)) {
                lo.exceptions = convertExceptionsToObject(exceptions[lang]);
                delete exceptions[lang];
            } else {
                lo.exceptions = {};
            }
            convertPatterns(lang);
            wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
            lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi');
            lo.prepared = true;
        }
        if (!!storage) {
            try {
                storage.setItem('Hyphenator_' + lang, window.JSON.stringify(lo));
            } catch (e) {
                //onError(e);
            }
        }
        
    },
    
    /**
     * @name Hyphenator-prepare
     * @description
     * This funtion prepares the Hyphenator-Object: If RemoteLoading is turned off, it assumes
     * that the patternfiles are loaded, all conversions are made and the callback is called.
     * If storage is active the object is retrieved there.
     * If RemoteLoading is on (default), it loads the pattern files and waits until they are loaded,
     * by repeatedly checking Hyphenator.languages. If a patterfile is loaded the patterns are
     * converted to their object style and the lang-object extended.
     * Finally the callback is called.
     * @private
     */
    prepare = function (callback) {
        var lang, interval, tmp1, tmp2;
        if (!enableRemoteLoading) {
            for (lang in Hyphenator.languages) {
                if (Hyphenator.languages.hasOwnProperty(lang)) {
                    prepareLanguagesObj(lang);
                }
            }
            state = 2;
            callback('*');
            return;
        }
        // get all languages that are used and preload the patterns
        state = 1;
        for (lang in docLanguages) {
            if (docLanguages.hasOwnProperty(lang)) {
                if (!!storage && storage.getItem('Hyphenator_' + lang)) {
                    Hyphenator.languages[lang] = window.JSON.parse(storage.getItem('Hyphenator_' + lang));
                    if (exceptions.hasOwnProperty('global')) {
                        tmp1 = convertExceptionsToObject(exceptions.global);
                        for (tmp2 in tmp1) {
                            if (tmp1.hasOwnProperty(tmp2)) {
                                Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
                            }
                        }
                    }
                    //Replace exceptions since they may have been changed:
                    if (exceptions.hasOwnProperty(lang)) {
                        tmp1 = convertExceptionsToObject(exceptions[lang]);
                        for (tmp2 in tmp1) {
                            if (tmp1.hasOwnProperty(tmp2)) {
                                Hyphenator.languages[lang].exceptions[tmp2] = tmp1[tmp2];
                            }
                        }
                        delete exceptions[lang];
                    }
                    //Replace genRegExp since it may have been changed:
                    tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}';
                    Hyphenator.languages[lang].genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + tmp1 + ')', 'gi');
                    
                    delete docLanguages[lang];
                    callback(lang);
                    continue;
                } else {
                    loadPatterns(lang);
                }
            }
        }
        // else async wait until patterns are loaded, then hyphenate
        interval = window.setInterval(function () {
            var finishedLoading = true, lang;
            for (lang in docLanguages) {
                if (docLanguages.hasOwnProperty(lang)) {
                    finishedLoading = false;
                    if (!!Hyphenator.languages[lang]) {
                        delete docLanguages[lang];
                        //do conversion while other patterns are loading:
                        prepareLanguagesObj(lang);
                        callback(lang);
                    }
                }
            }
            if (finishedLoading) {
                //console.log('callig callback for ' + contextWindow.location.href);
                window.clearInterval(interval);
                state = 2;
            }
        }, 100);
    },

    /**
     * @name Hyphenator-switchToggleBox
     * @description
     * Creates or hides the toggleBox: a small button to turn off/on hyphenation on a page.
     * @see Hyphenator.config
     * @private
     */     
    toggleBox = function () {
        var myBox, bdy, myIdAttribute, myTextNode, myClassAttribute,
        text = (Hyphenator.doHyphenation ? 'Hy-phen-a-tion' : 'Hyphenation');
        if (!!(myBox = contextWindow.document.getElementById('HyphenatorToggleBox'))) {
            myBox.firstChild.data = text;
        } else {
            bdy = contextWindow.document.getElementsByTagName('body')[0];
            myBox = createElem('div', contextWindow);
            myIdAttribute = contextWindow.document.createAttribute('id');
            myIdAttribute.nodeValue = 'HyphenatorToggleBox';
            myClassAttribute = contextWindow.document.createAttribute('class');
            myClassAttribute.nodeValue = dontHyphenateClass;
            myTextNode = contextWindow.document.createTextNode(text);
            myBox.appendChild(myTextNode);
            myBox.setAttributeNode(myIdAttribute);
            myBox.setAttributeNode(myClassAttribute);
            myBox.onclick =  Hyphenator.toggleHyphenation;
            myBox.style.position = 'absolute';
            myBox.style.top = '0px';
            myBox.style.right = '0px';
            myBox.style.margin = '0';
            myBox.style.backgroundColor = '#AAAAAA';
            myBox.style.color = '#FFFFFF';
            myBox.style.font = '6pt Arial';
            myBox.style.letterSpacing = '0.2em';
            myBox.style.padding = '3px';
            myBox.style.cursor = 'pointer';
            myBox.style.WebkitBorderBottomLeftRadius = '4px';
            myBox.style.MozBorderRadiusBottomleft = '4px';
            bdy.appendChild(myBox);
        }
    },


    /**
     * @name Hyphenator-hyphenateWord
     * @description
     * This function is the heart of Hyphenator.js. It returns a hyphenated word.
     *
     * If there's already a {@link Hyphenator-hypen} in the word, the word is returned as it is.
     * If the word is in the exceptions list or in the cache, it is retrieved from it.
     * If there's a '-' put a zeroWidthSpace after the '-' and hyphenate the parts.
     * @param {string} lang The language of the word
     * @param {string} word The word
     * @returns string The hyphenated word
     * @public
     */ 
    hyphenateWord = function (lang, word) {
        var lo = Hyphenator.languages[lang], parts, l, subst,
            w, characters, originalCharacters, wordLength, i, j, k, node, points = [],
            characterPoints = [], nodePoints, nodePointsLength, m = Math.max, trie,
            result = [''], pattern;
        if (word === '') {
            return '';
        }
        if (word.indexOf(hyphen) !== -1) {
            //word already contains shy; -> leave at it is!
            return word;
        }
        if (enableCache && lo.cache.hasOwnProperty(word)) { //the word is in the cache
            return lo.cache[word];
        }
        if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list
            return lo.exceptions[word].replace(/-/g, hyphen);
        }
        if (word.indexOf('-') !== -1) {
            //word contains '-' -> hyphenate the parts separated with '-'
            parts = word.split('-');
            for (i = 0, l = parts.length; i < l; i++) {
                parts[i] = hyphenateWord(lang, parts[i]);
            }
            return parts.join('-');
        }
        w = word = '_' + word + '_';
        if (!!lo.charSubstitution) {
            for (subst in lo.charSubstitution) {
                if (lo.charSubstitution.hasOwnProperty(subst)) {
                    w = w.replace(new RegExp(subst, 'g'), lo.charSubstitution[subst]);
                }
            }
        }
        if (word.indexOf("'") !== -1) {
            w = w.replace("'", "’"); //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns)
        }
        /** @license BSD licenced code
         * The following code is based on code from hypher.js
         * Copyright (c) 2011, Bram Stein
         */
        characters = w.toLowerCase().split('');
        originalCharacters = word.split('');
        wordLength = characters.length;
        trie = lo.patterns;
        for (i = 0; i < wordLength; i += 1) {
            points[i] = 0;
            characterPoints[i] = characters[i].charCodeAt(0);
        }
        for (i = 0; i < wordLength; i += 1) {
            pattern = '';
            node = trie;
            for (j = i; j < wordLength; j += 1) {
                node = node[characterPoints[j]];
                if (node) {
                    if (enableReducedPatternSet) {
                        pattern += String.fromCharCode(characterPoints[j]);
                    }
                    nodePoints = node.tpoints;
                    if (nodePoints) {
                        if (enableReducedPatternSet) {
                            if (!lo.redPatSet) {
                                lo.redPatSet = {};
                            }
                            lo.redPatSet[pattern] = recreatePattern(pattern, nodePoints);
                        }
                        for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) {
                            points[i + k] = m(points[i + k], nodePoints[k]);
                        }
                    }
                } else {
                    break;
                }
            }
        }
        for (i = 1; i < wordLength - 1; i += 1) {
            if (i > lo.leftmin && i < (wordLength - lo.rightmin) && points[i] % 2) {
                result.push(originalCharacters[i]);
            } else {
                result[result.length - 1] += originalCharacters[i];
            }
        }
        return result.join(hyphen);
        /**
         * end of BSD licenced code from hypher.js
         */
    },
        
    /**
     * @name Hyphenator-hyphenateURL
     * @description
     * Puts {@link Hyphenator-urlhyphen} after each no-alphanumeric char that my be in a URL.
     * @param {string} url to hyphenate
     * @returns string the hyphenated URL
     * @public
     */
    hyphenateURL = function (url) {
        return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen);
    },

    /**
     * @name Hyphenator-removeHyphenationFromElement
     * @description
     * Removes all hyphens from the element. If there are other elements, the function is
     * called recursively.
     * Removing hyphens is usefull if you like to copy text. Some browsers are buggy when the copy hyphenated texts.
     * @param {Object} el The element where to remove hyphenation.
     * @public
     */
    removeHyphenationFromElement = function (el) {
        var h, i = 0, n;
        switch (hyphen) {
        case '|':
            h = '\\|';
            break;
        case '+':
            h = '\\+';
            break;
        case '*':
            h = '\\*';
            break;
        default:
            h = hyphen;
        }
        while (!!(n = el.childNodes[i++])) {
            if (n.nodeType === 3) {
                n.data = n.data.replace(new RegExp(h, 'g'), '');
                n.data = n.data.replace(new RegExp(zeroWidthSpace, 'g'), '');
            } else if (n.nodeType === 1) {
                removeHyphenationFromElement(n);
            }
        }
    },
    
    
    /**
     * @name Hyphenator-registerOnCopy
     * @description
     * Huge work-around for browser-inconsistency when it comes to
     * copying of hyphenated text.
     * The idea behind this code has been provided by http://github.com/aristus/sweet-justice
     * sweet-justice is under BSD-License
     * @private
     */
    registerOnCopy = function (el) {
        var body = el.ownerDocument.getElementsByTagName('body')[0],
        shadow,
        selection,
        range,
        rangeShadow,
        restore,
        oncopyHandler = function (e) {
            e = e || window.event;
            var target = e.target || e.srcElement,
            currDoc = target.ownerDocument,
            body = currDoc.getElementsByTagName('body')[0],
            targetWindow = 'defaultView' in currDoc ? currDoc.defaultView : currDoc.parentWindow;
            if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) {
                //Safari needs this
                return;
            }
            //create a hidden shadow element
            shadow = currDoc.createElement('div');
            //Moving the element out of the screen doesn't work for IE9 (https://connect.microsoft.com/IE/feedback/details/663981/)
            //shadow.style.overflow = 'hidden';
            //shadow.style.position = 'absolute';
            //shadow.style.top = '-5000px';
            //shadow.style.height = '1px';
            //doing this instead:
            shadow.style.color = window.getComputedStyle ? targetWindow.getComputedStyle(body).backgroundColor : '#FFFFFF';
            shadow.style.fontSize = '0px';
            body.appendChild(shadow);
            if (!!window.getSelection) {
                //FF3, Webkit, IE9
                e.stopPropagation();
                selection = targetWindow.getSelection();
                range = selection.getRangeAt(0);
                shadow.appendChild(range.cloneContents());
                removeHyphenationFromElement(shadow);
                selection.selectAllChildren(shadow);
                restore = function () {
                    shadow.parentNode.removeChild(shadow);
                    selection.removeAllRanges(); //IE9 needs that
                    selection.addRange(range);
                };
            } else {
                // IE<9
                e.cancelBubble = true;
                selection = targetWindow.document.selection;
                range = selection.createRange();
                shadow.innerHTML = range.htmlText;
                removeHyphenationFromElement(shadow);
                rangeShadow = body.createTextRange();
                rangeShadow.moveToElementText(shadow);
                rangeShadow.select();
                restore = function () {
                    shadow.parentNode.removeChild(shadow);
                    if (range.text !== "") {
                        range.select();
                    }
                };
            }
            window.setTimeout(restore, 0);
        };
        if (!body) {
            return;
        }
        el = el || body;
        if (window.addEventListener) {
            el.addEventListener("copy", oncopyHandler, true);
        } else {
            el.attachEvent("oncopy", oncopyHandler);
        }
    },
    
    /**
     * @name Hyphenator-unhideElement
     * @description
     * Unhides an element and removes the visibility attr if set by hyphenator
     * @param Object The Element object from ElementCollection
     * @private
     */ 
    unhideElement = function (elo) {
        var el = elo.element,
        hyphenatorSettings = elo.data;
        el.style.visibility = 'visible';
        elo.data.isHidden = false;
        if (!hyphenatorSettings.hasOwnStyle) {
            el.setAttribute('style', ''); // without this, removeAttribute doesn't work in Safari (thanks to molily)
            el.removeAttribute('style');
        } else {
            if (el.style.removeProperty) {
                el.style.removeProperty('visibility');
            } else if (el.style.removeAttribute) { // IE
                el.style.removeAttribute('visibility');
            }  
        }
    },

    /**
     * @name Hyphenator-checkIfAllDone
     * @description
     * Checks if all Elements are hyphenated, unhides them and fires onHyphenationDone()
     * @private
     */     
    checkIfAllDone = function () {
        var allDone = true;
        elements.each(function (lang, list) {
            var i, l = list.length;
            for (i = 0; i < l; i++) {
                allDone = allDone && list[i].hyphenated;
                if (intermediateState === 'hidden' && unhide === 'wait') {
                    unhideElement(list[i]);
                }
            }
        });
        if (allDone) {
            state = 3;
            onHyphenationDone();
        }
    },


    /**
     * @name Hyphenator-hyphenateElement
     * @description
     * Takes the content of the given element and - if there's text - replaces the words
     * by hyphenated words. If there's another element, the function is called recursively.
     * When all words are hyphenated, the visibility of the element is set to 'visible'.
     * @param {Object} el The element to hyphenate
     * @private
     */
    hyphenateElement = function (lang, elo) {
        var hyphenatorSettings = elo.data,
            el = elo.element,
            hyphenate, n, i,
            controlOrphans = function (part) {
                var h, r;
                switch (hyphen) {
                case '|':
                    h = '\\|';
                    break;
                case '+':
                    h = '\\+';
                    break;
                case '*':
                    h = '\\*';
                    break;
                default:
                    h = hyphen;
                }
                if (orphanControl >= 2) {
                    //remove hyphen points from last word
                    r = part.split(' ');
                    r[1] = r[1].replace(new RegExp(h, 'g'), '');
                    r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), '');
                    r = r.join(' ');
                }
                if (orphanControl === 3) {
                    //replace spaces by non breaking spaces
                    r = r.replace(/[ ]+/g, String.fromCharCode(160));
                }
                return r;
            };
        if (Hyphenator.languages.hasOwnProperty(lang)) {
            hyphenate = function (word) {
                if (!Hyphenator.doHyphenation) {
                    return word;
                } else if (urlOrMailRE.test(word)) {
                    return hyphenateURL(word);
                } else {
                    return hyphenateWord(lang, word);
                }
            };
            if (safeCopy && (el.tagName.toLowerCase() !== 'body')) {
                registerOnCopy(el);
            }
            i = 0;
            while (!!(n = el.childNodes[i++])) {
                if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate!
                    n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
                    if (orphanControl !== 1) {
                        n.data = n.data.replace(/[\S]+ [\S]+$/, controlOrphans);
                    }
                }
            }
        }
        if (hyphenatorSettings.isHidden && intermediateState === 'hidden' && unhide === 'progressive') {
            unhideElement(elo);
        }
        elo.hyphenated = true;
        elements.hyCount += 1;
        if (elements.count <= elements.hyCount) {
            checkIfAllDone();
        }
    },
    

    /**
     * @name Hyphenator-hyphenateLanguageElements
     * @description
     * Calls hyphenateElement() for all elements of the specified language.
     * If the language is '*' then all elements are hyphenated.
     * This is done with a setTimout
     * to prevent a "long running Script"-alert when hyphenating large pages.
     * Therefore a tricky bind()-function was necessary.
     * @private
     */
    hyphenateLanguageElements = function (lang) {
        function bind(fun, arg1, arg2) {
            return function () {
                return fun(arg1, arg2);
            };
        }
        var el, i, l;
        if (lang === '*') {
            elements.each(function (lang, langels) {
                var i, l = langels.length;
                for (i = 0; i < l; i++) {
                    window.setTimeout(bind(hyphenateElement, lang, langels[i]), 0);
                }
            });
        } else {
            if (elements.list.hasOwnProperty(lang)) {
                l = elements.list[lang].length;
                for (i = 0; i < l; i++) {
                    window.setTimeout(bind(hyphenateElement, lang, elements.list[lang][i]), 0);
                }
            }
        }
    },
    
    /**
     * @name Hyphenator-removeHyphenationFromDocument
     * @description
     * Does what it says ;-)
     * @private
     */
    removeHyphenationFromDocument = function () {
        elements.each(function (lang, elo) {
            var i, l = elo.length, el;
            for (i = 0; i < l; i++) {
                removeHyphenationFromElement(elo[i].element);
                elo[i].hyphenated = false;
            }
        });
        state = 4;
    },
        
    /**
     * @name Hyphenator-createStorage
     * @description
     * inits the private var storage depending of the setting in storageType
     * and the supported features of the system.
     * @private
     */
    createStorage = function () {
        try {
            if (storageType !== 'none' &&
                typeof(window.localStorage) !== 'undefined' &&
                typeof(window.sessionStorage) !== 'undefined' &&
                typeof(window.JSON.stringify) !== 'undefined' &&
                typeof(window.JSON.parse) !== 'undefined') {
                switch (storageType) {
                case 'session':
                    storage = window.sessionStorage;
                    break;
                case 'local':
                    storage = window.localStorage;
                    break;
                default:
                    storage = undefined;
                    break;
                }
            }
        } catch (f) {
            //FF throws an error if DOM.storage.enabled is set to false
        }
    },
    
    /**
     * @name Hyphenator-storeConfiguration
     * @description
     * Stores the current config-options in DOM-Storage
     * @private
     */
    storeConfiguration = function () {
        if (!storage) {
            return;
        }
        var settings = {
            'STORED': true,
            'classname': hyphenateClass,
            'donthyphenateclassname': dontHyphenateClass,
            'minwordlength': min,
            'hyphenchar': hyphen,
            'urlhyphenchar': urlhyphen,
            'togglebox': toggleBox,
            'displaytogglebox': displayToggleBox,
            'remoteloading': enableRemoteLoading,
            'enablecache': enableCache,
            'onhyphenationdonecallback': onHyphenationDone,
            'onerrorhandler': onError,
            'intermediatestate': intermediateState,
            'selectorfunction': selectorFunction,
            'safecopy': safeCopy,
            'doframes': doFrames,
            'storagetype': storageType,
            'orphancontrol': orphanControl,
            'dohyphenation': Hyphenator.doHyphenation,
            'persistentconfig': persistentConfig,
            'defaultlanguage': defaultLanguage
        };
        storage.setItem('Hyphenator_config', window.JSON.stringify(settings));
    },
    
    /**
     * @name Hyphenator-restoreConfiguration
     * @description
     * Retrieves config-options from DOM-Storage and does configuration accordingly
     * @private
     */
    restoreConfiguration = function () {
        var settings;
        if (storage.getItem('Hyphenator_config')) {
            settings = window.JSON.parse(storage.getItem('Hyphenator_config'));
            Hyphenator.config(settings);
        }
    };

    return {
        
        /**
         * @name Hyphenator.version
         * @memberOf Hyphenator
         * @description
         * String containing the actual version of Hyphenator.js
         * [major release].[minor releas].[bugfix release]
         * major release: new API, new Features, big changes
         * minor release: new languages, improvements
         * @public
         */     
        version: '4.0.0',

        /**
         * @name Hyphenator.doHyphenation
         * @description
         * If doHyphenation is set to false (defaults to true), hyphenateDocument() isn't called.
         * All other actions are performed.
         */     
        doHyphenation: true,
        
        /**
         * @name Hyphenator.languages
         * @memberOf Hyphenator
         * @description
         * Objects that holds key-value pairs, where key is the language and the value is the
         * language-object loaded from (and set by) the pattern file.
         * The language object holds the following members:
         * <table>
         * <tr><th>key</th><th>desc></th></tr>
         * <tr><td>leftmin</td><td>The minimum of chars to remain on the old line</td></tr>
         * <tr><td>rightmin</td><td>The minimum of chars to go on the new line</td></tr>
         * <tr><td>shortestPattern</td><td>The shortes pattern (numbers don't count!)</td></tr>
         * <tr><td>longestPattern</td><td>The longest pattern (numbers don't count!)</td></tr>
         * <tr><td>specialChars</td><td>Non-ASCII chars in the alphabet.</td></tr>
         * <tr><td>patterns</td><td>the patterns</td></tr>
         * </table>
         * And optionally (or after prepareLanguagesObj() has been called):
         * <table>
         * <tr><td>exceptions</td><td>Excpetions for the secified language</td></tr>
         * </table>
         * @public
         */     
        languages: {},
        

        /**
         * @name Hyphenator.config
             * @description
         * Config function that takes an object as an argument. The object contains key-value-pairs
         * containig Hyphenator-settings. This is a shortcut for calling Hyphenator.set...-Methods.
         * @param {Object} obj <table>
         * <tr><th>key</th><th>values</th><th>default</th></tr>
         * <tr><td>classname</td><td>string</td><td>'hyphenate'</td></tr>
         * <tr><td>donthyphenateclassname</td><td>string</td><td>''</td></tr>
         * <tr><td>minwordlength</td><td>integer</td><td>6</td></tr>
         * <tr><td>hyphenchar</td><td>string</td><td>'&amp;shy;'</td></tr>
         * <tr><td>urlhyphenchar</td><td>string</td><td>'zero with space'</td></tr>
         * <tr><td>togglebox</td><td>function</td><td>see code</td></tr>
         * <tr><td>displaytogglebox</td><td>boolean</td><td>false</td></tr>
         * <tr><td>remoteloading</td><td>boolean</td><td>true</td></tr>
         * <tr><td>enablecache</td><td>boolean</td><td>true</td></tr>
         * <tr><td>enablereducedpatternset</td><td>boolean</td><td>false</td></tr>
         * <tr><td>onhyphenationdonecallback</td><td>function</td><td>empty function</td></tr>
         * <tr><td>onerrorhandler</td><td>function</td><td>alert(onError)</td></tr>
         * <tr><td>intermediatestate</td><td>string</td><td>'hidden'</td></tr>
         * <tr><td>selectorfunction</td><td>function</td><td>[…]</td></tr>
         * <tr><td>safecopy</td><td>boolean</td><td>true</td></tr>
         * <tr><td>doframes</td><td>boolean</td><td>false</td></tr>
         * <tr><td>storagetype</td><td>string</td><td>'none'</td></tr>
         * </table>
         * @public
         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *     Hyphenator.config({'minwordlength':4,'hyphenchar':'|'});
         *     Hyphenator.run();
         * &lt;/script&gt;
         */
        config: function (obj) {
            var assert = function (name, type) {
                    if (typeof obj[name] === type) {
                        return true;
                    } else {
                        onError(new Error('Config onError: ' + name + ' must be of type ' + type));
                        return false;
                    }
                },
                key;

            if (obj.hasOwnProperty('storagetype')) {
                if (assert('storagetype', 'string')) {
                    storageType = obj.storagetype;
                }
                if (!storage) {
                    createStorage();
                }           
            }
            if (!obj.hasOwnProperty('STORED') && storage && obj.hasOwnProperty('persistentconfig') && obj.persistentconfig === true) {
                restoreConfiguration();
            }
            
            for (key in obj) {
                if (obj.hasOwnProperty(key)) {
                    switch (key) {
                    case 'STORED':
                        break;
                    case 'classname':
                        if (assert('classname', 'string')) {
                            hyphenateClass = obj[key];
                        }
                        break;
                    case 'donthyphenateclassname':
                        if (assert('donthyphenateclassname', 'string')) {
                            dontHyphenateClass = obj[key];
                        }                       
                        break;
                    case 'minwordlength':
                        if (assert('minwordlength', 'number')) {
                            min = obj[key];
                        }
                        break;
                    case 'hyphenchar':
                        if (assert('hyphenchar', 'string')) {
                            if (obj.hyphenchar === '&shy;') {
                                obj.hyphenchar = String.fromCharCode(173);
                            }
                            hyphen = obj[key];
                        }
                        break;
                    case 'urlhyphenchar':
                        if (obj.hasOwnProperty('urlhyphenchar')) {
                            if (assert('urlhyphenchar', 'string')) {
                                urlhyphen = obj[key];
                            }
                        }
                        break;
                    case 'togglebox':
                        if (assert('togglebox', 'function')) {
                            toggleBox = obj[key];
                        }
                        break;
                    case 'displaytogglebox':
                        if (assert('displaytogglebox', 'boolean')) {
                            displayToggleBox = obj[key];
                        }
                        break;
                    case 'remoteloading':
                        if (assert('remoteloading', 'boolean')) {
                            enableRemoteLoading = obj[key];
                        }
                        break;
                    case 'enablecache':
                        if (assert('enablecache', 'boolean')) {
                            enableCache = obj[key];
                        }
                        break;
                    case 'enablereducedpatternset':
                        if (assert('enablereducedpatternset', 'boolean')) {
                            enableReducedPatternSet = obj[key];
                        }
                        break;
                    case 'onhyphenationdonecallback':
                        if (assert('onhyphenationdonecallback', 'function')) {
                            onHyphenationDone = obj[key];
                        }
                        break;
                    case 'onerrorhandler':
                        if (assert('onerrorhandler', 'function')) {
                            onError = obj[key];
                        }
                        break;
                    case 'intermediatestate':
                        if (assert('intermediatestate', 'string')) {
                            intermediateState = obj[key];
                        }
                        break;
                    case 'selectorfunction':
                        if (assert('selectorfunction', 'function')) {
                            selectorFunction = obj[key];
                        }
                        break;
                    case 'safecopy':
                        if (assert('safecopy', 'boolean')) {
                            safeCopy = obj[key];
                        }
                        break;
                    case 'doframes':
                        if (assert('doframes', 'boolean')) {
                            doFrames = obj[key];
                        }
                        break;
                    case 'storagetype':
                        if (assert('storagetype', 'string')) {
                            storageType = obj[key];
                        }                       
                        break;
                    case 'orphancontrol':
                        if (assert('orphancontrol', 'number')) {
                            orphanControl = obj[key];
                        }
                        break;
                    case 'dohyphenation':
                        if (assert('dohyphenation', 'boolean')) {
                            Hyphenator.doHyphenation = obj[key];
                        }
                        break;
                    case 'persistentconfig':
                        if (assert('persistentconfig', 'boolean')) {
                            persistentConfig = obj[key];
                        }
                        break;
                    case 'defaultlanguage':
                        if (assert('defaultlanguage', 'string')) {
                            defaultLanguage = obj[key];
                        }
                        break;
                    case 'useCSS3hyphenation':
                        if (assert('useCSS3hyphenation', 'boolean')) {
                            css3 = obj[key];
                        }
                        break;
                    case 'unhide':
                        if (assert('unhide', 'string')) {
                            unhide = obj[key];
                        }
                        break;
                    default:
                        onError(new Error('Hyphenator.config: property ' + key + ' not known.'));
                    }
                }
            }
            if (storage && persistentConfig) {
                storeConfiguration();
            }
        },

        /**
         * @name Hyphenator.run
             * @description
         * Bootstrap function that starts all hyphenation processes when called.
         * @public
         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *   Hyphenator.run();
         * &lt;/script&gt;
         */
        run: function () {
            documentCount = 0;
            var process = function () {
                try {
                    if (contextWindow.document.getElementsByTagName('frameset').length > 0) {
                        return; //we are in a frameset
                    }
                    documentCount++;
                    autoSetMainLanguage(undefined);
                    gatherDocumentInfos();
                    //console.log('preparing for ' + contextWindow.location.href);
                    prepare(hyphenateLanguageElements);
                    if (displayToggleBox) {
                        toggleBox();
                    }
                } catch (e) {
                    onError(e);
                }
            }, i, haveAccess, fl = window.frames.length;
            
            if (!storage) {
                createStorage();
            }
            if (!documentLoaded && !isBookmarklet) {
                runOnContentLoaded(window, process);
            }
            if (isBookmarklet || documentLoaded) {
                if (doFrames && fl > 0) {
                    for (i = 0; i < fl; i++) {
                        haveAccess = undefined;
                        //try catch isn't enough for webkit
                        try {
                            //opera throws only on document.toString-access
                            haveAccess = window.frames[i].document.toString();
                        } catch (e) {
                            haveAccess = undefined;
                        }
                        if (!!haveAccess) {
                            contextWindow = window.frames[i];
                            process();
                        }                       
                    }
                }
                contextWindow = window;
                process();
            }
        },
        
        /**
         * @name Hyphenator.addExceptions
             * @description
         * Adds the exceptions from the string to the appropriate language in the 
         * {@link Hyphenator-languages}-object
         * @param {string} lang The language
         * @param {string} words A comma separated string of hyphenated words WITH spaces.
         * @public
         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         *   Hyphenator.addExceptions('de','ziem-lich, Wach-stube');
         *   Hyphenator.run();
         * &lt;/script&gt;
         */
        addExceptions: function (lang, words) {
            if (lang === '') {
                lang = 'global';
            }
            if (exceptions.hasOwnProperty(lang)) {
                exceptions[lang] += ", " + words;
            } else {
                exceptions[lang] = words;
            }
        },
        
        /**
         * @name Hyphenator.hyphenate
             * @public
         * @description
         * Hyphenates the target. The language patterns must be loaded.
         * If the target is a string, the hyphenated string is returned,
         * if it's an object, the values are hyphenated directly.
         * @param {string|Object} target the target to be hyphenated
         * @param {string} lang the language of the target
         * @returns string
         * @example &lt;script src = "Hyphenator.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script src = "patterns/en.js" type = "text/javascript"&gt;&lt;/script&gt;
         * &lt;script type = "text/javascript"&gt;
         * var t = Hyphenator.hyphenate('Hyphenation', 'en'); //Hy|phen|ation
         * &lt;/script&gt;
         */
        hyphenate: function (target, lang) {
            var hyphenate, n, i;
            if (Hyphenator.languages.hasOwnProperty(lang)) {
                if (!Hyphenator.languages[lang].prepared) {
                    prepareLanguagesObj(lang);
                }
                hyphenate = function (word) {
                    if (urlOrMailRE.test(word)) {
                        return hyphenateURL(word);
                    } else {
                        return hyphenateWord(lang, word);
                    }
                };
                if (typeof target === 'string' || target.constructor === String) {
                    return target.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
                } else if (typeof target === 'object') {
                    i = 0;
                    while (!!(n = target.childNodes[i++])) {
                        if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate!
                            n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate);
                        } else if (n.nodeType === 1) {
                            if (n.lang !== '') {
                                Hyphenator.hyphenate(n, n.lang);
                            } else {
                                Hyphenator.hyphenate(n, lang);
                            }
                        }
                    }
                }
            } else {
                onError(new Error('Language "' + lang + '" is not loaded.'));
            }
        },
        
        /**
         * @name Hyphenator.getRedPatternSet
             * @description
         * Returns {@link Hyphenator-isBookmarklet}.
         * @param {string} lang the language patterns are stored for
         * @returns object {'patk': pat}
         * @public
         */
        getRedPatternSet: function (lang) {
            return Hyphenator.languages[lang].redPatSet;
        },
        
        /**
         * @name Hyphenator.isBookmarklet
             * @description
         * Returns {@link Hyphenator-isBookmarklet}.
         * @returns boolean
         * @public
         */
        isBookmarklet: function () {
            return isBookmarklet;
        },

        getConfigFromURI: function () {
            var loc = null, re = {}, jsArray = document.getElementsByTagName('script'), i, j, l, s, gp, option;
            for (i = 0, l = jsArray.length; i < l; i++) {
                if (!!jsArray[i].getAttribute('src')) {
                    loc = jsArray[i].getAttribute('src');
                }
                if (!loc) {
                    continue;
                } else {
                    s = loc.indexOf('Hyphenator.js?');
                    if (s === -1) {
                        continue;
                    }
                    gp = loc.substring(s + 14).split('&');
                    for (j = 0; j < gp.length; j++) {
                        option = gp[j].split('=');
                        if (option[0] === 'bm') {
                            continue;
                        }
                        if (option[1] === 'true') {
                            re[option[0]] = true;
                            continue;
                        }
                        if (option[1] === 'false') {
                            re[option[0]] = false;
                            continue;
                        }
                        if (isFinite(option[1])) {
                            re[option[0]] = parseInt(option[1], 10);
                            continue;
                        }
                        if (option[0] === 'onhyphenationdonecallback') {
                            re[option[0]] = new Function('', option[1]);
                            continue;
                        }
                        re[option[0]] = option[1];
                    }
                    break;
                }
            }
            return re;
        },

        /**
         * @name Hyphenator.toggleHyphenation
             * @description
         * Checks the current state of the ToggleBox and removes or does hyphenation.
         * @public
         */
        toggleHyphenation: function () {
            if (Hyphenator.doHyphenation) {
                removeHyphenationFromDocument();
                Hyphenator.doHyphenation = false;
                storeConfiguration();
                toggleBox();
            } else {
                hyphenateLanguageElements('*');
                Hyphenator.doHyphenation = true;
                storeConfiguration();
                toggleBox();
            }
        }
    };
}(window));

//Export properties/methods (for google closure compiler)
Hyphenator['languages'] = Hyphenator.languages;
Hyphenator['config'] = Hyphenator.config;
Hyphenator['run'] = Hyphenator.run;
Hyphenator['addExceptions'] = Hyphenator.addExceptions;
Hyphenator['hyphenate'] = Hyphenator.hyphenate;
Hyphenator['getRedPatternSet'] = Hyphenator.getRedPatternSet;
Hyphenator['isBookmarklet'] = Hyphenator.isBookmarklet;
Hyphenator['getConfigFromURI'] = Hyphenator.getConfigFromURI;
Hyphenator['toggleHyphenation'] = Hyphenator.toggleHyphenation;
window['Hyphenator'] = Hyphenator;

if (Hyphenator.isBookmarklet()) {
    Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', doframes: true});
    Hyphenator.config(Hyphenator.getConfigFromURI());
    Hyphenator.run();
}
