Source for file ISBN.php
Documentation is available at ISBN.php
* Handle, Convert and Validate ISBN Numbers
* LICENSE: LGPL (In cases LGPL is not appropriate, it is licensed under GPL )
* Package to handle, convert and validate ISBN numbers. It includes:
* - ISBN specifics: EAN/PrefixArrayAccess (integer)
* - ISBN specifics: Group/Registration Group [2001: Group identifier] (integer)
* - ISBN specifics: GroupTitle/Registration Group Title (string)
* - ISBN specifics: Publisher/Registrant [2001: Publisher identifier] (string)
* - ISBN specifics: Title/Publication [2001: Title identifier] (string)
* - ISBN specifics: Checkdigit (string)
* - ISBN specifics: 'ISBNBody' (string)
* - ISBN specifics: 'ISBNSubbody' (string)
* - ISBN Version handling
* - Syntactical Validation plus Validation based on real ISBN Data
* - ISBN-10 (ISO 2108) checksum calculation
* - Validation (ISBN-10 and ISBN-13-978)
* - Conversion to ISBN-13-978
* - ISBN-13-978 (2005 Handbook, ISO pending; ISBN-13)
* - ISBN-13 checksum calculation (EAN)
* Based on standards published by international ISBN Agency
* http://www.isbn-international.org/
* @author Tom Klingenberg <tkli-php@lastflood.net>
* @copyright 2006-2007 Tom Klingenberg
* @license LGPL http://www.gnu.org/licenses/lgpl.txt
* @version v 0.1.4 CVS: <cvs_id>
* @link http://isbn.lastflood.com online docs
* @todo License for .js file
* 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 ISBN Version for class input / usage
define('ISBN_DEFAULT_INPUTVERSION', ISBN_VERSION_UNKNOWN);
* Default ISBN Seperator string
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
* This can be defined as a default value wihtin this constant.
define('ISBN_DEFAULT_PRINT_LANG_SPECIFIC_PREFIX', '');
require_once 'PEAR/Exception.php';
* @author Tom Klingenberg <tkli-php@lastflood.net>
* @copyright 2006-2007 Tom Klingenberg
* @license LGPL http://www.gnu.org/licenses/lgpl.txt
* @link http://isbn.lastflood.com/
* @since Class available since Release 0.1.3
* Class to Handle, Convert and Validate ISBN Numbers
* @author Tom Klingenberg <tkli-php@lastflood.net>
* @copyright 2006-2007 Tom Klingenberg
* @license LGPL http://www.gnu.org/licenses/lgpl.txt
* @link http://isbn.lastflood.com/
* @since Class available since Release 0.0.0
* @var string ISBN Registration Group
private $isbn_group = '';
* @var string ISBN Publisher
private $isbn_publisher = '';
private $isbn_title = '';
* @var mixed ISBN number version
private $ver = ISBN_VERSION_NONE;
* @param array $isbn String of ISBN Value to use
* @param mixed $ver Optional Version Constant
* @throws ISBN_Exception in case it fails
function __construct($isbn = '', $ver = ISBN_DEFAULT_INPUTVERSION)
/* validate & handle optional isbn parameter */
/* validate version parameter */
if (self::_isbnVersionIs($ver) == false) {
'ISBN Version parameter is not an ISBN Version'
/* ISBN has been passed, check the version now:
* if it is unknown, try to dertine it, if this fails
$verguess = self::_isbnVersionGuess($isbn);
if (self::_isbnVersionIsValid($verguess)) {
/* throw new ISBN_Exception(
*'ISBN Version couldn\'t determined.');
/* handle a complete invalid ISBN of which a version could
/* the isbn is invalid and not set, sothat this
* ISBN object will be set to a blank value. */
// {{{ _extractCheckdigit()
* extract Checkdigit of an ISBN-Number
* @param string $isbnn normalized ISBN string
* @return string|falseISBN-Body or false if failed
private static function _extractCheckdigit($isbnn)
$checkdigit = substr($isbnn, - 1);
return (string) $checkdigit;
// {{{ _extractEANPrefix()
* extracts EAN-Prefix of a normalized isbn string
* @param string $isbnn normalized isbn string
* @return string|falsePrefix or false if failed
private static function _extractEANPrefix($isbnn)
$prefix = substr($isbnn, 0, 3);
* extract Registration Group of an ISBN-Body
* @param string $isbnbody ISBN-Body
* @return integer|false Registration Group or false if failed
private static function _extractGroup($isbnbody)
$r = self::_isbnBodyParts($isbnbody, $group, $subbody);
// {{{ _extractISBNBody()
* extract ISBN-Body of an ISBN-Number
* @param string $isbnn normalized ISBN string
* @return string|falseISBN-Body or false if failed
private static function _extractISBNBody($isbnn)
$isbnn = (string) $isbnn;
$body = substr($isbnn, 0, - 1);
$body = substr($isbnn, 3, - 1);
* 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
private static function _isbnBodyParts($isbnbody,
/* validate input (should not be needed, @access private) */
/* extract registraion group
* boundaries see p.13 2005 handbook
$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 = substr($isbnbody, 0, 5);
$segmentvalue = intval($segment);
/* test segment value against boundaries */
foreach ($boundaries as $boundary) {
if ($segmentvalue >= $boundary[0] && $segmentvalue <= $boundary[1]) {
/* $r is 0 when the boundary is not defined */
$registrationgroup = substr($isbnbody, 0, $r);
$isbnsubbody = substr($isbnbody, $r);
// {{{ _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
private static function _isbnSubbodyParts($isbnsubbody,
/* validate input (should not be needed, @access private) */
$r = settype($isbnsubbody, 'string');
if ($groupid < 0 || $groupid > 99999) {
/* extract registrant based on group and registrant range
* parse this specific group format:
* 'English speaking area',
* '00-09;10-19;200-699;7000-8499;85000-89999;' .
* '900000-949999;9500000-9999999'
$group = self::_getISBN10Group($groupid);
$len = self::_getRegistrantLength($isbnsubbody, $group[1]);
$registrant = substr($isbnsubbody, 0, $len);
$publication = substr($isbnsubbody, $len);
// {{{ _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 ISBN-Subbody
* @param string $grouprange Grouprange in the Format '#a1-#z1;#a2-z2[...]'
* @return boolean|int False if failed or Length (in chars) of Registrant
private static function _getRegistrantLength($isbnsubbody, $grouprange)
$r = settype($grouprange, 'string');
if (strlen($grouprange) < 3) {
$ranges = explode(';', $grouprange);
foreach ($ranges as $range) {
if (count($fromto) !== 2) {
* 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
if ($l != strlen($fromto[1])) {
/* check that from/to values are in order */
if (strcmp($fromto[0], $fromto[1]) >= 0) {
/* compare and fire if matched */
if (strcmp($fromto[0], $compare) < 1 &&
strcmp($fromto[1], $compare) > - 1) {
* 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)
$groups = self::_getISBN10Groups();
if (isset ($groups[$id]) === false) {
// {{{ _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()
/* ISBN Code-Generator - Ranges */
$groups[ 0] = array('English speaking area',
'00-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;'.
$groups[ 2] = array('French speaking area',
'00-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;'.
$groups[ 4] = array('Japan',
'00-19;200-699;7000-8499;85000-89999;'.
'900000-949999;9500000-9999999');
$groups[ 5] = array('Russian Federation',
'00-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;'.
$groups[ 80] = array('Czech Republic; Slovakia',
'00-19;200-699;7000-8499;85000-89999;'.
$groups[ 81] = array('India',
'00-19;200-699;7000-8499;85000-89999;'.
$groups[ 82] = array('Norway',
'00-19;200-699;7000-8999;90000-98999;'.
$groups[ 83] = array('Poland',
'00-19;200-599;60000-69999;7000-8499;'.
'85000-89999;900000-999999');
$groups[ 84] = array('Spain',
'00-19;200-699;7000-8499;85000-89999;9000-9199;'.
'920000-923999;92400-92999;930000-949999;'.
'95000-96999;9700-9999');
$groups[ 85] = array('Brazil',
'00-19;200-599;60000-69999;7000-8499;'.
'85000-89999;900000-979999;98000-99999');
$groups[ 86] = array('Serbia and Montenegro',
'00-29;300-599;6000-7999;80000-89999;'.
$groups[ 87] = array('Denmark',
'00-29;400-649;7000-7999;85000-94999;'.
$groups[ 88] = array('Italian speaking area',
'00-19;200-599;6000-8499;85000-89999;'.
'900000-949999;95000-99999');
$groups[ 89] = array('Korea',
'00-24;250-549;5500-8499;85000-94999;'.
$groups[ 90] = array('Netherlands, Belgium (Flemish)',
'00-19;200-499;5000-6999;70000-79999;'.
'800000-849999;8500-8999;900000-909999;'.
$groups[ 91] = array('Sweden',
'0-1;20-49;500-649;7000-7999;85000-94999;'.
$groups[ 92] = array('International Publishers (Unesco, EU), Europe...',
'0-5;60-79;800-899;9000-9499;95000-98999;'.
$groups[ 93] = array('India - no ranges fixed yet','');
$groups[ 950] = array('Argentina',
'00-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;'.
$groups[ 953] = array('Croatia',
'0-0;10-14;150-549;55000-59999;6000-9499;'.
$groups[ 954] = array('Bulgaria',
'00-29;300-799;8000-8999;90000-92999;9300-9999');
$groups[ 955] = array('Sri Lanka',
'0-0;1000-1999;20-54;550-799;8000-9499;'.
$groups[ 956] = array('Chile',
'00-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;'.
$groups[ 958] = array('Colombia',
'00-59;600-799;8000-9499;95000-99999');
$groups[ 959] = array('Cuba',
'00-19;200-699;7000-8499');
$groups[ 960] = array('Greece',
'00-19;200-659;6600-6899;690-699;7000-8499;'.
$groups[ 961] = array('Slovenia',
'00-19;200-599;6000-8999;90000-94999');
$groups[ 962] = array('Hong Kong',
'00-19;200-699;7000-8499;85000-86999;8700-8999;'.
$groups[ 963] = array('Hungary',
'00-19;200-699;7000-8499;85000-89999;9000-9999');
$groups[ 964] = array('Iran',
'00-14;150-249;2500-2999;300-549;5500-8999;'.
'90000-96999;970-989;9900-9999');
$groups[ 965] = array('Israel',
'00-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-39;400-499;5000-7999;800-899;9000-9999');
$groups[ 969] = array('Pakistan',
'0-1;20-39;400-799;8000-9999');
$groups[ 970] = array('Mexico',
'01-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-19;200-699;7000-8499;85000-89999;'.
'90000-94999;9500-9999');
$groups[ 975] = array('Turkey',
'00000-00999;01-24;250-599;6000-9199;'.
$groups[ 976] = array('Caribbean Community',
'0-3;40-59;600-799;8000-9499;95000-99999');
$groups[ 977] = array('Egypr',
'00-19;200-499;5000-6999;700-999');
$groups[ 978] = array('Nigeria',
'000-199;2000-2999;30000-79999;8000-8999;'.
$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-19;200-599;6000-9999');
$groups[ 981] = array('Singapore',
'00-19;200-299;3000-9999');
$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-39;400-799;8000-8999;90000-99999');
$groups[ 985] = array('Belarus',
'00-39;400-599;6000-8999;90000-99999');
$groups[ 986] = array('Taiwan, China',
'00-11;120-559;5600-7999;80000-99999');
$groups[ 987] = array('Argentina',
'00-09;1000-1999;20000-29999;30-49;500-899;'.
'9000-9499;95000-99999');
$groups[ 988] = array('Hongkong',
'00-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-00;010-079;08-39;400-569;57-57;580-849;'.
$groups[ 9946] = array('Korea, P.D.R.',
'0-1;20-39;400-899;9000-9999');
$groups[ 9947] = array('Algeria',
$groups[ 9948] = array('United Arab Emirates',
'00-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-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-39;400-929;9300-9999');
$groups[ 9956] = array('Cameroon',
'0-0;10-39;400-899;9000-9999');
$groups[ 9957] = array('Jordan',
'00-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-59;600-899;9000-9999');
$groups[ 9961] = array('Algeria',
'0-2;30-69;700-949;9500-9999');
$groups[ 9962] = array('Panama',
'00-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',
$groups[ 9965] = array('Kazakhstan',
'00-39;400-899;9000-9999');
$groups[ 9966] = array('Kenya',
'00-69;7000-7499;750-959;9600-9999');
$groups[ 9967] = array('Kyrgyzstan',
'00-39;400-899;9000-9999');
$groups[ 9968] = array('Costa Rica',
'00-49;500-939;9400-9999');
$groups[ 9970] = array('Uganda',
'00-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;'.
$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-89;900-989;9900-9999');
$groups[ 9978] = array('Ecuador',
'00-29;300-399;40-94;950-989;9900-9999');
$groups[ 9979] = array('Iceland',
'0-4;50-64;650-659;66-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;'.
$groups[ 9982] = array('Zambia',
'00-79;800-889;9900-9999');
$groups[ 9983] = array('Gambia',
'80-94;950-989;9900-9999');
$groups[ 9984] = array('Latvia',
'00-49;500-899;9000-9999');
$groups[ 9985] = array('Estonia',
'0-4;50-79;800-899;9000-9999');
$groups[ 9986] = array('Lithuania',
'00-39;400-899;9000-9399;940-969;97-99');
$groups[ 9987] = array('Tanzania',
'00-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',
$groups[99902] = array('Gabon - no ranges fixed yet','');
$groups[99903] = array('Mauritius',
$groups[99904] = array('Netherlands Antilles; Aruba, Neth. Ant',
$groups[99905] = array('Bolivia',
$groups[99906] = array('Kuwait',
'0-2;30-59;600-699;70-89;9-9');
$groups[99908] = array('Malawi',
$groups[99909] = array('Malta',
$groups[99910] = array('Sierra Leone',
$groups[99911] = array('Lesotho',
$groups[99912] = array('Botswana',
'0-3;400-599;60-89;900-999');
$groups[99913] = array('Andorra',
$groups[99914] = array('Suriname',
$groups[99915] = array('Maldives',
$groups[99916] = array('Namibia',
$groups[99917] = array('Brunei Darussalam',
$groups[99918] = array('Faroe Islands',
$groups[99919] = array('Benin',
$groups[99920] = array('Andorra',
$groups[99921] = array('Qatar',
'0-1;20-69;700-799;8-8;90-99');
$groups[99922] = array('Guatemala',
$groups[99923] = array('El Salvador',
$groups[99924] = array('Nicaragua',
$groups[99925] = array('Paraguay',
$groups[99926] = array('Honduras',
$groups[99927] = array('Albania',
$groups[99928] = array('Georgia',
$groups[99929] = array('Mongolia',
$groups[99930] = array('Armenia',
$groups[99931] = array('Seychelles',
$groups[99932] = array('Malta',
'0-0;10-59;600-699;7-7;80-99');
$groups[99933] = array('Nepal',
$groups[99934] = array('Dominican Republic',
$groups[99935] = array('Haiti',
'0-2;7-8;30-59;600-699;90-99');
$groups[99936] = array('Bhutan',
$groups[99937] = array('Macau',
$groups[99938] = array('Srpska',
'0-1;20-59;600-899;90-99');
$groups[99939] = array('Guatemala',
$groups[99940] = array('Georgia',
$groups[99941] = array('Armenia',
$groups[99942] = array('Sudan',
$groups[99943] = array('Alsbania',
$groups[99944] = array('Ethiopia',
$groups[99945] = array('Namibia',
$groups[99946] = array('Nepal',
$groups[99947] = array('Tajikistan',
$groups[99948] = array('Eritrea',
$groups[99949] = array('Mauritius',
$groups[99950] = array('Cambodia',
$groups[99951] = array('Congo - no ranges fixed yet','');
$groups[99952] = array('Mali',
$groups[99953] = array('Paraguay',
$groups[99954] = array('Bolivia',
$groups[99955] = array('Srpska',
'0-1;20-59;600-899;90-99');
* 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
private static function _getVersion($isbn)
$ver = self::_isbnVersionGuess($isbn);
$r = self::_isbnVersionIsValid($ver);
// {{{ _checkdigitISBN10()
* Calculate checkdigit of an ISBN-10 string (ISBN-Body)
* as documented on pp.4-5 2001 handbook.
* @param string $isbnbody ISBN-Body
* @return string|falseCheckdigit [0-9,X] or false if failed
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)
for ($i = 0; $i < 10; $i++ ) {
$checkdigit = 11 - $remainder;
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 string|falseCheckdigit [0-9] or false if failed
private static function _checkdigitISBN13($isbnbody, $prefix = '978')
$prefixandisbnbody = $prefix . $isbnbody;
/* Step 1: Determine the sum of the weighted products for the first 12
* digits of the ISBN (see p.10 2005 handbook)
for ($i = 0; $i < 13; $i++ ) {
/* 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)
/* 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
$checkdigit = 10 - $remainder;
/* return string value */
$checkdigit = (string) $checkdigit;
* @param string $isbn Number to validate
* @param string $ver Version to validate against
* @return integer|false Returns the Version to signal validity or false if
* ISBN number is not valid
private static function _isIsbnValid($isbn, $ver = ISBN_DEFAULT_INPUTVERSION)
$r = self::_isbnVersionIs($ver);
$ver = self::_isbnVersionGuess($isbn);
if (self::_isbnVersionIsValid($ver) === false) {
/* since a version is available now, normalise the ISBN input */
$isbnn = self::_normaliseISBN($isbn);
/* normalzied ISBN and Version available, it's ok now
* to perform indepth checks per version */
/* check syntax against checkdigit */
$isbnbody = self::_extractISBNBody($isbnn);
$check = self::_extractCheckdigit($isbnn);
$checkdigit = self::_checkdigitISBN10($isbnbody);
if ($checkdigit === false) {
if ($checkdigit !== $check) {
/* check registrationgroup validity */
$registrationgroup = false;
$r = self::_isbnBodyParts($isbnbody, $registrationgroup, $subbody);
/* check for undefined registrationgroup */
if (strlen($registrationgroup) == 0) {
/* check registrant validity */
$groupid = intval($registrationgroup);
$r = self::_isbnSubbodyParts($subbody, $groupid,
$registrant, $publication);
/* validate EAN Prefix */
$ean = self::_extractEANPrefix($isbnn);
/* check syntax against checkdigit */
$isbnbody = self::_extractISBNBody($isbnn);
$check = self::_extractCheckdigit($isbnn);
$checkdigit = self::_checkdigitISBN13($isbnbody);
if ($checkdigit === false) {
if ($check !== $checkdigit) {
$isbnbody = self::_extractISBNBody($isbnn);
if ($isbnbody === false) {
$registrationgroup = false;
$r = self::_isbnBodyParts($isbnbody, $registrationgroup, $subbody);
/* check for undefined registrationgroup */
if (strlen($registrationgroup) == 0) {
$r = self::_isbnSubbodyParts($subbody, $registrationgroup,
$registrant, $publication);
/* not yet standarized */
// {{{ _isbnVersionGuess()
* Guesses the version of an ISBN
* @param string $isbn ISBN Number of which the Version to guess
* @return integer|falseVersion Value or false (ISBN_VERSION_NONE) if failed
private static function _isbnVersionGuess($isbn)
$isbn = self::_normaliseISBN($isbn);
* 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
private static function _isbnVersionIs($ver)
// {{{ _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
private static function _isbnVersionIsValid($ver)
$r = self::_isbnVersionIs($ver);
* 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 $isbn ISBN String to normalise
* @return string|falsenormalised ISBN Number or false if the function was
* not able to normalise the input
private static function _normaliseISBN($isbn)
/* normalize (trim & case)*/
/* remove lang specific prefix (if any) */
$isbn = self::_normaliseISBNremoveLangSpecific($isbn);
/* remove ISBN-10: or ISBN-13: prefix (if any) */
$prefix = substr($isbn, 0, 8);
if ($prefix == 'ISBN-10:' || $prefix == 'ISBN-13:') {
/* remove lang specific prefix again (if any) */
$isbn = self::_normaliseISBNremoveLangSpecific($isbn);
/* remove "ISBN" prefix (if any)*/
if (substr($isbn, 0, 4) == 'ISBN') {
/* 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 */
if ($l != 10 && $l != 13) {
// {{{ _normaliseISBNremoveLangSpecific()
* 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
private static function _normaliseISBNremoveLangSpecific($isbn)
if (substr($isbn, 0, $l) == $lang) {
* 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 string|falseconverted ISBN Number or false if conversion failed
public static function convert($isbnin, $verfrom = ISBN_VERSION_ISBN_10,
$verto = ISBN_VERSION_ISBN_13)
if (!self::_isbnVersionIsValid($verfrom)) {
if (!self::_isbnVersionIsValid($verto)) {
$r = self::validate($isbnin, $verfrom);
$isbnn = self::_normaliseISBN($isbnin);
/* input is ok now, let's convert */
$isbnbody = self::_extractISBNBody($isbnn);
if ($isbnbody === false) {
$isbnout = '978' . $isbnbody . self::_checkdigitISBN13($isbnbody);
* Get the Checkdigit Part of ISBN Number
* @return string|falseCheckdigit or false if failed
$check = self::_checkdigitISBN10($this->_getISBNBody());
$check = self::_checkdigitISBN13($this->_getISBNBody());
* Get the EAN Prefix of ISBN Number (ISBN-13)
* @return string|falseEAN Prefix or false if failed
* Get the Registrationgroup Part of the ISBN Number
* @return string|falseGroup Identifier or false if failed
return $this->isbn_group;
* Setter for the Registrationgroup Part of the ISBN Number
* @param string $group Registrationsgroup to set
* @throws ISBN_Exception in case it fails
private function _setGroup($group)
$testbody = substr($group . '000000000', 0, 9);
$testgroup = self::_extractGroup($testbody);
if ($testgroup === false ) {
if ($testgroup != $group) {
$this->isbn_group = $group;
* @return string ISBN Number (unformatted); empty string if this is
$r = self::_isbnVersionIsValid($ver);
$isbn .= $this->_getISBNBody();
* @param string $isbn ISBN Number
* this is a valid ISBN Number or it is an Empty string
* which will reset the class
* @throws ISBN_Exception in case it fails
$this->isbn_publisher = '';
$isbnn = self::_normaliseISBN($isbn);
$ver = self::_getVersion($isbnn);
'ISBN Version of passed ISBN (' . $ver . ') '.
'does not match existing (' . $this->ver . ').'
$body = self::_extractISBNBody($isbnn);
$this->_setISBNBody($body);
'Invalid ISBN (invalid body "' . $body . '")', $e
* @return string ISBN Body (not an offical term)
private function _getISBNBody()
$body .= $this->_getISBNSubbody();
* @param string $body ISBNBody
* @throws ISBN_Exception in case it fails
private function _setISBNBody($body)
/* validate body by extracting and validating parts */
$r = self::_isbnBodyParts($body, $group, $subbody);
$this->_setGroup($group);
throw new Exception('Invalid Body: Group is invalid', $e);
$this->_setISBNSubbody($subbody);
private function _getISBNSubbody()
* Setter for the ISBN Subbody
* @param string $subbody ISBN Subbody
* @throws ISBN_Exception in case it fails
private function _setISBNSubbody($subbody)
/* validate by setting apart */
$groupid = intval($this->isbn_group);
$r = self::_isbnSubbodyParts($subbody, $groupid, $registrant, $publication);
/* edit+ setter/getter for Registrant/Publisher and Title/Publication */
$this->isbn_publisher = $registrant;
$this->isbn_title = $publication;
* Get the Publication Part of the ISBN Number
* @return string|falsePublisher or false if failed
return $this->isbn_publisher;
* Get the Title Part of the ISBN Number
* @return string|falseTitle or false if failed
return $this->isbn_title;
* Returns this ISBN validity
* @param string $isbn ISBN to validate
* @param integer $ver ISBN-Version to validate against
* @return integer|false Version value of a valid ISBN or false
public static function validate($isbn, $ver = ISBN_DEFAULT_INPUTVERSION)
$r = self::_isbnVersionIs($ver);
$ver = self::_isbnVersionGuess($isbn);
if (self::_isbnVersionIsValid($ver) === false) {
$r = self::_isIsbnValid($isbn, $ver);
* Returns version of this objects ISBN
* @return integer|false Version value or ISBN_VERSION_NONE
* Guesses ISBN version of passed string
* Note: This is not Validation. To get the validated
* version of an ISBN Number use self::validate();
* @param string $isbn ISBN Number to guess Version of
* @return integer|false Version Value or false if failed
$r = self::_isbnVersionGuess($isbn);
|