1286432Sbapt/* 2286432Sbapt * Copyright 2011 Nexenta Systems, Inc. All rights reserved. 3286432Sbapt * Copyright 2012 Garrett D'Amore <garrett@damore.org> All rights reserved. 4286432Sbapt * Copyright 2015 John Marino <draco@marino.st> 5286432Sbapt * 6286432Sbapt * This source code is derived from the illumos localedef command, and 7286432Sbapt * provided under BSD-style license terms by Nexenta Systems, Inc. 8286432Sbapt * 9286432Sbapt * Redistribution and use in source and binary forms, with or without 10286432Sbapt * modification, are permitted provided that the following conditions 11286432Sbapt * are met: 12286432Sbapt * 13286432Sbapt * 1. Redistributions of source code must retain the above copyright 14286432Sbapt * notice, this list of conditions and the following disclaimer. 15286432Sbapt * 2. Redistributions in binary form must reproduce the above copyright 16286432Sbapt * notice, this list of conditions and the following disclaimer in the 17286432Sbapt * documentation and/or other materials provided with the distribution. 18286432Sbapt * 19286432Sbapt * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20286432Sbapt * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21286432Sbapt * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22286432Sbapt * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23286432Sbapt * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24286432Sbapt * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25286432Sbapt * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26286432Sbapt * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27286432Sbapt * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28286432Sbapt * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29286432Sbapt * POSSIBILITY OF SUCH DAMAGE. 30286432Sbapt */ 31286432Sbapt 32286432Sbapt/* 33286432Sbapt * LC_CTYPE database generation routines for localedef. 34286432Sbapt */ 35286432Sbapt#include <sys/cdefs.h> 36286432Sbapt__FBSDID("$FreeBSD: stable/11/usr.bin/localedef/ctype.c 341631 2018-12-06 11:52:07Z yuripv $"); 37286432Sbapt 38286481Sbapt#include <sys/tree.h> 39286432Sbapt 40286432Sbapt#include <stdio.h> 41286432Sbapt#include <stdlib.h> 42286432Sbapt#include <stddef.h> 43286432Sbapt#include <string.h> 44286432Sbapt#include <sys/types.h> 45286432Sbapt#include <wchar.h> 46286432Sbapt#include <ctype.h> 47286432Sbapt#include <wctype.h> 48286432Sbapt#include <unistd.h> 49286432Sbapt#include "localedef.h" 50286432Sbapt#include "parser.h" 51286432Sbapt#include "runefile.h" 52286432Sbapt 53286432Sbapt 54289263Sbapt/* Needed for bootstrapping, _CTYPE_N */ 55289263Sbapt#ifndef _CTYPE_N 56289263Sbapt#define _CTYPE_N 0x00400000L 57289263Sbapt#endif 58289263Sbapt 59286432Sbapt#define _ISUPPER _CTYPE_U 60286432Sbapt#define _ISLOWER _CTYPE_L 61286432Sbapt#define _ISDIGIT _CTYPE_D 62286432Sbapt#define _ISXDIGIT _CTYPE_X 63286432Sbapt#define _ISSPACE _CTYPE_S 64286432Sbapt#define _ISBLANK _CTYPE_B 65286432Sbapt#define _ISALPHA _CTYPE_A 66286432Sbapt#define _ISPUNCT _CTYPE_P 67286432Sbapt#define _ISGRAPH _CTYPE_G 68286432Sbapt#define _ISPRINT _CTYPE_R 69286432Sbapt#define _ISCNTRL _CTYPE_C 70286432Sbapt#define _E1 _CTYPE_Q 71286432Sbapt#define _E2 _CTYPE_I 72286432Sbapt#define _E3 0 73289263Sbapt#define _E4 _CTYPE_N 74286432Sbapt#define _E5 _CTYPE_T 75286432Sbapt 76286432Sbaptstatic wchar_t last_ctype; 77286481Sbaptstatic int ctype_compare(const void *n1, const void *n2); 78286432Sbapt 79286432Sbapttypedef struct ctype_node { 80286432Sbapt wchar_t wc; 81286432Sbapt int32_t ctype; 82286432Sbapt int32_t toupper; 83286432Sbapt int32_t tolower; 84286481Sbapt RB_ENTRY(ctype_node) entry; 85286432Sbapt} ctype_node_t; 86286432Sbapt 87286484Sbaptstatic RB_HEAD(ctypes, ctype_node) ctypes; 88286482SbaptRB_GENERATE_STATIC(ctypes, ctype_node, entry, ctype_compare); 89286432Sbapt 90286432Sbaptstatic int 91286432Sbaptctype_compare(const void *n1, const void *n2) 92286432Sbapt{ 93286432Sbapt const ctype_node_t *c1 = n1; 94286432Sbapt const ctype_node_t *c2 = n2; 95286432Sbapt 96286432Sbapt return (c1->wc < c2->wc ? -1 : c1->wc > c2->wc ? 1 : 0); 97286432Sbapt} 98286432Sbapt 99286432Sbaptvoid 100286432Sbaptinit_ctype(void) 101286432Sbapt{ 102286481Sbapt RB_INIT(&ctypes); 103286432Sbapt} 104286432Sbapt 105286432Sbapt 106286432Sbaptstatic void 107286432Sbaptadd_ctype_impl(ctype_node_t *ctn) 108286432Sbapt{ 109286432Sbapt switch (last_kw) { 110286432Sbapt case T_ISUPPER: 111286432Sbapt ctn->ctype |= (_ISUPPER | _ISALPHA | _ISGRAPH | _ISPRINT); 112286432Sbapt break; 113286432Sbapt case T_ISLOWER: 114286432Sbapt ctn->ctype |= (_ISLOWER | _ISALPHA | _ISGRAPH | _ISPRINT); 115286432Sbapt break; 116286432Sbapt case T_ISALPHA: 117286432Sbapt ctn->ctype |= (_ISALPHA | _ISGRAPH | _ISPRINT); 118286432Sbapt break; 119286432Sbapt case T_ISDIGIT: 120289586Sbapt ctn->ctype |= (_ISDIGIT | _ISGRAPH | _ISPRINT | _ISXDIGIT | _E4); 121286432Sbapt break; 122286432Sbapt case T_ISSPACE: 123341631Syuripv /* 124341631Syuripv * This can be troublesome as <form-feed>, <newline>, 125341631Syuripv * <carriage-return>, <tab>, and <vertical-tab> are defined both 126341631Syuripv * as space and cntrl, and POSIX doesn't allow cntrl/print 127341631Syuripv * combination. We will take care of this in dump_ctype(). 128341631Syuripv */ 129341631Syuripv ctn->ctype |= (_ISSPACE | _ISPRINT); 130286432Sbapt break; 131286432Sbapt case T_ISCNTRL: 132286432Sbapt ctn->ctype |= _ISCNTRL; 133286432Sbapt break; 134286432Sbapt case T_ISGRAPH: 135286432Sbapt ctn->ctype |= (_ISGRAPH | _ISPRINT); 136286432Sbapt break; 137286432Sbapt case T_ISPRINT: 138286432Sbapt ctn->ctype |= _ISPRINT; 139286432Sbapt break; 140286432Sbapt case T_ISPUNCT: 141286432Sbapt ctn->ctype |= (_ISPUNCT | _ISGRAPH | _ISPRINT); 142286432Sbapt break; 143286432Sbapt case T_ISXDIGIT: 144289590Sbapt ctn->ctype |= (_ISXDIGIT | _ISPRINT); 145286432Sbapt break; 146286432Sbapt case T_ISBLANK: 147286432Sbapt ctn->ctype |= (_ISBLANK | _ISSPACE); 148286432Sbapt break; 149286432Sbapt case T_ISPHONOGRAM: 150286432Sbapt ctn->ctype |= (_E1 | _ISPRINT | _ISGRAPH); 151286432Sbapt break; 152286432Sbapt case T_ISIDEOGRAM: 153286432Sbapt ctn->ctype |= (_E2 | _ISPRINT | _ISGRAPH); 154286432Sbapt break; 155286432Sbapt case T_ISENGLISH: 156286432Sbapt ctn->ctype |= (_E3 | _ISPRINT | _ISGRAPH); 157286432Sbapt break; 158286432Sbapt case T_ISNUMBER: 159286432Sbapt ctn->ctype |= (_E4 | _ISPRINT | _ISGRAPH); 160286432Sbapt break; 161286432Sbapt case T_ISSPECIAL: 162286432Sbapt ctn->ctype |= (_E5 | _ISPRINT | _ISGRAPH); 163286432Sbapt break; 164286432Sbapt case T_ISALNUM: 165286432Sbapt /* 166286432Sbapt * We can't do anything with this. The character 167286432Sbapt * should already be specified as a digit or alpha. 168286432Sbapt */ 169286432Sbapt break; 170286432Sbapt default: 171286432Sbapt errf("not a valid character class"); 172286432Sbapt } 173286432Sbapt} 174286432Sbapt 175286432Sbaptstatic ctype_node_t * 176286432Sbaptget_ctype(wchar_t wc) 177286432Sbapt{ 178286432Sbapt ctype_node_t srch; 179286432Sbapt ctype_node_t *ctn; 180286432Sbapt 181286432Sbapt srch.wc = wc; 182286481Sbapt if ((ctn = RB_FIND(ctypes, &ctypes, &srch)) == NULL) { 183286432Sbapt if ((ctn = calloc(1, sizeof (*ctn))) == NULL) { 184286432Sbapt errf("out of memory"); 185286432Sbapt return (NULL); 186286432Sbapt } 187286432Sbapt ctn->wc = wc; 188286432Sbapt 189286481Sbapt RB_INSERT(ctypes, &ctypes, ctn); 190286432Sbapt } 191286432Sbapt return (ctn); 192286432Sbapt} 193286432Sbapt 194286432Sbaptvoid 195286432Sbaptadd_ctype(int val) 196286432Sbapt{ 197286432Sbapt ctype_node_t *ctn; 198286432Sbapt 199286432Sbapt if ((ctn = get_ctype(val)) == NULL) { 200286432Sbapt INTERR; 201286432Sbapt return; 202286432Sbapt } 203286432Sbapt add_ctype_impl(ctn); 204286432Sbapt last_ctype = ctn->wc; 205286432Sbapt} 206286432Sbapt 207286432Sbaptvoid 208290517Sbaptadd_ctype_range(wchar_t end) 209286432Sbapt{ 210286432Sbapt ctype_node_t *ctn; 211286432Sbapt wchar_t cur; 212286432Sbapt 213286432Sbapt if (end < last_ctype) { 214286432Sbapt errf("malformed character range (%u ... %u))", 215286432Sbapt last_ctype, end); 216286432Sbapt return; 217286432Sbapt } 218286432Sbapt for (cur = last_ctype + 1; cur <= end; cur++) { 219286432Sbapt if ((ctn = get_ctype(cur)) == NULL) { 220286432Sbapt INTERR; 221286432Sbapt return; 222286432Sbapt } 223286432Sbapt add_ctype_impl(ctn); 224286432Sbapt } 225286432Sbapt last_ctype = end; 226286432Sbapt 227286432Sbapt} 228286432Sbapt 229286432Sbapt/* 230286432Sbapt * A word about widths: if the width mask is specified, then libc 231286432Sbapt * unconditionally honors it. Otherwise, it assumes printable 232286432Sbapt * characters have width 1, and non-printable characters have width 233286432Sbapt * -1 (except for NULL which is special with with 0). Hence, we have 234286432Sbapt * no need to inject defaults here -- the "default" unset value of 0 235286432Sbapt * indicates that libc should use its own logic in wcwidth as described. 236286432Sbapt */ 237286432Sbaptvoid 238286432Sbaptadd_width(int wc, int width) 239286432Sbapt{ 240286432Sbapt ctype_node_t *ctn; 241286432Sbapt 242286432Sbapt if ((ctn = get_ctype(wc)) == NULL) { 243286432Sbapt INTERR; 244286432Sbapt return; 245286432Sbapt } 246286432Sbapt ctn->ctype &= ~(_CTYPE_SWM); 247286432Sbapt switch (width) { 248286432Sbapt case 0: 249286432Sbapt ctn->ctype |= _CTYPE_SW0; 250286432Sbapt break; 251286432Sbapt case 1: 252286432Sbapt ctn->ctype |= _CTYPE_SW1; 253286432Sbapt break; 254286432Sbapt case 2: 255286432Sbapt ctn->ctype |= _CTYPE_SW2; 256286432Sbapt break; 257286432Sbapt case 3: 258286432Sbapt ctn->ctype |= _CTYPE_SW3; 259286432Sbapt break; 260286432Sbapt } 261286432Sbapt} 262286432Sbapt 263286432Sbaptvoid 264286432Sbaptadd_width_range(int start, int end, int width) 265286432Sbapt{ 266286432Sbapt for (; start <= end; start++) { 267286432Sbapt add_width(start, width); 268286432Sbapt } 269286432Sbapt} 270286432Sbapt 271286432Sbaptvoid 272286432Sbaptadd_caseconv(int val, int wc) 273286432Sbapt{ 274286432Sbapt ctype_node_t *ctn; 275286432Sbapt 276286432Sbapt ctn = get_ctype(val); 277286432Sbapt if (ctn == NULL) { 278286432Sbapt INTERR; 279286432Sbapt return; 280286432Sbapt } 281286432Sbapt 282286432Sbapt switch (last_kw) { 283286432Sbapt case T_TOUPPER: 284286432Sbapt ctn->toupper = wc; 285286432Sbapt break; 286286432Sbapt case T_TOLOWER: 287286432Sbapt ctn->tolower = wc; 288286432Sbapt break; 289286432Sbapt default: 290286432Sbapt INTERR; 291286432Sbapt break; 292286432Sbapt } 293286432Sbapt} 294286432Sbapt 295286432Sbaptvoid 296286432Sbaptdump_ctype(void) 297286432Sbapt{ 298286432Sbapt FILE *f; 299286432Sbapt _FileRuneLocale rl; 300286432Sbapt ctype_node_t *ctn, *last_ct, *last_lo, *last_up; 301286432Sbapt _FileRuneEntry *ct = NULL; 302286432Sbapt _FileRuneEntry *lo = NULL; 303286432Sbapt _FileRuneEntry *up = NULL; 304286432Sbapt wchar_t wc; 305286432Sbapt 306286432Sbapt (void) memset(&rl, 0, sizeof (rl)); 307286432Sbapt last_ct = NULL; 308286432Sbapt last_lo = NULL; 309286432Sbapt last_up = NULL; 310286432Sbapt 311286432Sbapt if ((f = open_category()) == NULL) 312286432Sbapt return; 313286432Sbapt 314286432Sbapt (void) memcpy(rl.magic, _FILE_RUNE_MAGIC_1, 8); 315315224Spfg (void) strlcpy(rl.encoding, get_wide_encoding(), sizeof (rl.encoding)); 316286432Sbapt 317286432Sbapt /* 318286432Sbapt * Initialize the identity map. 319286432Sbapt */ 320286432Sbapt for (wc = 0; (unsigned)wc < _CACHED_RUNES; wc++) { 321286432Sbapt rl.maplower[wc] = wc; 322286432Sbapt rl.mapupper[wc] = wc; 323286432Sbapt } 324286432Sbapt 325286481Sbapt RB_FOREACH(ctn, ctypes, &ctypes) { 326286432Sbapt int conflict = 0; 327286432Sbapt 328286432Sbapt wc = ctn->wc; 329286432Sbapt 330286432Sbapt /* 331286432Sbapt * POSIX requires certain portable characters have 332286432Sbapt * certain types. Add them if they are missing. 333286432Sbapt */ 334286432Sbapt if ((wc >= 1) && (wc <= 127)) { 335286432Sbapt if ((wc >= 'A') && (wc <= 'Z')) 336286432Sbapt ctn->ctype |= _ISUPPER; 337286432Sbapt if ((wc >= 'a') && (wc <= 'z')) 338286432Sbapt ctn->ctype |= _ISLOWER; 339286432Sbapt if ((wc >= '0') && (wc <= '9')) 340286432Sbapt ctn->ctype |= _ISDIGIT; 341297057Spfg if (wc == ' ') 342297057Spfg ctn->ctype |= _ISPRINT; 343286432Sbapt if (strchr(" \f\n\r\t\v", (char)wc) != NULL) 344286432Sbapt ctn->ctype |= _ISSPACE; 345286432Sbapt if (strchr("0123456789ABCDEFabcdef", (char)wc) != NULL) 346286432Sbapt ctn->ctype |= _ISXDIGIT; 347286432Sbapt if (strchr(" \t", (char)wc)) 348286432Sbapt ctn->ctype |= _ISBLANK; 349286432Sbapt 350286432Sbapt /* 351286432Sbapt * Technically these settings are only 352286432Sbapt * required for the C locale. However, it 353286432Sbapt * turns out that because of the historical 354286432Sbapt * version of isprint(), we need them for all 355286432Sbapt * locales as well. Note that these are not 356286432Sbapt * necessarily valid punctation characters in 357286432Sbapt * the current language, but ispunct() needs 358286432Sbapt * to return TRUE for them. 359286432Sbapt */ 360286432Sbapt if (strchr("!\"'#$%&()*+,-./:;<=>?@[\\]^_`{|}~", 361286432Sbapt (char)wc)) 362286432Sbapt ctn->ctype |= _ISPUNCT; 363286432Sbapt } 364286432Sbapt 365286432Sbapt /* 366286432Sbapt * POSIX also requires that certain types imply 367286432Sbapt * others. Add any inferred types here. 368286432Sbapt */ 369286432Sbapt if (ctn->ctype & (_ISUPPER |_ISLOWER)) 370286432Sbapt ctn->ctype |= _ISALPHA; 371286432Sbapt if (ctn->ctype & _ISDIGIT) 372286432Sbapt ctn->ctype |= _ISXDIGIT; 373286432Sbapt if (ctn->ctype & _ISBLANK) 374286432Sbapt ctn->ctype |= _ISSPACE; 375286432Sbapt if (ctn->ctype & (_ISALPHA|_ISDIGIT|_ISXDIGIT)) 376286432Sbapt ctn->ctype |= _ISGRAPH; 377286432Sbapt if (ctn->ctype & _ISGRAPH) 378286432Sbapt ctn->ctype |= _ISPRINT; 379286432Sbapt 380286432Sbapt /* 381341631Syuripv * POSIX requires that certain combinations are invalid. 382341631Syuripv * Try fixing the cases we know about (see add_ctype_impl()). 383286432Sbapt */ 384341631Syuripv if ((ctn->ctype & (_ISSPACE|_ISCNTRL)) == (_ISSPACE|_ISCNTRL)) 385341631Syuripv ctn->ctype &= ~_ISPRINT; 386341631Syuripv 387341631Syuripv /* 388341631Syuripv * Finally, don't flag remaining cases as a fatal error, 389341631Syuripv * and just warn about them. 390341631Syuripv */ 391286432Sbapt if ((ctn->ctype & _ISALPHA) && 392286432Sbapt (ctn->ctype & (_ISPUNCT|_ISDIGIT))) 393286432Sbapt conflict++; 394315224Spfg if ((ctn->ctype & _ISPUNCT) && 395286432Sbapt (ctn->ctype & (_ISDIGIT|_ISALPHA|_ISXDIGIT))) 396286432Sbapt conflict++; 397286432Sbapt if ((ctn->ctype & _ISSPACE) && (ctn->ctype & _ISGRAPH)) 398286432Sbapt conflict++; 399315224Spfg if ((ctn->ctype & _ISCNTRL) && (ctn->ctype & _ISPRINT)) 400286432Sbapt conflict++; 401286432Sbapt if ((wc == ' ') && (ctn->ctype & (_ISPUNCT|_ISGRAPH))) 402286432Sbapt conflict++; 403286432Sbapt 404286432Sbapt if (conflict) { 405286432Sbapt warn("conflicting classes for character 0x%x (%x)", 406286432Sbapt wc, ctn->ctype); 407286432Sbapt } 408286432Sbapt /* 409286432Sbapt * Handle the lower 256 characters using the simple 410286432Sbapt * optimization. Note that if we have not defined the 411286432Sbapt * upper/lower case, then we identity map it. 412286432Sbapt */ 413286432Sbapt if ((unsigned)wc < _CACHED_RUNES) { 414286432Sbapt rl.runetype[wc] = ctn->ctype; 415286432Sbapt if (ctn->tolower) 416286432Sbapt rl.maplower[wc] = ctn->tolower; 417286432Sbapt if (ctn->toupper) 418286432Sbapt rl.mapupper[wc] = ctn->toupper; 419286432Sbapt continue; 420286432Sbapt } 421286432Sbapt 422308330Sbapt if ((last_ct != NULL) && (last_ct->ctype == ctn->ctype) && 423308330Sbapt (last_ct->wc + 1 == wc)) { 424286432Sbapt ct[rl.runetype_ext_nranges-1].max = wc; 425286432Sbapt } else { 426286432Sbapt rl.runetype_ext_nranges++; 427286432Sbapt ct = realloc(ct, 428286432Sbapt sizeof (*ct) * rl.runetype_ext_nranges); 429286432Sbapt ct[rl.runetype_ext_nranges - 1].min = wc; 430286432Sbapt ct[rl.runetype_ext_nranges - 1].max = wc; 431286432Sbapt ct[rl.runetype_ext_nranges - 1].map = ctn->ctype; 432286432Sbapt } 433308330Sbapt last_ct = ctn; 434286432Sbapt if (ctn->tolower == 0) { 435286432Sbapt last_lo = NULL; 436286432Sbapt } else if ((last_lo != NULL) && 437286432Sbapt (last_lo->tolower + 1 == ctn->tolower)) { 438286432Sbapt lo[rl.maplower_ext_nranges-1].max = wc; 439286432Sbapt last_lo = ctn; 440286432Sbapt } else { 441286432Sbapt rl.maplower_ext_nranges++; 442286432Sbapt lo = realloc(lo, 443286432Sbapt sizeof (*lo) * rl.maplower_ext_nranges); 444286432Sbapt lo[rl.maplower_ext_nranges - 1].min = wc; 445286432Sbapt lo[rl.maplower_ext_nranges - 1].max = wc; 446286432Sbapt lo[rl.maplower_ext_nranges - 1].map = ctn->tolower; 447286432Sbapt last_lo = ctn; 448286432Sbapt } 449286432Sbapt 450286432Sbapt if (ctn->toupper == 0) { 451286432Sbapt last_up = NULL; 452286432Sbapt } else if ((last_up != NULL) && 453286432Sbapt (last_up->toupper + 1 == ctn->toupper)) { 454286432Sbapt up[rl.mapupper_ext_nranges-1].max = wc; 455286432Sbapt last_up = ctn; 456286432Sbapt } else { 457286432Sbapt rl.mapupper_ext_nranges++; 458286432Sbapt up = realloc(up, 459286432Sbapt sizeof (*up) * rl.mapupper_ext_nranges); 460286432Sbapt up[rl.mapupper_ext_nranges - 1].min = wc; 461286432Sbapt up[rl.mapupper_ext_nranges - 1].max = wc; 462286432Sbapt up[rl.mapupper_ext_nranges - 1].map = ctn->toupper; 463286432Sbapt last_up = ctn; 464286432Sbapt } 465286432Sbapt } 466286432Sbapt 467286432Sbapt if ((wr_category(&rl, sizeof (rl), f) < 0) || 468286432Sbapt (wr_category(ct, sizeof (*ct) * rl.runetype_ext_nranges, f) < 0) || 469286432Sbapt (wr_category(lo, sizeof (*lo) * rl.maplower_ext_nranges, f) < 0) || 470286432Sbapt (wr_category(up, sizeof (*up) * rl.mapupper_ext_nranges, f) < 0)) { 471286432Sbapt return; 472286432Sbapt } 473286432Sbapt 474286432Sbapt close_category(f); 475286432Sbapt} 476