<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
// +----------------------------------------------------------------------+
// | Copyright (c) 2006 Tom Klingenberg                                   |
// +----------------------------------------------------------------------+
// | license info GPL/LGPL                                                |
// +----------------------------------------------------------------------+
// | Author: Tom Klingenberg <tklingenberg@lastflood.com>                 |
// +----------------------------------------------------------------------+
//
/**
 * ISBN
 *
 * Handle, Convert and Validate ISBN Numbers
 *
 * PHP version 5
 *
 * LICENSE: GPL/LGPL
 *
 * Package to handle, convert and validate ISBN numbers. It includes:
 *     - ISBN specifics:
 *        - EAN/Prefix (integer)
 *        - 'ISBNBody' (string)
 *        - Group/Registration Group [2001: Group identifier] (integer)
 *        (- GroupTitle/Registration Group Title (string))
 *        - 'ISBNSubbody'' (string)
 *        - Publisher/Registrant [2001: Publisher identifier] (string)
 *        - Title/Publication [2001: Title identifier] (string)
 *        - Checkdigit (string)
 *        - Version (integer) value as of a CONST
 *     - Syntactical Validation plus Validation based on real ISBN Data
 *   - ISBN-10 (ISO 2108)
 *      - checksum calculation (ISBN-10)
 *        - validation
 *      - conversion to ISBN-13-978
 *   - ISBN-13-978 (2005 Handbook, ISO pending; ISBN-13)
 *      - checksum calculation (EAN)
 *        - validation
 *
 * Based on standards published by international ISBN Agency
 * http://www.isbn-international.org/
 *
 *
 * @category    UNKNOWN
 * @package        ISBN
 * @author        Tom Klingenberg <tklingenberg@lastflood.com>
 * @copyright    2006 Tom Klingenberg
 * @license        GPL/LGPL
 * @version        0.1.0
 */

// {{{ constants
/**
 * ISBN Versions supported
 */
define("ISBN_VERSION_NONE", false);
/*
 * VERSION_UNKNOWN is by the caller only, this shall never
 * be a value returned by a public function or getter
 */
define("ISBN_VERSION_UNKNOWN", 0);
define("ISBN_VERSION_ISBN_10", 10);
define("ISBN_VERSION_ISBN_13", 13978);
define("ISBN_VERSION_ISBN_13_978", ISBN_VERSION_ISBN_13);
define("ISBN_VERSION_ISBN_13_979", 13979);  /* reserved */

/**
 * Default Additonal ISBN (formatting) Values
 */
define("ISBN_DEFAULT_INPUTVERSION", ISBN_VERSION_UNKNOWN);
define("ISBN_DEFAULT_COSMETIC_SEPERATOR", "-");
/*
 * ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX
 *
 * When printed, the ISBN is always preceded by the letters "ISBN".
 * Note: In countries where the Latin alphabet is not used, an abbreviation
 * in the characters of the local script may be used in addition to the
 * Latin letters "ISBN".
 * This can be defined as a default value wihtin this constant.
 */
define("ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX", "");
// }}}

// {{{ ISBN
/**
 * class ISBN
 *
 * @since      Class available since Release 0.0.0
 */
class ISBN
{
    
/**
     * @var string ISBN Registration Group
     */
    
private $isbn_group = "";
    
/**
     * @var string ISBN Publisher
     */
    
private $isbn_publisher = "";
    
/**
     * @var string ISBN Title
     */
    
private $isbn_title = "";

    
/**
     * @var mixed ISBN number version
     */
    
private $ver = ISBN_VERSION_NONE;

    
// {{{ __construct
    /**
     * Constructor
     *
     * @param array    $isbn    String of ISBN Value to use
     * @param mixed    $ver    Optional Version Constant
     *
     * @access public
     */
    
function __construct($isbn = "", $ver = ISBN_DEFAULT_INPUTVERSION) {
        
/* validate & handle optional isbn parameter */
        
if (is_string($isbn) == false ) {
            throw new 
Exception("ISBN parameter must be a string", 4);
        }
        if (
strlen($isbn) == 0) {
            
$this->setISBN($isbn);
            return;
        }

        
/* validate version parameter */
        
if (self::_ISBNVersionIs($ver) == false) {
            throw new 
Exception("ISBN Version parameter is not an ISBN Version", 3);
        }

        
/* ISBN has been passed, check the version now:
         *     if it is unknown, try to dertine it, if this fails
         *     throw an exception
         */
        
if ($ver == ISBN_VERSION_UNKNOWN) {
            
$verguess = self::_ISBNVersionGuess($isbn);
            if (
self::_ISBNVersionIsValid($verguess)) {
                
$ver = $verguess;
            } else {
                
// throw new Exception("Unknown ISBN Version could not be guessed.", 5);
                
$ver = ISBN_VERSION_NONE;
            }
        }
        
/* version determined */
        
$this->ver = $ver;

        
/* handle a complete invalid ISBN of which a version could
         * not be determined. */
        
if ($ver === ISBN_VERSION_NONE) {
            
$this->setISBN("");
            return;
        }

        try {
            
$this->setISBN($isbn);
        } catch(
Exception $e) {
            
/* the isbn is invalid and not set, sothat this
             * ISBN object will be set to a blank value. */
            
$this->setISBN("");
        }

    }
    
// }}}

    // {{{ __extractCheckdigit()
    /**
     *
     * extract Checkdigit of an ISBN-Number
     *
     * @param string    $isbnn    normalized ISBN string
     *
     * @return boolean    False if failed
     * @return string    string containing the ISBN-Body
     *
     */
    
private static function __extractCheckdigit($isbnn) {
        
$checkdigit = false;
        
$checkdigit = substr($isbnn,-1);
        return (string) 
$checkdigit;
    }
    
// }}}

    // {{{ __extractEANPrefix()
    /**
     * extracts EAN-Prefix of a normalized isbn string
     *
     *
     * @param string    $isbnn    normalized isbn string
     *
     * @return boolean    False if failed
     * @return string    Prefix
     */
    
private static function __extractEANPrefix($isbnn)
    {
        
$r = settype($isbnn, "string");
        if (
$r === false) {
            return 
false;
        }
        if (
strlen($isbnn) < 3) {
            return 
false;
        }
        
$prefix = substr($isbnn, 0, 3);
        return 
$prefix;
    }
    
// }}}

    // {{{ __extractGroup()
    /**
     * extract Registration Group of an ISBN-Body
     *
     * @param string    $isbnbody    ISBN-Body
     *
     * @return boolean    false if failed
     * @return integer    Value of Registration Group
     */
    
private static function __extractGroup($isbnbody) {
        
$group = '';
        
$subbody = '';
        
$r = self::__ISBNBodyParts($isbnbody, $group, $subbody);
        if (
$r === false) {
            return 
false;
        }
        return 
$group;
    }
    
// }}}

    // {{{ __extractISBNBody()
    /**
     * extract ISBN-Body of an ISBN-Number
     *
     * @param string    $isbnn    normalized ISBN string
     *
     * @return boolean    False if failed
     * @return string    string containing the ISBN-Body
     */
    
private static function __extractISBNBody($isbnn) {
        
/* extract */
        
$body = false;
        
$isbnn = (string) $isbnn;
        
$l = strlen($isbnn);
        if (
$l == 10) {
            
$body =  substr($isbnn, 0, -1);
        } elseif (
$l == 13) {
            
$body =  substr($isbnn, 3, -1);
        } else {
            return 
false;
        }
        
/* verify */
        
$r = settype($body, "string");
        if (
$r === false) {
            return 
false;
        }
        if (
strlen($body) != 9) {
            return 
false;
        }
        if (
ctype_digit($body) === false) {
            return 
false;
        }
        return 
$body;
    }
    
// }}}

    // {{{ __ISBNBodyParts()
    /**
     * Get the 2 Parts of the ISBN-Body (ISBN-10/ISBN-13-978)
     *
     * @param string    $isbnbody            ISBN-Body
     * @param string    &$registrationgroup    Registration Group
     * @param string    &$isbnsubbody        ISBN-Subbody
     *
     * @return boolean    False if failed, True on success
     *
     * @access private
     */
    
private static function __ISBNBodyParts($isbnbody, &$registrationgroup, &$isbnsubbody) {
        
/* validate input (should not be needed, @access private) */
        
$r = settype($isbnbody, "string");
        if (
$r === false) {
            return 
false;
        }
        if (
strlen($isbnbody) != 9) {
            return 
false;
        }
        if (
ctype_digit($isbnbody) === false) {
            return 
false;
        }
        
/* extract registraion group
         * boundaries see p.13 2005 handbook
         */
        
$boundaries = array();
        
$boundaries[] = array(    0, 59999, 1);
        
$boundaries[] = array(60000, 60099, 3); // Iran 2006-12-05
        
$boundaries[] = array(60100, 69999, 0);
        
$boundaries[] = array(70000, 79999, 1);
        
$boundaries[] = array(80000, 94999, 2);
        
$boundaries[] = array(95000, 98999, 3);
        
$boundaries[] = array(99000, 99899, 4);
        
$boundaries[] = array(99900, 99999, 5);
        
/* segment value */
        
$segment = substr($isbnbody, 0, 5);
        
$segmentvalue = intval($segment);
        
/* test segment value against boundaries */
        
$r = false;
        foreach (
$boundaries as $boundary) {
            if (
$segmentvalue >= $boundary[0] && $segmentvalue <= $boundary[1]) {
                
$r = $boundary[2];
            }
        }
        if (
$r === false) {
            return 
false;
        }
        
/* $r is 0 when the boundary is not defined */
        
if ($r === 0) {
            return 
false;
        }
        
$registrationgroup = substr($isbnbody, 0, $r);
        
$isbnsubbody = substr($isbnbody, $r);
        return 
true;
    }
    
// }}}

    // {{{ __ISBNSubbodyParts()
    /**
     * Get the 2 Parts of the ISBN-Subbody (ISBN-10/ISBN-13)
     *
     * @param string    $isbnsubbody    ISBN-Subbody
     * @param integer    $groupid        Registrationgroup
     * @param string    &$registrant    Registrant
     * @param string    &$publication    Publication
     *
     * @return boolean    False if failed, true on success
     *
     * @access private
     */
    
private static function __ISBNSubbodyParts($isbnsubbody, $groupid, &$registrant, &$publication) {
        
/* validate input (should not be needed, @access private) */
        
$r = settype($isbnsubbody, "string");
        if (
$r === false) {
            return 
false;
        }
        
$l = strlen($isbnsubbody);
        if ( 
$l < 1 || $l > 8) {
            return 
false;
        }
        if (
ctype_digit($isbnsubbody) === false) {
            return 
false;
        }
        
$r = settype($groupid, 'integer');
        if (
$r === false) {
            return 
false;
        }
        if (
$groupid < 0 || $groupid > 99999) {
            return 
false;
        }
        
/* extract registrant based on group and registrant range
         * parse this specific group format: array("English speaking area",
         *   "00-09;10-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999");
         */
        
$group = self::__getISBN10Group($groupid);
        if (
$group === false) {
            return 
false;
        }
        
$len = self::__getRegistrantLength($isbnsubbody, $group[1]);
        if (
$len === false) {
            return 
false;
        }
        
$registrant = substr($isbnsubbody, 0, $len);
        
$publication = substr($isbnsubbody, $len);
        return 
true;
    }
    
// }}}

    // {{{ __getRegistrantLength()
    /*
     * Return Length of Registrant part within an ISBNSubbody in a specific
     * grouprange in this specific format:
     *
     * "00-09;10-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999"
     *
     * Info: This function is compatible with Groupranges formatted in the
     * .js file and might become obsolete if new formats are more fitting.
     *
     * @param string    $isbnsubbody
     * @param string    $grouprange        Grouprange in the Format "#a1-#z1;#a2-z2[...]"
     *
     * @return boolean    False if failed
     * @return int        Length (in chars) of Registrant
     *
     * @access private
     */
    
private static function __getRegistrantLength($isbnsubbody, $grouprange) {
        
$r = settype($grouprange, 'string');
        if (
$r === false) {
            return 
false;
        }
        if (
strlen($grouprange) < 3) {
            return 
false;
        }
        
$sl = strlen($isbnsubbody);
        
$ranges = explode(";", $grouprange);
        foreach(
$ranges as $range) {
            
$range = trim($range);
            
$fromto = explode("-", $range);
            if (
count($fromto) !== 2) {
                return 
false;
            }
            
/* validation:
             * from and to need to be in the same class,
             * having the same length.
             * registrant can not be bigger or same then the
             * whole subbody, at least there is one digit for
             * the publication.
             */

            
$l = strlen($fromto[0]);
            if (
$l != strlen($fromto[1])) {
                return 
false;
            }
            if (
$l >= $sl) {
                return 
false;
            }

            
/* check that from/to values are in order */
            
if (strcmp($fromto[0], $fromto[1]) >= 0) {
                return 
false;
            }

            
/* compare and fire if matched */
            
$compare = intval(substr($isbnsubbody, 0, $l));

            if (
strcmp($fromto[0], $compare) < 1 && strcmp($fromto[1], $compare) > -1) {
                return 
$l;
            }
        }
        return 
false;
    }
    
// }}}

    // {{{ __getISBN10Group()
    /**
     * Get ISBN-10 Registration Group Data by its numeric ID
     *
     * @param integer    $id    Registration Group Identifier
     *
     * @return mixed    array:     group array
     *                    boolean: False if failed
     */
    
private static function __getISBN10Group($id) {
        
$r = settype($id, "integer");
        if (
$r === false) {
            return 
false;
        }
        
$groups = self::__getISBN10Groups();
        if (
$groups === false) {
            return 
false;
        }
        if (isset(
$groups[$id]) === false) {
            return 
false;
        }
        
$group = $groups[$id];
        return 
$group;
    }
    
// }}}

    // {{{ __getISBN10Groups()
    /**
     * Get all ISBN-10 Registration Groups
     *
     * @return array    groups array
     *
     * Info: This function connects outer world data into this class logic
     *       which can be generated with the supplied tools.
     *         A user should not alter the array data. This data should be altered
     *         together with the international ISBN Agency only.
     */
    
private static function __getISBN10Groups() {
        
$groups = array();
        
/* ISBN Code-Generator - Ranges */
        
$groups[0]     = array("English speaking area"    , "00-09;10-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999");
        
$groups[1]     = array("English speaking area"    , "00-09;100-399;4000-5499;55000-86979;869800-998999");
        
$groups[2]     = array("French speaking area"    , "00-09;10-19;200-349;35000-39999;400-699;7000-8399;84000-89999;900000-949999;9500000-9999999");
        
$groups[3]     = array("German speaking area"    , "00-02;030-033;0340-0369;03700-03999;04-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999");
        
$groups[4]     = array("Japan"    , "00-09;10-19;200-699;7000-8499;85000-89999;900000-949999;9500000-9999999");
        
$groups[5]     = array("Russian Federation"    , "00-09;10-19;200-699;7000-8499;85000-89999;900000-909999;91000-91999;9200-9299;93000-94999;9500-9799;98000-98999;9900000-9909999;9910-9999");
        
$groups[600]   = array("Iran"    , "00-09;100-499;5000-8999;90000-99999");
        
$groups[7]     = array("China, People's Republic"    , "00-09;100-499;5000-7999;80000-89999;900000-999999");
        
$groups[80]    = array("Czech Republic; Slovakia"    , "00-09;10-19;200-699;7000-8499;85000-89999;900000-999999");
        
$groups[81]    = array("India"    , "00-09;10-19;200-699;7000-8499;85000-89999;900000-999999");
        
$groups[82]    = array("Norway"    , "00-09;10-19;200-699;7000-8999;90000-98999;990000-999999");
        
$groups[83]    = array("Poland"    , "00-09;10-19;200-599;60000-69999;7000-8499;85000-89999;900000-999999");
        
$groups[84]    = array("Spain"    , "00-09;10-19;200-699;7000-8499;85000-89999;9000-9199;920000-923999;92400-92999;930000-949999;95000-96999;9700-9999");
        
$groups[85]    = array("Brazil"    , "00-09;10-19;200-599;60000-69999;7000-8499;85000-89999;900000-979999;98000-99999");
        
$groups[86]    = array("Serbia and Montenegro"    , "00-09;10-29;300-599;6000-7999;80000-89999;900000-999999");
        
$groups[87]    = array("Denmark"    , "00-09;10-29;400-649;7000-7999;85000-94999;970000-999999");
        
$groups[88]    = array("Italian speaking area"    , "00-09;10-19;200-599;6000-8499;85000-89999;900000-949999;95000-99999");
        
$groups[89]    = array("Korea"    , "00-09;10-24;250-549;5500-8499;85000-94999;950000-999999");
        
$groups[90]    = array("Netherlands, Belgium (Flemish)"    , "00-09;10-19;200-499;5000-6999;70000-79999;800000-849999;8500-8999;900000-909999;940000-949999");
        
$groups[91]    = array("Sweden"    , "0-1;20-49;500-649;7000-7999;85000-94999;970000-999999");
        
$groups[92]    = array("International Publishers (Unesco, EU), European Community Organizations"    , "0-5;60-79;800-899;9000-9499;95000-98999;990000-999999");
        
$groups[93]    = array("India - no ranges fixed yet"    , "");
        
$groups[950]   = array("Argentina"    , "00-09;10-49;500-899;9000-9899;99000-99999");
        
$groups[951]   = array("Finland"    , "0-1;20-54;550-889;8900-9499;95000-99999");
        
$groups[952]   = array("Finland"    , "00-19;200-499;5000-5999;60-65;6600-6699;67000-69999;7000-7999;80-94;9500-9899;99000-99999");
        
$groups[953]   = array("Croatia"    , "0-0;10-14;150-599;6000-9499;95000-99999");
        
$groups[954]   = array("Bulgaria"    , "00-09;10-29;300-799;8000-8999;90000-92999;9300-9999");
        
$groups[955]   = array("Sri Lanka"    , "0-0;1000-1999;20-54;550-799;8000-9499;95000-99999");
        
$groups[956]   = array("Chile"    , "00-09;10-19;200-699;7000-9999");
        
$groups[957]   = array("Taiwan, China"    , "00-02;0300-0499;05-19;2000-2099;21-27;28000-30999;31-43;440-819;8200-9699;97000-99999");
        
$groups[958]   = array("Colombia"    , "00-09;10-59;600-799;8000-9499;95000-99999");
        
$groups[959]   = array("Cuba"    , "00-09;10-19;200-699;7000-8499");
        
$groups[960]   = array("Greece"    , "00-09;10-19;200-659;6600-6899;690-699;7000-8499;85000-99999");
        
$groups[961]   = array("Slovenia"    , "00-09;10-19;200-599;6000-8999;90000-94999");
        
$groups[962]   = array("Hong Kong"    , "00-09;10-19;200-699;7000-8499;85000-86999;8700-8999;900-999");
        
$groups[963]   = array("Hungary"    , "00-09;10-19;200-699;7000-8499;85000-89999;9000-9999");
        
$groups[964]   = array("Iran"    , "00-09;10-14;150-249;2500-2999;300-549;5500-8999;90000-96999;970-989;9900-9999");
        
$groups[965]   = array("Israel"    , "00-09;10-19;200-599;7000-7999;90000-99999");
        
$groups[966]   = array("Ukraine"    , "00-19;2000-2999;300-699;7000-8999;90000-99999");
        
$groups[967]   = array("Malaysia"    , "0-5;60-89;900-989;9900-9989;99900-99999");
        
$groups[968]   = array("Mexico"    , "01-09;10-39;400-499;5000-7999;800-899");
        
$groups[969]   = array("Pakistan"    , "0-1;20-39;400-799;8000-9999");
        
$groups[970]   = array("Mexico"    , "01-09;10-59;600-899;9000-9099;91000-96999;9700-9999");
        
$groups[971]   = array("Philippines?"    , "000-019;02-02;0300-0599;06-09;10-49;500-849;8500-9099;91000-99999");
        
$groups[972]   = array("Portugal"    , "0-1;20-54;550-799;8000-9499;95000-99999");
        
$groups[973]   = array("Romania"    , "0-0;100-169;1700-1999;20-54;550-759;7600-8499;85000-88999;8900-9499;95000-99999");
        
$groups[974]   = array("Thailand"    , "00-09;10-19;200-699;7000-8499;85000-89999;90000-94999;9500-9999");
        
$groups[975]   = array("Turkey"    , "00000-00999;01-09;10-24;250-599;6000-9199;92000-98999;990-999");
        
$groups[976]   = array("Caribbean Community"    , "0-3;40-59;600-799;8000-9499;95000-99999");
        
$groups[977]   = array("Egypr"    , "00-09;10-19;200-499;5000-6999;700-999");
        
$groups[978]   = array("Nigeria"    , "000-199;2000-2999;30000-79999;8000-8999;900-999");
        
$groups[979]   = array("Indonesia"    , "000-099;1000-1499;15000-19999;20-29;3000-3999;400-799;8000-9499;95000-99999");
        
$groups[980]   = array("Venezuela"    , "00-09;10-19;200-599;6000-9999");
        
$groups[981]   = array("Singapore"    , "200-299;3000-9999;00-09");
        
$groups[982]   = array("South Pacific"    , "00-09;100-699;70-89;9000-9999");
        
$groups[983]   = array("Malaysia"    , "00-01;020-199;2000-3999;40000-49999;50-79;800-899;9000-9899;99000-99999");
        
$groups[984]   = array("Bangladesh"    , "00-09;10-39;400-799;8000-8999;90000-99999");
        
$groups[985]   = array("Belarus"    , "00-09;10-39;400-599;6000-8999;90000-99999");
        
$groups[986]   = array("Taiwan, China"    , "120-559;5600-7999;80000-99999;00-09;10-11");
        
$groups[987]   = array("Argentina"    , "00-09;1000-1999;20000-29999;30-49;500-899;9000-9499;95000-99999");
        
$groups[988]   = array("Hongkong"    , "00-09;10-19;200-799;8000-9699;97000-99999");
        
$groups[989]   = array("Portugal"    , "0-1;20-54;550-799;8000-9499;95000-99999");
        
$groups[9940]  = array("Montenegro"    , "0-1;20-49;500-899;9000-9999");
        
$groups[9941]  = array("Georgia"    , "0-0;10-39;400-899;9000-9999");
        
$groups[9942]  = array("Ecuador"    , "00-89;900-994;9950-9999");
        
$groups[9943]  = array("Uzbekistan"    , "00-29;300-399;4000-9999");
        
$groups[9944]  = array("Turkey"    , "0-2;300-499;5000-5999;60-89;900-999");
        
$groups[9945]  = array("Dominican Republic"    , "00-09;10-39;400-849;8500-9999");
        
$groups[9946]  = array("Korea, P.D.R."    , "0-1;20-39;400-899;9000-9999");
        
$groups[9947]  = array("Algeria"    , "0-1;20-79;800-999");
        
$groups[9948]  = array("United Arab Emirates"    , "00-09;10-39;400-849;8500-9999");
        
$groups[9949]  = array("Estonia"    , "0-0;10-39;400-899;9000-9999");
        
$groups[9950]  = array("Palestine"    , "00-29;300-840;8500-9999");
        
$groups[9951]  = array("Kosova"    , "00-09;10-39;400-849;8500-9999");
        
$groups[9952]  = array("Azerbaijan"    , "0-1;20-39;400-799;8000-9999");
        
$groups[9953]  = array("Lebanon"    , "0-0;10-39;400-599;60-89;9000-9999");
        
$groups[9954]  = array("Morocco"    , "0-1;20-39;400-799;8000-9999");
        
$groups[9955]  = array("Lithuania"    , "00-09;10-39;400-929;9300-9999");
        
$groups[9956]  = array("Cameroon"    , "0-0;10-39;400-899;9000-9999");
        
$groups[9957]  = array("Jordan"    , "00-09;10-39;400-849;8500-9999");
        
$groups[9958]  = array("Bosnia and Herzegovina"    , "0-0;10-49;500-899;9000-9999");
        
$groups[9959]  = array("Libya"    , "0-1;20-79;800-949;9500-9999");
        
$groups[9960]  = array("Saudi Arabia"    , "00-09;10-59;600-899;9000-9999");
        
$groups[9961]  = array("Algeria"    , "0-2;30-69;700-949;9500-9999");
        
$groups[9962]  = array("Panama"    , "00-09;10-54;5500-5599;56-59;600-849;8500-9999");
        
$groups[9963]  = array("Cyprus"    , "0-2;30-54;550-749;7500-9999");
        
$groups[9964]  = array("Ghana"    , "0-6;70-94;950-999");
        
$groups[9965]  = array("Kazakhstan"    , "00-09;10-39;400-899;9000-9999");
        
$groups[9966]  = array("Kenya"    , "00-09;10-69;7000-7499;750-959;9600-9999");
        
$groups[9967]  = array("Kyrgyzstan"    , "00-09;10-39;400-899;9000-9999");
        
$groups[9968]  = array("Costa Rica"    , "00-09;10-49;500-939;9400-9999");
        
$groups[9970]  = array("Uganda"    , "00-09;10-39;400-899;9000-9999");
        
$groups[9971]  = array("Singapore"    , "0-5;60-89;900-989;9900-9999");
        
$groups[9972]  = array("Peru"    , "00-09;1;200-249;2500-2999;30-59;600-899;9000-9999");
        
$groups[9973]  = array("Tunisia"    , "0-0;10-69;700-969;9700-9999");
        
$groups[9974]  = array("Uruguay"    , "0-2;30-54;550-749;7500-9499;95-99");
        
$groups[9975]  = array("Moldova"    , "0;100-399;4000-4499;45-89;900-949;9500-9999");
        
$groups[9976]  = array("Tanzania"    , "0-5;60-89;900-989;9990-9999");
        
$groups[9977]  = array("Costa Rica"    , "00-09;10-89;900-989;9900-9999");
        
$groups[9978]  = array("Ecuador"    , "00-09;10-29;300-399;40-94;950-989;9900-9999");
        
$groups[9979]  = array("Iceland"    , "0-4;50-75;760-899;9000-9999");
        
$groups[9980]  = array("Papua New Guinea"    , "0-3;40-89;900-989;9900-9999");
        
$groups[9981]  = array("Morocco"    , "00-09;100-159;1600-1999;20-79;800-949;9500-9999");
        
$groups[9982]  = array("Zambia"    , "00-09;10-79;800-889;9900-9999");
        
$groups[9983]  = array("Gambia"    , "80-94;950-989;9900-9999");
        
$groups[9984]  = array("Latvia"    , "00-09;10-49;500-899;9000-9999");
        
$groups[9985]  = array("Estonia"    , "0-4;50-79;800-899;9000-9999");
        
$groups[9986]  = array("Lithuania"    , "00-09;10-39;400-899;9000-9399;940-969;97-99");
        
$groups[9987]  = array("Tanzania"    , "00-09;10-39;400-879;8800-9999");
        
$groups[9988]  = array("Ghana"    , "0-2;30-54;550-749;7500-9999");
        
$groups[9989]  = array("Macedonia"    , "0-0;100-199;2000-2999;30-59;600-949;9500-9999");
        
$groups[99901] = array("Bahrain"    , "00-09;10-49;500-799;80-99");
        
$groups[99902] = array("Gabon - no ranges fixed yet"    , "");
        
$groups[99903] = array("Mauritius"    , "0-1;20-89;900-999");
        
$groups[99904] = array("Netherlands Antilles; Aruba, Neth. Ant"    , "0-5;60-89;900-999");
        
$groups[99905] = array("Bolivia"    , "0-3;40-79;800-999");
        
$groups[99906] = array("Kuwait"    , "0-2;30-59;600-699;70-89;9-9");
        
$groups[99908] = array("Malawi"    , "0-0;10-89;900-999");
        
$groups[99909] = array("Malta"    , "0-3;40-94;950-999");
        
$groups[99910] = array("Sierra Leone"    , "0-2;30-89;900-999");
        
$groups[99911] = array("Lesotho"    , "00-09;10-59;600-999");
        
$groups[99912] = array("Botswana"    , "0-3;400-599;60-89;900-999");
        
$groups[99913] = array("Andorra"    , "0-2;30-35;600-604");
        
$groups[99914] = array("Suriname"    , "0-4;50-89;900-949");
        
$groups[99915] = array("Maldives"    , "0-4;50-79;800-999");
        
$groups[99916] = array("Namibia"    , "0-2;30-69;700-999");
        
$groups[99917] = array("Brunei Darussalam"    , "0-2;30-89;900-999");
        
$groups[99918] = array("Faroe Islands"    , "0-3;40-79;800-999");
        
$groups[99919] = array("Benin"    , "0-2;40-69;900-999");
        
$groups[99920] = array("Andorra"    , "0-4;50-89;900-999");
        
$groups[99921] = array("Qatar"    , "0-1;20-69;700-799;8-8;90-99");
        
$groups[99922] = array("Guatemala"    , "0-3;40-69;700-999");
        
$groups[99923] = array("El Salvador"    , "0-1;20-79;800-999");
        
$groups[99924] = array("Nicaragua"    , "0-2;30-79;800-999");
        
$groups[99925] = array("Paraguay"    , "0-3;40-79;800-999");
        
$groups[99926] = array("Honduras"    , "0-0;10-59;600-999");
        
$groups[99927] = array("Albania"    , "0-2;30-59;600-999");
        
$groups[99928] = array("Georgia"    , "0-0;10-79;800-999");
        
$groups[99929] = array("Mongolia"    , "0-4;50-79;800-999");
        
$groups[99930] = array("Armenia"    , "0-4;50-79;800-999");
        
$groups[99931] = array("Seychelles"    , "0-4;50-79;800-999");
        
$groups[99932] = array("Malta"    , "0-0;10-59;600-699;7-7;80-99");
        
$groups[99933] = array("Nepal"    , "0-2;30-59;600-999");
        
$groups[99934] = array("Dominican Republic"    , "0-1;20-79;800-999");
        
$groups[99935] = array("Haiti"    , "0-2;7-8;30-59;600-699;90-99");
        
$groups[99936] = array("Bhutan"    , "0-0;10-59;600-999");
        
$groups[99937] = array("Macau"    , "0-1;20-59;600-999");
        
$groups[99938] = array("Srpska"    , "0-1;20-59;600-899;90-99");
        
$groups[99939] = array("Guatemala"    , "0-5;60-89;900-999");
        
$groups[99940] = array("Georgia"    , "0-0;10-69;700-999");
        
$groups[99941] = array("Armenia"    , "0-2;30-79;800-999");
        
$groups[99942] = array("Sudan"    , "0-4;50-79;800-999");
        
$groups[99943] = array("Alsbania"    , "0-2;30-59;600-999");
        
$groups[99944] = array("Ethiopia"    , "0-4;50-79;800-999");
        
$groups[99945] = array("Namibia"    , "0-5;60-89;900-999");
        
$groups[99946] = array("Nepal"    , "0-2;30-59;600-999");
        
$groups[99947] = array("Tajikistan"    , "0-2;30-69;700-999");
        
$groups[99948] = array("Eritrea"    , "0-4;50-79;800-999");
        
$groups[99949] = array("Mauritius"    , "0-1;20-80;900-999");
        
$groups[99950] = array("Cambodia"    , "0-4;50-79;800-999");
        
$groups[99951] = array("Congo - no ranges fixed yet"    , "");
        
$groups[99952] = array("Mali"    , "0-4;50-79;800-999");
        
$groups[99953] = array("Paraguay"    , "0-2;30-79;800-999");
        
$groups[99954] = array("Bolivia"    , "0-2;30-69;700-999");
        
$groups[99955] = array("Srpska"    , "0-1;20-59;600-899;90-99");
        return 
$groups;
    }
    
// }}}

    // {{{ __getVersion()
    /**
     * Get the Version of am ISBN Number
     *
     * @param string    $isbn    ISBN Number ofwhich the version to get
     *
     * @return mixed    false for no, or fully identifyable ISBN
     *                                Version Constant
     *
     * @access private
     */
    
private static function __getVersion($isbn) {
        
$ver = self::_ISBNVersionGuess($isbn);
        
$r = self::_ISBNVersionIsValid($ver);
        return 
$r;
    }
    
// }}}

    // {{{ _checkdigitISBN10()
     /**
     * Calculate checkdigit of an ISBN-10 string (ISBN-Body)
     * as documented on pp.4-5 2001 handbook.
     *
     * @param string    $isbnbody    ISBN-Body
     *
     * @return boolean    False if failed
     * @return string    Checkdigit [0-9,X]
     *
     * @access private
     */
    
private static function _checkdigitISBN10($isbnbody) {
        
/* The check digit is the last digit of an ISBN. It is calculated
         * on a modulus 11 with weights 10-2, using X in lieu of 10 where
         * ten would occur as a check digit.
         * This means that each of the first nine digits of the ISBN –
         * excluding the check digit itself – is multiplied by a number
         * ranging from 10 to 2 and that the resulting sum of the products,
         * plus the check digit, must be divisible by 11 without a
         * remainder. (pp.4-5 2001 Handbook)
         */
        
if (strlen($isbnbody) != 9) {
            return 
false;
        }
        
$sum = 0;
        for(
$i = 0; $i < 10; $i++) {
            
$v = intval(substr($isbnbody, $i, 1));
            
$sum += $v * (10 - $i);
        }
        
$remainder = $sum % 11;
        
$checkdigit = 11 - $remainder;
        if (
$remainder == 0) {
            
$checkdigit = 0;
        }
        if (
$checkdigit == 10) {
            
$checkdigit = "X";
        }
        return (string) 
$checkdigit;
    }
    
// }}}

    // {{{ _checkdigitISBN13()
     /**
     * Calculate checkdigit of an ISBN-13 string (Prefix + ISBN-Body)
     * as documented on pp.10-11 2005 handbook.
     *
     * @param string    $isbnbody    ISBN-Body
     * @param string    $prefix        EAN-Prefix (Default 978 for ISBN13-978)
     *
     * @return boolean    False if failed
     * @return string    Checkdigit [0-9]
     *
     * @access private
     */
    
private static function _checkdigitISBN13($isbnbody, $prefix = "978") {
        
$prefixandisbnbody = $prefix . $isbnbody;
        
$t = $prefixandisbnbody;
        
$l = strlen($t);
        if (
$l != 12) {
            return 
false;
        }
        
/* Step 1: Determine the sum of the weighted products for the first 12
        *  digits of the ISBN (see p.10 2005 handbook)
        */
        
$ii = 1;
        
$sum = 0;
        for(
$i = 0; $i < 13; $i++) {
            
$ii = 1 - $ii;
            
$sum += intval(substr($t, $i, 1)) * ($ii * 2 + 1);
        }
        
/* Step 2: Divide the sum of the weighted products of the first 12
         * digits of the ISBN calculated in step 1 by 10, determining the
         * remainder. (see p.11 2005 handbook)
         */
        
$remainder = $sum % 10;

        
/* Step 3: Subtract the remainder calculated in step 2 from 10. The
         * resulting difference is the value of the check digit with one
         * exception. If the remainder from step 2 is 0, the check
         * digit is 0. (ebd.)
         */
        
$checkdigit = 10 - $remainder;
        if (
$remainder == 0) {
            
$checkdigit = 0;
        }
        
/* return string value */
        
if (is_int($checkdigit)) {
            
$checkdigit = (string) $checkdigit;
        }
        if (
is_string($checkdigit) == false) {
            return 
false;
        }
        return 
$checkdigit;
    }
    
// }}}

    // {{{ _isISBNValid()
    /**
     * Validate an ISBN value
     *
     * @param string    $isbn     Number to validate
     *
     * @return boolean    False if ISBN number is not valid
     * @return mixed    Returns the Version to signal validity
     *
     * @access private
     */
    
private static function _isISBNValid($isbn, $ver = ISBN_DEFAULT_INPUTVERSION) {
        
/* version handling */
        
$r = self::_ISBNVersionIs($ver);
        if (
$r === false) {
            return 
false;
        }
        if (
$ver === ISBN_VERSION_UNKNOWN) {
            
$ver = self::_ISBNVersionGuess($isbn);
        }
        if (
self::_ISBNVersionIsValid($ver) === false) {
            return 
false;
        }
        
/* since a version is available now, normalise the ISBN input */
        
$isbnn = self::_normaliseISBN($isbn);
        if (
$isbnn === false) {
            return 
false;
        }
        
/* normalzied ISBN and Version available, it's ok now
         * to perform indepth checks per version */
        
switch ($ver) {
            case 
ISBN_VERSION_ISBN_10:

                
/* check syntax against checkdigit */
                
$isbnbody = self::__extractISBNBody($isbnn);
                
$check = self::__extractCheckdigit($isbnn);
                if (
$check === false) {
                    return 
false;
                }
                
$checkdigit = self::_checkdigitISBN10($isbnbody);
                if (
$checkdigit === false) {
                    return 
false;
                }
                if (
$checkdigit !== $check) {
                    return 
false;
                }

                
/* check registrationgroup validity */
                
$registrationgroup = false;
                
$subbody = false;
                
$r = self::__ISBNBodyParts($isbnbody, $registrationgroup, $subbody);
                if (
$r == false) {
                    return 
false;
                }

                
/* check for undefined registrationgroup */
                
if (strlen($registrationgroup) == 0) {
                    return 
false;
                }

                
/* check registrant validity */
                
$groupid = intval($registrationgroup);
                
$registrant = false;
                
$publication = false;
                
$r = self::__ISBNSubbodyParts($subbody, $groupid, $registrant, $publication);
                if (
$r == false) {
                    return 
false;
                }
                return 
true;

            case 
ISBN_VERSION_ISBN_13:
            case 
ISBN_VERSION_ISBN_13_978:

                
/* validate EAN Prefix */
                
$ean = self::__extractEANPrefix($isbnn);
                if (
$ean !== "978") {
                    return 
false;
                }

                
/* check syntax against checkdigit */
                
$isbnbody = self::__extractISBNBody($isbnn);
                
$check = self::__extractCheckdigit($isbnn);
                if (
$check === false) {
                    return 
false;
                }
                
$checkdigit = self::_checkdigitISBN13($isbnbody);
                if (
$checkdigit === false) {
                    return 
false;
                }
                if (
$check !== $checkdigit) {
                    return 
false;
                }

                
/* validate group */
                
$isbnbody = self::__extractISBNBody($isbnn);
                if (
$isbnbody === false) {
                    return 
false;
                }

                
$registrationgroup = false;
                
$subbody = false;
                
$r = self::__ISBNBodyParts($isbnbody, $registrationgroup, $subbody);
                if (
$r === false) {
                    return 
false;
                }

                
/* check for undefined registrationgroup */
                
if (strlen($registrationgroup) == 0) {
                    return 
false;
                }

                
/* validate publisher */
                
$registrant = false;
                
$publication = false;
                
$r = self::__ISBNSubbodyParts($subbody, $registrationgroup, $registrant, $publication);
                if (
$r === false) {
                    return 
false;
                }
                return 
$ver;

            case 
ISBN_VERSION_ISBN_13_979:
                
/* not yet standarized */
                
return false;
        }
        return 
false;
    }
    
// }}}

    // {{{ _ISBNVersionGuess()
    /**
     * Guesses the version of an ISBN
     *
     * @param string $isbn    ISBN Number of which the Version to guess
     *
     * @return bool        False/ISBN_VERSION_NONE if failed
     * @return integer    Value of any ISBN_VERSION_* Constant
     *                    excl. ISBN_VERSION_NONE
     * @access private
     */
    
private static function _ISBNVersionGuess($isbn) {
        
$isbn = self::_normaliseISBN($isbn);
        if (
$isbn === false) {
            return 
ISBN_VERSION_NONE;
        }
        if ( 
strlen($isbn) == 10) {
            return 
ISBN_VERSION_ISBN_10;
        } else {
            return 
ISBN_VERSION_ISBN_13;
        }
    }
    
// }}}

    // {{{ _ISBNVersionIs()
    /**
     * Validate an ISBN Version value
     *
     * @param mixed $ver    version to be checked being a valid ISBN Version
     *
     * @return bool    true if value is valid, false if not
     *
     * @access private
     */
    
private static function _ISBNVersionIs($ver) {
        if (
is_bool($ver) === false && is_integer($ver) === false) {
            return 
false;
        }
        switch (
$ver) {
            case 
ISBN_VERSION_NONE:
            case 
ISBN_VERSION_UNKNOWN:
            case 
ISBN_VERSION_ISBN_10:
            case 
ISBN_VERSION_ISBN_13:
            case 
ISBN_VERSION_ISBN_13_978:
            case 
ISBN_VERSION_ISBN_13_979:
                return 
true;
            default:
                return 
false;
        }
    }
    
// }}}

    // {{{ _ISBNVersionIsValid()
    /**
     * Validate an ISBN value being a valid (identifyable -10 / -13) value
     *
     * @param mixed $ver    value to be checked being a valid ISBN Version
     *
     * @return bool    true if value is valid, false if not
     *
     * @access private
     */
    
private static function _ISBNVersionIsValid($ver) {
        
$r = self::_ISBNVersionIs($ver);
        if (
$r === false) {
            return 
false;
        }
        switch (
$ver) {
            case 
ISBN_VERSION_ISBN_10:
            case 
ISBN_VERSION_ISBN_13_978:
                return 
true;
            default:
                return 
false;
        }
    }
    
// }}}

    // {{{ _normaliseISBN()
    /**
     * downformat "any" ISBN Number to the very basics
     * an isbn number is a 10 or 13 digit. with the
     * 10 digit string, the last digit can be 0-9 and
     * X as well, all other are 0-9 only
     * additionally this fucntion can be used to validate
     * the isbn against correct length and chars
     *
     * @param string    $number     ISBN String to normalise
     *
     * @return boolean    false if this function was not able
     *                    to normalise the input
     * @return string    normalised ISBN Number
     *
     * @access private
     */
    
private static function _normaliseISBN($isbn) {
        
/* validate input */
        
$r = settype($isbn, 'string');
        if (
$r === false) {
            return 
false;
        }

        
/* normalize (trim & case)*/
        
$isbn = trim($isbn);
        
$isbn = strtoupper($isbn);

        
/* remove lang specific prefix (if any) */
        
$isbn = self::_normaliseISBN_removeLangSpecific($isbn);

        
/* remove ISBN-10: or ISBN-13: prefix (if any) */
        
if (strlen($isbn > 8)) {
            
$prefix = substr($isbn, 0, 8);
            if (
$prefix == "ISBN-10:" || $prefix == "ISBN-13:") {
                
$isbn = substr($isbn, 8);
                
$isbn = ltrim($isbn);
            }
        }

        
/* remove lang specific prefix again (if any) */
        
$isbn = self::_normaliseISBN_removeLangSpecific($isbn);

        
/* remove "ISBN" prefix (if any)*/
        
if (substr($isbn, 0, 4) == 'ISBN') {
            
$isbn = substr($isbn, 4);
        }

        
/* remove cosmetic chars and different type of spaces */
        
$isbn = str_replace(array("-", " ", "\t", "\n"), "", $isbn);

        
/* take the length to check and differ between versions
         * sothat a syntaxcheck can be made */
        
$l = strlen($isbn);
        if (
$l != 10 && $l != 13) {
            return 
false;
        } elseif (
$l == 10) {
            if (!
preg_match("/^[0-9]{9}[0-9X]$/", $isbn)) {
                return 
false;
            }
        } elseif (
$l == 13) {
            if (!
ctype_digit($isbn)) {
                return 
false;
            }
        }
        return 
$isbn;
    }
    
// }}}

    // {{{ _normaliseISBN_removeLangSpecific()
    /**
     * helper function for _normaliseISBN to
     * remove lang sepcific ISBN prefix
     *
     * @param string    $isbn     ISBN String to check (partially normalised)
     *
     * @return string    input value passed through helper
     *
     * @access private
     */
    
private static function _normaliseISBN_removeLangSpecific($isbn) {
        
$lang = strtoupper(ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX);
        
$l = strlen($lang);
        if (
$l > 0 ) {
            if (
substr($isbn, 0, $l) == $lang) {
                
$isbn = substr($isbn, $l);
            }
        }
        return 
$isbn;
    }
    
// }}}

    // {{{ convert()
    /**
     * converts an ISBN number from one version to another
     *
     * @param string     $isbnin     ISBN to convert, must be a valid ISBN Number
     * @param integer     $verfrom     version value of the input ISBN
     * @param integer    $verto        version value to convert to
     *
     * @return bool     false if conversion failed
     * @return string     converted ISBN Number
     */
    
public static function convert($isbnin, $verfrom = ISBN_VERSION_ISBN_10, $verto = ISBN_VERSION_ISBN_13) {
        
/* validate input */
        
if (!self::_ISBNVersionIsValid($verfrom)) {
            return 
false;
        }
        if (!
self::_ISBNVersionIsValid($verto)) {
            return 
false;
        }
        
$r = self::validate($isbnin, $verfrom);
        if (
$r === false) {
            return 
false;
        }
        
/* normalize input */
        
$isbnn = self::_normaliseISBN($isbnin);
        
/* input is ok now, let's convert */
        
switch(true) {
            case 
$verfrom == ISBN_VERSION_ISBN_10 && $verto == ISBN_VERSION_ISBN_13:
                
/* convert 10 to 13 */
                
$isbnbody = self::__extractISBNBody($isbnn);
                if (
$isbnbody === false) {
                    return 
false;
                }
                
$isbnout = "978" . $isbnbody . self::_checkdigitISBN13($isbnbody);
                return 
$isbnout;
        }
        return 
false;
    }
    
// }}}

    // {{{ getCheckdigit()
    /**
     * Get the Checkdigit Part of ISBN Number
     *
     * @return mixed    (bool) False if failed, (string) Checkdigit
     */
    
public function getCheckdigit() {
        
$ver = $this->getVersion();
        
$check = false;
        switch (
$ver) {
            case 
ISBN_VERSION_ISBN_10:
                
$check = self::_checkdigitISBN10($this->getISBNBody());
                break;
            case 
ISBN_VERSION_ISBN_13:
                
$check = self::_checkdigitISBN13($this->getISBNBody());
                break;
        }
        return 
$check;
    }
    
// }}}

    // {{{ getEAN()
    /**
     * Get the EAN Prefix of ISBN Number
     *
     * @return mixed    (bool) False if failed, (string) EAN Prefix
     */
    
public function getEAN() {
        
$ver = $this->getVersion();
        if (
$ver === false ) {
            return 
false;
        }
        if (
$ver == ISBN_VERSION_ISBN_13_978) {
            return 
"978";
        }
        if (
$ver == ISBN_VERSION_ISBN_13_979) {
            return 
"979";
        }
        return 
"";
    }
    
// }}}

    // {{{ getGroup()
    /**
     * Get the Registrationgroup Part of the ISBN Number
     *
     * @return mixed    (bool) False if failed, (string) Group
     */
    
public function getGroup() {
        return 
$this->isbn_group;
    }
    
// }}}

    // {{{ setGroup()
    /**
     * Setter for the Registrationgroup Part of the ISBN Number
     *
     * @access private
     */
    
private function setGroup($group) {
        if (
is_string($group) == false) {
            throw new 
Exception("Wrong Vartype", 13);
        }
        
$l = strlen($group);
        if (
$l < 1 || $l > 5) {
            throw new 
Exception("Wrong Group", 11);
        }
        
$testbody = substr($group . "000000000", 0, 9);
        
$testgroup = self::__extractGroup($testbody);
        if (
$testgroup === false ) {
            throw new 
Exception("Invalid Group", 12);
        }
        if (
$testgroup != $group) {
            throw new 
Exception("Invalid Group", 12);
        }
        
$this->isbn_group = $group;
    }

    
// {{{ getISBN()
    /**
     * Get whole ISBN Number
     *
     * @return string ISBN Number (unformatted); empty string if this is not a valid ISBN
     */
    
public function getISBN() {
        
$ver = $this->getVersion();
        if (
$ver === false ) {
            return 
"";
        }

        
$isbn = "";

        
$r = self::_ISBNVersionIsValid($ver);
        if (
$r === false ) {
            return 
$isbn;
        }

        
$isbn .= $this->getEAN();
        
$isbn .= $this->getISBNBody();
        
$isbn .= $this->getCheckdigit();

        return 
$isbn;
    }
    
// }}}

    // {{{ setISBN()
    /**
     * Setter for ISBN
     *
     * @param strign ISBN Number
     *             this is a valid ISBN Number or it is an Empty string
     *             which will reset the class
     *
     * @access public
     */
    
public function setISBN($isbn) {
        if (
$isbn == "") {
            
$this->ver = ISBN_VERSION_NONE;
            
$this->isbn_group = "";
            
$this->isbn_publisher = "";
            
$this->isbn_title = "";
        } else {
            
$isbnn = self::_normaliseISBN($isbn);
            
$ver = self::__getVersion($isbnn);
            if (
$ver === false) {
                throw new 
Exception("Invalid ISBN", 1);
            }
            if (
$ver != $this->ver) {
                throw new 
Exception("ISBN Version of passed ISBN is not the same like this class-ISBN ISBN Version.", 2);
            }
            
$body = self::__extractISBNBody($isbnn);
            if (
$body === false) {
                throw new 
Exception("Invalid ISBN (could not extract body)", 15);
            }
            try {
                
$this->setISBNBody($body);
            } catch (
Exception $e) {
                throw new 
Exception("Invalid ISBN (invalid body)", 16);
            }
        }
    }
    
// }}}

    // {{{ getISBNBody()
    /**
     * GetISBNBody()
     *
     * @return string ISBN Body (not an offical term)
     */
    
private function getISBNBody() {
        
$body = "";
        
$body .= $this->getGroup();
        
$body .= $this->getISBNSubbody();
        return 
$body;
    }
    
// }}}

    // {{{ setISBNBody()
    /**
     * setISBNBody()
     *
     * Setter for ISBNBody
     *
     * @param string $body ISBNBody
     */
     
private function setISBNBody($body) {
         
/* validate parameter */
         
if (is_string($body) == false) {
             throw new 
Exception("Wrong Vartype", 13);
         }
        if (
strlen($body) != 9) {
            throw new 
Exception("Not a Body", 7);
        }
        if (
ctype_digit($body) !== true) {
            throw new 
Exception("Not a Body", 7);
        }

        
/* validate body by extracting and validating parts */
        
$group = false;
        
$subbody = false;
        
$r = self::__ISBNBodyParts($body, $group, $subbody);
        if (
$r == false) {
            throw new 
Exception("Invalid Body", 8);
        }

        try {
            
$this->setGroup($group);
        } catch (
Exception $e) {
            throw new 
Exception("Invalid Body (The Group Part is invalid)", 9);
        }

        try {
            
$this->setISBNSubbody($subbody);
        } catch (
Exception $e) {
            throw new 
Exception("Invalid Body (The Subbody Part is invalid)", 10);
        }
     }
    
// }}}

    // {{{ getISBNSubbody()
    /**
     * Get ISBNSubbody ()
     *
     * @return ISBN Subbody
     */
    
private function getISBNSubbody() {
        
$subbody = "";
        
$subbody .= $this->getPublisher();
        
$subbody .= $this->getTitle();
        return 
$subbody;
    }
    
// }}}

    // {{{ setISBNSubbody()
    /**
     * setISBNSubbody
     *
     * Setter for the ISBN Subbody
     *
     * @param string $subbody ISBN Subbody
     */
    
private function setISBNSubbody($subbody) {
        
/* validate parameter */
        
if (is_string($subbody) == false) {
            throw new 
Exception("Wrong Vartype", 13);
        }
        
$l = strlen($subbody);
        if ( 
$l < 4 || $l > 8) {
            throw new 
Exception("Not a Subbody", 14);
        }
        
/* validate by setting apart */
        
$registrant = false;
        
$publication = false;
        
$groupid = intval($this->isbn_group);
        
$r = self::__ISBNSubbodyParts($subbody, $groupid, $registrant, $publication);
        if (
$r === false) {
            throw new 
Exception("Invalid Subbody", 15);
        }
        
/* edit+ setter/getter for Registrant/Publisher and Title/Publication */
        
$this->isbn_publisher = $registrant;
        
$this->isbn_title = $publication;
    }

    
// {{{ getPublisher()
    /**
     * Get the Publication Part of the ISBN Number
     *
     * @return mixed    (bool) False if failed, (string) Publisher
     */
    
public function getPublisher() {
        return 
$this->isbn_publisher;
    }
    
// }}}

    // {{{ getTitle()
    /**
     * Get the Title Part of the ISBN Number
     *
     * @return mixed    (bool) False if failed, (string) Title
     */
    
public function getTitle() {
        return 
$this->isbn_title;
    }
    
// }}}


    // {{{ isValid()
    /**
     * Returns this ISBN validity
     *
     * @return
     * @return boolean
     */
    
public function isValid() {
        
$isbn = $this->getISBN();
        
$r = self::validate($this->getISBN(), $this->getVersion());
        return (bool) 
$r;
    }

    
// {{{ validate()
    /**
     * Validates an ISBN
     *
     * @return
     * @return boolean    False if failed
     * @return integer    Version Value
     */
    
public static function validate($isbn, $ver = ISBN_DEFAULT_INPUTVERSION) {
        
$r = self::_ISBNVersionIs($ver);
        if (
$r === false) {
            return 
false;
        }
        if (
$ver === ISBN_VERSION_UNKNOWN) {
            
$ver = self::_ISBNVersionGuess($isbn);
        }
        if (
self::_ISBNVersionIsValid($ver) === false) {
            return 
false;
        }
        
$r = self::_isISBNValid($isbn, $ver);
        if (
$r === false) {
            return 
false;
        }
        return 
$ver;
    }
    
// }}}

    // {{{ getVersion()
    /**
     * Returns version of this objects ISBN
     *
     * @return boolean    Version Value (ISBN_VERSION_NONE)
     * @return integer    Version Value
     */
    
public function getVersion() {
        return 
$this->ver;
    }


    
// {{{ version()
    /**
     * Returns guessed ISBN version of passed string
     *
     * Note: This is not Validation. To get the validated
     * version of an ISBN Number use self::validate();
     *
     * @see    validate();
     *
     * @param string    $isbn    ISBN Number to guess Version of
     *
     * @return boolean    False if failed
     * @return integer    Version Value
     */
    
public static function guessVersion($isbn) {
        
/* if the optional parameter is missing, use
         * this objects own isbn number (not static then)
         */
        
$r = self::_ISBNVersionGuess($isbn);
        return 
$r;
    }
    
// }}}

}
?>