I know you've asked for a package-free solution, but for reference the following the the approach we are likely to take to this problem in expl3
. The 'design brief' here is that case folding (as defined by the Unicode people) should take place to leave 'caseless' data. Clearly this is only fully doable for LuaTeX/XeTeX: the 'fall back' for pdfTeX is to restrict the data used to only ASCII letters. Note that the code here is from Bruno: I tried a simpler approach but the following is more elegant and much faster!
The concept of string case folding is now available in expl3
as \str_foldcase:n
as described by the 'design brief' above. For constructing csnames, where case changing may be required, there are also \str_uppercase:n
and \str_lowercase:n
. Note these are separate from text case changing, also available in and expandable manner in expl3
.
Original code (before addition to expl3
): note the the current implementation creates the stored data dynamically from UnicodeData.txt
and related source files.
The 'business end' of the approach is a set up (using expl3
syntax):
\cs_new:Npn \str_fold_case:n #1
{
\exp_after:wN \__str_fold_auxi:w \tl_to_str:n {#1}
{ ~ \c_empty_tl } \__str_fold_end:w ? ~
}
\cs_new:Npn \__str_fold_auxi:w #1 ~
{
\__str_fold_auxii:N #1 { ~ \c_space_tl }
\__str_fold_auxi:w
}
\cs_new:Npn \__str_fold_auxii:N #1
{
\exp_after:wN \__str_fold_auxiii:NNNNNNNN
\int_use:N \__int_eval:w 1000000 + `#1 \__int_eval_end: #1
}
\cs_new:Npn \__str_fold_auxiii:NNNNNNNN #1#2#3#4#5#6#7#8
{
\exp_args:NNv \str_case_x:nnF #8
{ c__str_case_#6_X_#7_tl }
{ #8 \exp_after:wN \use_none:n #8 }
\__str_fold_auxii:N
}
\cs_new:Npn \__str_fold_end:w ? #1 \__str_fold_auxi:w { }
which is therefore ultimately dependent on \pdfstrcmp
(hidden inside \str_case_x:nnF
) to do a string comparison. The above also needs data: this can be auto-generated from the Unicode case-folding file and currently comes out as:
\tl_const:cn {c__str_case_0_X_0_tl} {ÈèĬĭƐɛǴǵϨϩҰұԔԕḔḕṸṹỜờᾤ{ὤι}ⒸⓒⰤⱔⲈⲉꙨꙩ}
\tl_const:cn {c__str_case_0_X_1_tl} {ÉéƑƒჍⴭᾥ{ὥι}ⒹⓓⰥⱕⳭⳮ}
\tl_const:cn {c__str_case_0_X_2_tl} {ÊêĮįǶƕΆάϪϫҲҳԖԗḖḗṺṻỞởᾦ{ὦι}ⒺⓔⰦⱖⲊⲋꙪꙫꜲꜳ}
\tl_const:cn {c__str_case_0_X_3_tl} {ËëƓɠǷƿᾧ{ὧι}ⒻⓕⰧⱗ}
\tl_const:cn {c__str_case_0_X_4_tl} {Ììİ{i̇}ƔɣǸǹΈέϬϭҴҵԘԙḘḙṼṽỠỡᾨ{ὠι}ⒼⓖⰨⱘⲌⲍꙬꙭꜴꜵ}
\tl_const:cn {c__str_case_0_X_5_tl} {ÍíΉήᾩ{ὡι}ⒽⓗⰩⱙ}
\tl_const:cn {c__str_case_0_X_6_tl} {ÎîIJijƖɩǺǻΊίϮϯҶҷԚԛḚḛṾṿỢợᾪ{ὢι}ⒾⓘⰪⱚⲎⲏⳲⳳꜶꜷ}
\tl_const:cn {c__str_case_0_X_7_tl} {ÏïƗɨᾫ{ὣι}ⒿⓙⰫⱛ}
\tl_const:cn {c__str_case_0_X_8_tl} {ÐðĴĵƘƙǼǽΌόϰκҸҹԜԝḜḝẀẁỤụὈὀᾬ{ὤι}ⓀⓚⰬⱜⲐⲑꜸꜹ}
\tl_const:cn {c__str_case_0_X_9_tl} {ÑñϱρὉὁᾭ{ὥι}ⓁⓛⰭⱝ}
\tl_const:cn {c__str_case_1_X_0_tl} {ÒòĶķǾǿΎύҺһԞԟḞḟẂẃỦủὊὂᾮ{ὦι}ⓂⓜⰮⱞⲒⲓꜺꜻ}
\tl_const:cn {c__str_case_1_X_1_tl} {ÓóΏώὋὃᾯ{ὧι}Ⓝⓝ}
\tl_const:cn {c__str_case_1_X_2_tl} {ÔôƜɯȀȁΐ{ΐ}ϴθҼҽԠԡḠḡẄẅỨứὌὄⓄⓞⲔⲕꜼꜽꞠꞡ}
\tl_const:cn {c__str_case_1_X_3_tl} {ÕõĹĺƝɲΑαϵεὍὅⓅⓟAa}
\tl_const:cn {c__str_case_1_X_4_tl} {ÖöȂȃΒβҾҿԢԣḢḣẆẇỪừᾲ{ὰι}ⓆⓠⲖⲗꜾꜿꞢꞣBb}
\tl_const:cn {c__str_case_1_X_5_tl} {ĻļƟɵΓγϷϸև{եւ}ᾳ{αι}ⓇⓡCc}
\tl_const:cn {c__str_case_1_X_6_tl} {ØøƠơȄȅΔδӀӏԤԥḤḥẈẉỬửὐ{ὐ}ᾴ{άι}ⓈⓢⲘⲙꝀꝁꞤꞥDd}
\tl_const:cn {c__str_case_1_X_7_tl} {ÙùĽľΕεϹϲӁӂⓉⓣEe}
\tl_const:cn {c__str_case_1_X_8_tl} {ÚúƢƣȆȇΖζϺϻԦԧḦḧẊẋỮữὒ{ὒ}ᾶ{ᾶ}ⓊⓤⲚⲛꝂꝃꞦꞧFf}
\tl_const:cn {c__str_case_1_X_9_tl} {ÛûĿŀΗηӃӄᾷ{ᾶι}ⓋⓥGg}
\tl_const:cn {c__str_case_2_X_0_tl} {ÜüƤƥȈȉΘθѠѡḨḩẌẍỰựὔ{ὔ}ᾸᾰⓌⓦⲜⲝꝄꝅꞨꞩHh}
\tl_const:cn {c__str_case_2_X_1_tl} {ÝýŁłΙιϽͻӅӆᾹᾱⓍⓧIi}
\tl_const:cn {c__str_case_2_X_2_tl} {ÞþƦʀȊȋΚκϾͼѢѣḪḫẎẏỲỳὖ{ὖ}ᾺὰⓎⓨⲞⲟꝆꝇꞪɦJj}
\tl_const:cn {c__str_case_2_X_3_tl} {ß{ss}ŃńƧƨΛλϿͽӇӈΆάⓏⓩKk}
\tl_const:cn {c__str_case_2_X_4_tl} {ȌȍΜμЀѐѤѥḬḭẐẑỴỵᾼ{αι}ⲠⲡꚀꚁꝈꝉLl}
\tl_const:cn {c__str_case_2_X_5_tl} {ŅņƩʃΝνЁёӉӊὙὑMm}
\tl_const:cn {c__str_case_2_X_6_tl} {ȎȏΞξЂђѦѧḮḯẒẓỶỷιιⲢⲣꚂꚃꝊꝋNn}
\tl_const:cn {c__str_case_2_X_7_tl} {ŇňΟοЃѓӋӌὛὓOo}
\tl_const:cn {c__str_case_2_X_8_tl} {ƬƭȐȑΠπЄєѨѩḰḱẔẕỸỹⲤⲥꚄꚅꝌꝍPp}
\tl_const:cn {c__str_case_2_X_9_tl} {ʼn{ʼn}ΡρЅѕӍӎԱաὝὕQq}
\tl_const:cn {c__str_case_3_X_0_tl} {ŊŋƮʈȒȓІіѪѫԲբḲḳẖ{ẖ}Ỻỻῂ{ὴι}ⲦⲧꚆꚇꝎꝏRr}
\tl_const:cn {c__str_case_3_X_1_tl} {ƯưΣσЇїԳգẗ{ẗ}Ὗὗῃ{ηι}Ss}
\tl_const:cn {c__str_case_3_X_2_tl} {ŌōȔȕΤτЈјѬѭӐӑԴդḴḵẘ{ẘ}Ỽỽῄ{ήι}ⲨⲩꚈꚉꝐꝑTt}
\tl_const:cn {c__str_case_3_X_3_tl} {ƱʊΥυЉљԵեẙ{ẙ}Uu}
\tl_const:cn {c__str_case_3_X_4_tl} {ŎŏƲʋȖȗΦφЊњѮѯӒӓԶզḶḷẚ{aʾ}Ỿỿῆ{ῆ}ⲪⲫꚊꚋꝒꝓVv}
\tl_const:cn {c__str_case_3_X_5_tl} {ƳƴΧχЋћԷէẛṡῇ{ῆι}Ww}
\tl_const:cn {c__str_case_3_X_6_tl} {ŐőȘșΨψЌќѰѱӔӕԸըḸḹῈὲⲬⲭꚌꚍꝔꝕXx}
\tl_const:cn {c__str_case_3_X_7_tl} {ƵƶͅιΩωЍѝԹթΈέYy}
\tl_const:cn {c__str_case_3_X_8_tl} {ŒœȚțΪϊЎўѲѳӖӗԺժḺḻẞ{ss}ῊὴⲮⲯꚎꚏꝖꝗZz}
\tl_const:cn {c__str_case_3_X_9_tl} {ƷʒΫϋЏџԻիΉή}
\tl_const:cn {c__str_case_4_X_0_tl} {ŔŕƸƹȜȝАаѴѵӘәԼլḼḽẠạὨὠῌ{ηι}ⲰⲱꚐꚑꝘꝙ}
\tl_const:cn {c__str_case_4_X_1_tl} {БбԽխὩὡ}
\tl_const:cn {c__str_case_4_X_2_tl} {ŖŗȞȟВвѶѷӚӛԾծḾḿẢảὪὢⲲⲳꚒꚓꝚꝛ}
\tl_const:cn {c__str_case_4_X_3_tl} {ГгԿկὫὣ}
\tl_const:cn {c__str_case_4_X_4_tl} {ŘřƼƽȠƞΰ{ΰ}ДдѸѹӜӝՀհṀṁẤấἈἀὬὤⅠⅰⲴⲵꚔꚕꝜꝝ}
\tl_const:cn {c__str_case_4_X_5_tl} {ЕеՁձἉἁὭὥⅡⅱ}
\tl_const:cn {c__str_case_4_X_6_tl} {ŚśȢȣЖжѺѻӞӟՂղṂṃẦầἊἂὮὦῒ{ῒ}ⅢⅲⲶⲷꚖꚗꝞꝟ}
\tl_const:cn {c__str_case_4_X_7_tl} {ЗзՃճἋἃὯὧΐ{ΐ}Ⅳⅳ}
\tl_const:cn {c__str_case_4_X_8_tl} {ŜŝȤȥИиѼѽӠӡՄմṄṅẨẩἌἄⅤⅴⲸⲹꝠꝡ}
\tl_const:cn {c__str_case_4_X_9_tl} {ЙйՅյἍἅⅥⅵ}
\tl_const:cn {c__str_case_5_X_0_tl} {ŞşȦȧКкѾѿӢӣՆնṆṇẪẫἎἆῖ{ῖ}ⅦⅶⲺⲻꝢꝣ}
\tl_const:cn {c__str_case_5_X_1_tl} {ЛлՇշἏἇῗ{ῗ}Ⅷⅷ}
\tl_const:cn {c__str_case_5_X_2_tl} {ŠšDŽdžȨȩМмҀҁӤӥՈոṈṉẬậῘῐⅨⅸⲼⲽꝤꝥ}
\tl_const:cn {c__str_case_5_X_3_tl} {DždžНнՉչῙῑⅩⅹ}
\tl_const:cn {c__str_case_5_X_4_tl} {ŢţȪȫОоӦӧՊպṊṋẮắῚὶⅪⅺⲾⲿꝦꝧ}
\tl_const:cn {c__str_case_5_X_5_tl} {LJljПпՋջΊίⅫⅻ}
\tl_const:cn {c__str_case_5_X_6_tl} {ĀāŤťLjljȬȭРрӨөՌռႠⴀṌṍẰằⅬⅼⳀⳁꝨꝩff{ff}}
\tl_const:cn {c__str_case_5_X_7_tl} {СсՍսႡⴁⅭⅽfi{fi}}
\tl_const:cn {c__str_case_5_X_8_tl} {ĂăŦŧNJnjȮȯТтӪӫՎվႢⴂṎṏẲẳⅮⅾⳂⳃꝪꝫfl{fl}}
\tl_const:cn {c__str_case_5_X_9_tl} {NjnjУуՏտႣⴃⅯⅿffi{ffi}}
\tl_const:cn {c__str_case_6_X_0_tl} {ĄąŨũȰȱФфӬӭՐրႤⴄṐṑẴẵἘἐⱠⱡⳄⳅꙀꙁꝬꝭffl{ffl}𐐀𐐨}
\tl_const:cn {c__str_case_6_X_1_tl} {ǍǎХхՑցႥⴅἙἑſt{st}𐐁𐐩}
\tl_const:cn {c__str_case_6_X_2_tl} {ĆćŪūȲȳςσЦцҊҋӮӯՒւႦⴆṒṓẶặἚἒῢ{ῢ}ⱢɫⳆⳇꙂꙃꝮꝯst{st}𐐂𐐪}
\tl_const:cn {c__str_case_6_X_3_tl} {ǏǐЧчՓփႧⴇἛἓΰ{ΰ}Ᵽᵽ𐐃𐐫}
\tl_const:cn {c__str_case_6_X_4_tl} {ĈĉŬŭШшҌҍӰӱՔքႨⴈṔṕẸẹἜἔᾀ{ἀι}ῤ{ῤ}ⰀⰰⱤɽⳈⳉꙄꙅ𐐄𐐬}
\tl_const:cn {c__str_case_6_X_5_tl} {AaǑǒЩщՕօႩⴉἝἕᾁ{ἁι}Ⰱⰱ𐐅𐐭}
\tl_const:cn {c__str_case_6_X_6_tl} {BbĊċŮůЪъҎҏӲӳՖֆႪⴊṖṗẺẻᾂ{ἂι}ῦ{ῦ}ⰂⰲⳊⳋꙆꙇ𐐆𐐮}
\tl_const:cn {c__str_case_6_X_7_tl} {CcǓǔЫыႫⴋᾃ{ἃι}ῧ{ῧ}ⰃⰳⱧⱨ𐐇𐐯}
\tl_const:cn {c__str_case_6_X_8_tl} {DdČčŰűЬьҐґӴӵႬⴌṘṙẼẽᾄ{ἄι}ῨῠⰄⰴⳌⳍꙈꙉ𐐈𐐰}
\tl_const:cn {c__str_case_6_X_9_tl} {EeǕǖЭэႭⴍᾅ{ἅι}ῩῡⰅⰵⱩⱪ𐐉𐐱}
\tl_const:cn {c__str_case_7_X_0_tl} {FfĎďŲųȺⱥЮюҒғӶӷႮⴎṚṛẾếᾆ{ἆι}ῪὺⰆⰶⳎⳏꙊꙋ𐐊𐐲}
\tl_const:cn {c__str_case_7_X_1_tl} {GgǗǘȻȼЯяႯⴏᾇ{ἇι}ΎύⰇⰷⱫⱬ𐐋𐐳}
\tl_const:cn {c__str_case_7_X_2_tl} {HhĐđŴŵҔҕӸӹႰⴐṜṝỀềᾈ{ἀι}ῬῥⰈⰸⳐⳑꙌꙍ𐐌𐐴}
\tl_const:cn {c__str_case_7_X_3_tl} {IiǙǚȽƚႱⴑᾉ{ἁι}ⰉⰹⱭɑꝹꝺ𐐍𐐵}
\tl_const:cn {c__str_case_7_X_4_tl} {JjĒēŶŷȾⱦҖҗӺӻႲⴒṞṟỂểᾊ{ἂι}ⰊⰺⱮɱⳒⳓꙎꙏ𐐎𐐶}
\tl_const:cn {c__str_case_7_X_5_tl} {KkǛǜϏϗႳⴓᾋ{ἃι}ⰋⰻⱯɐꝻꝼﬓ{մն}𐐏𐐷}
\tl_const:cn {c__str_case_7_X_6_tl} {LlĔĕŸÿϐβҘҙӼӽႴⴔṠṡỄễἨἠᾌ{ἄι}ⰌⰼⱰɒⳔⳕꙐꙑﬔ{մե}𐐐𐐸}
\tl_const:cn {c__str_case_7_X_7_tl} {MmŹźɁɂϑθႵⴕἩἡᾍ{ἅι}ⰍⰽꝽᵹﬕ{մի}𐐑𐐹}
\tl_const:cn {c__str_case_7_X_8_tl} {NnĖėǞǟҚқӾӿႶⴖṢṣỆệἪἢᾎ{ἆι}ῲ{ὼι}ⰎⰾⱲⱳⳖⳗꙒꙓꝾꝿﬖ{վն}𐐒𐐺}
\tl_const:cn {c__str_case_7_X_9_tl} {OoŻżɃƀႷⴗἫἣᾏ{ἇι}ῳ{ωι}ↃↄⰏⰿﬗ{մխ}𐐓𐐻}
\tl_const:cn {c__str_case_8_X_0_tl} {PpĘęǠǡɄʉͰͱҜҝԀԁႸⴘḀḁṤṥỈỉἬἤᾐ{ἠι}ῴ{ώι}ⰐⱀⳘⳙꙔꙕꞀꞁ𐐔𐐼}
\tl_const:cn {c__str_case_8_X_1_tl} {QqµμŽžɅʌϕφႹⴙἭἥᾑ{ἡι}ⰑⱁⱵⱶ𐐕𐐽}
\tl_const:cn {c__str_case_8_X_2_tl} {RrĚěǢǣɆɇͲͳϖπҞҟԂԃႺⴚḂḃṦṧỊịἮἦᾒ{ἢι}ῶ{ῶ}ⰒⱂⳚⳛꙖꙗꞂꞃ𐐖𐐾}
\tl_const:cn {c__str_case_8_X_3_tl} {SsſsႻⴛἯἧᾓ{ἣι}ῷ{ῶι}Ⱃⱃ𐐗𐐿}
\tl_const:cn {c__str_case_8_X_4_tl} {TtĜĝǤǥɈɉϘϙҠҡԄԅႼⴜḄḅṨṩỌọᾔ{ἤι}ῸὸⰔⱄⳜⳝꙘꙙꞄꞅ𐐘𐑀}
\tl_const:cn {c__str_case_8_X_5_tl} {UuƁɓႽⴝᾕ{ἥι}ΌόⰕⱅ𐐙𐑁}
\tl_const:cn {c__str_case_8_X_6_tl} {VvĞğƂƃǦǧɊɋͶͷϚϛҢңԆԇႾⴞḆḇṪṫỎỏᾖ{ἦι}ῺὼΩωⰖⱆⳞⳟꙚꙛꜢꜣꞆꞇ𐐚𐑂}
\tl_const:cn {c__str_case_8_X_7_tl} {WwႿⴟᾗ{ἧι}ΏώⰗⱇ𐐛𐑃}
\tl_const:cn {c__str_case_8_X_8_tl} {XxĠġƄƅǨǩɌɍϜϝҤҥԈԉჀⴠḈḉṬṭỐốᾘ{ἠι}ῼ{ωι}ⰘⱈⳠⳡꙜꙝꜤꜥ𐐜𐑄}
\tl_const:cn {c__str_case_8_X_9_tl} {YyჁⴡᾙ{ἡι}Ⱉⱉ𐐝𐑅}
\tl_const:cn {c__str_case_9_X_0_tl} {ZzĢģƆɔǪǫɎɏϞϟҦҧԊԋჂⴢḊḋṮṯỒồᾚ{ἢι}KkⰚⱊⱾȿⳢⳣꙞꙟꜦꜧ𐐞𐑆}
\tl_const:cn {c__str_case_9_X_1_tl} {ƇƈჃⴣᾛ{ἣι}ÅåⰛⱋⱿɀꞋꞌ𐐟𐑇}
\tl_const:cn {c__str_case_9_X_2_tl} {ÀàĤĥǬǭϠϡҨҩԌԍჄⴤḌḍṰṱỔổἸἰᾜ{ἤι}ⰜⱌⲀⲁꙠꙡꜨꜩ𐐠𐑈}
\tl_const:cn {c__str_case_9_X_3_tl} {ÁáƉɖჅⴥἹἱᾝ{ἥι}ⰝⱍꞍɥ𐐡𐑉}
\tl_const:cn {c__str_case_9_X_4_tl} {ÂâĦħƊɗǮǯϢϣҪҫԎԏḎḏṲṳỖỗἺἲᾞ{ἦι}ⰞⱎⲂⲃꙢꙣꜪꜫ𐐢𐑊}
\tl_const:cn {c__str_case_9_X_5_tl} {ÃãƋƌჇⴧἻἳᾟ{ἧι}Ⱏⱏ𐐣𐑋}
\tl_const:cn {c__str_case_9_X_6_tl} {ÄäĨĩǰ{ǰ}ϤϥҬҭԐԑḐḑṴṵỘộἼἴᾠ{ὠι}ⰠⱐⲄⲅꙤꙥꜬꜭꞐꞑ𐐤𐑌}
\tl_const:cn {c__str_case_9_X_7_tl} {ÅåDZdzἽἵᾡ{ὡι}Ⱑⱑ𐐥𐑍}
\tl_const:cn {c__str_case_9_X_8_tl} {ÆæĪīƎǝDzdzϦϧҮүԒԓḒḓṶṷỚớἾἶᾢ{ὢι}ℲⅎⒶⓐⰢⱒⲆⲇꙦꙧꜮꜯꞒꞓ𐐦𐑎}
\tl_const:cn {c__str_case_9_X_9_tl} {ÇçƏəἿἷᾣ{ὣι}ⒷⓑⰣⱓⳫⳬ𐐧𐑏}
The 'trick' here is to divide up the rather long list of chars into blocks, which means that the string comparison (relatively slow) doesn't have to map over the entire list (over 1000 chars) to find a match. As you'll see, it's only when there is a change available that there is any data to store at all.
The above should get added to expl3
'real soon now', depending on my time availability.
\macrot
and\macroT
, so the case is not relevant.\active
. Therefore I need to deal in macros with lowercase letters only in their names (note my use ofstringstrings
in that answer).;-)
However,\string A
gives a category code 12A
independently of the current category code ofA
.