You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

shortname.cc 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. #include <QtCore/QString>
  2. #include <QtCore/QRegexp>
  3. #include "shortname.h"
  4. const QString kVowels("aeiou");
  5. static struct replacements {
  6. const char* orig;
  7. const char* replacement;
  8. } replacements[] = {
  9. {"zero", "0"},
  10. {"one", "1"},
  11. {"two", "2"},
  12. {"three", "3"},
  13. {"four", "4"},
  14. {"five", "5"},
  15. {"six", "6"},
  16. {"seven", "7"},
  17. {"eight", "8"},
  18. {"nine", "9"},
  19. {NULL, NULL}
  20. };
  21. QString Shortname::DeleteLastVowel(int start, const QString& s,
  22. bool& replaced)
  23. {
  24. QString out = s;
  25. replaced = false;
  26. #if 1
  27. for (int i = s.length() -1; i > start; i--) {
  28. QChar c = s[i];
  29. // Preserve vowel if first of a word.
  30. if (i > 0) {
  31. if (s[i-1].isSpace()) {
  32. continue;
  33. }
  34. }
  35. if (kVowels.contains(c, Qt::CaseInsensitive)) {
  36. out = out.remove(i, 1);
  37. replaced = true;
  38. }
  39. }
  40. #endif
  41. return out;
  42. }
  43. QString Shortname::Replace(const QString& in)
  44. {
  45. QString out = in;
  46. struct replacements* r;
  47. for (r = replacements; r->orig; r++) {
  48. // while(int i = out.indexOf(r->orig, 1) > 2) {
  49. int i = out.indexOf(r->orig, 1);
  50. if (i > 1) {
  51. fprintf(stderr, "%d\n", i);
  52. if (out[i-1] == ' ') {
  53. out = out.replace(i, strlen(r->orig), r->replacement);
  54. }
  55. }
  56. }
  57. return out;
  58. }
  59. QString Shortname::Shorten(const QString& in)
  60. {
  61. // Eliminate leading and trailing whitespace and replace internal
  62. // whitespace sequences (tabs, multiple spaces, etc.) with a single space.
  63. QString out = in.simplified();
  64. if ((target_len_ == 6) && (out.length() == 7) && out.startsWith("GC")) {
  65. out = out.mid(1);
  66. }
  67. // Whack leading 'the'.
  68. if (out.startsWith("tHe ", Qt::CaseInsensitive)) {
  69. out = out.mid(4);
  70. }
  71. if (!whitespace_ok_) {
  72. out = out.replace(' ', "");
  73. }
  74. if (must_upper_) {
  75. out = out.toUpper();
  76. }
  77. out = Replace(out);
  78. // Strip forbidden characters.
  79. out.remove(QRegExp("[" + bad_chars_ + "]"));
  80. bool replaced = true;
  81. if (target_len_ > 14) {
  82. replaced = false;
  83. }
  84. while (replaced && out.length() > target_len_) {
  85. out = DeleteLastVowel(1, out, replaced);
  86. }
  87. // Preserve any trailing digits.
  88. int trailing_digits = out.indexOf(QRegExp("[0-9]*$"));
  89. if ((out.length() > target_len_) && (trailing_digits > 0)) {
  90. int digit_len = out.length() - trailing_digits;
  91. out.remove(target_len_ - digit_len, out.length() - target_len_);
  92. } else {
  93. out.truncate(target_len_);
  94. }
  95. if (must_unique_) {
  96. QString t(out);
  97. while (name_list_.contains(t)) {
  98. printf("Conflict %d\n", conflict_counter_);
  99. QString trailer = QString(".%1").arg(conflict_counter_);
  100. fprintf(stderr, "Trailer: %s\n", qPrintable(trailer));
  101. t.truncate(target_len_ - trailer.length());
  102. fprintf(stderr, "Truncate: %s\n", qPrintable(t));
  103. t = t.append(trailer);
  104. conflict_counter_++;
  105. }
  106. out = t;
  107. name_list_.insert(out);
  108. }
  109. fprintf(stderr, "Returning %s\n", qPrintable(out));
  110. assert(out.length() <= target_len_);
  111. return out;
  112. }
  113. #if 1
  114. char* foo[] = {
  115. "VwthPst# 3700.706N 08627.588W 0000000m View the Past #2 ",
  116. "PilotRoc 3655.270N 08717.173W 0000000m Pilot Rock ",
  117. "MrCycsNg 3652.407N 08728.890W 0000000m Mr. Cayces Neighborhood ",
  118. "SOLDIER 3640.691N 08726.660W 0000000m SOLDIER&#8217;S TRIBUTE ",
  119. "ZOOMZOOM 3636.659N 08721.793W 0000000m ZOOM ZOOM ZOOM by Feros Family",
  120. "SOCLOSEB 3636.494N 08722.086W 0000000m SO CLOSE BUT YET by Kyle of Fe",
  121. "InSrchfS 3636.363N 08636.363W 0000000m In Search of Steam by BigHank ",
  122. "RdBlngSp 3632.119N 08550.956W 0000000m Red Boiling Springs by Firedog",
  123. "HelngWtr 3631.729N 08550.481W 0000000m Healing Waters by FiredogPotte",
  124. "AHHtheVi 3629.020N 08533.891W 0000000m ogPotter ",
  125. "LstCrkCc 3628.167N 08801.656W 0000000m Lost Creek Cache by Paul Kathy",
  126. "DlvrncTr 3626.412N 08729.249W 0000000m Deliverance Train by Team Skay",
  127. "FrQrtrRn 3438.502N 08646.926W 0000000m Four Quarter Rendezvous by Zay",
  128. "Jstlttlc 3620.647N 08814.298W 0000000m Just a little cache by Paul Ka",
  129. "BrryPtch 3618.786N 08616.344W 0000000m Berry Patch Cache by White Dog",
  130. "AStrllDw 3342.752N 08630.829W 0000000m A Stroll Down Memory Lane by t",
  131. "QunfTnns 3606.413N 08651.962W 0000000m Queen of Tennessee by A182pilo",
  132. "GoneFish 3618.199N 08655.171W 0000000m Gone Fishin' by White Dog Pack",
  133. "GrnwysFn 3610.942N 08642.061W 0000000m Greenways Fence by Ukulele And",
  134. "AStnsThr 3611.240N 08638.324W 0000000m A Stone's Throw by Murrcat & S",
  135. "Nashvlls 3617.112N 08642.359W 0000000m Nashville's Zoo by White Dog P",
  136. "BltzMcr4 3517.127N 08622.211W 0000000m Blitz Micro Number 4 ",
  137. "NkdnthWn 3437.145N 08651.693W 0000000m Naked in the Wind by Zaybex ",
  138. "ANcPlctR 3603.389N 08654.418W 0000000m A Nice Place to Rest by JoGPS ",
  139. "welcomtT 3638.155N 08720.130W 0000000m welcome to TN by Raf of the se",
  140. "welcomtK 3638.956N 08721.011W 0000000m welcome to KY by raf of the se",
  141. "BltzMcr5 3506.191N 08634.277W 0000000m Blitz Micro Number 5 ",
  142. "JmsFmlyG 3615.887N 08649.846W 0000000m James Family Grocery by White ",
  143. "seekngrf 3629.262N 08742.333W 0000000m seekeing refuge by raf of the ",
  144. "SecrtFll 3614.927N 08534.180W 0000000m Secret Falls ",
  145. "ApstlcTh 3613.870N 08645.108W 0000000m Apostolic Thistle Walk by Jame",
  146. "WllIllBD 3609.258N 08637.268W 0000000m Well....I'll Be \"Dammed\" byi",
  147. "BettysBt 3608.857N 08550.564W 0000000m Betty's Booty by White Dog Pac",
  148. "SmthngSm 3439.748N 08643.522W 0000000m Something Smells Fishy by Zayb",
  149. "RckyRd(C 3605.315N 08549.326W 0000000m Rocky Road (Center Hill Lake) ",
  150. "Brdwtchr 3436.605N 08651.243W 0000000m Birdwatcher's Dream by Zaybex ",
  151. "JcksnsHl 3605.185N 08619.439W 0000000m Jackson's Halls by White Dog P",
  152. "FrgttnP2 3509.599N 08633.282W 0000000m Forgotten Park 2 ",
  153. "SOLDIERS 3640.691N 08726.660W 0000000m SOLDIERS TRIBUTE by Feros Fami",
  154. "EndofRop 3433.820N 08650.460W 0000000m End of Rope by Big Rock ",
  155. "VwthPst1 3659.263N 08627.114W 0000000m View the Past #1 ",
  156. "VwthPst2 3700.706N 08627.588W 0000000m View the Past #2 ",
  157. "Trash#8 3603.102N 08655.144W 0000000m Cache In Trash Out # 8 ",
  158. "SlwwwwCc 3602.543N 08535.953W 0000000m Sloowwww Cache by Tntcacher ",
  159. "Leavttbv 3602.514N 08638.686W 0000000m Leave it to beaver by A182pilo",
  160. "WhrrthHr 3436.594N 08654.759W 0000000m Where are the Horses? by Zaybe",
  161. "ButtonCc 3433.401N 08645.294W 0000000m Button Cache by Zaybex ",
  162. "MrcsLbrr 3436.842N 08655.972W 0000000m Marco's Library by Marco ",
  163. "Octopus 3526.743N 08534.757W 0000000m Octopus by White Dog Pack ",
  164. "WtrFllsV 3544.140N 08527.861W 0000000m Water Falls Valley by Cave Rat",
  165. "DeddrpPn 3448.126N 08719.696W 0000000m Dead-drop Pink by Marco ",
  166. "JWhlrRvr 3448.157N 08719.914W 0000000m Joe Wheeler River Walk by Marc",
  167. "CvSprngs 3432.797N 08651.084W 0000000m Cave Springs Cache by Marco.. ",
  168. "CnyFrkOv 3550.876N 08518.446W 0000000m Fork Overlook ",
  169. "SheepsCa 3550.527N 08519.480W 0000000m Sheep's Cave ",
  170. "VrgnFlls 3550.308N 08519.904W 0000000m Virgin Falls Cache ",
  171. "ShrtctVr 3550.170N 08519.590W 0000000m Shortcut Virtual ",
  172. "KlylFlls 3549.105N 08539.814W 0000000m Klaylee Falls Cache by pattytr",
  173. "FshngfrB 3548.923N 08538.558W 0000000m BADGER by M&Mk ",
  174. "TpfthHll 3548.808N 08601.722W 0000000m Top of the Hill Pet Cache by M",
  175. "TwnFllsC 3548.560N 08537.996W 0000000m Twin Falls Cache by SLCREW a",
  176. "WtchsCst 3548.197N 08537.673W 0000000m Witch's Castle Keys by SLCREW ",
  177. "ThatCave 3544.901N 08522.854W 0000000m That Cave by JaDan150 and AprJ",
  178. "BssltwnW 3541.174N 08801.489W 0000000m Busseltown Wildlife Refuge by ",
  179. "Riverfrn 3540.968N 08546.995W 0000000m Riverfront by SLCREW and M&M",
  180. "Basement 3540.086N 08521.304W 0000000m The Basement ",
  181. "EfflTwrC 3617.132N 08818.371W 0000000m Eiffel Tower Cache by Dick Wan",
  182. "KeyholeC 3544.562N 08524.098W 0000000m Keyhole Cave by Cave Rat ",
  183. "(MC^2)Mn 3444.990N 08630.218W 0000000m (MC^2) Monte Sano Cuts Cache b",
  184. "WildctCc 3636.823N 08808.087W 0000000m Wildcat Cache by The Storm ",
  185. "NAlbm/Tn 3444.365N 08632.688W 0000000m N. Alabama / Tennessee Valley ",
  186. "CalebsCa 3444.215N 08633.103W 0000000m Caleb's Cave by Papaw and Cale",
  187. "MntSnPrs 3444.201N 08632.591W 0000000m Monte Sano Preserve by Evan & ",
  188. "FltRckFl 3444.475N 08629.958W 0000000m Flat Rock Falls Cache by Jason",
  189. "PanormCc 3443.961N 08631.638W 0000000m The Panorama Cache by IHTFP an",
  190. "TnnssScv 3602.861N 08652.751W 0000000m Tennessee Scavenger Hunt Cache",
  191. "Geocache 3435.209N 08655.968W 0000000m Geocache by Papaw & Caleb ",
  192. "Skellig 3444.100N 08656.566W 0000000m Skellig by Zaybex ",
  193. "Deceptio 3433.450N 08655.711W 0000000m Deception by Papaw and Caleb ",
  194. "AwlknthD 3433.310N 08655.635W 0000000m A walk in the Desert by Papa",
  195. "MiniMsQs 3558.934N 08650.674W 0000000m Mini Me's Quest by CrotalusRex",
  196. "BakrsBlf 3541.891N 08717.300W 0000000m Bakers Bluff by Flower Child &",
  197. "GoFlyAKi 3435.664N 08659.267W 0000000m Go Fly A Kite by Marco.. ",
  198. "FlntCrkA 3432.028N 08656.806W 0000000m Flint Creek Adventure by Marco",
  199. "HonordMn 3534.680N 08612.557W 0000000m Honored Mount by Southpaw ",
  200. "SafariZo 3440.697N 08700.774W 0000000m Safari Zone by Zaybex ",
  201. "JckDnlsC 3517.077N 08622.260W 0000000m Jack Daniels Cache by Rmearse ",
  202. "FrgttnPr 3509.599N 08633.282W 0000000m Forgotten Park ",
  203. "DntOvrlk 3513.326N 08616.031W 0000000m Dont Overlook Me Cache ",
  204. "ArYStmpd 3513.039N 08615.110W 0000000m Are You Stumped Yet? cache ",
  205. "CchtthBn 3512.532N 08614.691W 0000000m Cache at the Bend ",
  206. "Thtsnkng 3609.009N 08530.314W 0000000m That sinking feeling by Tntcac",
  207. "GamersCc 3449.136N 08635.836W 0000000m mer's Cache by avoral ",
  208. "CchMIfYC 3452.455N 08620.648W 0000000m Cache Me If You Can! by Glen H",
  209. "SavageVs 3526.915N 08535.136W 0000000m Savage Vista by White Dog Pack",
  210. "PrtrnG15 3555.479N 08653.274W 0000000m Praetorian Guards Hail Caesar #15!",
  211. "WtrlnAmp 3443.722N 08632.535W 0000000m Waterline Amphitheater by Fath",
  212. "BysLttlC 3447.569N 08638.448W 0000000m Boys' Little Cache by Zaybex ",
  213. "DrgnsBrt 3443.779N 08635.188W 0000000m Dragon's Breath by Zaybex ",
  214. "CryBbyHl 3430.733N 08657.362W 0000000m Cry Baby Hollow Cache by La Pa",
  215. "Parmer 3606.218N 08651.590W 0000000m Parmer by A182pilot & Family ",
  216. "JnnfrndJ 3438.141N 08632.991W 0000000m Jennifer and Jonathans Cliff C",
  217. "ALDRIDGE 3435.305N 08632.868W 0000000m ALDRIDGE CREEK LOTTA LOOT!! by",
  218. "RcktCtyS 3440.032N 08631.352W 0000000m Rocket City Stash by David Upt",
  219. "TrgcAccd 3608.561N 08648.381W 0000000m Tragic Accident by Campaholics",
  220. "FALLENTR 3439.210N 08631.104W 0000000m FALLEN TREE 4 MILE POST by zac",
  221. "TrshOt15 3558.964N 08646.064W 0000000m Cache In Trash Out # 15 ",
  222. "TrshOt13 3602.214N 08650.428W 0000000m Cache In Trash Out #13 ",
  223. "PrcysDrp 3604.312N 08653.465W 0000000m Percys Dripping Springs by KLi",
  224. "TrshOt14 3605.292N 08648.560W 0000000m Cache In Trash Out # 14 ",
  225. "PrtrnGr5 3557.621N 08640.278W 0000000m Praetorian Guards Hail Caesar #5!",
  226. "PrtrnGr4 3557.370N 08640.201W 0000000m Praetorian Guards Hail Caesar #4!",
  227. "PrtrnGr3 3557.250N 08640.047W 0000000m Praetorian Guards Hail Caesar #3!",
  228. "GrnMntnC 3439.120N 08631.100W 0000000m Green Mountain Cache by Steve ",
  229. "TrshOt12 3605.330N 08635.817W 0000000m Cache In Trash Out #12 ",
  230. "BlncngAc 3608.579N 08648.120W 0000000m Balancing Act by Campaholics ",
  231. "DittoCac 3434.652N 08633.310W 0000000m Ditto Cache by mookey ",
  232. "EraserCc 3431.888N 08633.024W 0000000m Eraser Cache by Zaybex ",
  233. "FrMlPstE 3439.440N 08630.180W 0000000m Four Mile Post Extension Cache",
  234. "MllrdFxC 3439.578N 08706.552W 0000000m Mallard-Fox Creek Cache by bam",
  235. "FireCach 3443.908N 08630.318W 0000000m he by Glen Pam Chase M ",
  236. "FlntRvrC 3443.170N 08625.990W 0000000m Flint River Canoe Cache by Ran",
  237. "ArabinCc 3419.104N 08628.765W 0000000m The Arabian Cache by WesNSpace",
  238. "CvrdBrdg 3412.406N 08659.392W 0000000m Covered Bridge Cache by pmarkh",
  239. "MilesTCc 3424.470N 08611.720W 0000000m Miles Too Cache by Rmearse ",
  240. "MbryOvrl 3423.803N 08611.922W 0000000m Mabrey Overlook Me by DDVaughn",
  241. "LwEnfrcm 3423.218N 08612.258W 0000000m Law Enforcement Cache by DDVau",
  242. "GrndDddy 3423.128N 08612.025W 0000000m Grand Daddys Home by Rmearse ",
  243. "BamaCach 3421.459N 08611.686W 0000000m The Bama Cache by DDVaughn & T",
  244. "Canyons 3440.085N 08600.910W 0000000m The Canyons by Aubrey and Josh",
  245. "ADamGodV 3511.677N 08616.587W 0000000m A Dam Good View by mdawg & muf",
  246. "UNDERTHE 3446.918N 08739.790W 0000000m UNDER THE ROCK by RUNNINGWILD ",
  247. "SQUIRREL 3448.712N 08741.681W 0000000m SQUIRREL'S NEST by RUNNINGWILD",
  248. "WlknthPr 3448.273N 08741.844W 0000000m Walk in the Park by Runningwil",
  249. "NetsClue 3448.494N 08741.977W 0000000m Net's Clues by Runningwild Adv",
  250. "NatrlBrd 3405.583N 08736.909W 0000000m Natural Bridge by Southeast Xt",
  251. "TrnglPrk 3341.448N 08640.980W 0000000m Triangle Park Cache by Charles",
  252. "LttlRvrI 3421.855N 08539.597W 0000000m Little River Initiative by spa",
  253. "GimmShlt 3430.087N 08536.834W 0000000m Gimme Shelter by Big Rock & Po",
  254. "GnomeTrs 3433.081N 08535.849W 0000000m Gnome Treasure by Big Rock ",
  255. "FlyingTr 3608.594N 08648.179W 0000000m Flying Torso by Campaholics ",
  256. "CultivtC 3608.582N 08648.064W 0000000m Cultivate a Cure by Campahol"
  257. }
  258. ;
  259. extern "C" {
  260. struct short_handle* mkshort_new_handle(void);
  261. char* mkshort(struct short_handle*, char*);
  262. void setshort_whitespace_ok(struct short_handle*, int n);
  263. }
  264. main()
  265. {
  266. char** foop = foo;
  267. bool r;
  268. Shortname sn;
  269. // printf("%s\n", qPrintable(sn.Shorten("The Troll")));
  270. #if 0
  271. printf("%s\n", mkshort("The Troll"));
  272. printf("%s\n", mkshort("EFI"));
  273. printf("%s\n", mkshort("the Troll"));
  274. printf("%s\n", mkshort("the Trolley"));
  275. printf("%s\n", mkshort("The Troll Lives Under The Bridge"));
  276. printf("%s\n", mkshort("The \"Troll\" Lives Under $$ The Bridge!"));
  277. printf("%s\n", mkshort("The Trolley Goes Round"));
  278. printf("%s\n", mkshort("Cache In / Trash Out #1"));
  279. printf("%s\n", mkshort("Cache In / Trash Out #2"));
  280. printf("%s\n", mkshort("Cache In / Trash Out #137"));
  281. #endif
  282. #if 1
  283. struct short_handle* mh = mkshort_new_handle();
  284. setshort_whitespace_ok(mh, 0);
  285. sn.target_length(8);
  286. sn.whitespace_ok(false);
  287. while (*foop) {
  288. // printf("%s %s\n", mkshort(*foop+39), *foop+39);
  289. QString s = sn.Shorten(*foop + 39);
  290. QString r = (*foop);
  291. r.truncate(8);
  292. //fprintf(stderr, "zzz%s\n", *foop + 39);
  293. char* os = mkshort(mh, *foop + 39);
  294. if (s != os) {
  295. fprintf(stderr, "%s/%s\n", qPrintable(r), os);
  296. }
  297. // assert(r == s);
  298. foop++;
  299. }
  300. #endif
  301. #define X(a,b) assert(a == sn.Shorten(b));
  302. X("GC12345", "GC12345");
  303. sn.target_length(6);
  304. X("C12345", "GC12345");
  305. X("abc", " abc ");
  306. X("a bc", " a\t\tbc ");
  307. X("a b c", " a b c ");
  308. sn.whitespace_ok(false);
  309. X("def", " d e f ");
  310. sn.must_upper(true);
  311. X("GHI", " g h i ");
  312. sn.whitespace_ok(true);
  313. sn.must_upper(false);
  314. // X("pickle 2", "pickle two");
  315. // X("two 2", "two two");
  316. // X("one 1 throne", " one one throne ");
  317. X("dori", "do-ri");
  318. X("dora", "do$ra");
  319. sn.badchars("or");
  320. X("d-i", "do-ri");
  321. X("d$i", "do$ri");
  322. sn.badchars(QString());
  323. sn.target_length(6);
  324. X("10", "10");
  325. X("a10", "a10");
  326. X("aa10", "aa10");
  327. X("aaa10", "aaa10");
  328. X("baaa10", "baaa10");
  329. X("ccccc0", "ccccccc0");
  330. X("dccc10", "dcccccccccccccccc10");
  331. X("zark", "zark");
  332. X("zark.0", "zark");
  333. X("zark.1", "zark");
  334. X("zark.2", "zark");
  335. X("zark.3", "zark");
  336. X("zark.4", "zark");
  337. sn.target_length(8);
  338. X("quick br", "the quick brown fox");
  339. X("quick .5", "the quick brown foo");
  340. X("aexx", "aexx");
  341. X("aeab1234", "aeabcdefg1234");
  342. X("eat a pc", "eat a peach");
  343. X("eat a or", "eat a orange");
  344. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(5, "eat a peach", r)));
  345. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(5, "eat a orange", r)));
  346. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "the quick brown foo", r)));
  347. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "the quick brown fox", r)));
  348. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "eat a peach", r)));
  349. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(5, "eat a peach", r)));
  350. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(5, "eat a orange", r)));
  351. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "xxx", r)));
  352. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "ixxx", r)));
  353. printf("%s\n", qPrintable(Shortname::DeleteLastVowel(0, "aexxx", r)));
  354. }
  355. #endif