[comp.sources.misc] string compare for 8-bit non-English characters

root@hobbes.UUCP (John Plocher) (08/17/87)

In response to the discussion in comp.std.internat and the amiga group
about how programmers don't take into account non-English character sets
and their properties, I submit the following:  A strcmp() replacement
which correctly handles accented characters and non-ASCII collating.

	-John Plocher

/* This is NOT a shar */

/****************************************************************************
 *
 *	stracmp.c	string compare with accented characters and
 *			non-ASCII collating sequences
 *
 *	Copyright 1985, 1987, 1987 by John Plocher    (plocher@hobbes.UUCP)
 *	May be used in any product as long as this notice is retained and
 *	credit is given.
 *
 ****************************************************************************
 *
 *	Revision Control Information
 *
 *	By:		$Author: plocher $
 *			$Revision: 1.3 $
 *	Last modified:	$Date: 87/08/15 01:32:36 $
 *	Source is in:	$Source: /usr/src/local/biblos/RCS/stracmp.c,v $
 *	Release state:	$State: Usenet $
 *
 *	Library module
 *
 *	Modification Log
 *	----------------
 *
 *	$Log:	stracmp.c,v $
 *	Revision 1.3  87/08/15  01:32:36  plocher
 *	fixed crt-independent 8-bit character output
 *	
 *	Revision 1.2  87/08/15  01:17:28  plocher
 *	passes lint with no complaints
 *
 *	Revision 1.1  86/04/12
 *	Revision 1.0  85/05/27
 *	
 *
 ****************************************************************************
 *
 *	Compile with
 *
 *		cc -c stracmp.c			# for a library object file
 *		  - or -
 *              cc -o stracmp -DMAIN stracmp.c	# for a standalone testbed
 *
 ****************************************************************************
 *
 *	stracmp() implements a string compare which correctly handles
 *	accented (non English) characters which have been encoded using
 *	8-bit characters.  It uses character lookup tables for doing 
 *	string compares when accented characters are present and/or a
 *	non-ASCII collating sequence is desired.
 *
 *	Also, because this is used in bibliographic lookups, this routine
 *	supports the concept of comments within a string.  Everything
 *	between [ and ] (inclusive) is ignored for all comparisons.
 *	Comments may NOT be nested.  Comments are also delimited by
 *	an end of string ('\0'), but that is not the "correct" way.
 *
 *	Reference:
 *
 *	Gibaldi, and Walter S. Achtert.  _MLA_Handbook_for_Writers_of_Research_
 *	    Papers_.  New York: Modern Language Association of America, 1984.
 *	    Page 76.
 *
 ****************************************************************************
 *
 *	Theory:
 *	  The correct way of sorting (or comparing) strings which contain
 *	accented characters is to first compare the strings with all accents
 *	stripped. If the two strings are the same, then and only then are the
 *	accents used.  This second comparison involves only the accents.
 *	You can think of this as comparing the two strings with all the letters
 *	stripped.
 *
 *	  Also, there are times when the "normal" ASCII collating sequence is
 *	not appropriate for lexical ordering.  (ie.  A <AE> B C <CEDILLA> D ...>
 *
 ****************************************************************************
 *	Examples:
 *****
 *			     ,  :
 *	Comparing Junta and Junta	(the second word has diacritical
 *					 marks over the two vowels)
 *
 *	    first we compare("Junta", "Junta")	which shows them EQUAL
 *	then we must compare("     ", " '  :")
 *
 *				  ,  :
 *	Thus, Junta comes before Junta in the lexical ordering of the two words.
 *
 *****
 *		   ,          ,
 *	Comparing Junta  and Junto	(both words have accented 'u's)
 *
 *	    first we compare("Junta", "Junto"); since they are
 *	different  we do not need to do anything more with the accents:
 *	  ,                    ,
 *	"Junta" is less than "Junto".
 *
 ****************************************************************************
 *
 *	Implementation:
 *
 *	The accented string is broken into two strings:
 *		1) a string of letter values with accents stripped, and
 *		2) a string of accent values with letters stripped.
 *
 *	The comparison is table based in order to speed things up and
 *	allow arbitrary collating sequences.
 *
 *	For a given character x, translate[x] is the "value"
 *	used for sorting with strcmp(), and accent[x]
 *	tells whether the character carries an accent, should 
 *	be ignored, or is a normal character.  If accent[] indicates
 *	that the character carries a diacritical, the value of accent[]
 *	is used to rank the accented character against the same letter
 *	but different diacritics:
 *			   ,              .
 *	    ie. The letter a differs from a; which is less depends on the
 *	values of accent[].  If the values in accent[] for these two letters
 *      are the same, the accented letters are considered identical.
 *
 *	The stracmp() routine is fully protected against NUL pointers
 *	being passed as parameters,
 *	All internal space needed is taken from the heap with a single malloc()
 *	and free()'d on exit.  The heap space needed is
 *		2 * ( strlen(s1) + strlen(s2) ) + 4
 *	The stack space needed is 3 ints and 4 pointers.
 *	There are two static 256 element arrays of unsigned chars used for
 *	defining the accents and collation sequence.
 *
 *	The runtime time is
 *			       TIME( strlen(s1) )
 *			     + TIME( strcpy(x,s1) ) * K
 *			     + TIME( strlen(s2) )
 *			     + TIME( strcpy(x,s2) ) * K
 *			     + TIME( strcmp(t1,t2) )
 *			     +[TIME( strcmp(a1,a2) )] (* iff needed *)
 *			     + TIME( malloc() )
 *			     + TIME( free() )
 *     where 1 < K < 2
 *
 ****************************************************************************
 */

#define VERSION		"$Revision: 1.3 $"
/* #define MAIN			/* compile as a test program, not a library */
/* #define ON_IBMPC		/* iff MAIN is defined does your crt show */
				/* the IBM character set?		  */
#define BRACKET_COMMENTS	/* if defined, stuff within [ ]'s is ignored */

#define IBMPC_ROM		/* Tables match the IBM PC ROM tables */
/* #define ISO_LATIN_1		/* Tables for ISO LATIN-1 (ISO 8859-1) */

/***************************************************************************/

#if defined(IBMPC_ROM) + defined(ISO_LATIN_1) != 1

   One and only one of these may be defined.

#endif


#ifdef MAIN
#  include <stdio.h>		/* For confidence test */
#  ifdef ON_IBMPC
#    define PRINT printf
#  else
#    define PRINT crtaccent
#  endif
#endif

#ifndef lint
    static char rcsid[] =
      "$Header: stracmp.c,v 1.3 87/08/15 01:32:36 plocher Usenet $";
#endif

extern char *malloc();
extern void exit();
extern void free();


#ifdef IBMPC_ROM

/* IBM-PC ROM based character set */

/* The translate table maps from a printable character to a "value".   This
 * "value" is used to determine sorting order ( a smaller "value" is less
 * than a larger "value" ).  
 *
 *  Note that in the following table, the letters 'C' and <Cedilla> are
 * both given the same "value".  This is because these two letters are
 * "the same" WHEN ACCENT MARKS ARE IGNORED.  (Same for all other accented
 * characters - they share the same value with the underlying character.)
 *
 *  The table following this has the entry for <Cedilla> flagged as an
 * accent, the entry for 'C' does not.  Therefore, when sorting, a 
 * <Cedilla> will sort with, but following, the entries beginning with 'C'.
 *
 *  The accent table is used solely to differentiate between letters which
 * have the same value in the translate table.  The reasons for two tables
 * instead of one table of shorts are that strcmp() works with char*'s, not
 * short*'s, and that the tables are easier to understand this way.
 *
 * One could also increment the values for 'D'..'~' by 1 and give the value of
 * <Cedilla> as value('C') + 1.  In this case the accent table would not be
 * needed to distinguish between the two.
 */

static unsigned char translate[256] = {
/*     0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F  */
/*     -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -  */
/*0*/  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
/*1*/ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
/*2*/ ' ','!',34, '#','$','%','&',39, '(',')','*','+',',','-','.','/',
/*3*/ '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
/*4*/ '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
/*5*/ 'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',
/*6*/ '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
/*7*/ 'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',127,

/*8*/ 'C','u','e','a','a','a','a','c','e','e','e','i','i','i','A','A',
/*9*/ 'E',145,146,'o','o','o','u','u','y','O','U',155,156,157,158,159,
/*A*/ 'a','i','o','u','n','N','a','o','?',169,170,171,172,'!',174,175,
/*B*/ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
/*C*/ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
/*D*/ 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
/*E*/ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
/*F*/ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
};


/*
 * for a given character x, accent[x] determines if the
 * character should be ignored (0), or used as given by translate[x] but
 * marked as an accent (1..n).
 *
 * Accents have a sorting order given by the value stored in this table.
 *  (This feature is currently used in the following way:  Accent value=
 *		0	Character is totally ignored in all sorting operations
 *		1	Normal unaccented character (ASCII)
 *		2..n	accents from the extended IBM charset
 */
 
static unsigned char accent[256] = {
/*     0  1  2  3   4  5  6  7      8  9  A  B   C  D  E  F  */
/*     -  -  -  -   -  -  -  -      -  -  -  -   -  -  -  -  */
/*0*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* control */
/*1*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* chars   */
/*2*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1, /* alphanumerics */
/*3*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*4*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*5*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*6*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*7*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 0, /* DEL */

/*8*/  2, 2, 2, 2,  2, 2, 2, 2,     2, 2, 2, 2,  2, 2, 2, 2, /* accented chars*/
/*9*/  2, 0, 0, 2,  2, 2, 2, 2,     2, 2, 2, 0,  0, 0, 0, 0,
/*A*/  2, 2, 2, 2,  2, 2, 0, 0,     2, 0, 0, 0,  0, 2, 0, 0, /* aeiou ? ! */
/*B*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* line graphics */
/*C*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* line graphics */
/*D*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* line graphics */
/*E*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* greek */
/*F*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0  /* math */
};

#endif

#ifdef ISO_LATIN_1

/* ISO Latin-1 character set */

/* 
 * Attached is the repertoire of ISO Latin Alphabet Nr 1 (IS 8859-1). I have
 * indicated an alternate name where there might be confusion in the U.S..
 *
 *  List is from  Tim Lasko  Digital Equipment Corporation  Maynard, MA
 * 
 * R/C - row/column of code table
 * Dec - Decimal
 * Oct - Octal
 *
 * R/C  Dec Oct Symbol Name 
 *
 * 02/00 032 040   SP   SPACE
 *  ... same as USASCII ...
 * 07/14 126 176   ~    TILDE
 *  
 * 10/00 160 240  NBSP  NO-BREAK SPACE 
 * 10/01 161 241        INVERTED EXCLAMATION MARK
 * 10/02 162 242        CENT SIGN
 * 10/03 163 243        POUND SIGN
 * 10/04 164 244        CURRENCY SIGN                                
 * 10/05 165 245        YEN SIGN
 * 10/06 166 246        BROKEN BAR                                   
 * 10/07 167 247        PARAGRAPH SIGN, (U.S.) SECTION SIGN 
 * 10/08 168 250        DIERESIS                                    
 * 10/09 169 251        COPYRIGHT SIGN
 * 10/10 170 252        FEMININE ORDINAL INDICATOR
 * 10/11 171 253        LEFT ANGLE QUOTATION MARK
 * 10/12 172 254        NOT SIGN                                     
 * 10/13 173 255   SHY  SOFT HYPHEN                               
 * 10/14 174 256        REGISTERED TRADEMARK SIGN                   
 * 10/15 175 257        MACRON                                       
 *  
 * 11/00 176 260        RING ABOVE, DEGREE SIGN
 * 11/01 177 261        PLUS-MINUS SIGN
 * 11/02 178 262        SUPERSCRIPT TWO
 * 11/03 179 263        SUPERSCRIPT THREE
 * 11/04 180 264        ACUTE ACCENT                                 
 * 11/05 181 265        MICRO SIGN
 * 11/06 182 266        PILCROW SIGN, (U.S.) PARAGRAPH
 * 11/07 183 267        MIDDLE DOT                      
 * 11/08 184 270        CEDILLA
 * 11/09 185 271        SUPERSCRIPT ONE
 * 11/10 186 272        MASCULINE ORDINAL INDICATOR
 * 11/11 187 273        RIGHT ANGLE QUOTATION MARK
 * 11/12 188 274        VULGAR FRACTION ONE QUARTER
 * 11/13 189 275        VULGAR FRACTION ONE HALF
 * 11/14 190 276        VULGAR FRACTION THREE QUARTERS               
 * 11/15 191 277        INVERTED QUESTION MARK
 *  
 * 12/00 192 300        LATIN CAPITAL LETTER A WITH GRAVE ACCENT
 * 12/01 193 301        LATIN CAPITAL LETTER A WITH ACUTE ACCENT
 * 12/02 194 302        LATIN CAPITAL LETTER A WITH CIRCUMFLEX ACCENT
 * 12/03 195 303        LATIN CAPITAL LETTER A WITH TILDE
 * 12/04 196 304        LATIN CAPITAL LETTER A WITH DIAERESIS
 * 12/05 197 305        LATIN CAPITAL LETTER A WITH RING ABOVE
 * 12/06 198 306        CAPITAL DIPHTHONG AE
 * 12/07 199 307        LATIN CAPITAL LETTER C WITH CEDILLA
 * 12/08 200 310        LATIN CAPITAL LETTER E WITH GRAVE ACCENT 
 * 12/09 201 311        LATIN CAPITAL LETTER E WITH ACUTE ACCENT 
 * 12/10 202 312        LATIN CAPITAL LETTER E WITH CIRCUMFLEX ACCENT
 * 12/11 203 313        LATIN CAPITAL LETTER E WITH DIAERESIS
 * 12/12 204 314        LATIN CAPITAL LETTER I WITH GRAVE ACCENT 
 * 12/13 205 315        LATIN CAPITAL LETTER I WITH ACUTE ACCENT 
 * 12/14 206 316        LATIN CAPITAL LETTER I WITH CIRCUMFLEX ACCENT
 * 12/15 207 317        LATIN CAPITAL LETTER I WITH DIAERESIS
 *  
 * 13/00 208 320        CAPITAL ICELANDIC LETTER ETH                 
 * 13/01 209 321        LATIN CAPITAL LETTER N WITH TILDE
 * 13/02 210 322        LATIN CAPITAL LETTER O WITH GRAVE ACCENT 
 * 13/03 211 323        LATIN CAPITAL LETTER O WITH ACUTE ACCENT 
 * 13/04 212 324        LATIN CAPITAL LETTER O WITH CIRCUMFLEX ACCENT
 * 13/05 213 325        LATIN CAPITAL LETTER O WITH TILDE
 * 13/06 214 326        LATIN CAPITAL LETTER O WITH DIAERESIS
 * 13/07 215 327        MULTIPLICATION SIGN                          
 * 13/08 216 330        LATIN CAPITAL LETTER O WITH OBLIQUE STROKE
 * 13/09 217 331        LATIN CAPITAL LETTER U WITH GRAVE ACCENT 
 * 13/10 218 332        LATIN CAPITAL LETTER U WITH ACUTE ACCENT 
 * 13/11 219 333        LATIN CAPITAL LETTER U WITH CIRCUMFLEX
 * 13/12 220 334        LATIN CAPITAL LETTER U WITH DIAERESIS
 * 13/13 221 335        LATIN CAPITAL LETTER Y WITH ACUTE ACCENT  
 * 13/14 222 336        CAPITAL ICELANDIC LETTER THORN               
 * 13/15 223 337        SMALL GERMAN LETTER SHARP s
 *  
 * 14/00 224 340        LATIN SMALL LETTER a WITH GRAVE ACCENT
 * 14/01 225 341        LATIN SMALL LETTER a WITH ACUTE ACCENT
 * 14/02 226 342        LATIN SMALL LETTER a WITH CIRCUMFLEX ACCENT
 * 14/03 227 343        LATIN SMALL LETTER a WITH TILDE
 * 14/04 228 344        LATIN SMALL LETTER a WITH DIAERESIS
 * 14/05 229 345        LATIN SMALL LETTER a WITH RING ABOVE
 * 14/06 230 346        SMALL DIPHTHONG ae
 * 14/07 231 347        LATIN SMALL LETTER c WITH CEDILLA
 * 14/08 232 350        LATIN SMALL LETTER e WITH GRAVE ACCENT
 * 14/09 233 351        LATIN SMALL LETTER e WITH ACUTE ACCENT
 * 14/10 234 352        LATIN SMALL LETTER e WITH CIRCUMFLEX ACCENT
 * 14/11 235 353        LATIN SMALL LETTER e WITH DIAERESIS
 * 14/12 236 354        LATIN SMALL LETTER i WITH GRAVE ACCENT
 * 14/13 237 355        LATIN SMALL LETTER i WITH ACUTE ACCENT
 * 14/14 238 356        LATIN SMALL LETTER i WITH CIRCUMFLEX ACCENT
 * 14/15 239 357        LATIN SMALL LETTER i WITH DIAERESIS
 *  
 * 15/00 240 360        SMALL ICELANDIC LETTER ETH                   
 * 15/01 241 361        LATIN SMALL LETTER n WITH TILDE
 * 15/02 242 362        LATIN SMALL LETTER o WITH GRAVE ACCENT
 * 15/03 243 363        LATIN SMALL LETTER o WITH ACUTE ACCENT
 * 15/04 244 364        LATIN SMALL LETTER o WITH CIRCUMFLEX ACCENT
 * 15/05 245 365        LATIN SMALL LETTER o WITH TILDE
 * 15/06 246 366        LATIN SMALL LETTER o WITH DIAERESIS
 * 15/07 247 367        DIVISION SIGN                                
 * 15/08 248 370        LATIN SMALL LETTER o WITH OBLIQUE STROKE
 * 15/09 249 371        LATIN SMALL LETTER u WITH GRAVE ACCENT
 * 15/10 250 372        LATIN SMALL LETTER u WITH ACUTE ACCENT
 * 15/11 251 373        LATIN SMALL LETTER u WITH CIRCUMFLEX ACCENT
 * 15/12 252 374        LATIN SMALL LETTER u WITH DIAERESIS
 * 15/13 253 375        LATIN SMALL LETTER y WITH ACUTE ACCENT       
 * 15/14 254 376        SMALL ICELANDIC LETTER THORN                 
 * 15/15 255 377        LATIN SMALL LETTER y WITH DIAERESIS          
 */


unsigned char translate[256] = {
/*     0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F  */
/*     -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -  */
/*0*/  0 ,' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
/*1*/ ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
/*2*/ ' ','!',34, '#','$','%','&',39, '(',')','*','+',',','-','.','/',
/*3*/ '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
/*4*/ '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
/*5*/ 'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',
/*6*/ '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
/*7*/ 'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',127,

/*8*/ ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
/*9*/ ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
/*A*/ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
/*B*/ 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
/*C*/ 'A','A','A','A','A','A',198,'C','E','E','E','E','I','I','I','I',
/*D*/ 208,'N','O','O','O','O','O',215,'O','U','U','U','U','Y',222,'s',
/*E*/ 'a','a','a','a','a','a',230,'c','e','e','e','e','i','i','i','i',
/*F*/ 240,'n','o','o','o','o','o',247,'o','u','u','u','u','y',254,'y'
};

static unsigned char accent[256] = {
/*     0  1  2  3   4  5  6  7      8  9  A  B   C  D  E  F  */
/*     -  -  -  -   -  -  -  -      -  -  -  -   -  -  -  -  */
/*0*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* control */
/*1*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* chars   */
/*2*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1, /* alphanumerics */
/*3*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*4*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*5*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*6*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 1,
/*7*/  1, 1, 1, 1,  1, 1, 1, 1,     1, 1, 1, 1,  1, 1, 1, 0, /* DEL */

/*8*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* control */
/*9*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* chars   */
/*A*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* punctuation */
/*B*/  0, 0, 0, 0,  0, 0, 0, 0,     0, 0, 0, 0,  0, 0, 0, 0, /* punctuation */
/*C*/  2, 2, 2, 2,  2, 2, 3, 2,     2, 2, 2, 2,  2, 2, 2, 2, /* A C, E I */
/*D*/  2, 2, 2, 2,  2, 2, 2, 0,     2, 2, 2, 2,  2, 2, 0, 2, /* O N U Y */
/*E*/  2, 2, 2, 2,  2, 2, 3, 2,     2, 2, 2, 2,  2, 2, 2, 2, /* a c, e i */
/*F*/  2, 2, 2, 2,  2, 2, 2, 0,     2, 2, 2, 2,  2, 2, 0, 2  /* o n u y */
};

#endif

#ifdef BRACKET_COMMENTS
#define REDUCE(ORIG, ACCENTS, ASCII)	{				\
    char *pa, *pt;							\
    pa = ACCENTS;							\
    pt = ASCII;								\
    while (*ORIG) {							\
	if ( *ORIG == '[' ) {						\
	    while ( *ORIG && *ORIG != ']' )				\
		ORIG++;		/* ignore anything within []'s */	\
	    if (*ORIG)							\
		ORIG++;		/* skip trailing ] */			\
	    continue;							\
	}								\
	if (accent[ *ORIG ]) {						\
	    *pa++ = accent[ (unsigned)(*ORIG) ];			\
	    *pt++ = translate[ (unsigned)(*ORIG) ]; /* set collating seq */ \
	}								\
	ORIG++;								\
    }									\
    *pa = *pt = '\0';							\
}
#else
#define REDUCE(ORIG, ACCENTS, ASCII)	{				\
    char *pa, *pt;							\
    pa = ACCENTS;							\
    pt = ASCII;								\
    while (*ORIG) {							\
	if (accent[ *ORIG ]) {						\
	    *pa++ = accent[ (unsigned)(*ORIG) ];			\
	    *pt++ = translate[ (unsigned)(*ORIG) ]; /* set collating seq */ \
	}								\
	ORIG++;								\
    }									\
    *pa = *pt = '\0';							\
}
#endif

#define MALLOC( pointer, type, size )			\
    pointer = ( type *)malloc((unsigned) size );	\
    if ( pointer == (type *)NULL) {			\
	(void)printf("\n MALLOC returned NULL:  pointer (size)");	\
	exit(0);					\
    }

#define FREE( pointer )		\
	(void)free( (char *)pointer );

int stracmp(s1,s2)
unsigned char *s1, *s2;
{
    int value;
    unsigned int i1,  i2;	/* length of given strings */
    char *as1, *as2;		/* accent strings */
    char *ts1, *ts2;		/* strings with accent marks stripped */


    if (s1 == NULL)			/* cover our ass */
	if (s2 == NULL)
	    return 0;		/* NULL == NULL :-) */
	else return -1;		/* NULL < "anything" */
    else if (s2 == NULL)
	return 1;		/* "anything > NULL */
    
    i1 = strlen((char *)s1) + 1;
    i2 = strlen((char *)s2) + 1;

    MALLOC(as1, char, 2 * (i1 + i2) + 4);	/* accent chars */
    ts1 = as1 + i1 + 1;
    as2 = ts1 + i1 + 1;
    ts2 = as2 + i2 + 1;

    REDUCE( s1, as1, ts1);
    REDUCE( s2, as2, ts2);
    
    if ( (value = strcmp(ts1, ts2) ) ) {
	FREE(as1);
	return( value );		/* strings differ already */
    }
    /*
     *	at this point, ts1 == ts2, and we need to decide if
     *  the accents (if any) break the tie.
     */
    value = strcmp( as1, as2 );
    FREE(as1);
    return value;
}


#ifdef MAIN

#ifndef ON_IBMPC
#include <ctype.h>

void crtaccents(s)
unsigned char *s;
{
    while ( s && *s ) {
	switch( *s ) {
	    case '\r':	(void)printf("\\r");		break;
	    case '\b':	(void)printf("\\b");		break;
	    case '\t':	(void)printf("\\t");		break;
	    case '\f':	(void)printf("\\f");		break;
	    default  :  if (isascii( *s ))
	                    (void)putchar(*s);
			else
	                    (void)printf("\\%03o",*s);
			break;
	}
	s++;
    }
}
#endif

#define COMPARE( check, s1, s2 )	{	\
    (void)PRINT(s1) ;				\
    result = stracmp( (unsigned char *)s1, (unsigned char *)s2 );	\
    if (result < 0) (void)printf(" < ");	\
    else if (result > 0) (void)printf(" > ");	\
    else (void)printf(" = ");			\
    (void)PRINT(s2); (void)putchar('\t');	\
    if (result == check) (void)printf("OK\n");	\
    else (void)printf("WRONG!!!\n");		\
}

main() {
    int  result;
    
    (void)printf("stracmp demo - version %s\n", VERSION);
    
    /* These tests assume IBM ROM tables */

    COMPARE( 0,"John Plocher", "John Plocher");			/* = */
    COMPARE( 0,"John[ Michael] Plocher[@hobbes.UUCP]", "John Plocher");	/* = */
    COMPARE( 0,"John Plocher", "John[ Michael] Plocher");	/* = */
    COMPARE(-1,"John Plocher", "J\242hn Plocher");		/* < */
    COMPARE( 1,"J\242hn Pl\242cher", "J\242hn Plocher");	/* > */
    COMPARE( 1,"J\242hn P\242lcher", "J\242hn Pl\242cher");	/* > */
    COMPARE( 0,"J\242hn Pl\242cher", "J\242hn Pl\242cher");	/* = */
    COMPARE( 1,"J\242\242n Pl\242cher", "J\242hn Pl\242cher");	/* > */
    return(0);
}

#endif

-- 
John Plocher uwvax!geowhiz!uwspan!plocher  plocher%uwspan.UUCP@uwvax.CS.WISC.EDU