1 /* -*- mode: c; c-file-style: "k&r" -*-
2 
3   strnatcmp.c -- Perform 'natural order' comparisons of strings in C.
4   Copyright (C) 2000, 2004 by Martin Pool <mbp sourcefrog net>
5 
6   This software is provided 'as-is', without any express or implied
7   warranty.  In no event will the authors be held liable for any damages
8   arising from the use of this software.
9 
10   Permission is granted to anyone to use this software for any purpose,
11   including commercial applications, and to alter it and redistribute it
12   freely, subject to the following restrictions:
13 
14   1. The origin of this software must not be misrepresented; you must not
15      claim that you wrote the original software. If you use this software
16      in a product, an acknowledgment in the product documentation would be
17      appreciated but is not required.
18   2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20   3. This notice may not be removed or altered from any source distribution.
21 */
22 
23 /* partial change history:
24  *
25  * 2004-10-10 mbp: Lift out character type dependencies into macros.
26  *
27  * Eric Sosman pointed out that ctype functions take a parameter whose
28  * value must be that of an unsigned int, even on platforms that have
29  * negative chars in their default char type.
30  */
31 
32 #include <ctype.h>
33 #include <string.h>
34 #include <assert.h>
35 #include <stdio.h>
36 
37 #include "strnatcmp.h"
38 
39 // Compare two right-aligned numbers:
40 // The longest run of digits wins.  That aside, the greatest
41 // value wins, but we can't know that it will until we've scanned
42 // both numbers to know that they have the same magnitude, so we
43 // remember it in BIAS.
compare_right(char const * a,char const * b)44 static int compare_right(char const *a, char const *b)
45 {
46     int bias = 0;
47 
48     for (;; a++, b++) {
49         if (!isdigit(*a) && !isdigit(*b))
50             return bias;
51         else if (!isdigit(*a))
52             return -1;
53         else if (!isdigit(*b))
54             return +1;
55         else if (*a < *b) {
56             if (!bias)
57                 bias = -1;
58         } else if (*a > *b) {
59             if (!bias)
60                 bias = +1;
61         } else if (!*a && !*b)
62             return bias;
63     }
64 
65     return 0;
66 }
67 
68 // Compare two left-aligned numbers:
69 // The first to have a different value wins.
compare_left(char const * a,char const * b)70 static int compare_left(char const *a, char const *b)
71 {
72     for (;; a++, b++) {
73         if (!isdigit(*a) && !isdigit(*b))
74             return 0;
75         else if (!isdigit(*a))
76             return -1;
77         else if (!isdigit(*b))
78             return +1;
79         else if (*a < *b)
80             return -1;
81         else if (*a > *b)
82             return +1;
83     }
84 
85     return 0;
86 }
87 
strnatcmp0(char const * a,char const * b,int ignore_case)88 static int strnatcmp0(char const *a, char const *b, int ignore_case)
89 {
90     assert(a && b);
91 
92     int ai, bi;
93     ai = bi = 0;
94     while (1) {
95         char ca = a[ai];
96         char cb = b[bi];
97 
98         // Skip over leading spaces
99         while (isspace(ca)) {
100             ai++;
101             ca = a[ai];
102         }
103 
104         while (isspace(cb)) {
105             bi++;
106             cb = b[bi];
107         }
108 
109         // Process run of digits
110         if (isdigit(ca) && isdigit(cb)) {
111             int fractional = (ca == '0' || cb == '0');
112 
113             if (fractional) {
114                 int result = compare_left(a + ai, b + bi);
115                 if (result)
116                     return result;
117             } else {
118                 int result = compare_right(a + ai, b + bi);
119                 if (result)
120                     return result;
121             }
122         }
123 
124         if (!ca && !cb) {
125             // The strings compare the same.  Perhaps the caller will want to call strcmp to break the tie.
126             return 0;
127         }
128 
129         if (ignore_case) {
130             ca = toupper(ca);
131             cb = toupper(cb);
132         }
133 
134         if (ca < cb)
135             return -1;
136         else if (ca > cb)
137             return +1;
138 
139         ai++;
140         bi++;
141     }
142 }
143 
strnatcmp(char const * a,char const * b)144 int strnatcmp(char const *a, char const *b)
145 {
146     return strnatcmp0(a, b, 0);
147 }
148 
strnatcasecmp(char const * a,char const * b)149 int strnatcasecmp(char const *a, char const *b)
150 {
151     return strnatcmp0(a, b, 1);
152 }
153