<?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;
}
// }}}
}
?>