• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 *   Copyright (C) 2011-2014, International Business Machines
6 *   Corporation and others.  All Rights Reserved.
7 *******************************************************************************
8 *   file name:  ppucd.cpp
9 *   encoding:   UTF-8
10 *   tab size:   8 (not used)
11 *   indentation:4
12 *
13 *   created on: 2011dec11
14 *   created by: Markus W. Scherer
15 */
16 
17 #include "unicode/utypes.h"
18 #include "unicode/uchar.h"
19 #include "charstr.h"
20 #include "cstring.h"
21 #include "ppucd.h"
22 #include "uassert.h"
23 #include "uparse.h"
24 
25 #include <stdio.h>
26 #include <string.h>
27 
28 U_NAMESPACE_BEGIN
29 
~PropertyNames()30 PropertyNames::~PropertyNames() {}
31 
UniProps()32 UniProps::UniProps()
33         : start(U_SENTINEL), end(U_SENTINEL),
34           bmg(U_SENTINEL), bpb(U_SENTINEL),
35           scf(U_SENTINEL), slc(U_SENTINEL), stc(U_SENTINEL), suc(U_SENTINEL),
36           digitValue(-1), numericValue(nullptr),
37           name(nullptr), nameAlias(nullptr) {
38     memset(binProps, 0, sizeof(binProps));
39     memset(intProps, 0, sizeof(intProps));
40     memset(age, 0, 4);
41 }
42 
~UniProps()43 UniProps::~UniProps() {}
44 
45 const int32_t PreparsedUCD::kNumLineBuffers;
46 
PreparsedUCD(const char * filename,UErrorCode & errorCode)47 PreparsedUCD::PreparsedUCD(const char *filename, UErrorCode &errorCode)
48         : pnames(nullptr),
49           file(nullptr),
50           defaultLineIndex(-1), blockLineIndex(-1), lineIndex(0),
51           lineNumber(0),
52           lineType(NO_LINE),
53           fieldLimit(nullptr), lineLimit(nullptr) {
54     if(U_FAILURE(errorCode)) { return; }
55 
56     if(filename==nullptr || *filename==0 || (*filename=='-' && filename[1]==0)) {
57         filename=nullptr;
58         file=stdin;
59     } else {
60         file=fopen(filename, "r");
61     }
62     if(file==nullptr) {
63         perror("error opening preparsed UCD");
64         fprintf(stderr, "error opening preparsed UCD file %s\n", filename ? filename : "\"no file name given\"");
65         errorCode=U_FILE_ACCESS_ERROR;
66         return;
67     }
68 
69     memset(ucdVersion, 0, 4);
70     lines[0][0]=0;
71 }
72 
~PreparsedUCD()73 PreparsedUCD::~PreparsedUCD() {
74     if(file!=stdin) {
75         fclose(file);
76     }
77 }
78 
79 // Same order as the LineType values.
80 static const char *lineTypeStrings[]={
81     nullptr,
82     nullptr,
83     "ucd",
84     "property",
85     "binary",
86     "value",
87     "defaults",
88     "block",
89     "cp",
90     "unassigned",
91     "algnamesrange"
92 };
93 
94 PreparsedUCD::LineType
readLine(UErrorCode & errorCode)95 PreparsedUCD::readLine(UErrorCode &errorCode) {
96     if(U_FAILURE(errorCode)) { return NO_LINE; }
97     // Select the next available line buffer.
98     while(!isLineBufferAvailable(lineIndex)) {
99         ++lineIndex;
100         if (lineIndex == kNumLineBuffers) {
101             lineIndex = 0;
102         }
103     }
104     char *line=lines[lineIndex];
105     *line=0;
106     lineLimit=fieldLimit=line;
107     lineType=NO_LINE;
108     char *result=fgets(line, sizeof(lines[0]), file);
109     if(result==nullptr) {
110         if(ferror(file)) {
111             perror("error reading preparsed UCD");
112             fprintf(stderr, "error reading preparsed UCD before line %ld\n", (long)lineNumber);
113             errorCode=U_FILE_ACCESS_ERROR;
114         }
115         return NO_LINE;
116     }
117     ++lineNumber;
118     if(*line=='#') {
119         fieldLimit=strchr(line, 0);
120         return lineType=EMPTY_LINE;
121     }
122     // Remove trailing /r/n.
123     char c;
124     char *limit=strchr(line, 0);
125     while(line<limit && ((c=*(limit-1))=='\n' || c=='\r')) { --limit; }
126     // Remove trailing white space.
127     while(line<limit && ((c=*(limit-1))==' ' || c=='\t')) { --limit; }
128     *limit=0;
129     lineLimit=limit;
130     if(line==limit) {
131         fieldLimit=limit;
132         return lineType=EMPTY_LINE;
133     }
134     // Split by ';'.
135     char *semi=line;
136     while((semi=strchr(semi, ';'))!=nullptr) { *semi++=0; }
137     fieldLimit=strchr(line, 0);
138     // Determine the line type.
139     int32_t type;
140     for(type=EMPTY_LINE+1;; ++type) {
141         if(type==LINE_TYPE_COUNT) {
142             fprintf(stderr,
143                     "error in preparsed UCD: unknown line type (first field) '%s' on line %ld\n",
144                     line, (long)lineNumber);
145             errorCode=U_PARSE_ERROR;
146             return NO_LINE;
147         }
148         if(0==strcmp(line, lineTypeStrings[type])) {
149             break;
150         }
151     }
152     lineType=(LineType)type;
153     if(lineType==UNICODE_VERSION_LINE && fieldLimit<lineLimit) {
154         u_versionFromString(ucdVersion, fieldLimit+1);
155     }
156     return lineType;
157 }
158 
159 const char *
firstField()160 PreparsedUCD::firstField() {
161     char *field=lines[lineIndex];
162     fieldLimit=strchr(field, 0);
163     return field;
164 }
165 
166 const char *
nextField()167 PreparsedUCD::nextField() {
168     if(fieldLimit==lineLimit) { return nullptr; }
169     char *field=fieldLimit+1;
170     fieldLimit=strchr(field, 0);
171     return field;
172 }
173 
174 const UniProps *
getProps(UnicodeSet & newValues,UErrorCode & errorCode)175 PreparsedUCD::getProps(UnicodeSet &newValues, UErrorCode &errorCode) {
176     if(U_FAILURE(errorCode)) { return nullptr; }
177     newValues.clear();
178     if(!lineHasPropertyValues()) {
179         errorCode=U_ILLEGAL_ARGUMENT_ERROR;
180         return nullptr;
181     }
182     firstField();
183     const char *field=nextField();
184     if(field==nullptr) {
185         // No range field after the type.
186         fprintf(stderr,
187                 "error in preparsed UCD: missing default/block/cp range field "
188                 "(no second field) on line %ld\n",
189                 (long)lineNumber);
190         errorCode=U_PARSE_ERROR;
191         return nullptr;
192     }
193     UChar32 start, end;
194     if(!parseCodePointRange(field, start, end, errorCode)) { return nullptr; }
195     UniProps *props;
196     UBool insideBlock=false;  // true if cp or unassigned range inside the block range.
197     switch(lineType) {
198     case DEFAULTS_LINE:
199         // Should occur before any block/cp/unassigned line.
200         if(blockLineIndex>=0) {
201             fprintf(stderr,
202                     "error in preparsed UCD: default line %ld after one or more block lines\n",
203                     (long)lineNumber);
204             errorCode=U_PARSE_ERROR;
205             return nullptr;
206         }
207         if(defaultLineIndex>=0) {
208             fprintf(stderr,
209                     "error in preparsed UCD: second line with default properties on line %ld\n",
210                     (long)lineNumber);
211             errorCode=U_PARSE_ERROR;
212             return nullptr;
213         }
214         if(start!=0 || end!=0x10ffff) {
215             fprintf(stderr,
216                     "error in preparsed UCD: default range must be 0..10FFFF, not '%s' on line %ld\n",
217                     field, (long)lineNumber);
218             errorCode=U_PARSE_ERROR;
219             return nullptr;
220         }
221         props=&defaultProps;
222         defaultLineIndex=lineIndex;
223         break;
224     case BLOCK_LINE:
225         blockProps=defaultProps;  // Block inherits default properties.
226         props=&blockProps;
227         blockLineIndex=lineIndex;
228         break;
229     case CP_LINE:
230     case UNASSIGNED_LINE:
231         if(blockProps.start<=start && end<=blockProps.end) {
232             insideBlock=true;
233             if(lineType==CP_LINE) {
234                 // Code point range fully inside the last block inherits the block properties.
235                 cpProps=blockProps;
236             } else {
237                 // Unassigned line inside the block is based on default properties
238                 // which override block properties.
239                 cpProps=defaultProps;
240                 newValues=blockValues;
241                 // Except, it inherits the one blk=Block property.
242                 int32_t blkIndex=UCHAR_BLOCK-UCHAR_INT_START;
243                 cpProps.intProps[blkIndex]=blockProps.intProps[blkIndex];
244                 newValues.remove((UChar32)UCHAR_BLOCK);
245             }
246         } else if(start>blockProps.end || end<blockProps.start) {
247             // Code point range fully outside the last block inherits the default properties.
248             cpProps=defaultProps;
249         } else {
250             // Code point range partially overlapping with the last block is illegal.
251             fprintf(stderr,
252                     "error in preparsed UCD: cp range %s on line %ld only "
253                     "partially overlaps with block range %04lX..%04lX\n",
254                     field, (long)lineNumber, (long)blockProps.start, (long)blockProps.end);
255             errorCode=U_PARSE_ERROR;
256             return nullptr;
257         }
258         props=&cpProps;
259         break;
260     default:
261         // Will not occur because of the range check above.
262         errorCode=U_ILLEGAL_ARGUMENT_ERROR;
263         return nullptr;
264     }
265     props->start=start;
266     props->end=end;
267     while((field=nextField())!=nullptr) {
268         if(!parseProperty(*props, field, newValues, errorCode)) { return nullptr; }
269     }
270     if(lineType==BLOCK_LINE) {
271         blockValues=newValues;
272     } else if(lineType==UNASSIGNED_LINE && insideBlock) {
273         // Unset newValues for values that are the same as the block values.
274         for(int32_t prop=0; prop<UCHAR_BINARY_LIMIT; ++prop) {
275             if(newValues.contains(prop) && cpProps.binProps[prop]==blockProps.binProps[prop]) {
276                 newValues.remove(prop);
277             }
278         }
279         for(int32_t prop=UCHAR_INT_START; prop<UCHAR_INT_LIMIT; ++prop) {
280             int32_t index=prop-UCHAR_INT_START;
281             if(newValues.contains(prop) && cpProps.intProps[index]==blockProps.intProps[index]) {
282                 newValues.remove(prop);
283             }
284         }
285     }
286     return props;
287 }
288 
289 static const struct {
290     const char *name;
291     int32_t prop;
292 } ppucdProperties[]={
293     { "Name_Alias", PPUCD_NAME_ALIAS },
294     { "Conditional_Case_Mappings", PPUCD_CONDITIONAL_CASE_MAPPINGS },
295     { "Turkic_Case_Folding", PPUCD_TURKIC_CASE_FOLDING }
296 };
297 
298 // Returns true for "ok to continue parsing fields".
299 UBool
parseProperty(UniProps & props,const char * field,UnicodeSet & newValues,UErrorCode & errorCode)300 PreparsedUCD::parseProperty(UniProps &props, const char *field, UnicodeSet &newValues,
301                             UErrorCode &errorCode) {
302     CharString pBuffer;
303     const char *p=field;
304     const char *v=strchr(p, '=');
305     int binaryValue;
306     if(*p=='-') {
307         if(v!=nullptr) {
308             fprintf(stderr,
309                     "error in preparsed UCD: mix of binary-property-no and "
310                     "enum-property syntax '%s' on line %ld\n",
311                     field, (long)lineNumber);
312             errorCode=U_PARSE_ERROR;
313             return false;
314         }
315         binaryValue=0;
316         ++p;
317     } else if(v==nullptr) {
318         binaryValue=1;
319     } else {
320         binaryValue=-1;
321         // Copy out the property name rather than modifying the field (writing a NUL).
322         pBuffer.append(p, (int32_t)(v-p), errorCode);
323         p=pBuffer.data();
324         ++v;
325     }
326     int32_t prop=pnames->getPropertyEnum(p);
327     if(prop<0) {
328         for(int32_t i=0;; ++i) {
329             if(i==UPRV_LENGTHOF(ppucdProperties)) {
330                 // Ignore unknown property names.
331                 return true;
332             }
333             if(0==uprv_stricmp(p, ppucdProperties[i].name)) {
334                 prop=ppucdProperties[i].prop;
335                 U_ASSERT(prop>=0);
336                 break;
337             }
338         }
339     }
340     if(prop<UCHAR_BINARY_LIMIT) {
341         if(binaryValue>=0) {
342             props.binProps[prop]=(UBool)binaryValue;
343         } else {
344             // No binary value for a binary property.
345             fprintf(stderr,
346                     "error in preparsed UCD: enum-property syntax '%s' "
347                     "for binary property on line %ld\n",
348                     field, (long)lineNumber);
349             errorCode=U_PARSE_ERROR;
350         }
351     } else if(binaryValue>=0) {
352         // Binary value for a non-binary property.
353         fprintf(stderr,
354                 "error in preparsed UCD: binary-property syntax '%s' "
355                 "for non-binary property on line %ld\n",
356                 field, (long)lineNumber);
357         errorCode=U_PARSE_ERROR;
358     } else if (prop < UCHAR_INT_START) {
359         fprintf(stderr,
360                 "error in preparsed UCD: prop value is invalid: '%d' for line %ld\n",
361                 prop, (long)lineNumber);
362         errorCode=U_PARSE_ERROR;
363     } else if(prop<UCHAR_INT_LIMIT) {
364         int32_t value=pnames->getPropertyValueEnum(prop, v);
365         if(value==UCHAR_INVALID_CODE && prop==UCHAR_CANONICAL_COMBINING_CLASS) {
366             // TODO: Make getPropertyValueEnum(UCHAR_CANONICAL_COMBINING_CLASS, v) work.
367             char *end;
368             unsigned long ccc=uprv_strtoul(v, &end, 10);
369             if(v<end && *end==0 && ccc<=254) {
370                 value=(int32_t)ccc;
371             }
372         }
373         if(value==UCHAR_INVALID_CODE) {
374             fprintf(stderr,
375                     "error in preparsed UCD: '%s' is not a valid value on line %ld\n",
376                     field, (long)lineNumber);
377             errorCode=U_PARSE_ERROR;
378         } else {
379             props.intProps[prop-UCHAR_INT_START]=value;
380         }
381     } else if(*v=='<') {
382         // Do not parse default values like <code point>, just set null values.
383         switch(prop) {
384         case UCHAR_BIDI_MIRRORING_GLYPH:
385             props.bmg=U_SENTINEL;
386             break;
387         case UCHAR_BIDI_PAIRED_BRACKET:
388             props.bpb=U_SENTINEL;
389             break;
390         case UCHAR_SIMPLE_CASE_FOLDING:
391             props.scf=U_SENTINEL;
392             break;
393         case UCHAR_SIMPLE_LOWERCASE_MAPPING:
394             props.slc=U_SENTINEL;
395             break;
396         case UCHAR_SIMPLE_TITLECASE_MAPPING:
397             props.stc=U_SENTINEL;
398             break;
399         case UCHAR_SIMPLE_UPPERCASE_MAPPING:
400             props.suc=U_SENTINEL;
401             break;
402         case UCHAR_CASE_FOLDING:
403             props.cf.remove();
404             break;
405         case UCHAR_LOWERCASE_MAPPING:
406             props.lc.remove();
407             break;
408         case UCHAR_TITLECASE_MAPPING:
409             props.tc.remove();
410             break;
411         case UCHAR_UPPERCASE_MAPPING:
412             props.uc.remove();
413             break;
414         case UCHAR_SCRIPT_EXTENSIONS:
415             props.scx.clear();
416             break;
417         default:
418             fprintf(stderr,
419                     "error in preparsed UCD: '%s' is not a valid default value on line %ld\n",
420                     field, (long)lineNumber);
421             errorCode=U_PARSE_ERROR;
422         }
423     } else {
424         char c;
425         switch(prop) {
426         case UCHAR_NUMERIC_VALUE:
427             props.numericValue=v;
428             c=*v;
429             if('0'<=c && c<='9' && v[1]==0) {
430                 props.digitValue=c-'0';
431             } else {
432                 props.digitValue=-1;
433             }
434             break;
435         case UCHAR_NAME:
436             props.name=v;
437             break;
438         case UCHAR_AGE:
439             u_versionFromString(props.age, v);  // Writes 0.0.0.0 if v is not numeric.
440             break;
441         case UCHAR_BIDI_MIRRORING_GLYPH:
442             props.bmg=parseCodePoint(v, errorCode);
443             break;
444         case UCHAR_BIDI_PAIRED_BRACKET:
445             props.bpb=parseCodePoint(v, errorCode);
446             break;
447         case UCHAR_SIMPLE_CASE_FOLDING:
448             props.scf=parseCodePoint(v, errorCode);
449             break;
450         case UCHAR_SIMPLE_LOWERCASE_MAPPING:
451             props.slc=parseCodePoint(v, errorCode);
452             break;
453         case UCHAR_SIMPLE_TITLECASE_MAPPING:
454             props.stc=parseCodePoint(v, errorCode);
455             break;
456         case UCHAR_SIMPLE_UPPERCASE_MAPPING:
457             props.suc=parseCodePoint(v, errorCode);
458             break;
459         case UCHAR_CASE_FOLDING:
460             parseString(v, props.cf, errorCode);
461             break;
462         case UCHAR_LOWERCASE_MAPPING:
463             parseString(v, props.lc, errorCode);
464             break;
465         case UCHAR_TITLECASE_MAPPING:
466             parseString(v, props.tc, errorCode);
467             break;
468         case UCHAR_UPPERCASE_MAPPING:
469             parseString(v, props.uc, errorCode);
470             break;
471         case PPUCD_NAME_ALIAS:
472             props.nameAlias=v;
473             break;
474         case PPUCD_CONDITIONAL_CASE_MAPPINGS:
475         case PPUCD_TURKIC_CASE_FOLDING:
476             // No need to parse their values: They are hardcoded in the runtime library.
477             break;
478         case UCHAR_SCRIPT_EXTENSIONS:
479             parseScriptExtensions(v, props.scx, errorCode);
480             break;
481         case UCHAR_IDENTIFIER_TYPE:
482             parseIdentifierType(v, props.idType, errorCode);
483             break;
484         default:
485             // Ignore unhandled properties.
486             return true;
487         }
488     }
489     if(U_SUCCESS(errorCode)) {
490         newValues.add((UChar32)prop);
491         return true;
492     } else {
493         return false;
494     }
495 }
496 
497 UBool
getRangeForAlgNames(UChar32 & start,UChar32 & end,UErrorCode & errorCode)498 PreparsedUCD::getRangeForAlgNames(UChar32 &start, UChar32 &end, UErrorCode &errorCode) {
499     if(U_FAILURE(errorCode)) { return false; }
500     if(lineType!=ALG_NAMES_RANGE_LINE) {
501         errorCode=U_ILLEGAL_ARGUMENT_ERROR;
502         return false;
503     }
504     firstField();
505     const char *field=nextField();
506     if(field==nullptr) {
507         // No range field after the type.
508         fprintf(stderr,
509                 "error in preparsed UCD: missing algnamesrange range field "
510                 "(no second field) on line %ld\n",
511                 (long)lineNumber);
512         errorCode=U_PARSE_ERROR;
513         return false;
514     }
515     return parseCodePointRange(field, start, end, errorCode);
516 }
517 
518 UChar32
parseCodePoint(const char * s,UErrorCode & errorCode)519 PreparsedUCD::parseCodePoint(const char *s, UErrorCode &errorCode) {
520     char *end;
521     uint32_t value=(uint32_t)uprv_strtoul(s, &end, 16);
522     if(end<=s || *end!=0 || value>=0x110000) {
523         fprintf(stderr,
524                 "error in preparsed UCD: '%s' is not a valid code point on line %ld\n",
525                 s, (long)lineNumber);
526         errorCode=U_PARSE_ERROR;
527         return U_SENTINEL;
528     }
529     return (UChar32)value;
530 }
531 
532 UBool
parseCodePointRange(const char * s,UChar32 & start,UChar32 & end,UErrorCode & errorCode)533 PreparsedUCD::parseCodePointRange(const char *s, UChar32 &start, UChar32 &end, UErrorCode &errorCode) {
534     uint32_t st, e;
535     u_parseCodePointRange(s, &st, &e, &errorCode);
536     if(U_FAILURE(errorCode)) {
537         fprintf(stderr,
538                 "error in preparsed UCD: '%s' is not a valid code point range on line %ld\n",
539                 s, (long)lineNumber);
540         return false;
541     }
542     start=(UChar32)st;
543     end=(UChar32)e;
544     return true;
545 }
546 
547 void
parseString(const char * s,UnicodeString & uni,UErrorCode & errorCode)548 PreparsedUCD::parseString(const char *s, UnicodeString &uni, UErrorCode &errorCode) {
549     char16_t *buffer=toUCharPtr(uni.getBuffer(-1));
550     int32_t length=u_parseString(s, buffer, uni.getCapacity(), nullptr, &errorCode);
551     if(errorCode==U_BUFFER_OVERFLOW_ERROR) {
552         errorCode=U_ZERO_ERROR;
553         uni.releaseBuffer(0);
554         buffer=toUCharPtr(uni.getBuffer(length));
555         length=u_parseString(s, buffer, uni.getCapacity(), nullptr, &errorCode);
556     }
557     uni.releaseBuffer(length);
558     if(U_FAILURE(errorCode)) {
559         fprintf(stderr,
560                 "error in preparsed UCD: '%s' is not a valid Unicode string on line %ld\n",
561                 s, (long)lineNumber);
562     }
563 }
564 
565 void
parseScriptExtensions(const char * s,UnicodeSet & scx,UErrorCode & errorCode)566 PreparsedUCD::parseScriptExtensions(const char *s, UnicodeSet &scx, UErrorCode &errorCode) {
567     if(U_FAILURE(errorCode)) { return; }
568     scx.clear();
569     CharString scString;
570     for(;;) {
571         const char *scs;
572         const char *scLimit=strchr(s, ' ');
573         if(scLimit!=nullptr) {
574             scs=scString.clear().append(s, (int32_t)(scLimit-s), errorCode).data();
575             if(U_FAILURE(errorCode)) { return; }
576         } else {
577             scs=s;
578         }
579         int32_t script=pnames->getPropertyValueEnum(UCHAR_SCRIPT, scs);
580         if(script==UCHAR_INVALID_CODE) {
581             fprintf(stderr,
582                     "error in preparsed UCD: '%s' is not a valid script code on line %ld\n",
583                     scs, (long)lineNumber);
584             errorCode=U_PARSE_ERROR;
585             return;
586         } else if(scx.contains(script)) {
587             fprintf(stderr,
588                     "error in preparsed UCD: scx has duplicate '%s' codes on line %ld\n",
589                     scs, (long)lineNumber);
590             errorCode=U_PARSE_ERROR;
591             return;
592         } else {
593             scx.add(script);
594         }
595         if(scLimit!=nullptr) {
596             s=scLimit+1;
597         } else {
598             break;
599         }
600     }
601     if(scx.isEmpty()) {
602         fprintf(stderr, "error in preparsed UCD: empty scx= on line %ld\n", (long)lineNumber);
603         errorCode=U_PARSE_ERROR;
604     }
605 }
606 
607 void
parseIdentifierType(const char * s,UnicodeSet & idType,UErrorCode & errorCode)608 PreparsedUCD::parseIdentifierType(const char *s, UnicodeSet &idType, UErrorCode &errorCode) {
609     if(U_FAILURE(errorCode)) { return; }
610     idType.clear();
611     CharString typeString;
612     for(;;) {
613         const char *typeChars;
614         const char *limit=strchr(s, ' ');
615         if(limit!=nullptr) {
616             typeChars=typeString.clear().append(s, (int32_t)(limit-s), errorCode).data();
617             if(U_FAILURE(errorCode)) { return; }
618         } else {
619             typeChars=s;
620         }
621         int32_t type=pnames->getPropertyValueEnum(UCHAR_IDENTIFIER_TYPE, typeChars);
622         if(type==UCHAR_INVALID_CODE) {
623             fprintf(stderr,
624                     "error in preparsed UCD: '%s' is not a valid Identifier_Type on line %ld\n",
625                     typeChars, (long)lineNumber);
626             errorCode=U_PARSE_ERROR;
627             return;
628         } else if(idType.contains(type)) {
629             fprintf(stderr,
630                     "error in preparsed UCD: Identifier_Type has duplicate '%s' values on line %ld\n",
631                     typeChars, (long)lineNumber);
632             errorCode=U_PARSE_ERROR;
633             return;
634         } else {
635             idType.add(type);
636         }
637         if(limit!=nullptr) {
638             s=limit+1;
639         } else {
640             break;
641         }
642     }
643     if(idType.isEmpty()) {
644         fprintf(stderr,
645                 "error in preparsed UCD: empty Identifier_Type= on line %ld\n",
646                 (long)lineNumber);
647         errorCode=U_PARSE_ERROR;
648     }
649 }
650 
651 U_NAMESPACE_END
652