    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: ISBNs Revisited</title>
        <link>https://members.accu.org/index.php/articles/931</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">CVu Journal Vol 11, #6 - Oct 1999</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c77/">CVu</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c129/">116</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;ISBNs Revisited</h1>
<p><strong>Author:</strong>&nbsp;</p>
<p>
<strong>Date:</strong> 04 October 1999 13:15:33 +01:00 or Mon, 04 October 1999 13:15:33 +01:00</p>
<p><strong>Summary:</strong>&nbsp;</p>
<p><strong>Body:</strong>&nbsp;<div class="sect1" lang="en">
<div class="titlepage">
<h2><a name="d0e20" id="d0e20"></a></h2>
</div>
<p>This 'competition proved to be the most popular that I have ever
run. It seems to have captured the imaginations of a lot of you. It
encouraged several to dip their toes in the water. But it has also
presented me with a problem in that I cannot find space for all
your submissions. This is sad because I like to publish what you
have made the effort to write. Every entry had merit and the
approaches were varied. I think there is still room for something
better. I shall be tackling this problem in my workshop on the
Friday afternoon of JACC. The final result from that session will
go up on our web site. To squeeze as much in as possible I have
taken some liberties with the code layout. If you hate that, please
put up with it.</p>
<p>Anything that was submitted will be on this issue's disk.
Incidentally this will be the last regular disk with issues of C
Vu. In future Code and other relevant material will be placed on
our web site. I am not sure whether this should be in a members
only area or not. What do you think?</p>
<p>Another issue arises out of dropping the disk and that is how we
should cater to members without Internet access. I hope that a few
of you will volunteer to provide such material to Internet-less
members local to you.</p>
<p>Finally, I hope you will all agree that David Beard's entry is a
deserving winner. If David contacts me I will see what would be
most suitable as a prize book for him.</p>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e30" id="d0e30"></a>From the
Harpist</h3>
</div>
<p>Francis consulted me about the various entries to this
competition. One thing struck me as curious. Every case processes
the putative ISBN left to right. Now given the existence of SBNs (9
characters) and ISSNs (8 characters) this seems to be lacking in
thought. In addition, no-one seems to have computed the last symbol
from the rest. As the last symbol is a special case it would seem
to make sense to treat it differently. Actually if you process
right to left, you might as well deal with the exception right
away. But it is always worth thinking about computing a check digit
and then comparing. Note these comments apply generally though they
maybe less suited to some languages than to others.</p>
<p>Consider the following function:</p>
<pre class="programlisting">
int checksns(const char source[]){
  int end = strlen(source);
  int multplier = 2;    /* start with penultimate character */
  int total = 0;
  if(source[end-1] == 'X' || 
        source[end-1] == 'x') total =10;
  else total = source[end-1]-'0';
  int i;
  for(i = end-multiplier; i&gt;=0;++i)) {
     total += (source[i] - '0') * multiplier;
    ++multiplier;
  }
  return (total % 11);
}
</pre>
<p>This assumes that the relevant code (ISBN, SBN, ISSN) has
already been compacted by stripping out dashes, spaces etc. but it
does not require that you know which of the codes you are
processing.`</p>
</div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e41" id="d0e41"></a>An awk solution
from Peter S Tillier <tt class="email">&lt;<a href=
"mailto:petert@eq1152.demon.co.uk">petert@eq1152.demon.co.uk</a>&gt;</tt></h3>
</div>
<p>This is in response to your request for other language solutions
to the ISBN validation problem I have often thought that the
programmers that I meet don't think about using different languages
to solve a particular problem. For example, awk and perl are very
useful for the purpose of validation of data and, depending on the
type of data, for reformatting data.</p>
<p>This code for ISBN.awk took about 20 minutes to type initially -
then I added comments.</p>
<pre class="programlisting">
# ISBN.awk - check a file of ISBN/SBN nos for validity (PST 19990711)
#            syntax: awk -f ISBN.awk [[filename] ...]
# An ISBN is formed of 9 digits followed by one digit  or an upper- or lower-case letter &quot;X&quot; representing ten.
# The ISBN is valid if:    mod(ISBN[1]*10 + ISBN[2]*9 .. +ISBN[10]*1,11) == 0
# When used the ten characters of the ISBN are separated  in different ways by either hyphens  or spaces .
# An SBN is formed of nine characters and can be coverted  to an ISBN by adding an initial zero, &quot;0&quot;
# Rule 1 - save original value
    {
    ISBN = $0; isSBN = 0
}
# Rule 2 - replace &quot;-&quot; and spaces by nulls.
/[ -]/ {
    gsub(&quot;[ -]&quot;,&quot;&quot;)
}
# Rule 3 - check length &amp; determine ISBN or SBN
    {
    if ((length &gt; 10) || (length &lt; 9)) {
        print ISBN &quot; is invalid. The length is wrong.&quot;
        next
    }
    else if (length == 9) { # it's an SBN, so add leading &quot;0&quot;
        $0 = &quot;0&quot; $0
        isSBN = 1
    }
}
# Rule 4 - check for nine digits plus digit, &quot;x&quot; or &quot;X&quot;.     NB for POSIX awk the pattern [0-9]{9}[0-9xX]
#          would be more explicit, but unnecessary as  we  know from Rule 3 that we have exactly 10 chars.
/^[0-9]+[0-9xX]$/ {
    total = 0
    for( i = 1; i &lt; 10; i++ )
        total += (11 - i)*substr($0,i,1)
    if ((substr($0,10,1) == &quot;x&quot;) || (substr($0,10,1) == &quot;X&quot;))
        total += 10
    else
        total += substr($0,10,1)
    if ((total % 11) != 0) {    # not a valid ISBN or SBN
        print ISBN &quot; is invalid.  The check-sum is wrong.&quot;
        next
    }
    if (isSBN)
        print ISBN &quot; is a valid SBN.&quot;
    else
        print ISBN &quot; is a valid ISBN.&quot;
    next
}
# Rule 5 - invalid by virtue of incorrect formation
    {
    print ISBN &quot; is improperly formed.&quot;
}
</pre>
<p>The ISBN.awk program puts all its validation messages out on
stdout. In a &quot;real life&quot; situation I would direct the error outputs
to stderr and the valid outputs to stdout using redirection on the
appropriate print statements.</p>
<p>Peter also provided a solution for ISSN's which is on this
issue's disk.</p>
</div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e56" id="d0e56"></a>From James
Holland</h3>
</div>
<p>I have taken the fragments of the student's code and assembled
them into a working program. I have tried to keep as much of the
original code as possible and to keep modifications to a minimum. I
will not comment on the more minor changes I have made, instead I
will outline what I consider to be the more important issues.</p>
<p>There is a problem with the first <tt class=
"literal">if</tt>-statement of the <tt class=
"function">validate()</tt> function. As Francis pointed out, the
function does not exclude the 'X' from the first nine characters. I
have attempted to alter the student's code to remedy this. I have
introduced the idea of a digit counter that keeps track of the
number of characters accepted. The 'X' is only permitted when the
digit counter has a value of 9 (i.e. the tenth character). I have
modified the <tt class="literal">if</tt>-statement accordingly as
shown in file <tt class="filename">modified\isbn.c</tt>. This part
of the program is becoming rather cumbersome and difficult to
follow. When this happens it is worth investigating to see if there
is a simpler way to tackle the problem. Francis provided a clue in
has original commentary. I will, however, persevere for the time
being.</p>
<p>Another problem with the code is that any dashes ('-'
characters) the user cares to enter are stored in an array. The
problem is that we do not know how many dashes the user will enter.
The size of the input buffer will have to be finite and so a limit
will have to be placed on the number of dashes accepted. I have
increased the size of the input buffer to something quite large in
the hope that the user will never enter such a large number of
dashes. If the user does enter a large number the <tt class=
"function">validate()</tt> function will terminate by means of the
outer <tt class="literal">for</tt>-statement. The loop will
terminate when the number of characters in the buffer is one less
than the size of the buffer. The last element of the array has to
be reserved for the null terminator.</p>
<p>The program is becoming very messy. There must be a simpler way.
The method I have adopted (in my version of the program) is not to
store the dashes at all. They are not wanted. The ISBN can be
written back to the user without the dashes. This will also remove
the need for a function to strip out the dashes. While we are on
the subject, there is a slight deficiency in the students
<tt class="function">dashes()</tt> function.</p>
<p>The <tt class="function">dashes()</tt> function will only remove
every other dash from a contiguous block of dashes. The loop must
not increment if a dash is detected as it may be replaced by
another dash during the string copy. I have removed the
incrementing of the loop variable from the <tt class=
"literal">for</tt>-statement and made it part of the <tt class=
"literal">if</tt>-statement. I have also made the loop exit when
the null terminator character is encountered. This is a more
natural way of handling strings.</p>
<p>As far as I can make out the function for checking the numbers
for ISBN compliance, <tt class="function">isbntest()</tt>, works
perfectly. It is, however, somewhat long winded. I suggest a
shorter method in shown in <tt class=
"filename">new_c\isbn.c</tt>.</p>
<p>When comparing ASCII characters with constants it is better to
express the constants in the form of characters, i.e., '0' and '9'
rather than their ASCII values. This will make the code easier to
read. There is a standard macro, <tt class=
"function">isdigit()</tt> that can de used to detect an ASCII digit
character. This would be simpler and less error prone than testing
for a range of values.</p>
<p>After returning from <tt class="function">isbntest()</tt>, the
student's code obtains the remainder after dividing the returned
value by ISBN (14). This is not required. The remainder after
division by 11 has already been found. The line <tt class=
"literal">result = (number % ISBN);</tt> is not required. The
variable in the following <tt class="literal">if</tt>-statement
should then be <tt class="varname">number</tt> as opposed to
<tt class="varname">result</tt>.</p>
<p>I now offer my solution to the problem of verifying ISBNs. The
problem is broken down into functional blocks, each represented by
a separate function. Prototypes of these functions are provided.
Function <tt class="function">main()</tt> is pretty simple. The
program flow is controlled by the <tt class=
"literal">do</tt>-<tt class="literal">while</tt> loops. All the
hard work is done in the functions.</p>
<p>The function <tt class="function">get_ISBN()</tt> is responsible
for obtaining a potential ISBN from the user. To start, it displays
a message explaining what the user is expected to do. The business
of accepting the various parts of the ISBN is divided into three
stages and controlled by three <tt class=
"literal">do</tt>-<tt class="literal">while</tt> loops. The first
loop is used for the first nine characters of the ISBN and accepts
only the digits '0' to '9'. Notice the use of the standard macro
<tt class="function">isdigit()</tt> for this purpose.</p>
<p>When the ninth character has been entered the second <tt class=
"literal">do</tt>-<tt class="literal">while</tt> loop takes over.
This loop is practically identical to the first except it accepts
the 'x' along with the digits '0' to '9'. As soon as the last
character is entered the program flow drops into the third and last
<tt class="literal">do</tt>-<tt class="literal">while</tt>
loop.</p>
<p>The third loop simply waits until the user finishes the ISBN by
entering a carriage return. Notice that all three loops accept and
display dashes and spaces but do not transfer them to the
<tt class="varname">digits</tt> buffer.</p>
<p>It could be argued that the common parts of these loops could be
combined in some way. This may be possible but to keep everything
simple and obvious has advantages for reliability and
maintenance.</p>
<p>The program now returns to <tt class="function">main()</tt>
where <tt class="function">user_confirm()</tt> executes. This
displays the ISBN and asks the user if the number is correct and
awaits a reply. The function <tt class="function">get_reply()</tt>
is called upon to ensure the user answers appropriately.</p>
<p>The program now executes <tt class="function">verify_ISBN()</tt>
which performs the task of checking ISBN conformance. Despite the
apparent difficulty of the calculations this routine turns out to
be quite simple. The function first checks to see if the last
character is an 'x'. If it is an 'x' it is replaced by a character
that is numerically one greater than '9'. What character this value
represents is not important. The next part of the function scans
the elements of the <tt class="varname">digits</tt> array. For each
character it subtracts the value '0'. This leaves the numerical
value of the character. This value is then multiplied by its
positional value. The result is accumulated in the <tt class=
"varname">check_sum</tt> variable. The final task of <tt class=
"function">verify_ISBN()</tt> is to determine if there is any
remainder after division by eleven.</p>
<p>The program returns to the <tt class=
"function">display_result()</tt> function. The user is informed as
to the outcome of <tt class="function">verify_ISBN()</tt>.</p>
<p>The outer <tt class="literal">do</tt>-<tt class=
"literal">while</tt> loop is controlled by the function <tt class=
"function">another_go()</tt>. A prompt is displayed and the user
replies. If the user enters a 'Y' or 'y' the program goes around
the outer loop one more time. If the user types a 'N' or 'n' the
program terminates. Any other response results in a warning tone
and a request to enter a 'Y or 'N'.</p>
<p>This completes the detailed description of the program. There
are, however, a couple of features that may be of some interest.
There is only one global variable, the array that holds the ISBN.
It is generally a good idea to keep the number of global variables
as low as possible. This keeps functions as self contained as
possible.</p>
<p>It is also considered a good idea not to have too many
preprocessor <tt class="literal">#define</tt>s. It turned out that
my program does not need any, but this has left some 'magic'
numbers scattered about. I have not tried to remove them as I think
their use is reasonably obvious. In fact I think, in this case, it
is best to keep them. Replacing the 'magic' numbers with names can
be even more obscure. The only problem with 'magic' numbers is that
when the program is changed a search has to be made for each one.
Whereas, if <tt class="literal">#define</tt>s had been used only
one place in the program need be changed.</p>
<p>I also provide a version of the program written in Pascal,
<tt class="filename">new_pas\isbn.pas</tt>. I must admit that it is
a direct copy of the C version with the syntax changed as required.
There are, however, some points of interest. The digits array needs
to be 10 elements long. This is because Pascal has length encoded
strings and so does not require a null terminator character.</p>
<p>C has <tt class="literal">do</tt>-<tt class=
"literal">while</tt>, Pascal has <tt class=
"literal">repeat</tt>-<tt class="literal">util</tt>. This meant
that logical expressions had to be negated. Pascal has Boolean
types and these were used where appropriate throughout the program.
Pascal does not have a <tt class="literal">break</tt> statement. To
keep the same structure of the C version of the <tt class=
"function">get_reply()</tt> function I have cheated a bit by
introducing a variable named break and modifying the <tt class=
"literal">while</tt> loop slightly. I think it worked reasonably
well in this case but had the code been more complicated I may have
had to rewrite the function using proper Pascal constructs.</p>
<p>Pascal is more particular about assigning variables of one type
to variables of another type (it is more strongly typed than C). I
have had to circumvent this type checking with a few type casts
here and there. This probably means I have not been programming in
the true spirit of Pascal. Had I to write the program in Pascal
from scratch things may have been different. I have not programmed
in Pascal for quite a few years and I found it difficult to
suddenly swap languages like this. I even had to look up some of
the syntax in the manual. Still, it was a good exercise.</p>
<pre class="programlisting">
/******************************************************************
*  Author: James Holland                                                     
*  Date: 18th July 1999                                                      
*  Compiler: Borland C/C++ version 5.02.                   
*  This program requests an International Standard Book Number from the user and then confirms that the ISBN *   is valid. The program was written for the Code Review Competition, C Vu volume 11 No 5 (page 36).               
******************************************************************/

void get_ISBN(void);      int user_confirm(void);      void display_result(const int);
int verify_ISBN(void);       int another_go(void);    int get_reply(void);
char digits[11];

int main(void) {
  do  {  /* While the user wants to enter another ISBN. */
    do  {   /* While the user does not accept the number. */  get_ISBN(); } while (!user_confirm());
    display_result(verify_ISBN());
  } while (another_go());
  return 0;
}


void get_ISBN(void) {
  char character;
  int digit_counter = 0;
  clrscr();
  puts(&quot;Please enter an International Standard Book Number.&quot;);
  do  { /* Go around this loop until nine digits have been entered. */
    character = getch();
    if (isdigit(character))  {   putch(character);  digits[digit_counter++] = character;  }
    else  if (character == '-' || character == ' ') putch(character);
  } while (digit_counter &lt; 9);
  do  { /* Go around this loop until a character or an X has been entered. */
    character = getch();
    if (isdigit(character) || character == 'x' || character == 'X') {
      putch(character);
      digits[9] = character;
      digit_counter++;
    }
    else  if (character == '-' || character == ' ') putch(character);
  } while (digit_counter &lt; 10);
  do  {  /* Loop until the user enters carriage return. */
    character = getch();
    if (character == '-' || character == ' ') putch(character);
  } while (character != 13);
}

int user_confirm(void) {
  clrscr();       printf(&quot;Is this number correct? %s&quot;, digits);
  return get_reply();
}
int verify_ISBN(void) {
  int digit_index;      int check_sum = 0;
  /* If the last digit is an x change it to the value 1 greater that '9'. */
  if (digits[9] == 'x' || digits[9] == 'X') digits[9] = '9' + 1;
  /* Calculate the sum of products of the ISBN */
  for (digit_index = 0; digit_index &lt; 10; digit_index++) 
    check_sum += (10 - digit_index) * (digits[digit_index] - '0');
  return check_sum % 11 == 0;
}
void display_result (const int result) {
  clrscr();
  if (result)  puts(&quot;You have entered a valid book number.&quot;);
  else  puts(&quot;You have entered an invalid book number.&quot;);
}
int another_go(void) { printf(&quot;Would you like to enter another ISBN?&quot;); return get_reply(); }

int get_reply(void) {
  char reply;
  while (1)   {
    reply = getch();
    if (reply != 'N' &amp;&amp; reply != 'n' &amp;&amp; reply != 'Y' &amp;&amp; reply != 'y')  
          printf(&quot;\a\nPlease enter Y or N...&quot;);
    else  break;
  }
  return reply == 'Y' || reply == 'y';
}
</pre></div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e261" id="d0e261"></a>From: &quot;Catriona
O'Connell&quot; <tt class="email">&lt;<a href=
"mailto:catriona38@hotmail.com">catriona38@hotmail.com</a>&gt;</tt></h3>
</div>
<p>Here is a critique of Phil Collings' ISBN code published in C Vu
11.5. I hope that Phil finds the comments useful.</p>
<div class="orderedlist">
<ol type="1">
<li>
<p>The variable names in the <tt class="literal">#define</tt>s are
not very informative. If something is defining a maximum length
then maybe it should be made clear in its name. MAX may also be a
less than optimal name as many programmers use MAX to return the
maximum of two values.</p>
</li>
<li>
<p>The <tt class="function">validate()</tt> function takes length
as an argument but has fixed size buffers internally. This may lead
to buffer overflow problems. The sizes of objects should be
determined in one place consistently. Here we have length and ISBN
potentially determining the number of characters that will be
processed, but no checks are made to ensure consistency.</p>
</li>
<li>
<p>Magic numbers are used extensively in the code that validates
the characters. It would be better to use the standard isxxxx
functions. This would also provide greater portability and
trans-parency. Not everyone works with ASCII character sets. I
would also put brackets around some of the test expressions just to
make it clear to future readers what the test intentions were.</p>
</li>
<li>
<p>Using <tt class="function">printf()</tt> to beep at the user is
a little bit heavy especially when later <tt class=
"function">puts()</tt> is used to print more text to the
screen.</p>
</li>
<li>
<p>The function <tt class="function">dashes()</tt> is declared to
return an <tt class="type">int</tt>, yet it is neither set nor
checked by the caller. Either define as <tt class="type">void</tt>
or do something with the return value.</p>
</li>
<li>
<p>I am very concerned about the use of <tt class=
"function">strcpy()</tt> to move parts of a string within itself.
This amounts to moving overlapping strings and <tt class=
"function">strcpy()</tt> is not the function for this because the
result may be undefined. You have no guarantee about how the
characters will be moved. If you really do have to save memory then
use the <tt class="function">memmove()</tt> function, which is
guaranteed to move overlapping storage correctly. Failing that, be
a devil and use a few more bytes of storage to create a temporary
string and then copy it back into start.</p>
</li>
<li>
<p>The line <tt class="literal">result = (number % ISBN)</tt> is
erroneous and pointless. The number returned by <tt class=
"function">isbntest()</tt> is already modulo 11, therefore taking
it modulo 14 (ISBN) makes no difference. However, there is no
requirement to take any number modulo 14 in the algorithm.</p>
</li>
<li>
<p><tt class="function">getch()</tt> returns an <tt class=
"type">int</tt>, not a <tt class="type">char</tt> but type
conversion will handle the difference.</p>
</li>
<li>
<p>I do not see why the <tt class="type">end[]</tt> array is
initialised since its elements are not modified, but replaced. In
fact there is no requirement to store the values in the <tt class=
"varname">end[]</tt> array as at each stage the value could be
added to sum directly.</p>
</li>
<li>
<p>The 11 lines of <tt class="literal">if</tt>-statements should
either be replaced by a select structure or a more algorithmic
approach.</p>
</li>
<li>
<p>No checks are in place to actually check that enough valid
characters have been entered. In the worst case a user can enter a
string of dashes and this will be accepted by the code, although it
will eventually report that it was a bad value. Similarly nothing
is done to enforce the rule that X may only appear as a check
digit, which is even more dangerous as a malformed ISBN may then be
accepted as valid.</p>
</li>
</ol>
</div>
<p>The following are my attempts at improving the code as presented
in C Vu 11.5. Three routines are presented as I thought of
different techniques for handling the input to the function in a
secure way. The actual calculation of the ISBN is the same in each
routine.</p>
<p>I have tried to make this a simple, robust and secure solution.
In my current job, I am responsible for ensuring that we identify
and apply the patches to software problems that may be exploited by
the less ethical members of humanity. Buffer overflows seem to be
one of the more common problems as is not validating the input. I
have restricted my attentions to a string validation func-tion and
have not considered an interactive input.</p>
<p>The first solution (<tt class="function">checkISBN</tt>)
allocates a 10 byte buffer and copies into it a maximum of 10 valid
ISBN characters, excluding separators and extra white space. The
technique of defining the valid characters is one I picked up from
writing secure CGI scripts. The length of an ISBN, (excluding
separators) is 10 characters and I have defined this with an enum
to reduce the number of &quot;magic numbers&quot; in the code. I have taken
your suggestion that the final digit (checksum) is treated
separately from the main body of the number.</p>
<p>The second solution (<tt class="function">checkISBN2</tt>)
dynamically allocates a buffer to hold the processed input string.
Subsequent processing is as before.</p>
<p>The third solution (<tt class="function">checkISBN3</tt> - there
is a pattern here) also dynamically allocates a buffer, but we use
the knowledge that the buffer will not contain more characters than
there are in the input string. This reduces the (slight) overhead
in the calls to <tt class="function">realloc()</tt>.</p>
<pre class="programlisting">
int main(){
  /*  Check ISBN.
   * Simple driver code to test the ISBN routines.  checkISBN2 may be replaced by checkISBN but it is less      * accurate.  Catriona Siobhan O'Connell 10 July 1999    */
  int checkISBN(char *strISBN);    int checkISBN2(char *strISBN);
  int checkISBN3(char *strISBN);  
int RetCode = checkISBN3(&quot;0-13-110362-8&quot;);
  printf(&quot;ISBN = 0-13-110362-8 Return Code = %d\n&quot;,RetCode);   /* Valid */
/* a set of similar tests snipped to save space */
  return(0);
}
</pre>
<pre class="programlisting">
int checkISBN(char *strISBN) {

   /*   Validate an ISBN number.
    *  Input: Null terminated string representing the ISBN.
    *  Output: Integer return code.
    *         0   Success
    *         &gt;0  Correct structure but not a valid number.
    *         -1  Incorrect number of valid ISBN characters.
    *         -2  X used in a non-terminal position.
    * Catriona Siobhan O'Connell 10 July 1999  */
   
  char *allowed_characters = &quot;0123456789Xx&quot;;
  char arrISBN[10];
  int ch;   int sum = 0;  int multiplier = 10;  int i=0;   int character_count = 0;
  enum ISBN_CONSTANTS {ISBN_LENGTH=10};

  /*  Extract the valid characters from the ISBN passed as an argument.  This technique allows for the separator to be any character - including  none. Only the first ten valid characters will be copied into the   arrISBN[] array.            */  

  while ((ch = (*strISBN++)) != '\0') 
    if (strchr(allowed_characters,ch) != NULL)    {
      if (i &lt; ISBN_LENGTH)  arrISBN[i] = toupper(ch);
</pre>
<i><span class="remark">I am a little uncomfortable with the logic
of this section. Francis</span></i>
<pre class="programlisting">
      i++;
    }  }
  /*  First validity check - is it long enough?  */
  if (i != ISBN_LENGTH) {
    fprintf(stderr,&quot;ISBN does not contain exactly 10 permitted characters\n&quot;);
    return(-1);
</pre>
<i><span class="remark">It would have been nice to have had the
magic return values hidden by some such device as an enum. And it
is a good idea to adopt a coding style that minimises multiple
return statements. Francis</span></i>
<pre class="programlisting">
  }
  /*  Assume that we are working with a valid ISBN.   This loop is over the first nine characters which should     *   all be numeric.  If they are not numeric then we have an invalid ISBN.  */
  for (i=0;i&lt;ISBN_LENGTH - 1;++i)  {
    if (isdigit(arrISBN[i]))   sum += (arrISBN[i] - '0') * (multiplier--);
    else {
      fprintf(stderr,&quot;X is only permitted as a final character\n&quot;);
      return(-2);
    }
  }

  /*   So far so good. The last character of an ISBN may be 0..9 or X (10).   Add the check digit to the sum      *  already calculated. */
  if (isdigit(arrISBN[ISBN_LENGTH -1 ]))sum += arrISBN[ISBN_LENGTH - 1] - '0';
  else if (arrISBN[ISBN_LENGTH -1 ] == 'X')   sum += 10;
  /*   The final sum should be divisble by 11. We return the sum  modulo 11, which will give 0 for a correct     *  ISBN and a positive  number for an incorrect ISBN.  */
  return (sum % 11);
}
</pre>
<i><span class="remark">I snipped the code for <tt class=
"function">checkISBN2</tt> <tt class="function">checkISBN3</tt> but
you can find it on this issue's disk. In general I would not use
dynamic storage for this kind of problem because the amount of
storage is relatively small and should not vary.</span></i>
<p class="c2"><span class="remark">However I have extracted the
following line because it highlights a couple of coding
problems:</span></p>
<pre class="programlisting">
  arrISBN = (char *)realloc(arrISBN, BufferSize * sizeof(char));
</pre>
<p class="c2"><span class="remark">The first is a minor one that
the compiler will handle by removing it. The <tt class=
"literal">sizeof(char)</tt> is 1 by definition. and so asking the
compiler to calculate it (<tt class="literal">sizeof</tt> is
evaluated at compile time) is unnecessary and just obscures the
code.</span></p>
<p class="c2"><span class="remark">More serious is the cast. I know
that <tt class="function">realloc()</tt> (along with <tt class=
"function">malloc</tt> and <tt class="function">calloc</tt>)
returns a <tt class="type">void*</tt> but not only does C not
require that a <tt class="type">void*</tt> be cast to other pointer
types (unlike C++) but doing so could obscure other problems. In
simple terms, be wary of unnecessary casts. (Generally in C++ you
should use <tt class="literal">new</tt> because it provides a
typesafe return type.) Francis</span></p>
</div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e432" id="d0e432"></a>From: &quot;R. Dixon&quot;
<tt class="email">&lt;<a href=
"mailto:p.m.dixon@btinternet.com">p.m.dixon@btinternet.com</a>&gt;</tt></h3>
</div>
<p>Life is never simple. I thought that from the sample numbers
provided, and by looking in several books, that ISBN numbers always
had the length of 23 with 10 digits and three spaces or hyphens and
wrote a program that included that as a check. I then thought that
perhaps there is other information embedded in the number, perhaps
there is, such as the subject. I therefore looked to see if I could
find this. No Luck! In doing this I found an ISBN number with two
spaces and a length of 12. As I do not know, and have not been able
to find out, any information about the structure of SBN numbers,
apart from the fact that they only contain 9 digits, I have not
been able to make the checks for these numbers as stringent as I
would otherwise.</p>
<i><span class="remark">The right hand digits of ISBN numbers
encode the publisher/imprint (though how many digits apply varies.
So 0-201 are all Addison Wesley books, 0-471 are John Wiley,
1-56592 are O'Reilly.) I believe that there was originally a plan
to have the leading digit give a geographical code but I do not
think it ever came to anything. You can convert an SBN into an ISBN
by prefixing a zero. The format of an ISBN is entirely optional
though most publishers use: d-publisher code-book
code-checkdigit.</span></i>
<p>I attach my effort in C and also one in Pascal. The latter
because it seemed very suitable for a problem of this type because
of sets. It also has the merit of making a smaller executable file,
5568, against 6028 using the Watcom C++ compiler, 6699 using the
Microsoft C compiler and 7050 using Turbo C++! I wondered whether
this had anything to do with the different program structures so
wrote two programs as near identical as the two languages would
allow and still the Pascal executable was smaller.</p>
<i><span class="remark">If you are interested Rob's code is on the
disk. Before you test the code with your compiler you will need to
remove a couple of coding errors. I also wonder to what extent the
metrics of the C/C++ versions reflect the way Rob has coded this
problem. And the optimisation switches may have a considerable
influence. In practice neither code size - within reason - nor
speed is going to matter much with this program. Clarity of code,
time to completion and ease of maintenance are going to be more
important.</span></i></div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e445" id="d0e445"></a>Mick Farmer
<tt class="email">&lt;<a href=
"mailto:mick@dcs.bbk.ac.uk">mick@dcs.bbk.ac.uk</a>&gt;</tt></h3>
</div>
<p>Here's my hack at the ISBN numbers program presented by Paul
Collings. I agree that the validation should be separate from the
input. I've also divided the validation into two parts for
simplicity. It's a bit lax about multiple hyphens and/or spaces,
but I wanted to keep it short.</p>
<pre class="programlisting">
#define MAX 256
/* Given a 10-digit number, verify checkdigit by
  * returning TRUE or FALSE.  */
int isisbn(char *s) {
  int i;
  int sum = 0;
  for (i = 0; i &lt; 9; i++) {
    sum += (10-i)*(s[i]-'0');
  }
  if (s[i] == 'x')   sum += 10;
  else  sum += (s[i]-'0');
  return !(sum%11);
}
/* Given a string, validate characters and put into canonical form by removing hyphens and spaces. A final 'X' is replaced by 'x'.  Return length.  */
int canonical(char *s) {
  int i;
  int j = 0;
  for (i = 0; s[i]; i++) {
    if (isdigit(s[i])) {
      s[j++] = s[i];
    } else if (s[i] != '-' &amp;&amp; s[i] != ' ') break;
  }
  if (!s[i]) {
    s[j] = 0;
    return j;
  }
  if (!s[i+1] &amp;&amp; (s[i] == 'X' || 
                s[i] == 'x')) {
    s[j++] = 'x';
    s[j] = 0;
    return j;
  }
  return -1;
}
/* Test harness. */
int main(void) {
  char buf[MAX];
  while (1) {
    char *p;
    printf(&quot;ISBN: &quot;);
    if (!fgets(buf, MAX, stdin)) {
      fprintf(stderr, &quot;Error in fgets\n&quot;);
      continue;
    }
    if (!(p = strchr(buf, '\n'))) {
      fprintf(stderr, &quot;Missing newline\n&quot;);
      continue;
    }
    *p = 0;
    if (canonical(buf) != 10) {
      fprintf(stderr, 
          &quot;Invalid ISBN (chars or length)\n&quot;);
      continue;
    }
    if (!isisbn(buf)) {
      fprintf(stderr, 
            &quot;Invalid ISBN (checkdigit)\n&quot;);
      continue;
    }
  }
}
</pre></div>
<div class="sect2" lang="en">
<div class="titlepage">
<h3><a name="d0e454" id="d0e454"></a>A ramble around
ISBN numbers by David Beard</h3>
</div>
<p>I am a Communications Engineer or, as some would have it, once
was. These days I lecture, mainly on data networking topics, mainly
to computing students. This, coupled with a mild dyslexia as far as
mnemonics are concerned, caused Paul Colling's article, &quot;ISBN
Numbers&quot; in the July 1999 C Vu issue to catch my attention. Having
determined it wasn't about ISDN(!), Paul's article and Francis's
comments nevertheless retained my interest and provoked a number of
thoughts some of which, to avoid rambling too far afield, I will
save for a possible, later, article.</p>
<p>I started learning C so long ago - at least 16 years- that I
can't remember precisely when I started. I'm still learning though,
and I don't imagine it will ever be otherwise. Starting out, I can
recollect particular problems with the <tt class="literal">for</tt>
statement and Paul's code suggests he had similar difficulties at
the time he wrote it. Looking back though, contrasting Paul's
efforts with my own at a similar stage, he rather puts me to shame.
I should also bow my head low because I confess I'd never
previously given any thought to ISBN numbers or their structure.
Now I know, so thanks to Paul for that, for the reminiscences he
unwittingly provoked, for an interesting article and the for the
stimulation it and Francis's comments provided.</p>
<p>Enough of the personal waffle, let me map out the general route
for the rest of this article. I detour around some, though not all,
of the invitations Francis made in his footnotes and confine myself
to offering criticism of Paul's code, providing a rewrite - still
in C - and making a few observations of a slightly more scenic
nature here and there. The first observation of this kind I would
make is that I agree wholeheartedly with Francis that separation of
data entry and validation is desirable. I would go further and
argue that task separation in general is almost always
desirable.</p>
<p>Paul's overall design suffers largely because it does not
separate tasks and is rather monolithic as a result. This a
inevitable consequence of the manner in which his <tt class=
"function">validate()</tt> function calls - directly or indirectly
- the other functions. The design of these assumes they will only
be called via <tt class="function">validate()</tt> and this limits
both their usefulness and that of the code overall. The use of
<tt class="function">getch()</tt> within <tt class=
"function">validate()</tt> might be viewed, according to taste, as
bonding the monolith to raw keyboard data entry or as a further
extension of the monolith - what results is not entirely to
mine.</p>
<p>There is a problem with the usage of <tt class=
"function">getch()</tt> relating to behaviour if &quot;non-ASCII keys&quot; -
i.e. extended, or extended key combinations - are pressed. If this
occurs, <tt class="function">getch()</tt> first returns 0x0 and
then the next call returns a key scan code identifying the physical
key or key combination. The problem is that scan codes may
correspond to one of the &quot;normal&quot; keystrokes expected. Thus, on my
keyboard, Paul's <tt class="function">validate()</tt> responds to a
ALT-N keypress, for example, by beeping as a result of reading the
0x0 and then displaying a '1' - the scan code in this case - is
read. Users sharing my miss-typing skills might well regard such
behaviour as beeping confusing.</p>
<p>There's a simple fix as my alternative to <tt class=
"function">validate()</tt> - I've called it <tt class=
"function">isbkbin()</tt> - demonstrates. Scan code tables can be
found in e.g.: Thom, &quot;<i class="citetitle">The PC Programmers
Sourcebook</i>&quot;, Microsoft Press. ISBN 1-55615-321-X - and yes,
it's a valid ISBN code, although that of my 1991, 2nd edition
which, like myself, is a little dog-eared and not quite up to date
in all respects.</p>
<p>A more serious problem with Paul's <tt class=
"function">validate()</tt> is that the user can press the enter or
return key before inserting a full 13 characters. I infer that
<tt class="function">check()</tt> presents the input data to the
user and allows selection from some &quot;proceed&quot; or &quot;cancel&quot; option?
But, suppose the user selected the &quot;proceed&quot; option in instances
where the input data from <tt class="function">validate()</tt> did
not constitute a legally formed code-word because insufficient data
were entered? Even if my guesses regarding <tt class=
"function">check()</tt> are wrong, those uninitialised buffers
littering the code suggest potential for &quot;illegal&quot; characters to
reach <tt class="function">isbntest()</tt> and its design simply
does not allow for this. On the same theme, and also underscoring
the previous observation, the intervening <tt class=
"function">dashes()</tt> function has a bug as it fails to fully
remove '-' occurrences unless these occur in isolation. Thus, one
'-' would be left if a pair occurred in the string passed to it and
this would, of course, reach <tt class=
"function">isbntest()</tt>.</p>
<p>One might contemplate modifying <tt class=
"function">validate()</tt> to ensure the data it returns is always
a legally formed code-word but, as the &quot;grammar&quot; involved here is
not context free, this is not without problems. Suppose, for
example, enter or return were disallowed as keystrokes. Suppose
also that separators were forbidden to occur in &quot;runs&quot; and that
these were also disallowed following entry of the 10th code symbol.
What now happens when the user proceeds to enter 10 valid code
symbols without separators? The user cannot proceed to enter a full
13 characters as there's nothing legal left to enter and <tt class=
"function">validate()</tt> can't return as it now needs a full 13
character entry before it can. We have created a deadlock and a
reboot would be required. Of course, edit facilities might be added
to <tt class="function">validate()</tt> to avoid deadlocks, but
other additions would be needed too. Following this path would
inevitably tend to make <tt class="function">validate()</tt> an
almost, &quot;free standing&quot;, platform-specific, separate solution, with
data from other sources being dealt with in some other way. Such a
divergent approach and the modifications involved suggest much pain
for rather dubious gains.</p>
<p>Taking all of the above into account, the obvious way to handle
the possibility of keyboard entered data being in error also
provides for handling the same problems with data from a plurality
of sources. What this entails is some replacement for <tt class=
"function">isbntest()</tt> with the added ability of being able to
test for legality of input - after separator removal that is -
whilst retaining the existing capability to perform a check sum if
the data proves legal.</p>
<p>Some detailed aspects of Paul's existing <tt class=
"function">validate()</tt> function give concern. It appears the
input buffer size sets a upper limit on what the user can be
allowed to enter. This size is determined using <tt class=
"constant">ISBN</tt> which is <tt class="literal">#define</tt>d to
14. Given this, the <i class="parameter"><tt>length</tt></i>
argument appears superfluous. It also strikes me that the outer
<tt class="literal">for</tt> loop is not only strange but also
redundant. Elsewhere, I'm unsure why a role was not found for the
standard library function, <tt class="function">isdigit()</tt>.
Possibly this is because the tests performed rather &quot;accentuate the
negative&quot; by checking for what things are not. This is fine but a
seemingly odd way of doing things given there are fewer valid than
invalid keystrokes.</p>
<p>Paul's <tt class="function">dashes()</tt> function, aside from
the bug identified earlier, is limited by its assumption that the
'-' character is the separator. Some other points regarding
<tt class="function">dashes()</tt> occur to me. Firstly, the return
it obtains from <tt class="function">isbntest()</tt> - a modulo 11
number - is subject to a &quot;modulo ISBN&quot; operation via:</p>
<pre class="programlisting">
result=(number%ISBN);     /*why the brackets incidentally?*/ 
</pre>
<p>Shurely shum mishtake? <tt class="constant">ISBN</tt> is
<tt class="literal">#define</tt>d to 14 so the operation is both
redundant and misleading. Secondly - <tt class="literal">for</tt>
loop problems again - Paul introduces a superfluous variable
<tt class="varname">index</tt> used for array indexing within the
<tt class="literal">for</tt> loop that kicks off <tt class=
"function">dashes()</tt>. The indexing can, of course, be performed
using the <tt class="varname">loop</tt> variable.</p>
<p>The calls to <tt class="function">clrscr()</tt> appearing in
<tt class="function">dashes()</tt> and in <tt class=
"function">isbntest()</tt> are unfortunate. Clearing the screen has
no logical association with either function and they don't need be
there anyway. Whilst no more logical in absolute terms, within the
context of Paul's design, a single call to <tt class=
"function">clrscr()</tt> from <tt class="function">validate()</tt>
- immediately preceding the call to <tt class=
"function">dashes()</tt> - would make more sense. As things stand,
one unnecessary screen clear occurs due to <tt class=
"function">clrscr()</tt> appearing in both <tt class=
"function">isbntest()</tt> and <tt class=
"function">dashes()</tt>.</p>
<p>Moving on to <tt class="function">dashes()</tt>, a more powerful
function is possible if the call to <tt class=
"function">isbntest()</tt> and also, of course, the bug I
identified earlier, are removed. My alternative, <tt class=
"function">str_rem()</tt>, strips occurrences of not just one
character but any occurrences of an arbitrarily specified set of
characters from a string and its usefulness transcends the present
application.</p>
<p>And so to <tt class="function">isbntest()</tt> itself. As
mentioned, this has fundamental weaknesses. It not only assumes the
string it handles contains exactly 10 &quot;legal&quot; characters, it also
doesn't appear to take account of positional constraints on
appearances of 'X' or 'x' character(s). Moreover, it is - as Paul
appears to recognise - rather obfuscated. Leaving aside the fact
that this doesn't address the weaknesses, what's wrong with doing
the obvious:-</p>
<pre class="programlisting">
result= ((start[0]-'0')*10) +(start[1]-'0')*9) + .........+((start[8]-'0')*2);
  if( (start[9] =='X') || (start[9]=='x' )) result+=10;
  else  result+=start[9];
return result%11;
</pre>
<i><span class="remark">Or we could write</span></i>
<pre class="programlisting c3">
<span class=
"remark">result += ((start[9] =='X') || (start[9] =='x'))? 10 : start[9];</span>
</pre>
<p>or, if all that typing doesn't appeal:-</p>
<pre class="programlisting">
for(loop=0;loop&lt;MAX-1;loop++) result+=(10-loop)*(start[loop]);
if( (start[loop] =='X')|| (start[loop]=='x') ) result+=10;
else  result+=start[loop];
return result%11;
</pre>
<p>My version of <tt class="function">isbntest()</tt> - I've called
it <tt class="function">isbnchk()</tt> - uses something of the
latter approach but adds checks to ensure it is dealing with
&quot;legal&quot; input - returning negative error values if it finds it
isn't.</p>
<p>I've referred to my alternative solution in piecemeal fashion.
An overall description might be useful. I rather liked the
interactive user interface Paul provided via <tt class=
"function">getch()</tt> for keyboard input and this has been
retained. Somewhat tighter con-straints on keyboard entries have
been applied whilst, at the same time, some freedom is allowed with
separators. However, <tt class="function">isbnkbin()</tt> does not
directly call <tt class="function">str_rem()</tt> and neither does
the latter call <tt class="function">isbnchk()</tt> - my
replacement for <tt class="function">isbntest()</tt>. These latter
functions are free-standing and neither assume the data they handle
is &quot;legal&quot; - hence these can handle input from any source. The
constraints on data entry set by <tt class=
"function">isbnkbin()</tt> play the role of providing feedback and
guidance to the user whilst the role of <tt class=
"function">isbnkbin()</tt> itself is relegated to one of providing
one of many possible sources of input data strings.</p>
<p>I'll conclude by presenting the detailed code. I've thrown in a
simple-minded <tt class="function">main()</tt> function to
test/exercise everything and there are a few comments which may
help to use the functions in other contexts than the one my
<tt class="function">main()</tt> function creates. This was all put
together in rather a hurry so everything's in one file and I've
dispensed with a few things - e.g. sentinels - as a result. No
matter, it can stand as is and perhaps leave exposed bad habits
I've let creep in over the years. If anyone would care to point out
the errors of my ways, I would appreciate criticism of my efforts
too. Here they are:-</p>
<pre class="programlisting">
#define ISBN  14
#define MAX   10
#define DONE  ISBN
#define SEPMAX 3
#define CRET  13
int isbnkbin(char *);    int  isbnchk(const char *);  void str_rem(char *, const char *);
enum badchk{illen=-3, illchar=-2, null_arg=-1};
/*************************************************************************
isbnkbin()   - roughly equivalent to validate() of original program.
Contrains data entry:-   ' ' or '-' allowed as separator.
      Separator disallowed as first entry or following entry of 10 code symbols. 
      Contiguous runs of separators disallowed.
      Valid code symbols '0'-'9' or 'X'/'x'. 'X'/'x' only accepted as 10th symbol.
      Separators can be ommitted.
User terminates input via ENTER/RETURN key and may do this at any point. (see also comments in code).
Returns: total number of code digits accepted.
*********************************************************************/
int  isbnkbin(char *input) {
int  keystroke, digits=0, sep_cnt=0,  previous;    int  inchar, retval=0,  state=0;
   while(state&lt;DONE){ 
/*actually, state can't reach DONE. Could change  to  force an immediate return once full 10  code symbols with 3 separators entered. Seemed a little confusing to use and consistency of always requiring enter or return to terminate user input seems  preferable. Change #define for DONE to ISBN-1 if you disagree.  */
  if( (keystroke=getch()) == 0 ){   getch();       /*Absorb scan code*/  putchar('\a');  continue;  }
  if(keystroke==CRET)  break;
  inchar=keystroke; /*Save actual keystroke*/
  if(keystroke=='x')/*so we can do some mapping*/ keystroke='X'; /*to save a little on testing*/
  if(keystroke==' ')  keystroke='-';
  if(state!=0){   /*get previous accepted char -if any*/
     previous=input[state-1];
     if(previous==' ') /*more mapping*/ previous='-';
  }
  if( isdigit(keystroke) &amp;&amp; (digits &lt; MAX ) ) digits++; 
  /*X is acceptable if digits==MAX-1*/
  else if( (keystroke=='X') &amp;&amp; (digits==MAX-1) )  digits++;
  /* '-' ok provided we haven't reached full quota AND its not the first entry AND  previous entry not '-' AND we havent already  got 10 code symbols entered and accepted.                    */
  else if((sep_cnt &lt; SEPMAX)&amp;&amp; keystroke=='-') &amp;&amp; (state!=0)&amp;&amp;(previous !='-')&amp;&amp; (digits!=MAX))
     sep_cnt++;
  else{ /* unnaceptable keystrokes*/ putchar('\a'); /*beep*/   continue;      /*and get out of way*/   }
  input[state++]=inchar;   retval=digits;  putchar(inchar);
   }/*end of while loop*/
putchar('\n'); input[state]='\0';
return retval;
}
/********************************************************************
This function removes occurrences of characters which are in the 2nd argument string from the first string. A NULL  pointer for either/both arguments is valid though not very useful. If the caller passes such thats the caller's problem!
*********************************************************************/
void str_rem(char *str,const char *strip) {
  char *tok1,*cp;
/*Can't strip anything from nothing and can't strip nothing from anything.*/
   if((str!=NULL)&amp;&amp;(strip!=NULL)){
  cp=str;    tok1=strtok(cp,strip);   if(tok1!=str)  /*leading chars to remove*/ strcpy(str,tok1);
  while(tok1!=NULL){  tok1=strtok(NULL,strip);  strcat(cp,tok1); }
 }
}
/*********************************************************************
isbnchk().
Returns:  a ISBN check sum if passed a string containing MAX (ie 10) legal code characters.
The value returned will be 0 if the string contains a valid ISBN code or some other value in the range 1-10 if the code fails the check sum but is otherwise legally formed. Other returned values are:-
nullarg  (-1)  argument is a NULL pointer, illchar  (-2)  argument string of acceptable length but contains illegal chars, 'X'/'x' is regarded as illegal if it not the MAXth (ie 10th) char.
illen    (-3)  argument string not of length MAX (ie 10) chars.
Note: isbnchk() has no notion of separators or other characters the caller may regard as &quot;innocuous&quot;-eg leading or trailing space. The caller must use the str_rem() function provided -or other means - to ensure such characters are not present in the string passed to isbnchk() for it to be able to compute a checksum.  Failure to do so will cause a return  of -2 or -3 in circumstances where the caller regards the string as containing a &quot;legally&quot; formed ISBN code-word.
Thinks... I'm not too convinced using MAX rather than 10 aids clarity.
*************************************************************************/
int isbnchk(const char *ptr) {
  int len, loop, retval, sum=0;
  if(ptr==NULL) retval=null_arg;
  else if ( (len=strlen(ptr)) !=MAX ) retval=illen;
  else {
    for(loop=0;loop&lt; len;loop++){
      if( !isdigit(ptr[loop])){  /*accept only '0'-'9' */
        if( loop &lt; (MAX-1) ){   /*unless last position*/ retval=illchar;  break; }  
                     /*in which case we can accept X or x*/
        else if((ptr[loop]!='X') &amp;&amp; ( ptr[loop] !='x') ){retval=illchar;  break; }
      }
/*At this point, characters we are dealing with are legal. The contribution each makes to the check sum is the product of decimal value it represents in the code and its &quot;place&quot; value.*/
      if((ptr[loop]=='x')||(ptr[loop]=='X'))  sum += 10*(10-loop);
      else  sum+=( ptr[loop]-'0' )*(10-loop);
     }/*end of loop*/
    if(loop==len) /*went right through so all input encountered was legal*/ retval= sum%11;
  }
  return retval;
}
/***** A simple main() function to exercise/test things ********/
int main() {
  int val;   const char *seperats=&quot; -&quot;;    char input[ISBN], raw[ISBN];
  puts(&quot;Enter ISBN code to check:&quot;);   val=isbnkbin(input);
  printf(&quot;iskbin() returned %d as digit length\n&quot;,val);
  strcpy(raw,input);  /*save input as str_rem() may alter it*/
  str_rem(input,seperats);    val=isbnchk(input);
  if(val==0) printf(&quot;entered code: %s is valid\n&quot;,raw);
  if(val&gt;0)   printf(&quot;entered code: %s failed check sum\n&quot;,raw);
  if((val==illen)||(val==illchar)) printf(&quot;entered code: %s is illegal\n&quot;,raw);
  getchar(); return val;
}
</pre></div>
</div>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
