| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444 | {**************************************************************************************************}{                                                                                                  }{ Project JEDI Code Library (JCL)                                                                  }{                                                                                                  }{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }{ you may not use this file except in compliance with the License. You may obtain a copy of the    }{ License at http://www.mozilla.org/MPL/                                                           }{                                                                                                  }{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF   }{ ANY KIND, either express or implied. See the License for the specific language governing rights  }{ and limitations under the License.                                                               }{                                                                                                  }{ The Original Code is JclStrings.pas.                                                             }{                                                                                                  }{ The Initial Developer of the Original Code is Marcel van Brakel.                                 }{ Portions created by Marcel van Brakel are Copyright (C) Marcel van Brakel. All rights reserved.  }{                                                                                                  }{ Contributor(s):                                                                                  }{   Alexander Radchenko                                                                            }{   Andreas Hausladen (ahuser)                                                                     }{   Anthony Steele                                                                                 }{   Azret Botash                                                                                   }{   Barry Kelly                                                                                    }{   Huanlin Tsai                                                                                   }{   Jack N.A. Bakker                                                                               }{   Jean-Fabien Connault (cycocrew)                                                                }{   John C Molyneux                                                                                }{   Kiriakos Vlahos                                                                                }{   Leonard Wennekers                                                                              }{   Marcel Bestebroer                                                                              }{   Martin Kimmings                                                                                }{   Martin Kubecka                                                                                 }{   Massimo Maria Ghisalberti                                                                      }{   Matthias Thoma (mthoma)                                                                        }{   Michael Winter                                                                                 }{   Nick Hodges                                                                                    }{   Olivier Sannier (obones)                                                                       }{   Pelle F. S. Liljendal                                                                          }{   Petr Vones (pvones)                                                                            }{   Rik Barker (rikbarker)                                                                         }{   Robert Lee                                                                                     }{   Robert Marquardt (marquardt)                                                                   }{   Robert Rossmair (rrossmair)                                                                    }{   Andreas Schmidt                                                                                }{   Sean Farrow (sfarrow)                                                                          }{                                                                                                  }{**************************************************************************************************}{                                                                                                  }{ Various character and string routines (searching, testing and transforming)                      }{                                                                                                  }{**************************************************************************************************}{                                                                                                  }{ Last modified: $Date::                                                                         $ }{ Revision:      $Rev::                                                                          $ }{ Author:        $Author::                                                                       $ }{                                                                                                  }{**************************************************************************************************}unit JclStrings;{$I jcl.inc}interfaceuses  {$IFDEF UNITVERSIONING}  JclUnitVersioning,  {$ENDIF UNITVERSIONING}  {$IFDEF HAS_UNITSCOPE}  {$IFDEF MSWINDOWS}  Winapi.Windows,  {$ENDIF MSWINDOWS}  {$IFDEF UNICODE_RTL_DATABASE}  System.Character,  {$ENDIF UNICODE_RTL_DATABASE}  System.Classes, System.SysUtils,  {$ELSE ~HAS_UNITSCOPE}  {$IFDEF MSWINDOWS}  Windows,  {$ENDIF MSWINDOWS}  {$IFDEF UNICODE_RTL_DATABASE}  Character,  {$ENDIF UNICODE_RTL_DATABASE}  Classes, SysUtils,  {$ENDIF ~HAS_UNITSCOPE}{$IFNDEF WINSCP}  JclAnsiStrings,  JclWideStrings,{$ENDIF ~WINSCP}  JclBase;// Exceptionstype  EJclStringError = class(EJclError);// Character constants and setsconst  // Misc. often used character definitions  NativeNull = Char(#0);  NativeSoh = Char(#1);  NativeStx = Char(#2);  NativeEtx = Char(#3);  NativeEot = Char(#4);  NativeEnq = Char(#5);  NativeAck = Char(#6);  NativeBell = Char(#7);  NativeBackspace = Char(#8);  NativeTab = Char(#9);  NativeLineFeed = JclBase.NativeLineFeed;  NativeVerticalTab = Char(#11);  NativeFormFeed = Char(#12);  NativeCarriageReturn = JclBase.NativeCarriageReturn;  NativeCrLf = JclBase.NativeCrLf;  NativeSo = Char(#14);  NativeSi = Char(#15);  NativeDle = Char(#16);  NativeDc1 = Char(#17);  NativeDc2 = Char(#18);  NativeDc3 = Char(#19);  NativeDc4 = Char(#20);  NativeNak = Char(#21);  NativeSyn = Char(#22);  NativeEtb = Char(#23);  NativeCan = Char(#24);  NativeEm = Char(#25);  NativeEndOfFile = Char(#26);  NativeEscape = Char(#27);  NativeFs = Char(#28);  NativeGs = Char(#29);  NativeRs = Char(#30);  NativeUs = Char(#31);  NativeSpace = Char(' ');  NativeComma = Char(',');  NativeBackslash = Char('\');  NativeForwardSlash = Char('/');  NativeDoubleQuote = Char('"');  NativeSingleQuote = Char('''');  NativeLineBreak = JclBase.NativeLineBreak;const  // CharType return values  C1_UPPER = $0001; // Uppercase  C1_LOWER = $0002; // Lowercase  C1_DIGIT = $0004; // Decimal digits  C1_SPACE = $0008; // Space characters  C1_PUNCT = $0010; // Punctuation  C1_CNTRL = $0020; // Control characters  C1_BLANK = $0040; // Blank characters  C1_XDIGIT = $0080; // Hexadecimal digits  C1_ALPHA = $0100; // Any linguistic character: alphabetic, syllabary, or ideographic  {$IFDEF MSWINDOWS}  {$IFDEF SUPPORTS_EXTSYM}  {$EXTERNALSYM C1_UPPER}  {$EXTERNALSYM C1_LOWER}  {$EXTERNALSYM C1_DIGIT}  {$EXTERNALSYM C1_SPACE}  {$EXTERNALSYM C1_PUNCT}  {$EXTERNALSYM C1_CNTRL}  {$EXTERNALSYM C1_BLANK}  {$EXTERNALSYM C1_XDIGIT}  {$EXTERNALSYM C1_ALPHA}  {$ENDIF SUPPORTS_EXTSYM}  {$ENDIF MSWINDOWS}type  TCharValidator = function(const C: Char): Boolean;function ArrayContainsChar(const Chars: array of Char; const C: Char): Boolean; overload;function ArrayContainsChar(const Chars: array of Char; const C: Char; out Index: SizeInt): Boolean; overload;// String Test Routinesfunction StrIsAlpha(const S: string): Boolean;function StrIsAlphaNum(const S: string): Boolean;function StrIsAlphaNumUnderscore(const S: string): Boolean;function StrContainsChars(const S: string; const Chars: TCharValidator; CheckAll: Boolean): Boolean; overload;function StrContainsChars(const S: string; const Chars: array of Char; CheckAll: Boolean): Boolean; overload;{$IFNDEF WINSCP}function StrConsistsOfNumberChars(const S: string): Boolean;{$ENDIF ~WINSCP}function StrIsDigit(const S: string): Boolean;function StrIsSubset(const S: string; const ValidChars: TCharValidator): Boolean; overload;function StrIsSubset(const S: string; const ValidChars: array of Char): Boolean; overload;function StrSame(const S1, S2: string; CaseSensitive: Boolean = False): Boolean;// String Transformation Routinesfunction StrCenter(const S: string; L: SizeInt; C: Char = ' '): string;function StrCharPosLower(const S: string; CharPos: SizeInt): string;function StrCharPosUpper(const S: string; CharPos: SizeInt): string;function StrDoubleQuote(const S: string): string;function StrEnsureNoPrefix(const Prefix, Text: string): string;function StrEnsureNoSuffix(const Suffix, Text: string): string;function StrEnsurePrefix(const Prefix, Text: string): string;function StrEnsureSuffix(const Suffix, Text: string): string;function StrEscapedToString(const S: string): string;function StrLower(const S: string): string;procedure StrLowerInPlace(var S: string);procedure StrLowerBuff(S: PChar);procedure StrMove(var Dest: string; const Source: string; const ToIndex,  FromIndex, Count: SizeInt);function StrPadLeft(const S: string; Len: SizeInt; C: Char = NativeSpace): string;function StrPadRight(const S: string; Len: SizeInt; C: Char = NativeSpace): string;function StrProper(const S: string): string;procedure StrProperBuff(S: PChar);function StrQuote(const S: string; C: Char): string;function StrRemoveChars(const S: string; const Chars: TCharValidator): string; overload;function StrRemoveChars(const S: string; const Chars: array of Char): string; overload;function StrRemoveLeadingChars(const S: string; const Chars: TCharValidator): string; overload;function StrRemoveLeadingChars(const S: string; const Chars: array of Char): string; overload;function StrRemoveEndChars(const S: string; const Chars: TCharValidator): string; overload;function StrRemoveEndChars(const S: string; const Chars: array of Char): string; overload;function StrKeepChars(const S: string; const Chars: TCharValidator): string; overload;function StrKeepChars(const S: string; const Chars: array of Char): string; overload;procedure StrReplace(var S: string; const Search, Replace: string; Flags: TReplaceFlags = []);function StrReplaceChar(const S: string; const Source, Replace: Char): string;function StrReplaceChars(const S: string; const Chars: TCharValidator; Replace: Char): string; overload;function StrReplaceChars(const S: string; const Chars: array of Char; Replace: Char): string; overload;function StrReplaceButChars(const S: string; const Chars: TCharValidator; Replace: Char): string; overload;function StrReplaceButChars(const S: string; const Chars: array of Char; Replace: Char): string; overload;function StrRepeat(const S: string; Count: SizeInt): string;function StrRepeatLength(const S: string; L: SizeInt): string;function StrReverse(const S: string): string;procedure StrReverseInPlace(var S: string);function StrSingleQuote(const S: string): string;procedure StrSkipChars(var S: PChar; const Chars: TCharValidator); overload;procedure StrSkipChars(var S: PChar; const Chars: array of Char); overload;procedure StrSkipChars(const S: string; var Index: SizeInt; const Chars: TCharValidator); overload;procedure StrSkipChars(const S: string; var Index: SizeInt; const Chars: array of Char); overload;function StrSmartCase(const S: string; const Delimiters: TCharValidator): string; overload;function StrSmartCase(const S: string; const Delimiters: array of Char): string; overload;function StrStringToEscaped(const S: string): string;{$IFNDEF WINSCP}function StrStripNonNumberChars(const S: string): string;{$ENDIF ~WINSCP}function StrToHex(const Source: string): string;function StrTrimCharLeft(const S: string; C: Char): string;function StrTrimCharsLeft(const S: string; const Chars: TCharValidator): string; overload;function StrTrimCharsLeft(const S: string; const Chars: array of Char): string; overload;function StrTrimCharRight(const S: string; C: Char): string;function StrTrimCharsRight(const S: string; const Chars: TCharValidator): string; overload;function StrTrimCharsRight(const S: string; const Chars: array of Char): string; overload;function StrTrimQuotes(const S: string): string;function StrUpper(const S: string): string;procedure StrUpperInPlace(var S: string);procedure StrUpperBuff(S: PChar);// String Managementprocedure StrAddRef(var S: string);procedure StrDecRef(var S: string);function StrLength(const S: string): SizeInt;function StrRefCount(const S: string): SizeInt;// String Search and Replace Routinesfunction StrCharCount(const S: string; C: Char): SizeInt; overload;function StrCharsCount(const S: string; const Chars: TCharValidator): SizeInt; overload;function StrCharsCount(const S: string; const Chars: array of Char): SizeInt; overload;function StrStrCount(const S, SubS: string): SizeInt;function StrCompare(const S1, S2: string; CaseSensitive: Boolean = False): SizeInt;function StrCompareRange(const S1, S2: string; Index, Count: SizeInt; CaseSensitive: Boolean = True): SizeInt;function StrCompareRangeEx(const S1, S2: string; Index, Count: SizeInt; CaseSensitive: Boolean): SizeInt;procedure StrFillChar(var S; Count: SizeInt; C: Char);function StrRepeatChar(C: Char; Count: SizeInt): string;function StrFind(const Substr, S: string; const Index: SizeInt = 1): SizeInt;function StrHasPrefix(const S: string; const Prefixes: array of string): Boolean;function StrHasSuffix(const S: string; const Suffixes: array of string): Boolean;function StrIndex(const S: string; const List: array of string; CaseSensitive: Boolean = False): SizeInt;function StrIHasPrefix(const S: string; const Prefixes: array of string): Boolean;function StrIHasSuffix(const S: string; const Suffixes: array of string): Boolean;function StrILastPos(const SubStr, S: string): SizeInt;function StrIPos(const SubStr, S: string): SizeInt;function StrIPrefixIndex(const S: string; const Prefixes: array of string): SizeInt;function StrIsOneOf(const S: string; const List: array of string): Boolean;function StrISuffixIndex(const S: string; const Suffixes: array of string): SizeInt;function StrLastPos(const SubStr, S: string): SizeInt;function StrMatch(const Substr, S: string; Index: SizeInt = 1): SizeInt;function StrMatches(const Substr, S: string; const Index: SizeInt = 1): Boolean;function StrNIPos(const S, SubStr: string; N: SizeInt): SizeInt;function StrNPos(const S, SubStr: string; N: SizeInt): SizeInt;function StrPrefixIndex(const S: string; const Prefixes: array of string): SizeInt;function StrSearch(const Substr, S: string; const Index: SizeInt = 1): SizeInt;function StrSuffixIndex(const S: string; const Suffixes: array of string): SizeInt;// String Extraction/// Returns the string after SubStrfunction StrAfter(const SubStr, S: string): string;/// Returns the String before SubStrfunction StrBefore(const SubStr, S: string): string;/// Splits a string at SubStr, returns true when SubStr is found, Left contains the/// string before the SubStr and Right the string behind SubStrfunction StrSplit(const SubStr, S: string;var Left, Right : string): boolean;/// Returns the string between Start and Stopfunction StrBetween(const S: string; const Start, Stop: Char): string;/// Returns all but rightmost N characters of the stringfunction StrChopRight(const S: string; N: SizeInt): string;{$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}/// Returns the left Count characters of the stringfunction StrLeft(const S: string; Count: SizeInt): string; {$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}/// Returns the string starting from position Start for the Count Charactersfunction StrMid(const S: string; Start, Count: SizeInt): string; {$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}/// Returns the string starting from position N to the endfunction StrRestOf(const S: string; N: SizeInt): string;{$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}/// Returns the right Count characters of the stringfunction StrRight(const S: string; Count: SizeInt): string;{$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}// Character Test Routinesfunction CharEqualNoCase(const C1, C2: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsAlpha(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsAlphaNum(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsBlank(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsControl(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsDelete(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsFracDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsHexDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsLower(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}{$IFNDEF WINSCP}function CharIsNumberChar(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}function CharIsNumber(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} {$IFDEF COMPILER16_UP} inline; {$ENDIF} {$ENDIF}{$ENDIF ~WINSCP}function CharIsPrintable(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsPunctuation(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsReturn(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsSpace(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsUpper(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsValidIdentifierLetter(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsWhiteSpace(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharIsWildcard(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharType(const C: Char): Word;// Character Transformation Routinesfunction CharHex(const C: Char): Byte;function CharLower(const C: Char): Char; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharUpper(const C: Char): Char; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function CharToggleCase(const C: Char): Char;// Character Search and Replacefunction CharPos(const S: string; const C: Char; const Index: SizeInt = 1): SizeInt;function CharLastPos(const S: string; const C: Char; const Index: SizeInt = 1): SizeInt;function CharIPos(const S: string; C: Char; const Index: SizeInt = 1): SizeInt;function CharReplace(var S: string; const Search, Replace: Char): SizeInt;// PCharVectortype  PCharVector = ^PChar;function StringsToPCharVector(var Dest: PCharVector; const Source: TStrings): PCharVector;function PCharVectorCount(Source: PCharVector): SizeInt;procedure PCharVectorToStrings(const Dest: TStrings; Source: PCharVector);procedure FreePCharVector(var Dest: PCharVector);{$IFNDEF WINSCP}// MultiSz Routinestype  PMultiSz = PChar;  PAnsiMultiSz = JclAnsiStrings.PAnsiMultiSz;  PWideMultiSz = JclWideStrings.PWideMultiSz;  TAnsiStrings = JclAnsiStrings.TJclAnsiStrings;  TWideStrings = JclWideStrings.TJclWideStrings;  TAnsiStringList = JclAnsiStrings.TJclAnsiStringList;  TWideStringList = JclWideStrings.TJclWideStringList;function StringsToMultiSz(var Dest: PMultiSz; const Source: TStrings): PMultiSz;procedure MultiSzToStrings(const Dest: TStrings; const Source: PMultiSz);function MultiSzLength(const Source: PMultiSz): SizeInt;procedure AllocateMultiSz(var Dest: PMultiSz; Len: SizeInt);procedure FreeMultiSz(var Dest: PMultiSz);function MultiSzDup(const Source: PMultiSz): PMultiSz;function AnsiStringsToAnsiMultiSz(var Dest: PAnsiMultiSz; const Source: TAnsiStrings): PAnsiMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure AnsiMultiSzToAnsiStrings(const Dest: TAnsiStrings; const Source: PAnsiMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function AnsiMultiSzLength(const Source: PAnsiMultiSz): SizeInt; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure AllocateAnsiMultiSz(var Dest: PAnsiMultiSz; Len: SizeInt); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure FreeAnsiMultiSz(var Dest: PAnsiMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function AnsiMultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function WideStringsToWideMultiSz(var Dest: PWideMultiSz; const Source: TWideStrings): PWideMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure WideMultiSzToWideStrings(const Dest: TWideStrings; const Source: PWideMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function WideMultiSzLength(const Source: PWideMultiSz): SizeInt; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure AllocateWideMultiSz(var Dest: PWideMultiSz; Len: SizeInt); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}procedure FreeWideMultiSz(var Dest: PWideMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}function WideMultiSzDup(const Source: PWideMultiSz): PWideMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF}{$ENDIF ~WINSCP}// TStrings Manipulationprocedure StrIToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True);procedure StrToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True);function StringsToStr(const List: TStrings; const Sep: string; const AllowEmptyString: Boolean = True): string; overload;function StringsToStr(const List: TStrings; const Sep: string; const NumberOfItems: SizeInt; const AllowEmptyString:    Boolean = True): string; overload;procedure TrimStrings(const List: TStrings; DeleteIfEmpty: Boolean = True);procedure TrimStringsRight(const List: TStrings; DeleteIfEmpty: Boolean = True);procedure TrimStringsLeft(const List: TStrings; DeleteIfEmpty: Boolean = True);function AddStringToStrings(const S: string; Strings: TStrings; const Unique: Boolean): Boolean;// Miscellaneous// (OF) moved to JclSysUtils// function BooleanToStr(B: Boolean): string; // AnsiString here because it is binary datafunction FileToString(const FileName: string): {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF};procedure StringToFile(const FileName: string; const Contents: {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF};  Append: Boolean = False);function StrToken(var S: string; Separator: Char): string;procedure StrTokens(const S: string; const List: TStrings);procedure StrTokenToStrings(S: string; Separator: Char; const List: TStrings);function StrWord(const S: string; var Index: SizeInt; out Word: string): Boolean; overload;function StrWord(var S: PChar; out Word: string): Boolean; overload;function StrIdent(const S: string; var Index: SizeInt; out Ident: string): Boolean; overload;function StrIdent(var S: PChar; out Ident: string): Boolean; overload;{$IFNDEF WINSCP}function StrToFloatSafe(const S: string): Float;function StrToIntSafe(const S: string): Integer;{$ENDIF ~WINSCP}procedure StrNormIndex(const StrLen: SizeInt; var Index: SizeInt; var Count: SizeInt); overload;function ArrayOf(List: TStrings): TDynStringArray; overload;type  FormatException = class(EJclError);  ArgumentException = class(EJclError);  ArgumentNullException = class(EJclError);  ArgumentOutOfRangeException = class(EJclError);  IToString = interface    ['{C4ABABB4-1029-46E7-B5FA-99800F130C05}']    function ToString: string;  end;  TCharDynArray = array of Char;  // The TStringBuilder class is a Delphi implementation of the .NET  // System.Text.StringBuilder.  // It is zero based and the methods that have a TObject argument (Append, Insert,  // AppendFormat) are limited to IToString implementors or Delphi 2009+ RTL.  // This class is not threadsafe. Any instance of TStringBuilder should not  // be used in different threads at the same time.  TJclStringBuilder = class(TInterfacedObject, IToString)  private    FChars: TCharDynArray;    FLength: SizeInt;    FMaxCapacity: SizeInt;    function GetCapacity: SizeInt;    procedure SetCapacity(const Value: SizeInt);    function GetChars(Index: SizeInt): Char;    procedure SetChars(Index: SizeInt; const Value: Char);    procedure Set_Length(const Value: SizeInt);  protected    function AppendPChar(Value: PChar; Count: SizeInt; RepeatCount: SizeInt = 1): TJclStringBuilder;    function InsertPChar(Index: SizeInt; Value: PChar; Count: SizeInt; RepeatCount: SizeInt = 1): TJclStringBuilder;  public    constructor Create(const Value: string; Capacity: SizeInt = 16); overload;    constructor Create(Capacity: SizeInt = 16; MaxCapacity: SizeInt = MaxInt); overload;    constructor Create(const Value: string; StartIndex, Length, Capacity: SizeInt); overload;    function Append(const Value: string): TJclStringBuilder; overload;    function Append(const Value: string; StartIndex, Length: SizeInt): TJclStringBuilder; overload;    function Append(Value: Boolean): TJclStringBuilder; overload;    function Append(Value: Char; RepeatCount: SizeInt = 1): TJclStringBuilder; overload;    function Append(const Value: array of Char): TJclStringBuilder; overload;    function Append(const Value: array of Char; StartIndex, Length: SizeInt): TJclStringBuilder; overload;    function Append(Value: Cardinal): TJclStringBuilder; overload;    function Append(Value: Integer): TJclStringBuilder; overload;    function Append(Value: Double): TJclStringBuilder; overload;    function Append(Value: Int64): TJclStringBuilder; overload;    function Append(Obj: TObject): TJclStringBuilder; overload;    function AppendFormat(const Fmt: string; const Args: array of const): TJclStringBuilder; overload;    function AppendFormat(const Fmt: string; Arg0: Variant): TJclStringBuilder; overload;    function AppendFormat(const Fmt: string; Arg0, Arg1: Variant): TJclStringBuilder; overload;    function AppendFormat(const Fmt: string; Arg0, Arg1, Arg2: Variant): TJclStringBuilder; overload;    function Insert(Index: SizeInt; const Value: string; Count: SizeInt = 1): TJclStringBuilder; overload;    function Insert(Index: SizeInt; Value: Boolean): TJclStringBuilder; overload;    function Insert(Index: SizeInt; const Value: array of Char): TJclStringBuilder; overload;    function Insert(Index: SizeInt; const Value: array of Char; StartIndex, Length: SizeInt): TJclStringBuilder;      overload;    function Insert(Index: SizeInt; Value: Cardinal): TJclStringBuilder; overload;    function Insert(Index: SizeInt; Value: Integer): TJclStringBuilder; overload;    function Insert(Index: SizeInt; Value: Double): TJclStringBuilder; overload;    function Insert(Index: SizeInt; Value: Int64): TJclStringBuilder; overload;    function Insert(Index: SizeInt; Obj: TObject): TJclStringBuilder; overload;    function Replace(OldChar, NewChar: Char; StartIndex: SizeInt = 0; Count: SizeInt = -1): TJclStringBuilder;      overload;    function Replace(OldValue, NewValue: string; StartIndex: SizeInt = 0; Count: SizeInt = -1): TJclStringBuilder;      overload;    function Remove(StartIndex, Length: SizeInt): TJclStringBuilder;    function EnsureCapacity(Capacity: SizeInt): SizeInt;    procedure Clear;    { IToString }    function ToString: string; {$IFDEF RTL200_UP} override; {$ENDIF RTL200_UP}    property __Chars__[Index: SizeInt]: Char read GetChars write SetChars; default;    property Chars: TCharDynArray read FChars;    property Length: SizeInt read FLength write Set_Length;    property Capacity: SizeInt read GetCapacity write SetCapacity;    property MaxCapacity: SizeInt read FMaxCapacity;  end;  {$IFDEF RTL200_UP}  TStringBuilder = {$IFDEF HAS_UNITSCOPE}System.{$ENDIF}SysUtils.TStringBuilder;  {$ELSE ~RTL200_UP}  TStringBuilder = TJclStringBuilder;  {$ENDIF ~RTL200_UP}// DotNetFormat() uses the .NET format style: "{argX}"function DotNetFormat(const Fmt: string; const Args: array of const): string; overload;function DotNetFormat(const Fmt: string; const Arg0: Variant): string; overload;function DotNetFormat(const Fmt: string; const Arg0, Arg1: Variant): string; overload;function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: Variant): string; overload;// TJclTabSettype  TJclTabSet = class (TInterfacedObject, IToString)  private    FData: TObject;    function GetCount: SizeInt;    function GetStops(Index: SizeInt): SizeInt;    function GetTabWidth: SizeInt;    function GetZeroBased: Boolean;    procedure SetStops(Index, Value: SizeInt);    procedure SetTabWidth(Value: SizeInt);    procedure SetZeroBased(Value: Boolean);  protected    function FindStop(Column: SizeInt): SizeInt;    function InternalTabStops: TDynSizeIntArray;    function InternalTabWidth: SizeInt;    procedure RemoveAt(Index: SizeInt);  public    constructor Create; overload;    constructor Create(Data: TObject); overload;    constructor Create(TabWidth: SizeInt); overload;    constructor Create(const Tabstops: array of SizeInt; ZeroBased: Boolean); overload;    constructor Create(const Tabstops: array of SizeInt; ZeroBased: Boolean; TabWidth: SizeInt); overload;    destructor Destroy; override;    // cloning and referencing    function Clone: TJclTabSet;    function NewReference: TJclTabSet;    // Tab stops manipulation    function Add(Column: SizeInt): SizeInt;    function Delete(Column: SizeInt): SizeInt;    // Usage    function Expand(const S: string): string; overload;    function Expand(const S: string; Column: SizeInt): string; overload;    procedure OptimalFillInfo(StartColumn, TargetColumn: SizeInt; out TabsNeeded, SpacesNeeded: SizeInt);    function Optimize(const S: string): string; overload;    function Optimize(const S: string; Column: SizeInt): string; overload;    function StartColumn: SizeInt;    function TabFrom(Column: SizeInt): SizeInt;    function UpdatePosition(const S: string): SizeInt; overload;    function UpdatePosition(const S: string; Column: SizeInt): SizeInt; overload;    function UpdatePosition(const S: string; var Column, Line: SizeInt): SizeInt; overload;    { IToString }    function ToString: string; overload; {$IFDEF RTL200_UP} override; {$ENDIF RTL200_UP}    // Conversions    function ToString(FormattingOptions: SizeInt): string; {$IFDEF RTL200_UP} reintroduce; {$ENDIF RTL200_UP} overload;    class function FromString(const S: string): TJclTabSet; {$IFDEF SUPPORTS_STATIC} static; {$ENDIF SUPPORTS_STATIC}    // Properties    property ActualTabWidth: SizeInt read InternalTabWidth;    property Count: SizeInt read GetCount;    property TabStops[Index: SizeInt]: SizeInt read GetStops write SetStops; default;    property TabWidth: SizeInt read GetTabWidth write SetTabWidth;    property ZeroBased: Boolean read GetZeroBased write SetZeroBased;  end;// Formatting constantsconst  TabSetFormatting_SurroundStopsWithBrackets = 1;  TabSetFormatting_EmptyBracketsIfNoStops = 2;  TabSetFormatting_NoTabStops = 4;  TabSetFormatting_NoTabWidth = 8;  TabSetFormatting_AutoTabWidth = 16;  // common combinations  TabSetFormatting_Default = 0;  TabSetFormatting_AlwaysUseBrackets = TabSetFormatting_SurroundStopsWithBrackets or    TabSetFormatting_EmptyBracketsIfNoStops;  TabSetFormatting_Full = TabSetFormatting_AlwaysUseBrackets or TabSetFormatting_AutoTabWidth;  // aliases  TabSetFormatting_StopsOnly = TabSetFormatting_NoTabWidth;  TabSetFormatting_TabWidthOnly = TabSetFormatting_NoTabStops;  TabSetFormatting_StopsWithoutBracketsAndTabWidth = TabSetFormatting_Default;// Tab expansion routinesfunction StrExpandTabs(S: string): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;function StrExpandTabs(S: string; TabWidth: SizeInt): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;function StrExpandTabs(S: string; TabSet: TJclTabSet): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;// Tab optimization routinesfunction StrOptimizeTabs(S: string): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;function StrOptimizeTabs(S: string; TabWidth: SizeInt): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;function StrOptimizeTabs(S: string; TabSet: TJclTabSet): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload;// move to JclBase?type  NullReferenceException = class(EJclError)  public    constructor Create; overload;  end;procedure StrResetLength(var S: WideString); overload;procedure StrResetLength(var S: AnsiString); overload;procedure StrResetLength(S: TJclStringBuilder); overload;{$IFDEF SUPPORTS_UNICODE_STRING}procedure StrResetLength(var S: UnicodeString); overload;{$ENDIF SUPPORTS_UNICODE_STRING}// natural comparison functions{$IFNDEF WINSCP}function CompareNaturalStr(const S1, S2: string): SizeInt;function CompareNaturalText(const S1, S2: string): SizeInt;{$ENDIF ~WINSCP}{$IFNDEF UNICODE_RTL_DATABASE}// internal structures published to make function inlining workingconst  MaxStrCharCount = Ord(High(Char)) + 1;       // # of chars in one set  StrLoOffset = MaxStrCharCount * 0;       // offset to lower case chars  StrUpOffset = MaxStrCharCount * 1;       // offset to upper case chars  StrReOffset = MaxStrCharCount * 2;       // offset to reverse case chars  StrCaseMapSize = MaxStrCharCount * 3;       // # of chars is a tablevar  StrCaseMap: array [0..StrCaseMapSize - 1] of Char; // case mappings  StrCaseMapReady: Boolean = False;         // true if case map exists  StrCharTypes: array [Char] of Word;{$ENDIF ~UNICODE_RTL_DATABASE}{$IFDEF UNITVERSIONING}const  UnitVersioning: TUnitVersionInfo = (    RCSfile: '$URL$';    Revision: '$Revision$';    Date: '$Date$';    LogPath: 'JCL\source\common';    Extra: '';    Data: nil    );{$ENDIF UNITVERSIONING}implementationuses  {$IFDEF HAS_UNIT_LIBC}  Libc,  {$ENDIF HAS_UNIT_LIBC}  {$IFDEF SUPPORTS_UNICODE}  {$IFDEF HAS_UNITSCOPE}  System.StrUtils,  {$ELSE ~HAS_UNITSCOPE}  StrUtils,  {$ENDIF ~HAS_UNITSCOPE}  {$ENDIF SUPPORTS_UNICODE}  {$IFNDEF WINSCP}JclLogic,{$ELSE}Math,{$ENDIF ~WINSCP} JclResources, JclStreams, JclSynch{$IFNDEF WINSCP}, JclSysUtils{$ENDIF ~WINSCP};//=== Internal ===============================================================type  TStrRec = packed record    RefCount: Longint;    Length: Longint;  end;  PStrRec = ^TStrRec;{$IFNDEF UNICODE_RTL_DATABASE}procedure LoadCharTypes;var  CurrChar: Char;  CurrType: Word;begin  for CurrChar := Low(CurrChar) to High(CurrChar) do  begin    {$IFDEF MSWINDOWS}    CurrType := 0;    GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, @CurrChar, 1, CurrType);    {$DEFINE CHAR_TYPES_INITIALIZED}    {$ENDIF MSWINDOWS}    {$IFDEF LINUX}    CurrType := 0;    if isupper(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_UPPER;    if islower(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_LOWER;    if isdigit(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_DIGIT;    if isspace(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_SPACE;    if ispunct(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_PUNCT;    if iscntrl(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_CNTRL;    if isblank(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_BLANK;    if isxdigit(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_XDIGIT;    if isalpha(Byte(CurrChar)) <> 0 then      CurrType := CurrType or C1_ALPHA;    {$DEFINE CHAR_TYPES_INITIALIZED}    {$ENDIF LINUX}    StrCharTypes[CurrChar] := CurrType;    {$IFNDEF CHAR_TYPES_INITIALIZED}    Implement case map initialization here    {$ENDIF ~CHAR_TYPES_INITIALIZED}  end;end;procedure LoadCaseMap;var  CurrChar, UpCaseChar, LoCaseChar, ReCaseChar: Char;begin  if not StrCaseMapReady then  begin    for CurrChar := Low(Char) to High(Char) do    begin      {$IFDEF MSWINDOWS}      LoCaseChar := CurrChar;      UpCaseChar := CurrChar;      {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CharLowerBuff(@LoCaseChar, 1);      {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CharUpperBuff(@UpCaseChar, 1);      {$DEFINE CASE_MAP_INITIALIZED}      {$ENDIF MSWINDOWS}      {$IFDEF LINUX}      LoCaseChar := Char(tolower(Byte(CurrChar)));      UpCaseChar := Char(toupper(Byte(CurrChar)));      {$DEFINE CASE_MAP_INITIALIZED}      {$ENDIF LINUX}      {$IFNDEF CASE_MAP_INITIALIZED}      Implement case map initialization here      {$ENDIF ~CASE_MAP_INITIALIZED}      if CharIsUpper(CurrChar) then        ReCaseChar := LoCaseChar      else      if CharIsLower(CurrChar) then        ReCaseChar := UpCaseChar      else        ReCaseChar := CurrChar;      StrCaseMap[Ord(CurrChar) + StrLoOffset] := LoCaseChar;      StrCaseMap[Ord(CurrChar) + StrUpOffset] := UpCaseChar;      StrCaseMap[Ord(CurrChar) + StrReOffset] := ReCaseChar;    end;    StrCaseMapReady := True;  end;end;// Uppercases or Lowercases a give string depending on the// passed offset. (UpOffset or LoOffset)procedure StrCase(var Str: string; const Offset: SizeInt);var  P: PChar;  I, L: SizeInt;begin  L := Length(Str);  if L > 0 then  begin    UniqueString(Str);    P := PChar(Str);    for I := 1 to L do    begin      P^ := StrCaseMap[Offset + Ord(P^)];      Inc(P);    end;  end;end;// Internal utility function// Uppercases or Lowercases a give null terminated string depending on the// passed offset. (UpOffset or LoOffset)procedure StrCaseBuff(S: PChar; const Offset: SizeInt);var  C: Char;begin  if S <> nil then  begin    repeat      C := S^;      S^ := StrCaseMap[Offset + Ord(C)];      Inc(S);    until C = #0;  end;end;{$ENDIF ~UNICODE_RTL_DATABASE}function StrEndW(Str: PWideChar): PWideChar;begin  Result := Str;  while Result^ <> #0 do    Inc(Result);end;function ArrayContainsChar(const Chars: array of Char; const C: Char): Boolean;var  idx: SizeInt;begin  Result := ArrayContainsChar(Chars, C, idx);end;function ArrayContainsChar(const Chars: array of Char; const C: Char; out Index: SizeInt): Boolean;{ optimized version for sorted arraysvar  I, L, H: SizeInt;begin  L := Low(Chars);  H := High(Chars);  while L <= H do  begin    I := (L + H) div 2;    if C = Chars[I] then    begin      Result := True;      Exit;    end    else    if C < Chars[I] then      H := I - 1    else      // C > Chars[I]      L := I + 1;  end;  Result := False;end;}begin  Index := High(Chars);  while (Index >= Low(Chars)) and (Chars[Index] <> C) do    Dec(Index);  Result := Index >= Low(Chars);end;// String Test Routinesfunction StrIsAlpha(const S: string): Boolean;var  I: SizeInt;begin  Result := S <> '';  for I := 1 to Length(S) do  begin    if not CharIsAlpha(S[I]) then    begin      Result := False;      Exit;    end;  end;end;function StrIsAlphaNum(const S: string): Boolean;var  I: SizeInt;begin  Result := S <> '';  for I := 1 to Length(S) do  begin    if not CharIsAlphaNum(S[I]) then    begin      Result := False;      Exit;    end;  end;end;{$IFNDEF WINSCP}function StrConsistsofNumberChars(const S: string): Boolean;var  I: SizeInt;begin  Result := S <> '';  for I := 1 to Length(S) do  begin    if not CharIsNumberChar(S[I]) then    begin      Result := False;      Exit;    end;  end;end;{$ENDIF ~WINSCP}function StrContainsChars(const S: string; const Chars: TCharValidator; CheckAll: Boolean): Boolean;var  I: SizeInt;begin  Result := False;  if CheckAll then  begin    // this will not work with the current definition of the validator. The validator would need to check each character    // it requires against the string (which is currently not provided to the Validator). The current implementation of    // CheckAll will check if all characters in S will be accepted by the provided Validator, which is wrong and incon-    // sistent with the documentation and the array-based overload.    for I := 1 to Length(S) do    begin      Result := Chars(S[I]);      if not Result then        Break;    end;  end  else  begin    for I := 1 to Length(S) do    begin      Result := Chars(S[I]);      if Result then        Break;    end;  end;end;function StrContainsChars(const S: string; const Chars: array of Char; CheckAll: Boolean): Boolean;var  I: SizeInt;begin  if CheckAll then  begin    Result := True;    I := High(Chars);    while (I >= 0) and Result do    begin      Result := CharPos(S, Chars[I]) > 0;      Dec(I);    end;  end  else  begin    Result := False;    for I := 1 to Length(S) do    begin      Result := ArrayContainsChar(Chars, S[I]);      if Result then        Break;    end;  end;end;function StrIsAlphaNumUnderscore(const S: string): Boolean;var  I: SizeInt;  C: Char;begin  for I := 1 to Length(S) do  begin    C := S[I];    if not (CharIsAlphaNum(C) or (C = '_')) then    begin      Result := False;      Exit;    end;  end;  Result := Length(S) > 0;end;function StrIsDigit(const S: string): Boolean;var  I: SizeInt;begin  Result := S <> '';  for I := 1 to Length(S) do  begin    if not CharIsDigit(S[I]) then    begin      Result := False;      Exit;    end;  end;end;function StrIsSubset(const S: string; const ValidChars: TCharValidator): Boolean;var  I: SizeInt;begin  for I := 1 to Length(S) do  begin    Result := ValidChars(S[I]);    if not Result then      Exit;  end;  Result := Length(S) > 0;end;function StrIsSubset(const S: string; const ValidChars: array of Char): Boolean;var  I: SizeInt;begin  for I := 1 to Length(S) do  begin    Result := ArrayContainsChar(ValidChars, S[I]);    if not Result then      Exit;  end;  Result := Length(S) > 0;end;function StrSame(const S1, S2: string; CaseSensitive: Boolean): Boolean;begin  Result := StrCompare(S1, S2, CaseSensitive) = 0;end;//=== String Transformation Routines =========================================function StrCenter(const S: string; L: SizeInt; C: Char = ' '): string;begin  if Length(S) < L then  begin    Result := StringOfChar(C, (L - Length(S)) div 2) + S;    Result := Result + StringOfChar(C, L - Length(Result));  end  else    Result := S;end;function StrCharPosLower(const S: string; CharPos: SizeInt): string;begin  Result := S;  if (CharPos > 0) and (CharPos <= Length(S)) then    Result[CharPos] := CharLower(Result[CharPos]);end;function StrCharPosUpper(const S: string; CharPos: SizeInt): string;begin  Result := S;  if (CharPos > 0) and (CharPos <= Length(S)) then    Result[CharPos] := CharUpper(Result[CharPos]);end;function StrDoubleQuote(const S: string): string;begin  Result := NativeDoubleQuote + S + NativeDoubleQuote;end;function StrEnsureNoPrefix(const Prefix, Text: string): string;var  PrefixLen: SizeInt;begin  PrefixLen := Length(Prefix);  if Copy(Text, 1, PrefixLen) = Prefix then    Result := Copy(Text, PrefixLen + 1, Length(Text))  else    Result := Text;end;function StrEnsureNoSuffix(const Suffix, Text: string): string;var  SuffixLen: SizeInt;  StrLength: SizeInt;begin  SuffixLen := Length(Suffix);  StrLength := Length(Text);  if Copy(Text, StrLength - SuffixLen + 1, SuffixLen) = Suffix then    Result := Copy(Text, 1, StrLength - SuffixLen)  else    Result := Text;end;function StrEnsurePrefix(const Prefix, Text: string): string;var  PrefixLen: SizeInt;begin  PrefixLen := Length(Prefix);  if Copy(Text, 1, PrefixLen) = Prefix then    Result := Text  else    Result := Prefix + Text;end;function StrEnsureSuffix(const Suffix, Text: string): string;var  SuffixLen: SizeInt;begin  SuffixLen := Length(Suffix);  if Copy(Text, Length(Text) - SuffixLen + 1, SuffixLen) = Suffix then    Result := Text  else    Result := Text + Suffix;end;function StrEscapedToString(const S: string): string;  procedure HandleHexEscapeSeq(const S: string; var I: SizeInt; Len: SizeInt; var Dest: string);  const    HexDigits = string('0123456789abcdefABCDEF');  var    StartI, Val, N: SizeInt;  begin    StartI := I;    N := Pos(S[I + 1], HexDigits) - 1;    if N < 0 then      // '\x' without hex digit following is not escape sequence      Dest := Dest + '\x'    else    begin      Inc(I); // Jump over x      if N >= 16 then        N := N - 6;      Val := N;      // Same for second digit      if I < Len then      begin        N := Pos(S[I + 1], HexDigits) - 1;        if N >= 0 then        begin          Inc(I); // Jump over first digit          if N >= 16 then            N := N - 6;          Val := Val * 16 + N;        end;      end;      if Val > Ord(High(Char)) then        raise EJclStringError.CreateResFmt(@RsNumericConstantTooLarge, [Val, StartI]);      Dest := Dest + Char(Val);    end;  end;  procedure HandleOctEscapeSeq(const S: string; var I: SizeInt; Len: SizeInt; var Dest: string);  const    OctDigits = string('01234567');  var    StartI, Val, N: SizeInt;  begin    StartI := I;    // first digit    Val := Pos(S[I], OctDigits) - 1;    if I < Len then    begin      N := Pos(S[I + 1], OctDigits) - 1;      if N >= 0 then      begin        Inc(I);        Val := Val * 8 + N;      end;      if I < Len then      begin        N := Pos(S[I + 1], OctDigits) - 1;        if N >= 0 then        begin          Inc(I);          Val := Val * 8 + N;        end;      end;    end;    if Val > Ord(High(Char)) then      raise EJclStringError.CreateResFmt(@RsNumericConstantTooLarge, [Val, StartI]);    Dest := Dest + Char(Val);  end;var  I, Len: SizeInt;begin  Result := '';  I := 1;  Len := Length(S);  while I <= Len do  begin    if not ((S[I] = '\') and (I < Len)) then      Result := Result + S[I]    else    begin      Inc(I); // Jump over escape character      case S[I] of        'a':          Result := Result + NativeBell;        'b':          Result := Result + NativeBackspace;        'f':          Result := Result + NativeFormFeed;        'n':          Result := Result + NativeLineFeed;        'r':          Result := Result + NativeCarriageReturn;        't':          Result := Result + NativeTab;        'v':          Result := Result + NativeVerticalTab;        '\':          Result := Result + '\';        '"':          Result := Result + '"';        '''':          Result := Result + ''''; // Optionally escaped        '?':          Result := Result + '?';  // Optionally escaped        'x':          if I < Len then            // Start of hex escape sequence            HandleHexEscapeSeq(S, I, Len, Result)          else            // '\x' at end of string is not escape sequence            Result := Result + '\x';        '0'..'7':          // start of octal escape sequence          HandleOctEscapeSeq(S, I, Len, Result);      else        // no escape sequence        Result := Result + '\' + S[I];      end;    end;    Inc(I);  end;end;function StrLower(const S: string): string;begin  Result := S;  StrLowerInPlace(Result);end;procedure StrLowerInPlace(var S: string);{$IFDEF UNICODE_RTL_DATABASE}var  P: PChar;  I, L: SizeInt;begin  L := Length(S);  if L > 0 then  begin    UniqueString(S);    P := PChar(S);    for I := 1 to L do    begin      P^ := TCharacter.ToLower(P^);      Inc(P);    end;  end;end;{$ELSE ~UNICODE_RTL_DATABASE}begin  StrCase(S, StrLoOffset);end;{$ENDIF ~UNICODE_RTL_DATABASE}procedure StrLowerBuff(S: PChar);begin  {$IFDEF UNICODE_RTL_DATABASE}  if S <> nil then  begin    repeat      S^ := TCharacter.ToLower(S^);      Inc(S);    until S^ = #0;  end;  {$ELSE ~UNICODE_RTL_DATABASE}  StrCaseBuff(S, StrLoOffset);  {$ENDIF ~UNICODE_RTL_DATABASE}end;procedure StrMove(var Dest: string; const Source: string;  const ToIndex, FromIndex, Count: SizeInt);begin  // Check strings  if (Source = '') or (Length(Dest) = 0) then    Exit;  // Check FromIndex  if (FromIndex <= 0) or (FromIndex > Length(Source)) or    (ToIndex <= 0) or (ToIndex > Length(Dest)) or    ((FromIndex + Count - 1) > Length(Source)) or ((ToIndex + Count - 1) > Length(Dest)) then     { TODO : Is failure without notice the proper thing to do here? }    Exit;  // Move  Move(Source[FromIndex], Dest[ToIndex], Count * SizeOf(Char));end;function StrPadLeft(const S: string; Len: SizeInt; C: Char): string;var  L: SizeInt;begin  L := Length(S);  if L < Len then    Result := StringOfChar(C, Len - L) + S  else    Result := S;end;function StrPadRight(const S: string; Len: SizeInt; C: Char): string;var  L: SizeInt;begin  L := Length(S);  if L < Len then    Result := S + StringOfChar(C, Len - L)  else    Result := S;end;function StrProper(const S: string): string;begin  Result := StrLower(S);  if Result <> '' then    Result[1] := UpCase(Result[1]);end;procedure StrProperBuff(S: PChar);begin  if (S <> nil) and (S^ <> #0) then  begin    StrLowerBuff(S);    S^ := CharUpper(S^);  end;end;function StrQuote(const S: string; C: Char): string;var  L: SizeInt;begin  L := Length(S);  Result := S;  if L > 0 then  begin    if Result[1] <> C then    begin      Result := C + Result;      Inc(L);    end;    if Result[L] <> C then      Result := Result + C;  end;end;function StrRemoveChars(const S: string; const Chars: TCharValidator): string;var  Source, Dest: PChar;  Len, Index:   SizeInt;begin  Len := Length(S);  SetLength(Result, Len);  UniqueString(Result);  Source := PChar(S);  Dest := PChar(Result);  for Index := 0 to Len - 1 do  begin    if not Chars(Source^) then    begin      Dest^ := Source^;      Inc(Dest);    end;    Inc(Source);  end;  SetLength(Result, Dest - PChar(Result));end;function StrRemoveChars(const S: string; const Chars: array of Char): string;var  Source, Dest: PChar;  Len, Index:   SizeInt;begin  Len := Length(S);  SetLength(Result, Len);  UniqueString(Result);  Source := PChar(S);  Dest := PChar(Result);  for Index := 0 to Len - 1 do  begin    if not ArrayContainsChar(Chars, Source^) then    begin      Dest^ := Source^;      Inc(Dest);    end;    Inc(Source);  end;  SetLength(Result, Dest - PChar(Result));end;function StrRemoveLeadingChars(const S: string; const Chars: TCharValidator): string;var  Len : SizeInt;  I: SizeInt;begin  Len := Length(S);  I := 1;  while (I <= Len) and Chars(s[I]) do    Inc(I);  Result := Copy (s, I, Len-I+1);end;function StrRemoveLeadingChars(const S: string; const Chars: array of Char): string;var  Len : SizeInt;  I: SizeInt;begin  Len := Length(S);  I := 1;  while (I <= Len) and ArrayContainsChar(Chars, s[I]) do    Inc(I);  Result := Copy (s, I, Len-I+1);end;function StrRemoveEndChars(const S: string; const Chars: TCharValidator): string;var  Len :   SizeInt;begin  Len := Length(S);  while (Len > 0) and Chars(s[Len]) do    Dec(Len);  Result := Copy (s, 1, Len);end;function StrRemoveEndChars(const S: string; const Chars: array of Char): string;var  Len :   SizeInt;begin  Len := Length(S);  while (Len > 0) and ArrayContainsChar(Chars, s[Len]) do    Dec(Len);  Result := Copy (s, 1, Len);end;function StrKeepChars(const S: string; const Chars: TCharValidator): string;var  Source, Dest: PChar;  Len, Index:   SizeInt;begin  Len := Length(S);  SetLength(Result, Len);  UniqueString(Result);  Source := PChar(S);  Dest := PChar(Result);  for Index := 0 to Len - 1 do  begin    if Chars(Source^) then    begin      Dest^ := Source^;      Inc(Dest);    end;    Inc(Source);  end;  SetLength(Result, Dest - PChar(Result));end;function StrKeepChars(const S: string; const Chars: array of Char): string;var  Source, Dest: PChar;  Len, Index:   SizeInt;begin  Len := Length(S);  SetLength(Result, Len);  UniqueString(Result);  Source := PChar(S);  Dest := PChar(Result);  for Index := 0 to Len - 1 do  begin    if ArrayContainsChar(Chars, Source^) then    begin      Dest^ := Source^;      Inc(Dest);    end;    Inc(Source);  end;  SetLength(Result, Dest - PChar(Result));end;function StrRepeat(const S: string; Count: SizeInt): string;var  Len, Index: SizeInt;  Dest, Source: PChar;begin  Len := Length(S);  SetLength(Result, Count * Len);  Dest := PChar(Result);  Source := PChar(S);  if Dest <> nil then    for Index := 0 to Count - 1 do    begin      Move(Source^, Dest^, Len * SizeOf(Char));      Inc(Dest, Len);    end;end;function StrRepeatLength(const S: string; L: SizeInt): string;var  Len: SizeInt;  Dest: PChar;begin  Result := '';  Len := Length(S);  if (Len > 0) and (S <> '') then  begin    SetLength(Result, L);    Dest := PChar(Result);    while (L > 0) do    begin      Move(S[1], Dest^, Min(L, Len) * SizeOf(Char));      Inc(Dest, Len);      Dec(L, Len);    end;  end;end;procedure StrReplace(var S: string; const Search, Replace: string; Flags: TReplaceFlags);var  SearchStr: string;  ResultStr: string; { result string }  SourcePtr: PChar;      { pointer into S of character under examination }  SourceMatchPtr: PChar; { pointers into S and Search when first character has }  SearchMatchPtr: PChar; { been matched and we're probing for a complete match }  ResultPtr: PChar;      { pointer into Result of character being written }  ResultIndex,  SearchLength,          { length of search string }  ReplaceLength,         { length of replace string }  BufferLength,          { length of temporary result buffer }  ResultLength: SizeInt; { length of result string }  C: Char;               { first character of search string }  IgnoreCase: Boolean;begin  if Search = '' then  begin    if S = '' then    begin      S := Replace;      Exit;    end    else      raise EJclStringError.CreateRes(@RsBlankSearchString);  end;  if S <> '' then  begin    IgnoreCase := rfIgnoreCase in Flags;    if IgnoreCase then      SearchStr := StrUpper(Search)    else      SearchStr := Search;    { avoid having to call Length() within the loop }    SearchLength := Length(Search);    ReplaceLength := Length(Replace);    ResultLength := Length(S);    BufferLength := ResultLength;    SetLength(ResultStr, BufferLength);    { get pointers to begin of source and result }    ResultPtr := PChar(ResultStr);    SourcePtr := PChar(S);    C := SearchStr[1];    { while we haven't reached the end of the string }    while True do    begin      { copy characters until we find the first character of the search string }      if IgnoreCase then        while (CharUpper(SourcePtr^) <> C) and (SourcePtr^ <> #0) do        begin          ResultPtr^ := SourcePtr^;          Inc(ResultPtr);          Inc(SourcePtr);        end      else        while (SourcePtr^ <> C) and (SourcePtr^ <> #0) do        begin          ResultPtr^ := SourcePtr^;          Inc(ResultPtr);          Inc(SourcePtr);        end;      { did we find that first character or did we hit the end of the string? }      if SourcePtr^ = #0 then        Break      else      begin        { continue comparing, +1 because first character was matched already }        SourceMatchPtr := SourcePtr + 1;        SearchMatchPtr := PChar(SearchStr) + 1;        if IgnoreCase then          while (CharUpper(SourceMatchPtr^) = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do          begin            Inc(SourceMatchPtr);            Inc(SearchMatchPtr);          end        else          while (SourceMatchPtr^ = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do          begin            Inc(SourceMatchPtr);            Inc(SearchMatchPtr);          end;        { did we find a complete match? }        if SearchMatchPtr^ = #0 then        begin          // keep track of result length          Inc(ResultLength, ReplaceLength - SearchLength);          if ReplaceLength > 0 then          begin            // increase buffer size if required            if ResultLength > BufferLength then            begin              BufferLength := ResultLength * 2;              ResultIndex := ResultPtr - PChar(ResultStr) + 1;              SetLength(ResultStr, BufferLength);              ResultPtr := @ResultStr[ResultIndex];            end;            { append replace to result and move past the search string in source }            Move((@Replace[1])^, ResultPtr^, ReplaceLength * SizeOf(Char));          end;          Inc(SourcePtr, SearchLength);          Inc(ResultPtr, ReplaceLength);          { replace all instances or just one? }          if not (rfReplaceAll in Flags) then          begin            { just one, copy until end of source and break out of loop }            while SourcePtr^ <> #0 do            begin              ResultPtr^ := SourcePtr^;              Inc(ResultPtr);              Inc(SourcePtr);            end;            Break;          end;        end        else        begin          { copy current character and start over with the next }          ResultPtr^ := SourcePtr^;          Inc(ResultPtr);          Inc(SourcePtr);        end;      end;    end;    { set result length and copy result into S }    SetLength(ResultStr, ResultLength);    S := ResultStr;  end;end;function StrReplaceChar(const S: string; const Source, Replace: Char): string;var  I: SizeInt;begin  Result := S;  for I := 1 to Length(S) do    if Result[I] = Source then      Result[I] := Replace;end;function StrReplaceChars(const S: string; const Chars: TCharValidator; Replace: Char): string;var  I: SizeInt;begin  Result := S;  for I := 1 to Length(S) do    if Chars(Result[I]) then      Result[I] := Replace;end;function StrReplaceChars(const S: string; const Chars: array of Char; Replace: Char): string;var  I: SizeInt;begin  Result := S;  for I := 1 to Length(S) do    if ArrayContainsChar(Chars, Result[I]) then      Result[I] := Replace;end;function StrReplaceButChars(const S: string; const Chars: TCharValidator;  Replace: Char): string;var  I: SizeInt;begin  Result := S;  for I := 1 to Length(S) do    if not Chars(Result[I]) then      Result[I] := Replace;end;function StrReplaceButChars(const S: string; const Chars: array of Char; Replace: Char): string;var  I: SizeInt;begin  Result := S;  for I := 1 to Length(S) do    if not ArrayContainsChar(Chars, Result[I]) then      Result[I] := Replace;end;function StrReverse(const S: string): string;begin  Result := S;  StrReverseInplace(Result);end;procedure StrReverseInPlace(var S: string);{ TODO -oahuser : Warning: This is dangerous for unicode surrogates }var  P1, P2: PChar;  C: Char;begin  UniqueString(S);  P1 := PChar(S);  P2 := P1 + (Length(S) - 1);  while P1 < P2 do  begin    C := P1^;    P1^ := P2^;    P2^ := C;    Inc(P1);    Dec(P2);  end;end;function StrSingleQuote(const S: string): string;begin  Result := NativeSingleQuote + S + NativeSingleQuote;end;procedure StrSkipChars(var S: PChar; const Chars: TCharValidator);begin  while Chars(S^) do    Inc(S);end;procedure StrSkipChars(var S: PChar; const Chars: array of Char);begin  while ArrayContainsChar(Chars, S^) do    Inc(S);end;procedure StrSkipChars(const S: string; var Index: SizeInt; const Chars: TCharValidator);begin  while Chars(S[Index]) do    Inc(Index);end;procedure StrSkipChars(const S: string; var Index: SizeInt; const Chars: array of Char);begin  while ArrayContainsChar(Chars, S[Index]) do    Inc(Index);end;function StrSmartCase(const S: string; const Delimiters: TCharValidator): string;var  Source, Dest: PChar;  Index, Len:   SizeInt;  InternalDelimiters: TCharValidator;begin  Result := '';  if Assigned(Delimiters) then    InternalDelimiters := Delimiters  else    InternalDelimiters := CharIsSpace;  if S <> '' then  begin    Result := S;    UniqueString(Result);    Len := Length(S);    Source := PChar(S);    Dest := PChar(Result);    Inc(Dest);    for Index := 2 to Len do    begin      if InternalDelimiters(Source^) and not InternalDelimiters(Dest^) then        Dest^ := CharUpper(Dest^);      Inc(Dest);      Inc(Source);    end;    Result[1] := CharUpper(Result[1]);  end;end;function StrSmartCase(const S: string; const Delimiters: array of Char): string;var  Source, Dest: PChar;  Index, Len:   SizeInt;begin  Result := '';  if S <> '' then  begin    Result := S;    UniqueString(Result);    Len := Length(S);    Source := PChar(S);    Dest := PChar(Result);    Inc(Dest);    for Index := 2 to Len do    begin      if ArrayContainsChar(Delimiters, Source^) and not ArrayContainsChar(Delimiters, Dest^) then        Dest^ := CharUpper(Dest^);      Inc(Dest);      Inc(Source);    end;    Result[1] := CharUpper(Result[1]);  end;end;function StrStringToEscaped(const S: string): string;var  I: SizeInt;begin  Result := '';  for I := 1 to Length(S) do  begin    case S[I] of      NativeBackspace:        Result := Result + '\b';      NativeBell:        Result := Result + '\a';      NativeCarriageReturn:        Result := Result + '\r';      NAtiveFormFeed:        Result := Result + '\f';      NativeLineFeed:        Result := Result + '\n';      NativeTab:        Result := Result + '\t';      NativeVerticalTab:        Result := Result + '\v';      NativeBackSlash:        Result := Result + '\\';      NativeDoubleQuote:        Result := Result + '\"';    else      // Characters < ' ' are escaped with hex sequence      if S[I] < #32 then        Result := Result + Format('\x%.2x', [SizeInt(S[I])])      else        Result := Result + S[I];    end;  end;end;{$IFNDEF WINSCP}function StrStripNonNumberChars(const S: string): string;var  I: SizeInt;  C: Char;begin  Result := '';  for I := 1 to Length(S) do  begin    C := S[I];    if CharIsNumberChar(C) then      Result := Result + C;  end;end;{$ENDIF ~WINSCP}function StrToHex(const Source: string): string;var  Index: SizeInt;  C, L, N: SizeInt;  BL, BH: Byte;  S:     string;begin  Result := '';  if Source <> '' then  begin    S := Source;    L := Length(S);    if Odd(L) then    begin      S := '0' + S;      Inc(L);    end;    Index := 1;    SetLength(Result, L div 2);    C := 1;    N := 1;    while C <= L do    begin      BH := CharHex(S[Index]);      Inc(Index);      BL := CharHex(S[Index]);      Inc(Index);      Inc(C, 2);      if (BH = $FF) or (BL = $FF) then      begin        Result := '';        Exit;      end;      Result[N] := Char((BH shl 4) or BL);      Inc(N);    end;  end;end;function StrTrimCharLeft(const S: string; C: Char): string;var  I, L: SizeInt;begin  I := 1;  L := Length(S);  while (I <= L) and (S[I] = C) do    Inc(I);  Result := Copy(S, I, L - I + 1);end;function StrTrimCharsLeft(const S: string; const Chars: TCharValidator): string;var  I, L: SizeInt;begin  I := 1;  L := Length(S);  while (I <= L) and Chars(S[I]) do    Inc(I);  Result := Copy(S, I, L - I + 1);end;function StrTrimCharsLeft(const S: string; const Chars: array of Char): string;var  I, L: SizeInt;begin  I := 1;  L := Length(S);  while (I <= L) and ArrayContainsChar(Chars, S[I]) do    Inc(I);  Result := Copy(S, I, L - I + 1);end;function StrTrimCharRight(const S: string; C: Char): string;var  I: SizeInt;begin  I := Length(S);  while (I >= 1) and (S[I] = C) do    Dec(I);  Result := Copy(S, 1, I);end;function StrTrimCharsRight(const S: string; const Chars: TCharValidator): string;var  I: SizeInt;begin  I := Length(S);  while (I >= 1) and Chars(S[I]) do    Dec(I);  Result := Copy(S, 1, I);end;function StrTrimCharsRight(const S: string; const Chars: array of Char): string;var  I: SizeInt;begin  I := Length(S);  while (I >= 1) and ArrayContainsChar(Chars, S[I]) do    Dec(I);  Result := Copy(S, 1, I);end;function StrTrimQuotes(const S: string): string;var  First, Last: Char;  L: SizeInt;begin  L := Length(S);  if L > 1 then  begin    First := S[1];    Last := S[L];    if (First = Last) and ((First = NativeSingleQuote) or (First = NativeDoubleQuote)) then      Result := Copy(S, 2, L - 2)    else      Result := S;  end  else    Result := S;end;function StrUpper(const S: string): string;begin  Result := S;  StrUpperInPlace(Result);end;procedure StrUpperInPlace(var S: string);{$IFDEF UNICODE_RTL_DATABASE}var  P: PChar;  I, L: SizeInt;begin  L := Length(S);  if L > 0 then  begin    UniqueString(S);    P := PChar(S);    for I := 1 to L do    begin      P^ := TCharacter.ToUpper(P^);      Inc(P);    end;  end;end;{$ELSE ~UNICODE_RTL_DATABASE}begin  StrCase(S, StrUpOffset);end;{$ENDIF ~UNICODE_RTL_DATABASE}procedure StrUpperBuff(S: PChar);begin  {$IFDEF UNICODE_RTL_DATABASE}  if S <> nil then  begin    repeat      S^ := TCharacter.ToUpper(S^);      Inc(S);    until S^ = #0;  end;  {$ELSE ~UNICODE_RTL_DATABASE}  StrCaseBuff(S, StrUpOffset);  {$ENDIF ~UNICODE_RTL_DATABASE}end;//=== String Management ======================================================procedure StrAddRef(var S: string);var  P: PStrRec;begin  P := Pointer(S);  if P <> nil then  begin    Dec(P);    if P^.RefCount = -1 then      UniqueString(S)    else      LockedInc(P^.RefCount);  end;end;procedure StrDecRef(var S: string);var  P: PStrRec;begin  P := Pointer(S);  if P <> nil then  begin    Dec(P);    case P^.RefCount of      -1, 0: { nothing } ;      1:        begin          Finalize(S);          Pointer(S) := nil;        end;    else      LockedDec(P^.RefCount);    end;  end;end;function StrLength(const S: string): SizeInt;var  P: PStrRec;begin  Result := 0;  P := Pointer(S);  if P <> nil then  begin    Dec(P);    Result := P^.Length and (not $80000000 shr 1);  end;end;function StrRefCount(const S: string): SizeInt;var  P: PStrRec;begin  Result := 0;  P := Pointer(S);  if P <> nil then  begin    Dec(P);    Result := P^.RefCount;  end;end;procedure StrResetLength(var S: WideString);var  I: SizeInt;begin  for I := 0 to Length(S) - 1 do    if S[I + 1] = #0 then    begin      SetLength(S, I);      Exit;    end;end;procedure StrResetLength(var S: AnsiString);var  I: SizeInt;begin  for I := 0 to Length(S) - 1 do    if S[I + 1] = #0 then    begin      SetLength(S, I);      Exit;    end;end;procedure StrResetLength(S: TJclStringBuilder);var  I: SizeInt;begin  if S <> nil then    for I := 0 to S.Length - 1 do      if S[I] = #0 then      begin        S.Length := I;        Exit;      end;end;{$IFDEF SUPPORTS_UNICODE_STRING}procedure StrResetLength(var S: UnicodeString);var  I: SizeInt;begin  for I := 0 to Length(S) - 1 do    if S[I + 1] = #0 then    begin      SetLength(S, I);      Exit;    end;end;{$ENDIF SUPPORTS_UNICODE_STRING}//=== String Search and Replace Routines =====================================function StrCharCount(const S: string; C: Char): SizeInt;var  I: SizeInt;begin  Result := 0;  for I := 1 to Length(S) do    if S[I] = C then      Inc(Result);end;function StrCharsCount(const S: string; const Chars: TCharValidator): SizeInt;var  I: SizeInt;begin  Result := 0;  for I := 1 to Length(S) do    if Chars(S[I]) then      Inc(Result);end;function StrCharsCount(const S: string; const Chars: array of Char): SizeInt;var  I: SizeInt;begin  Result := 0;  for I := 1 to Length(S) do    if ArrayContainsChar(Chars, S[I]) then      Inc(Result);end;function StrStrCount(const S, SubS: string): SizeInt;var  I: SizeInt;begin  Result := 0;  if (Length(SubS) > Length(S)) or (Length(SubS) = 0) or (Length(S) = 0) then    Exit;  if Length(SubS) = 1 then  begin    Result := StrCharCount(S, SubS[1]);    Exit;  end;  I := StrSearch(SubS, S, 1);  if I > 0 then    Inc(Result);  while (I > 0) and (Length(S) > I + Length(SubS)) do  begin    I := StrSearch(SubS, S, I + 1);    if I > 0 then      Inc(Result);  end;end;(*{ 1}  Test(StrCompareRange('', '', 1, 5), 0);{ 2}  Test(StrCompareRange('A', '', 1, 5), -1);{ 3}  Test(StrCompareRange('AB', '', 1, 5), -1);{ 4}  Test(StrCompareRange('ABC', '', 1, 5), -1);{ 5}  Test(StrCompareRange('', 'A', 1, 5), -1);{ 6}  Test(StrCompareRange('', 'AB',  1, 5), -1);{ 7}  Test(StrCompareRange('', 'ABC', 1, 5), -1);{ 8}  Test(StrCompareRange('A', 'a', 1, 5), -2);{ 9}  Test(StrCompareRange('A', 'a', 1, 1), -32);{10}  Test(StrCompareRange('aA', 'aB', 1, 1), 0);{11}  Test(StrCompareRange('aA', 'aB', 1, 2), -1);{12}  Test(StrCompareRange('aB', 'aA', 1, 2), 1);{13}  Test(StrCompareRange('aA', 'aa', 1, 2), -32);{14}  Test(StrCompareRange('aa', 'aA', 1, 2), 32);{15}  Test(StrCompareRange('', '', 1, 0), 0);{16}  Test(StrCompareRange('A', 'A', 1, 0), -2);{17}  Test(StrCompareRange('Aa', 'A', 1, 0), -2);{18}  Test(StrCompareRange('Aa', 'Aa', 1, 2), 0);{19}  Test(StrCompareRange('Aa', 'A', 1, 2), 0);{20}  Test(StrCompareRange('Ba', 'A', 1, 2), 1);*)function StrCompareRangeEx(const S1, S2: string; Index, Count: SizeInt; CaseSensitive: Boolean): SizeInt;var  Len1, Len2: SizeInt;  I: SizeInt;  C1, C2: Char;begin  if Pointer(S1) = Pointer(S2) then  begin    if (Count <= 0) and (S1 <> '') then      Result := -2 // no work    else      Result := 0;  end  else  if (S1 = '') or (S2 = '') then    Result := -1 // null string  else  if Count <= 0 then    Result := -2 // no work  else  begin    Len1 := Length(S1);    Len2 := Length(S2);    if (Index - 1) + Count > Len1 then      Result := -2    else    begin      if (Index - 1) + Count > Len2 then // strange behaviour, but the assembler code does it        Count := Len2 - (Index - 1);      if CaseSensitive then      begin        for I := 0 to Count - 1 do        begin          C1 := S1[Index + I];          C2 := S2[Index + I];          if C1 <> C2 then          begin            Result := Ord(C1) - Ord(C2);            Exit;          end;        end;      end      else      begin        for I := 0 to Count - 1 do        begin          C1 := S1[Index + I];          C2 := S2[Index + I];          if C1 <> C2 then          begin            C1 := CharLower(C1);            C2 := CharLower(C2);            if C1 <> C2 then            begin              Result := Ord(C1) - Ord(C2);              Exit;            end;          end;        end;      end;      Result := 0;    end;  end;end;function StrCompare(const S1, S2: string; CaseSensitive: Boolean): SizeInt;var  Len1, Len2: SizeInt;begin  if Pointer(S1) = Pointer(S2) then    Result := 0  else  begin    Len1 := Length(S1);    Len2 := Length(S2);    Result := Len1 - Len2;    if Result = 0 then      Result := StrCompareRangeEx(S1, S2, 1, Len1, CaseSensitive);  end;end;function StrCompareRange(const S1, S2: string; Index, Count: SizeInt; CaseSensitive: Boolean): SizeInt;begin  Result := StrCompareRangeEx(S1, S2, Index, Count, CaseSensitive);end;procedure StrFillChar(var S; Count: SizeInt; C: Char);{$IFDEF SUPPORTS_UNICODE}asm        // 32 --> EAX S        //        EDX Count        //        ECX C        // 64 --> RCX S        //        RDX Count        //        R8W C        {$IFDEF CPU32}        DEC     EDX        JS      @@Leave@@Loop:        MOV     [EAX], CX        ADD     EAX, 2        DEC     EDX        JNS     @@Loop        {$ENDIF CPU32}        {$IFDEF CPU64}        DEC     RDX        JS      @@Leave@@Loop:        MOV     WORD PTR [RCX], R8W        ADD     RCX, 2        DEC     RDX        JNS     @@Loop        {$ENDIF CPU64}@@Leave:end;{$ELSE ~SUPPORTS_UNICODE}begin  if Count > 0 then    FillChar(S, Count, C);end;{$ENDIF ~SUPPORTS_UNICODE}function StrRepeatChar(C: Char; Count: SizeInt): string;begin  SetLength(Result, Count);  if Count > 0 then    StrFillChar(Result[1], Count, C);end;function StrFind(const Substr, S: string; const Index: SizeInt): SizeInt;var  pos: SizeInt;begin  if (SubStr <> '') and (S <> '') then  begin    pos := StrIPos(Substr, Copy(S, Index, Length(S) - Index + 1));    if pos = 0 then      Result := 0    else      Result := Index + Pos - 1;  end  else    Result := 0;end;function StrHasPrefix(const S: string; const Prefixes: array of string): Boolean;begin  Result := StrPrefixIndex(S, Prefixes) > -1;end;function StrHasSuffix(const S: string; const Suffixes: array of string): Boolean;begin  Result := StrSuffixIndex(S, Suffixes) > -1;end;function StrIndex(const S: string; const List: array of string; CaseSensitive: Boolean): SizeInt;var  I: SizeInt;begin  Result := -1;  for I := Low(List) to High(List) do  begin    if StrCompare(S, List[I], CaseSensitive) = 0 then    begin      Result := I;      Break;    end;  end;end;function StrIHasPrefix(const S: string; const Prefixes: array of string): Boolean;begin  Result := StrIPrefixIndex(S, Prefixes) > -1;end;function StrIHasSuffix(const S: string; const Suffixes: array of string): Boolean;begin  Result := StrISuffixIndex(S, Suffixes) > -1;end;function StrILastPos(const SubStr, S: string): SizeInt;begin  Result := StrLastPos(StrUpper(SubStr), StrUpper(S));end;function StrIPos(const SubStr, S: string): SizeInt;begin  Result := Pos(StrUpper(SubStr), StrUpper(S));end;function StrIPrefixIndex(const S: string; const Prefixes: array of string): SizeInt;var  I: SizeInt;  Test: string;begin  Result := -1;  for I := Low(Prefixes) to High(Prefixes) do  begin    Test := StrLeft(S, Length(Prefixes[I]));    if CompareText(Test, Prefixes[I]) = 0 then    begin      Result := I;      Break;    end;  end;end;function StrIsOneOf(const S: string; const List: array of string): Boolean;begin  Result := StrIndex(S, List) > -1;end;function StrISuffixIndex(const S: string; const Suffixes: array of string): SizeInt;var  I: SizeInt;  Test: string;begin  Result := -1;  for I := Low(Suffixes) to High(Suffixes) do  begin    Test := StrRight(S, Length(Suffixes[I]));    if CompareText(Test, Suffixes[I]) = 0 then    begin      Result := I;      Break;    end;  end;end;function StrLastPos(const SubStr, S: string): SizeInt;var  Last, Current: PChar;begin  Result := 0;  Last := nil;  Current := PChar(S);  while (Current <> nil) and (Current^ <> #0) do  begin    Current := StrPos(PChar(Current), PChar(SubStr));    if Current <> nil then    begin      Last := Current;      Inc(Current);    end;  end;  if Last <> nil then    Result := Abs(PChar(S) - Last) + 1;end;// IMPORTANT NOTE: The StrMatch function does currently not work with the Asterix (*)// (*) acts like (?)function StrMatch(const Substr, S: string; Index: SizeInt): SizeInt;var  SI, SubI, SLen, SubLen: SizeInt;  SubC: Char;begin  SLen := Length(S);  SubLen := Length(Substr);  Result := 0;  if (Index > SLen) or (SubLen = 0) then    Exit;  while Index <= SLen do  begin    SubI := 1;    SI := Index;    while (SI <= SLen) and (SubI <= SubLen) do    begin      SubC := Substr[SubI];      if (SubC = '*') or (SubC = '?') or (SubC = S[SI]) then      begin        Inc(SI);        Inc(SubI);      end      else        Break;    end;    if SubI > SubLen then    begin      Result := Index;      Break;    end;    Inc(Index);  end;end;// Derived from "Like" by Michael Winterfunction StrMatches(const Substr, S: string; const Index: SizeInt): Boolean;var  StringPtr: PChar;  PatternPtr: PChar;  StringRes: PChar;  PatternRes: PChar;begin  if SubStr = '' then    raise EJclStringError.CreateRes(@RsBlankSearchString);  Result := SubStr = '*';  if Result or (S = '') then    Exit;  if (Index <= 0) or (Index > Length(S)) then    raise EJclStringError.CreateRes(@RsArgumentOutOfRange);  StringPtr := PChar(@S[Index]);  PatternPtr := PChar(SubStr);  StringRes := nil;  PatternRes := nil;  repeat    repeat      case PatternPtr^ of        #0:        begin          Result := StringPtr^ = #0;          if Result or (StringRes = nil) or (PatternRes = nil) then            Exit;          StringPtr := StringRes;          PatternPtr := PatternRes;          Break;        end;        '*':        begin          Inc(PatternPtr);          PatternRes := PatternPtr;          Break;        end;        '?':        begin          if StringPtr^ = #0 then            Exit;          Inc(StringPtr);          Inc(PatternPtr);        end;      else      begin        if StringPtr^ = #0 then          Exit;        if StringPtr^ <> PatternPtr^ then        begin          if (StringRes = nil) or (PatternRes = nil) then            Exit;          StringPtr := StringRes;          PatternPtr := PatternRes;          Break;        end        else        begin          Inc(StringPtr);          Inc(PatternPtr);        end;      end;      end;    until False;    repeat      case PatternPtr^ of        #0:        begin          Result := True;          Exit;        end;        '*':        begin          Inc(PatternPtr);          PatternRes := PatternPtr;        end;        '?':        begin          if StringPtr^ = #0 then            Exit;          Inc(StringPtr);          Inc(PatternPtr);        end;      else      begin        repeat          if StringPtr^ = #0 then            Exit;          if StringPtr^ = PatternPtr^ then            Break;          Inc(StringPtr);        until False;        Inc(StringPtr);        StringRes := StringPtr;        Inc(PatternPtr);        Break;      end;      end;    until False;  until False;end;function StrNPos(const S, SubStr: string; N: SizeInt): SizeInt;var  I, P: SizeInt;begin  if N < 1 then  begin    Result := 0;    Exit;  end;  Result := StrSearch(SubStr, S, 1);  I := 1;  while I < N do  begin    P := StrSearch(SubStr, S, Result + 1);    if P = 0 then    begin      Result := 0;      Break;    end    else    begin      Result := P;      Inc(I);    end;  end;end;function StrNIPos(const S, SubStr: string; N: SizeInt): SizeInt;var  I, P: SizeInt;begin  if N < 1 then  begin    Result := 0;    Exit;  end;  Result := StrFind(SubStr, S, 1);  I := 1;  while I < N do  begin    P := StrFind(SubStr, S, Result + 1);    if P = 0 then    begin      Result := 0;      Break;    end    else    begin      Result := P;      Inc(I);    end;  end;end;function StrPrefixIndex(const S: string; const Prefixes: array of string): SizeInt;var  I: SizeInt;  Test: string;begin  Result := -1;  for I := Low(Prefixes) to High(Prefixes) do  begin    Test := StrLeft(S, Length(Prefixes[I]));    if CompareStr(Test, Prefixes[I]) = 0 then    begin      Result := I;      Break;    end;  end;end;function StrSearch(const Substr, S: string; const Index: SizeInt): SizeInt;var  SP, SPI, SubP: PChar;  SLen: SizeInt;begin  SLen := Length(S);  if Index <= SLen then  begin    SP := PChar(S);    SubP := PChar(Substr);    SPI := SP;    Inc(SPI, Index);    Dec(SPI);    SPI := StrPos(SPI, SubP);    if SPI <> nil then      Result := SPI - SP + 1    else      Result := 0;  end  else    Result := 0;end;function StrSuffixIndex(const S: string; const Suffixes: array of string): SizeInt;var  I: SizeInt;  Test: string;begin  Result := -1;  for I := Low(Suffixes) to High(Suffixes) do  begin    Test := StrRight(S, Length(Suffixes[I]));    if CompareStr(Test, Suffixes[I]) = 0 then    begin      Result := I;      Break;    end;  end;end;//=== String Extraction ======================================================function StrAfter(const SubStr, S: string): string;var  P: SizeInt;begin  P := StrFind(SubStr, S, 1); // StrFind is case-insensitive pos  if P <= 0 then    Result := ''           // substr not found -> nothing after it  else    Result := StrRestOf(S, P + Length(SubStr));end;function StrBefore(const SubStr, S: string): string;var  P: SizeInt;begin  P := StrFind(SubStr, S, 1);  if P <= 0 then    Result := S  else    Result := StrLeft(S, P - 1);end;function StrSplit(const SubStr, S: string;var Left, Right : string): boolean;var  P: SizeInt;begin  P := StrFind(SubStr, S, 1);  Result:= p > 0;  if Result then  begin    Left := StrLeft(S, P - 1);    Right := StrRestOf(S, P + Length(SubStr));  end  else  begin    Left := '';    Right := '';  end;end;function StrBetween(const S: string; const Start, Stop: Char): string;var  PosStart, PosEnd: SizeInt;  L: SizeInt;begin  PosStart := Pos(Start, S);  PosEnd := StrSearch(Stop, S, PosStart + 1);  // PosEnd has to be after PosStart.  if (PosStart > 0) and (PosEnd > PosStart) then  begin    L := PosEnd - PosStart;    Result := Copy(S, PosStart + 1, L - 1);  end  else    Result := '';end;function StrChopRight(const S: string; N: SizeInt): string;begin  Result := Copy(S, 1, Length(S) - N);end;function StrLeft(const S: string; Count: SizeInt): string;begin  Result := Copy(S, 1, Count);end;function StrMid(const S: string; Start, Count: SizeInt): string;begin  Result := Copy(S, Start, Count);end;function StrRestOf(const S: string; N: SizeInt): string;begin  Result := Copy(S, N, (Length(S) - N + 1));end;function StrRight(const S: string; Count: SizeInt): string;begin  Result := Copy(S, Length(S) - Count + 1, Count);end;//=== Character (do we have it ;) ============================================function CharEqualNoCase(const C1, C2: Char): Boolean;begin  //if they are not equal chars, may be same letter different case  Result := (C1 = C2) or    (CharIsAlpha(C1) and CharIsAlpha(C2) and (CharLower(C1) = CharLower(C2)));end;function CharIsAlpha(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsLetter(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_ALPHA) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsAlphaNum(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsLetterOrDigit(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := ((StrCharTypes[C] and C1_ALPHA) <> 0) or ((StrCharTypes[C] and C1_DIGIT) <> 0);  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsBlank(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  //http://blogs.msdn.com/b/michkap/archive/2007/06/11/3230072.aspx  Result := (C = ' ') or (C = #$0009) or (C = #$00A0) or (C = #$3000);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := ((StrCharTypes[C] and C1_BLANK) <> 0);  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsControl(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsControl(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_CNTRL) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsDelete(const C: Char): Boolean;begin  Result := (C = #8);end;function CharIsDigit(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsDigit(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_DIGIT) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsFracDigit(const C: Char): Boolean;begin  Result := (C = '.') or CharIsDigit(C);end;function CharIsHexDigit(const C: Char): Boolean;begin  case C of    'A'..'F',    'a'..'f':      Result := True;  else    Result := CharIsDigit(C);  end;end;function CharIsLower(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsLower(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_LOWER) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;{$IFNDEF WINSCP}function CharIsNumberChar(const C: Char): Boolean;begin  Result := CharIsDigit(C) or (C = '+') or (C = '-') or (C = JclFormatSettings.DecimalSeparator);end;function CharIsNumber(const C: Char): Boolean;begin  Result := CharIsDigit(C) or (C = JclFormatSettings.DecimalSeparator);end;{$ENDIF ~WINSCP}function CharIsPrintable(const C: Char): Boolean;begin  Result := not CharIsControl(C);end;function CharIsPunctuation(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsPunctuation(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := ((StrCharTypes[C] and C1_PUNCT) <> 0);  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsReturn(const C: Char): Boolean;begin  Result := (C = NativeLineFeed) or (C = NativeCarriageReturn);end;function CharIsSpace(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsWhiteSpace(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_SPACE) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsUpper(const C: Char): Boolean;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.IsUpper(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := (StrCharTypes[C] and C1_UPPER) <> 0;  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharIsValidIdentifierLetter(const C: Char): Boolean;begin  case C of    {$IFDEF SUPPORTS_UNICODE}    // from XML specifications    #$00C0..#$00D6, #$00D8..#$00F6, #$00F8..#$02FF, #$0370..#$037D,    #$037F..#$1FFF, #$200C..#$200D, #$2070..#$218F, #$2C00..#$2FEF,    #$3001..#$D7FF, #$F900..#$FDCF, #$FDF0..#$FFFD, // #$10000..#$EFFFF, howto match surrogate pairs?    #$00B7, #$0300..#$036F, #$203F..#$2040,    {$ENDIF SUPPORTS_UNICODE}    '0'..'9', 'A'..'Z', 'a'..'z', '_':      Result := True;  else    Result := False;  end;end;function CharIsWhiteSpace(const C: Char): Boolean;begin  case C of    NativeTab,    NativeLineFeed,    NativeVerticalTab,    NativeFormFeed,    NativeCarriageReturn,    NativeSpace:      Result := True;  else    Result := False;  end;end;function CharIsWildcard(const C: Char): Boolean;begin  case C of    '*', '?':      Result := True;  else    Result := False;  end;end;function CharType(const C: Char): Word;begin  {$IFDEF UNICODE_RTL_DATABASE}  GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, @C, 1, Result);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := StrCharTypes[C];  {$ENDIF ~UNICODE_RTL_DATABASE}end;//=== PCharVector ============================================================function StringsToPCharVector(var Dest: PCharVector; const Source: TStrings): PCharVector;var  I: SizeInt;  S: string;  List: array of PChar;begin  Assert(Source <> nil);  Dest := AllocMem((Source.Count + SizeOf(Char)) * SizeOf(PChar));  SetLength(List, Source.Count + SizeOf(Char));  for I := 0 to Source.Count - 1 do  begin    S := Source[I];    List[I] := StrAlloc(Length(S) + SizeOf(Char));    StrPCopy(List[I], S);  end;  List[Source.Count] := nil;  Move(List[0], Dest^, (Source.Count + 1) * SizeOf(PChar));  Result := Dest;end;function PCharVectorCount(Source: PCharVector): SizeInt;begin  Result := 0;  if Source <> nil then  begin    while Source^ <> nil do    begin      Inc(Source);      Inc(Result);    end;  end;end;procedure PCharVectorToStrings(const Dest: TStrings; Source: PCharVector);var  I, Count: SizeInt;  List:     array of PChar;begin  Assert(Dest <> nil);  if Source <> nil then  begin    Count := PCharVectorCount(Source);    SetLength(List, Count);    Move(Source^, List[0], Count * SizeOf(PChar));    Dest.BeginUpdate;    try      Dest.Clear;      for I := 0 to Count - 1 do        Dest.Add(List[I]);    finally      Dest.EndUpdate;    end;  end;end;procedure FreePCharVector(var Dest: PCharVector);var  I, Count: SizeInt;  List:     array of PChar;begin  if Dest <> nil then  begin    Count := PCharVectorCount(Dest);    SetLength(List, Count);    Move(Dest^, List[0], Count * SizeOf(PChar));    for I := 0 to Count - 1 do      StrDispose(List[I]);    FreeMem(Dest, (Count + 1) * SizeOf(PChar));    Dest := nil;  end;end;//=== Character Transformation Routines ======================================function CharHex(const C: Char): Byte;begin  case C of    '0'..'9':      Result := Ord(C) - Ord('0');    'a'..'f':      Result := Ord(C) - Ord('a') + 10;    'A'..'F':      Result := Ord(C) - Ord('A') + 10;  else    Result := $FF;  end;end;function CharLower(const C: Char): Char;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.ToLower(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := StrCaseMap[Ord(C) + StrLoOffset];  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharToggleCase(const C: Char): Char;begin  {$IFDEF UNICODE_RTL_DATABASE}  if CharIsLower(C) then    Result := CharUpper(C)  else if CharIsUpper(C) then    Result := CharLower(C)  else    Result := C;  {$ELSE ~UNICODE_RTL_DATABASE}  Result := StrCaseMap[Ord(C) + StrReOffset];  {$ENDIF ~UNICODE_RTL_DATABASE}end;function CharUpper(const C: Char): Char;begin  {$IFDEF UNICODE_RTL_DATABASE}  Result := TCharacter.ToUpper(C);  {$ELSE ~UNICODE_RTL_DATABASE}  Result := StrCaseMap[Ord(C) + StrUpOffset];  {$ENDIF ~UNICODE_RTL_DATABASE}end;//=== Character Search and Replace ===========================================function CharLastPos(const S: string; const C: Char; const Index: SizeInt): SizeInt;begin  if (Index > 0) and (Index <= Length(S)) then  begin    for Result := Length(S) downto Index do      if S[Result] = C then        Exit;  end;  Result := 0;end;function CharPos(const S: string; const C: Char; const Index: SizeInt): SizeInt;begin  if (Index > 0) and (Index <= Length(S)) then  begin    for Result := Index to Length(S) do      if S[Result] = C then        Exit;  end;  Result := 0;end;function CharIPos(const S: string; C: Char; const Index: SizeInt): SizeInt;begin  if (Index > 0) and (Index <= Length(S)) then  begin    C := CharUpper(C);    for Result := Index to Length(S) do      if CharUpper(S[Result]) = C then        Exit;  end;  Result := 0;end;function CharReplace(var S: string; const Search, Replace: Char): SizeInt;var  P: PChar;  Index, Len: SizeInt;begin  Result := 0;  if Search <> Replace then  begin    UniqueString(S);    P := PChar(S);    Len := Length(S);    for Index := 0 to Len - 1 do    begin      if P^ = Search then      begin        P^ := Replace;        Inc(Result);      end;      Inc(P);    end;  end;end;{$IFNDEF WINSCP}//=== MultiSz ================================================================function StringsToMultiSz(var Dest: PMultiSz; const Source: TStrings): PMultiSz;var  I, TotalLength: SizeInt;  P: PMultiSz;begin  Assert(Source <> nil);  TotalLength := 1;  for I := 0 to Source.Count - 1 do    if Source[I] = '' then      raise EJclStringError.CreateRes(@RsInvalidEmptyStringItem)    else      Inc(TotalLength, StrLen(PChar(Source[I])) + 1);  AllocateMultiSz(Dest, TotalLength);  P := Dest;  for I := 0 to Source.Count - 1 do  begin    P := StrECopy(P, PChar(Source[I]));    Inc(P);  end;  P^ := #0;  Result := Dest;end;procedure MultiSzToStrings(const Dest: TStrings; const Source: PMultiSz);var  P: PMultiSz;begin  Assert(Dest <> nil);  Dest.BeginUpdate;  try    Dest.Clear;    if Source <> nil then    begin      P := Source;      while P^ <> #0 do      begin        Dest.Add(P);        P := StrEnd(P);        Inc(P);      end;    end;  finally    Dest.EndUpdate;  end;end;function MultiSzLength(const Source: PMultiSz): SizeInt;var  P: PMultiSz;begin  Result := 0;  if Source <> nil then  begin    P := Source;    repeat      Inc(Result, StrLen(P) + 1);      P := StrEnd(P);      Inc(P);    until P^ = #0;    Inc(Result);  end;end;procedure AllocateMultiSz(var Dest: PMultiSz; Len: SizeInt);begin  if Len > 0 then    GetMem(Dest, Len * SizeOf(Char))  else    Dest := nil;end;procedure FreeMultiSz(var Dest: PMultiSz);begin  if Dest <> nil then    FreeMem(Dest);  Dest := nil;end;function MultiSzDup(const Source: PMultiSz): PMultiSz;var  Len: SizeInt;begin  if Source <> nil then  begin    Len := MultiSzLength(Source);    Result := nil;    AllocateMultiSz(Result, Len);    Move(Source^, Result^, Len * SizeOf(Char));  end  else    Result := nil;end;function AnsiStringsToAnsiMultiSz(var Dest: PAnsiMultiSz; const Source: TAnsiStrings): PAnsiMultiSz;begin  Result := JclAnsiStrings.StringsToMultiSz(Dest, Source);end;procedure AnsiMultiSzToAnsiStrings(const Dest: TAnsiStrings; const Source: PAnsiMultiSz);begin  JclAnsiStrings.MultiSzToStrings(Dest, Source);end;function AnsiMultiSzLength(const Source: PAnsiMultiSz): SizeInt;begin  Result := JclAnsiStrings.MultiSzLength(Source);end;procedure AllocateAnsiMultiSz(var Dest: PAnsiMultiSz; Len: SizeInt);begin  JclAnsiStrings.AllocateMultiSz(Dest, Len);end;procedure FreeAnsiMultiSz(var Dest: PAnsiMultiSz);begin  JclAnsiStrings.FreeMultiSz(Dest);end;function AnsiMultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz;begin  Result := JclAnsiStrings.MultiSzDup(Source);end;function WideStringsToWideMultiSz(var Dest: PWideMultiSz; const Source: TWideStrings): PWideMultiSz;begin  Result := JclWideStrings.StringsToMultiSz(Dest, Source);end;procedure WideMultiSzToWideStrings(const Dest: TWideStrings; const Source: PWideMultiSz);begin  JclWideStrings.MultiSzToStrings(Dest, Source);end;function WideMultiSzLength(const Source: PWideMultiSz): SizeInt;begin  Result := JclWideStrings.MultiSzLength(Source);end;procedure AllocateWideMultiSz(var Dest: PWideMultiSz; Len: SizeInt);begin  JclWideStrings.AllocateMultiSz(Dest, Len);end;procedure FreeWideMultiSz(var Dest: PWideMultiSz);begin  JclWideStrings.FreeMultiSz(Dest);end;function WideMultiSzDup(const Source: PWideMultiSz): PWideMultiSz;begin  Result := JclWideStrings.MultiSzDup(Source);end;{$ENDIF ~WINSCP}//=== TStrings Manipulation ==================================================procedure StrToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True);var  I, L: SizeInt;  Left: string;begin  Assert(List <> nil);  List.BeginUpdate;  try    List.Clear;    L := Length(Sep);    I := Pos(Sep, S);    while I > 0 do    begin      Left := StrLeft(S, I - 1);      if (Left <> '') or AllowEmptyString then        List.Add(Left);      Delete(S, 1, I + L - 1);      I := Pos(Sep, S);    end;    if (S <> '') or AllowEmptyString then      List.Add(S);  // Ignore empty strings at the end (only if AllowEmptyString = False).  finally    List.EndUpdate;  end;end;procedure StrIToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True);var  I, L: SizeInt;  LowerCaseStr: string;  Left: string;begin  Assert(List <> nil);  LowerCaseStr := StrLower(S);  Sep := StrLower(Sep);  L := Length(Sep);  I := Pos(Sep, LowerCaseStr);  List.BeginUpdate;  try    List.Clear;    while I > 0 do    begin      Left := StrLeft(S, I - 1);      if (Left <> '') or AllowEmptyString then        List.Add(Left);      Delete(S, 1, I + L - 1);      Delete(LowerCaseStr, 1, I + L - 1);      I := Pos(Sep, LowerCaseStr);    end;    if (S <> '') or AllowEmptyString then      List.Add(S);  // Ignore empty strings at the end (only if AllowEmptyString = False).  finally    List.EndUpdate;  end;end;function StringsToStr(const List: TStrings; const Sep: string; const AllowEmptyString: Boolean = True): string;var  I, L: SizeInt;begin  Result := '';  for I := 0 to List.Count - 1 do  begin    if (List[I] <> '') or AllowEmptyString then    begin      // don't combine these into one addition, somehow it hurts performance      Result := Result + List[I];      Result := Result + Sep;    end;  end;  // remove terminating separator  if List.Count > 0 then  begin    L := Length(Sep);    Delete(Result, Length(Result) - L + 1, L);  end;end;function StringsToStr(const List: TStrings; const Sep: string; const NumberOfItems: SizeInt; const AllowEmptyString:    Boolean = True): string;var  I, L, N: SizeInt;begin  Result := '';  if List.Count > NumberOfItems then    N := NumberOfItems  else    N := List.Count;  for I := 0 to N - 1 do  begin    if (List[I] <> '') or AllowEmptyString then    begin      // don't combine these into one addition, somehow it hurts performance      Result := Result + List[I];      Result := Result + Sep;    end;  end;  // remove terminating separator  if N > 0 then  begin    L := Length(Sep);    Delete(Result, Length(Result) - L + 1, L);  end;end;procedure TrimStrings(const List: TStrings; DeleteIfEmpty: Boolean);var  I: SizeInt;begin  Assert(List <> nil);  List.BeginUpdate;  try    for I := List.Count - 1 downto 0 do    begin      List[I] := Trim(List[I]);      if (List[I] = '') and DeleteIfEmpty then        List.Delete(I);    end;  finally    List.EndUpdate;  end;end;procedure TrimStringsRight(const List: TStrings; DeleteIfEmpty: Boolean);var  I: SizeInt;begin  Assert(List <> nil);  List.BeginUpdate;  try    for I := List.Count - 1 downto 0 do    begin      List[I] := TrimRight(List[I]);      if (List[I] = '') and DeleteIfEmpty then        List.Delete(I);    end;  finally    List.EndUpdate;  end;end;procedure TrimStringsLeft(const List: TStrings; DeleteIfEmpty: Boolean);var  I: SizeInt;begin  Assert(List <> nil);  List.BeginUpdate;  try    for I := List.Count - 1 downto 0 do    begin      List[I] := TrimLeft(List[I]);      if (List[I] = '') and DeleteIfEmpty then        List.Delete(I);    end;  finally    List.EndUpdate;  end;end;function AddStringToStrings(const S: string; Strings: TStrings; const Unique: Boolean): Boolean;begin  Assert(Strings <> nil);  Result := Unique and (Strings.IndexOf(S) <> -1);  if not Result then    Result := Strings.Add(S) > -1;end;//=== Miscellaneous ==========================================================function FileToString(const FileName: string): {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF};var  fs: TFileStream;  Len: SizeInt;begin  fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);  try    Len := fs.Size;    SetLength(Result, Len);    if Len > 0 then      fs.ReadBuffer(Result[1], Len);  finally    fs.Free;  end;end;procedure StringToFile(const FileName: string; const Contents: {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF};  Append: Boolean);var  FS: TFileStream;  Len: SizeInt;begin  if Append and FileExists(filename) then    FS := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite)  else    FS := TFileStream.Create(FileName, fmCreate);  try    if Append then      FS.Seek(0, soEnd);  // faster than .Position := .Size    Len := Length(Contents);    if Len > 0 then      FS.WriteBuffer(Contents[1], Len);  finally    FS.Free;  end;end;function StrToken(var S: string; Separator: Char): string;var  I: SizeInt;begin  I := Pos(Separator, S);  if I <> 0 then  begin    Result := Copy(S, 1, I - 1);    Delete(S, 1, I);  end  else  begin    Result := S;    S := '';  end;end;procedure StrTokens(const S: string; const List: TStrings);var  Start: PChar;  Token: string;  Done:  Boolean;begin  Assert(List <> nil);  if List = nil then    Exit;  List.BeginUpdate;  try    List.Clear;    Start := Pointer(S);    repeat      Done := JclStrings.StrWord(Start, Token);      if Token <> '' then        List.Add(Token);    until Done;  finally    List.EndUpdate;  end;end;function StrWord(const S: string; var Index: SizeInt; out Word: string): Boolean;var  Start: SizeInt;  C: Char;begin  Word := '';  if (S = '') then  begin    Result := True;    Exit;  end;  Start := Index;  Result := False;  while True do  begin    C := S[Index];    case C of      #0:        begin          if Start <> 0 then            Word := Copy(S, Start, Index - Start);          Result := True;          Exit;        end;      NativeSpace, NativeLineFeed, NativeCarriageReturn:        begin          if Start <> 0 then          begin            Word := Copy(S, Start, Index - Start);            Exit;          end          else          begin            while CharIsWhiteSpace(C) do            begin              Inc(Index);              C := S[Index];            end;          end;        end;    else      if Start = 0 then        Start := Index;      Inc(Index);    end;  end;end;function StrWord(var S: PChar; out Word: string): Boolean;var  Start: PChar;begin  Word := '';  if S = nil then  begin    Result := True;    Exit;  end;  Start := nil;  Result := False;  while True do  begin    case S^ of      #0:      begin        if Start <> nil then          SetString(Word, Start, S - Start);        Result := True;        Exit;      end;      NativeSpace, NativeLineFeed, NativeCarriageReturn:      begin        if Start <> nil then        begin          SetString(Word, Start, S - Start);          Exit;        end        else          while CharIsWhiteSpace(S^) do            Inc(S);      end;    else      if Start = nil then        Start := S;      Inc(S);    end;  end;end;function StrIdent(const S: string; var Index: SizeInt; out Ident: string): Boolean;var  Start: SizeInt;  C: Char;begin  Ident := '';  if (S = '') then  begin    Result := True;    Exit;  end;  Start := Index;  Result := False;  while True do  begin    C := S[Index];    if CharIsValidIdentifierLetter(C) then    begin      if Start = 0 then        Start := Index;    end    else    if C = #0 then    begin      if Start <> 0 then        Ident := Copy(S, Start, Index - Start);      Result := True;      Exit;    end    else    begin      if Start <> 0 then      begin        Ident := Copy(S, Start, Index - Start);        Exit;      end;    end;    Inc(Index);  end;end;function StrIdent(var S: PChar; out Ident: string): Boolean;var  Start: PChar;  C: Char;begin  Ident := '';  if S = nil then  begin    Result := True;    Exit;  end;  Start := nil;  Result := False;  while True do  begin    C := S^;    if CharIsValidIdentifierLetter(C) then    begin      if Start = nil then        Start := S;    end    else    if C = #0 then    begin      if Start <> nil then        SetString(Ident, Start, S - Start);      Result := True;      Exit;    end    else    begin      if Start <> nil then      begin        SetString(Ident, Start, S - Start);        Exit;      end    end;    Inc(S);  end;end;procedure StrTokenToStrings(S: string; Separator: Char; const List: TStrings);var  Token: string;begin  Assert(List <> nil);  if List = nil then    Exit;  List.BeginUpdate;  try    List.Clear;    while S <> '' do    begin      Token := StrToken(S, Separator);      List.Add(Token);    end;  finally    List.EndUpdate;  end;end;{$IFNDEF WINSCP}function StrToFloatSafe(const S: string): Float;var  Temp: string;  I, J, K: SizeInt;  SwapSeparators, IsNegative: Boolean;  DecSep, ThouSep, C: Char;begin  DecSep := {$IFDEF RTL220_UP}FormatSettings.{$ENDIF}DecimalSeparator;  ThouSep := {$IFDEF RTL220_UP}FormatSettings.{$ENDIF}ThousandSeparator;  Temp := S;  SwapSeparators := False;  IsNegative := False;  J := 0;  for I := 1 to Length(Temp) do  begin    C := Temp[I];    if C = '-' then      IsNegative := not IsNegative    else    if (C <> ' ') and (C <> '(') and (C <> '+') then    begin        // if it appears prior to any digit, it has to be a decimal separator      SwapSeparators := Temp[I] = ThouSep;      J := I;      Break;    end;  end;  if not SwapSeparators then  begin    K := CharPos(Temp, DecSep);    SwapSeparators :=      // if it appears prior to any digit, it has to be a decimal separator      (K > J) and      // if it appears multiple times, it has to be a thousand separator      ((StrCharCount(Temp, DecSep) > 1) or      // we assume (consistent with Windows Platform SDK documentation),      // that thousand separators appear only to the left of the decimal      (K < CharPos(Temp, ThouSep)));  end;  if SwapSeparators then  begin    // assume a numerical string from a different locale,    // where DecimalSeparator and ThousandSeparator are exchanged    for I := 1 to Length(Temp) do      if Temp[I] = DecSep then        Temp[I] := ThouSep      else      if Temp[I] = ThouSep then        Temp[I] := DecSep;  end;  Temp := StrKeepChars(Temp, CharIsNumber);  if Length(Temp) > 0 then  begin    if Temp[1] = DecSep then      Temp := '0' + Temp;    if Temp[Length(Temp)] = DecSep then      Temp := Temp + '0';    Result := StrToFloat(Temp);    if IsNegative then      Result := -Result;  end  else    Result := 0.0;end;function StrToIntSafe(const S: string): Integer;begin  Result := Trunc(StrToFloatSafe(S));end;{$ENDIF ~WINSCP}procedure StrNormIndex(const StrLen: SizeInt; var Index: SizeInt; var Count: SizeInt); overload;begin  Index := Max(1, Min(Index, StrLen + 1));  Count := Max(0, Min(Count, StrLen + 1 - Index));end;function ArrayOf(List: TStrings): TDynStringArray;var  I: SizeInt;begin  if List <> nil then  begin    SetLength(Result, List.Count);    for I := 0 to List.Count - 1 do      Result[I] := List[I];  end  else    Result := nil;end;const  BoolToStr: array [Boolean] of string = ('false', 'true');type  TInterfacedObjectAccess = class(TInterfacedObject);procedure MoveChar(const Source; var Dest; Count: SizeInt);begin  if Count > 0 then    Move(Source, Dest, Count * SizeOf(Char));end;function DotNetFormat(const Fmt: string; const Arg0: Variant): string;begin  Result := DotNetFormat(Fmt, [Arg0]);end;function DotNetFormat(const Fmt: string; const Arg0, Arg1: Variant): string;begin  Result := DotNetFormat(Fmt, [Arg0, Arg1]);end;function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: Variant): string;begin  Result := DotNetFormat(Fmt, [Arg0, Arg1, Arg2]);end;function DotNetFormat(const Fmt: string; const Args: array of const): string;var  F, P: PChar;  Len, Capacity, Count: SizeInt;  Index: SizeInt;  ErrorCode: Integer;  S: string;  procedure Grow(Count: SizeInt);  begin    if Len + Count > Capacity then    begin      Capacity := Capacity * 5 div 3 + Count;      SetLength(Result, Capacity);    end;  end;  function InheritsFrom(AClass: TClass; const ClassName: string): Boolean;  begin    Result := True;    while AClass <> nil do    begin      if CompareText(AClass.ClassName, ClassName) = 0 then        Exit;      AClass := AClass.ClassParent;    end;    Result := False;  end;  function GetStringOf(const V: TVarData; Index: SizeInt): string; overload;  begin    case V.VType of      varEmpty, varNull:        raise ArgumentNullException.CreateRes(@RsArgumentIsNull);      varSmallInt:        Result := IntToStr(V.VSmallInt);      varInteger:        Result := IntToStr(V.VInteger);      varSingle:        Result := FloatToStr(V.VSingle);      varDouble:        Result := FloatToStr(V.VDouble);      varCurrency:        Result := CurrToStr(V.VCurrency);      varDate:        Result := DateTimeToStr(V.VDate);      varOleStr:        Result := V.VOleStr;      varBoolean:        Result := BoolToStr[V.VBoolean <> False];      varByte:        Result := IntToStr(V.VByte);      varWord:        Result := IntToStr(V.VWord);      varShortInt:        Result := IntToStr(V.VShortInt);      varLongWord:        Result := IntToStr(V.VLongWord);      varInt64:        Result := IntToStr(V.VInt64);      varString:        Result := string(V.VString);      {$IFDEF SUPPORTS_UNICODE_STRING}      varUString:        Result := string(V.VUString);      {$ENDIF SUPPORTS_UNICODE_STRING}      {varArray,      varDispatch,      varError,      varUnknown,      varAny,      varByRef:}    else      raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]);    end;  end;  function GetStringOf(Index: SizeInt): string; overload;  var    V: TVarRec;    Intf: IToString;  begin    V := Args[Index];    if (V.VInteger = 0) and      (V.VType in [vtExtended, vtString, vtObject, vtClass, vtCurrency,      vtInterface, vtInt64]) then      raise ArgumentNullException.CreateResFmt(@RsArgumentIsNull, [Index]);    case V.VType of      vtInteger:        Result := IntToStr(V.VInteger);      vtBoolean:        Result := BoolToStr[V.VBoolean];      vtChar:        Result := string(AnsiString(V.VChar));      vtExtended:        Result := FloatToStr(V.VExtended^);      vtString:        Result := string(V.VString^);      vtPointer:        Result := IntToHex(TJclAddr(V.VPointer), 8);      vtPChar:        Result := string(AnsiString(V.VPChar));      vtObject:        if (V.VObject is TInterfacedObject) and V.VObject.GetInterface(IToString, Intf) then        begin          Result := Intf.ToString;          Pointer(Intf) := nil; // do not release the object          // undo the RefCount change          Dec(TInterfacedObjectAccess(V.VObject).FRefCount);        end        else        if ((V.VObject is TComponent) or (V.VObject is TInterfacedPersistent)) and V.VObject.GetInterface(IToString, Intf) then          Result := Intf.ToString        {$IFDEF RTL200_UP}        else          Result := V.VObject.ToString;        {$ELSE}        else          raise ArgumentNullException.CreateResFmt(@RsDotNetFormatObjectArgumentNotSupported, [V.VObject.ClassName, Index]);        {$ENDIF RTL200_UP}      vtClass:        Result := V.VClass.ClassName;      vtWideChar:        Result := V.VWideChar;      vtPWideChar:        Result := V.VPWideChar;      vtAnsiString:        Result := string(V.VAnsiString);      vtCurrency:        Result := CurrToStr(V.VCurrency^);      vtVariant:        Result := GetStringOf(TVarData(V.VVariant^), Index);      vtInterface:        if IInterface(V.VInterface).QueryInterface(IToString, Intf) = 0 then          Result := IToString(Intf).ToString        else          raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]);      vtWideString:        Result := WideString(V.VWideString);      vtInt64:        Result := IntToStr(V.VInt64^);      {$IFDEF SUPPORTS_UNICODE_STRING}      vtUnicodeString:        Result := UnicodeString(V.VUnicodeString);      {$ENDIF SUPPORTS_UNICODE_STRING}    else      raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]);    end;  end;begin  if Length(Args) = 0 then  begin    Result := Fmt;    Exit;  end;  Len := 0;  Capacity := Length(Fmt);  SetLength(Result, Capacity);  if Capacity = 0 then    raise ArgumentNullException.CreateRes(@RsDotNetFormatNullFormat);  P := Pointer(Fmt);  F := P;  while True do  begin    if (P[0] = #0) or (P[0] = '{') then    begin      Count := P - F;      Inc(P);      if (P[-1] <> #0) and (P[0] = '{') then        Inc(Count); // include '{'      if Count > 0 then      begin        Grow(Count);        MoveChar(F[0], Result[Len + 1], Count);        Inc(Len, Count);      end;      if P[-1] = #0 then        Break;      if P[0] <> '{' then      begin        F := P;        Inc(P);        while (P[0] <> #0) and (P[0] <> '}') do          Inc(P);        SetString(S, F, P - F);        Val(S, Index, ErrorCode);        if ErrorCode <> 0 then          raise FormatException.CreateRes(@RsFormatException);        if (Index < 0) or (Index > High(Args)) then          raise FormatException.CreateRes(@RsFormatException);        S := GetStringOf(Index);        if S <> '' then        begin          Grow(Length(S));          MoveChar(S[1], Result[Len + 1], Length(S));          Inc(Len, Length(S));        end;        if P[0] = #0 then          Break;      end;      F := P + 1;    end    else    if (P[0] = '}') and (P[1] = '}') then    begin      Count := P - F + 1;      Inc(P); // skip next '}'      Grow(Count);      MoveChar(F[0], Result[Len + 1], Count);      Inc(Len, Count);      F := P + 1;    end;    Inc(P);  end;  SetLength(Result, Len);end;//=== { TJclStringBuilder } =====================================================constructor TJclStringBuilder.Create(Capacity: SizeInt; MaxCapacity: SizeInt);begin  inherited Create;  SetLength(FChars, Capacity);  FMaxCapacity := MaxCapacity;end;constructor TJclStringBuilder.Create(const Value: string; Capacity: SizeInt);begin  Create(Capacity);  Append(Value);end;constructor TJclStringBuilder.Create(const Value: string; StartIndex, Length, Capacity: SizeInt);begin  Create(Capacity);  Append(Value, StartIndex + 1, Length);end;function TJclStringBuilder.ToString: string;begin  if FLength > 0 then    SetString(Result, PChar(@FChars[0]), FLength)  else    Result := '';end;function TJclStringBuilder.EnsureCapacity(Capacity: SizeInt): SizeInt;begin  if System.Length(FChars) < Capacity then    SetCapacity(Capacity);  Result := System.Length(FChars);end;procedure TJclStringBuilder.Clear;begin  Length := 0;end;procedure TJclStringBuilder.SetCapacity(const Value: SizeInt);begin  if Value <> System.Length(FChars) then  begin    SetLength(FChars, Value);    if Value < FLength then      FLength := Value;  end;end;function TJclStringBuilder.GetChars(Index: SizeInt): Char;begin  Result := FChars[Index];end;procedure TJclStringBuilder.SetChars(Index: SizeInt; const Value: Char);begin  FChars[Index] := Value;end;procedure TJclStringBuilder.Set_Length(const Value: SizeInt);begin  FLength := Value;end;function TJclStringBuilder.GetCapacity: SizeInt;begin  Result := System.Length(FChars);end;function TJclStringBuilder.AppendPChar(Value: PChar; Count: SizeInt; RepeatCount: SizeInt): TJclStringBuilder;var  Capacity: SizeInt;begin  if (Count > 0) and (RepeatCount > 0) then  begin    repeat      Capacity := System.Length(FChars);      if Capacity + Count > MaxCapacity then        raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);      if Capacity < FLength + Count then        SetLength(FChars, Capacity * 5 div 3 + Count);      if Count = 1 then        FChars[FLength] := Value[0]      else        MoveChar(Value[0], FChars[FLength], Count);      Inc(FLength, Count);      Dec(RepeatCount);    until RepeatCount <= 0;  end;  Result := Self;end;function TJclStringBuilder.InsertPChar(Index: SizeInt; Value: PChar; Count,  RepeatCount: SizeInt): TJclStringBuilder;var  Capacity: SizeInt;begin  if (Index < 0) or (Index > FLength) then    raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);  if Index = FLength then    AppendPChar(Value, Count, RepeatCount)  else  if (Count > 0) and (RepeatCount > 0) then  begin    repeat      Capacity := System.Length(FChars);      if Capacity + Count > MaxCapacity then        raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);      if Capacity < FLength + Count then        SetLength(FChars, Capacity * 5 div 3 + Count);      MoveChar(FChars[Index], FChars[Index + Count], FLength - Index);      if Count = 1 then        FChars[Index] := Value[0]      else        MoveChar(Value[0], FChars[Index], Count);      Inc(FLength, Count);      Dec(RepeatCount);      Inc(Index, Count); // little optimization    until RepeatCount <= 0;  end;  Result := Self;end;function TJclStringBuilder.Append(const Value: array of Char): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if Len > 0 then    AppendPChar(@Value[0], Len);  Result := Self;end;function TJclStringBuilder.Append(const Value: array of Char; StartIndex, Length: SizeInt): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if (Length > 0) and (StartIndex < Len) then  begin    if StartIndex + Length > Len then      Length := Len - StartIndex;    AppendPChar(PChar(@Value[0]) + StartIndex, Length);  end;  Result := Self;end;function TJclStringBuilder.Append(Value: Char; RepeatCount: SizeInt = 1): TJclStringBuilder;begin  Result := AppendPChar(@Value, 1, RepeatCount);end;function TJclStringBuilder.Append(const Value: string): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if Len > 0 then    AppendPChar(Pointer(Value), Len);  Result := Self;end;function TJclStringBuilder.Append(const Value: string; StartIndex, Length: SizeInt): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if (Length > 0) and (StartIndex < Len) then  begin    if StartIndex + Length > Len then      Length := Len - StartIndex;    AppendPChar(PChar(Pointer(Value)) + StartIndex, Length);  end;  Result := Self;end;function TJclStringBuilder.Append(Value: Boolean): TJclStringBuilder;begin  Result := Append(BoolToStr[Value]);end;function TJclStringBuilder.Append(Value: Cardinal): TJclStringBuilder;begin  Result := Append(IntToStr(Value));end;function TJclStringBuilder.Append(Value: Integer): TJclStringBuilder;begin  Result := Append(IntToStr(Value));end;function TJclStringBuilder.Append(Value: Double): TJclStringBuilder;begin  Result := Append(FloatToStr(Value));end;function TJclStringBuilder.Append(Value: Int64): TJclStringBuilder;begin  Result := Append(IntToStr(Value));end;function TJclStringBuilder.Append(Obj: TObject): TJclStringBuilder;begin  Result := Append(DotNetFormat('{0}', [Obj]));end;function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0: Variant): TJclStringBuilder;begin  Result := Append(DotNetFormat(Fmt, [Arg0]));end;function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0, Arg1: Variant): TJclStringBuilder;begin  Result := Append(DotNetFormat(Fmt, [Arg0, Arg1]));end;function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0, Arg1, Arg2: Variant): TJclStringBuilder;begin  Result := Append(DotNetFormat(Fmt, [Arg0, Arg1, Arg2]));end;function TJclStringBuilder.AppendFormat(const Fmt: string; const Args: array of const): TJclStringBuilder;begin  Result := Append(DotNetFormat(Fmt, Args));end;function TJclStringBuilder.Insert(Index: SizeInt; const Value: array of Char): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if Len > 0 then    InsertPChar(Index, @Value[0], Len);  Result := Self;end;function TJclStringBuilder.Insert(Index: SizeInt; const Value: string; Count: SizeInt): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if Len > 0 then    InsertPChar(Index, Pointer(Value), Len, Count);  Result := Self;end;function TJclStringBuilder.Insert(Index: SizeInt; Value: Boolean): TJclStringBuilder;begin  Result := Insert(Index, BoolToStr[Value]);end;function TJclStringBuilder.Insert(Index: SizeInt; const Value: array of Char;  StartIndex, Length: SizeInt): TJclStringBuilder;var  Len: SizeInt;begin  Len := System.Length(Value);  if (Length > 0) and (StartIndex < Len) then  begin    if StartIndex + Length > Len then      Length := Len - StartIndex;    InsertPChar(Index, PChar(@Value[0]) + StartIndex, Length);  end;  Result := Self;end;function TJclStringBuilder.Insert(Index: SizeInt; Value: Double): TJclStringBuilder;begin  Result := Insert(Index, FloatToStr(Value));end;function TJclStringBuilder.Insert(Index: SizeInt; Value: Int64): TJclStringBuilder;begin  Result := Insert(Index, IntToStr(Value));end;function TJclStringBuilder.Insert(Index: SizeInt; Value: Cardinal): TJclStringBuilder;begin  Result := Insert(Index, IntToStr(Value));end;function TJclStringBuilder.Insert(Index: SizeInt; Value: Integer): TJclStringBuilder;begin  Result := Insert(Index, IntToStr(Value));end;function TJclStringBuilder.Insert(Index: SizeInt; Obj: TObject): TJclStringBuilder;begin  Result := Insert(Index, DotNetFormat('{0}', [Obj]));end;function TJclStringBuilder.Remove(StartIndex, Length: SizeInt): TJclStringBuilder;begin  if (StartIndex < 0) or (Length < 0) or (StartIndex + Length >= FLength) then    raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);  if Length > 0 then  begin    MoveChar(FChars[StartIndex + Length], FChars[StartIndex], FLength - (StartIndex + Length));    Dec(FLength, Length);  end;  Result := Self;end;function TJclStringBuilder.Replace(OldChar, NewChar: Char; StartIndex,  Count: SizeInt): TJclStringBuilder;var  I: SizeInt;begin  if Count = -1 then    Count := FLength;  if (StartIndex < 0) or (Count < 0) or (StartIndex + Count > FLength) then    raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);  if (Count > 0) and (OldChar <> NewChar) then  begin    for I := StartIndex to StartIndex + Length - 1 do      if FChars[I] = OldChar then        FChars[I] := NewChar;  end;  Result := Self;end;function TJclStringBuilder.Replace(OldValue, NewValue: string; StartIndex, Count: SizeInt): TJclStringBuilder;var  I: SizeInt;  Offset: SizeInt;  NewLen, OldLen, Capacity: SizeInt;begin  if Count = -1 then    Count := FLength;  if (StartIndex < 0) or (Count < 0) or (StartIndex + Count > FLength) then    raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);  if OldValue = '' then    raise ArgumentException.CreateResFmt(@RsArgumentIsNull, [0]);  if (Count > 0) and (OldValue <> NewValue) then  begin    OldLen := System.Length(OldValue);    NewLen := System.Length(NewValue);    Offset := NewLen - OldLen;    Capacity := System.Length(FChars);    for I := StartIndex to StartIndex + Length - 1 do      if FChars[I] = OldValue[1] then      begin        if OldLen > 1 then          if StrLComp(@FChars[I + 1], PChar(OldValue) + 1, OldLen - 1) <> 0 then            Continue;        if Offset <> 0 then        begin          if FLength - OldLen + NewLen > MaxCurrency then            raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);          if Capacity < FLength + Offset then          begin            Capacity := Capacity * 5 div 3 + Offset;            SetLength(FChars, Capacity);          end;          if Offset < 0 then            MoveChar(FChars[I - Offset], FChars[I], FLength - I)          else            MoveChar(FChars[I + OldLen], FChars[I + OldLen + Offset], FLength - OldLen - I);          Inc(FLength, Offset);        end;        if NewLen > 0 then        begin          if (OldLen = 1) and (NewLen = 1) then            FChars[I] := NewValue[1]          else            MoveChar(NewValue[1], FChars[I], NewLen);        end;      end;  end;  Result := Self;end;function StrExpandTabs(S: string): string;begin  // use an empty tab set, which will default to a tab width of 2  Result := TJclTabSet(nil).Expand(s);end;function StrExpandTabs(S: string; TabWidth: SizeInt): string;var  TabSet: TJclTabSet;begin  // create a tab set with no tab stops and the given tab width  TabSet := TJclTabSet.Create(TabWidth);  try    Result := TabSet.Expand(S);  finally    TabSet.Free;  end;end;function StrExpandTabs(S: string; TabSet: TJclTabSet): string;begin  // use the provided tab set to perform the expansion  Result := TabSet.Expand(S);end;function StrOptimizeTabs(S: string): string;begin  // use an empty tab set, which will default to a tab width of 2  Result := TJclTabSet(nil).Optimize(s);end;function StrOptimizeTabs(S: string; TabWidth: SizeInt): string;var  TabSet: TJclTabSet;begin  // create a tab set with no tab stops and the given tab width  TabSet := TJclTabSet.Create(TabWidth);  try    Result := TabSet.Optimize(S);  finally    TabSet.Free;  end;end;function StrOptimizeTabs(S: string; TabSet: TJclTabSet): string;begin  // use the provided tab set to perform the optimization  Result := TabSet.Optimize(S);end;// === { TTabSetData } ===================================================type  TTabSetData = class  public    FStops: TDynSizeIntArray;    FRealWidth: SizeInt;    FRefCount: SizeInt;    FWidth: SizeInt;    FZeroBased: Boolean;    constructor Create(TabStops: array of SizeInt; ZeroBased: Boolean; TabWidth: SizeInt);    function Add(Column: SizeInt): SizeInt;    function AddRef: SizeInt;    procedure CalcRealWidth;    function FindStop(Column: SizeInt): SizeInt;    function ReleaseRef: SizeInt;    procedure RemoveAt(Index: SizeInt);    procedure SetStops(Index, Value: SizeInt);  end;constructor TTabSetData.Create(TabStops: array of SizeInt; ZeroBased: Boolean; TabWidth: SizeInt);var  idx: SizeInt;begin  inherited Create;  FRefCount := 1;  for idx := 0 to High(Tabstops) do    Add(Tabstops[idx]);  FWidth := TabWidth;  FZeroBased := ZeroBased;  CalcRealWidth;end;function TTabSetData.Add(Column: SizeInt): SizeInt;var  I: SizeInt;begin  if Column < Ord(FZeroBased) then    raise ArgumentOutOfRangeException.Create('Column');  Result := FindStop(Column);  if Result < 0 then  begin    // the column doesn't exist; invert the result of FindStop to get the correct index position    Result := not Result;    // increase the tab stop array    SetLength(FStops, Length(FStops) + 1);    // shift rooms after the insert position    for I := High(FStops) - 1 downto Result do      FStops[I + 1] := FStops[I];    // add the tab stop at the correct location    FStops[Result] := Column;    CalcRealWidth;  end  else  begin    raise EJclStringError.CreateRes(@RsTabs_DuplicatesNotAllowed);  end;end;function TTabSetData.AddRef: SizeInt;begin  Result := LockedInc(FRefCount);end;procedure TTabSetData.CalcRealWidth;begin  if FWidth < 1 then  begin    if Length(FStops) > 1 then      FRealWidth := FStops[High(FStops)] - FStops[Pred(High(FStops))]    else    if Length(FStops) = 1 then      FRealWidth := FStops[0]    else      FRealWidth := 2;  end  else    FRealWidth := FWidth;end;function TTabSetData.FindStop(Column: SizeInt): SizeInt;begin  Result := High(FStops);  while (Result >= 0) and (FStops[Result] > Column) do    Dec(Result);  if (Result >= 0) and (FStops[Result] <> Column) then    Result := not Succ(Result);end;function TTabSetData.ReleaseRef: SizeInt;begin  Result := LockedDec(FRefCount);  if Result <= 0 then    Destroy;end;procedure TTabSetData.RemoveAt(Index: SizeInt);var  I: SizeInt;begin  for I := Index to High(FStops) - 1 do    FStops[I] := FStops[I + 1];  SetLength(FStops, High(FStops));  CalcRealWidth;end;procedure TTabSetData.SetStops(Index, Value: SizeInt);var  temp: SizeInt;begin  if (Index < 0) or (Index >= Length(FStops)) then  begin    raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange);  end  else  begin    temp := FindStop(Value);    if temp < 0 then    begin      // remove existing tab stop...      RemoveAt(Index);      // now add the new tab stop      Add(Value);    end    else    if temp <> Index then    begin      // new tab stop already present at another index      raise EJclStringError.CreateRes(@RsTabs_DuplicatesNotAllowed);    end;  end;end;//=== { TJclTabSet } =====================================================constructor TJclTabSet.Create;begin  // no tab stops, tab width set to auto  Create([], True, 0);end;constructor TJclTabSet.Create(TabWidth: SizeInt);begin  // no tab stops, specified tab width  Create([], True, TabWidth);end;constructor TJclTabSet.Create(const Tabstops: array of SizeInt; ZeroBased: Boolean);begin  // specified tab stops, tab width equal to distance between last two tab stops  Create(Tabstops, ZeroBased, 0);end;constructor TJclTabSet.Create(const Tabstops: array of SizeInt; ZeroBased: Boolean; TabWidth: SizeInt);begin  inherited Create;  FData := TTabSetData.Create(Tabstops, ZeroBased, TabWidth);end;constructor TJclTabSet.Create(Data: TObject);begin  inherited Create;  // add a reference to the data  TTabSetData(Data).AddRef;  // assign the data to this instance  FData := TTabSetData(Data);end;destructor TJclTabSet.Destroy;begin  // release the reference to the tab set data  TTabSetData(FData).ReleaseRef;  // make sure we won't accidentally refer to it later, just in case something goes wrong during destruction  FData := nil;  // really destroy the instance  inherited Destroy;end;function TJclTabSet.Add(Column: SizeInt): SizeInt;begin  if Self = nil then    raise NullReferenceException.Create;  Result := TTabSetData(FData).Add(Column);end;function TJclTabSet.Clone: TJclTabSet;begin  if Self <> nil then    Result := TJclTabSet.Create(TTabSetData(FData).FStops, TTabSetData(FData).FZeroBased, TTabSetData(FData).FWidth)  else    Result := nil;end;function TJclTabSet.Delete(Column: SizeInt): SizeInt;begin  Result := TTabSetData(FData).FindStop(Column);  if Result >= 0 then    TTabSetData(FData).RemoveAt(Result);end;function TJclTabSet.Expand(const S: string): string;begin  Result := Expand(s, StartColumn);end;function TJclTabSet.Expand(const S: string; Column: SizeInt): string;var  sb: TJclStringBuilder;  head: PChar;  cur: PChar;begin  if Column < StartColumn then    raise ArgumentOutOfRangeException.Create('Column');  sb := TJclStringBuilder.Create(Length(S));  try    cur := PChar(S);    while cur^ <> #0 do    begin      head := cur;      while (cur^ <> #0) and (cur^ <> #9) do      begin        if CharIsReturn(cur^) then          Column := StartColumn        else          Inc(Column);        Inc(cur);      end;      if cur > head then        sb.Append(head, 0, cur - head);      if cur^ = #9 then      begin        sb.Append(' ', TabFrom(Column) - Column);        Column := TabFrom(Column);        Inc(cur);      end;    end;    Result := sb.ToString;  finally    sb.Free;  end;end;function TJclTabSet.FindStop(Column: SizeInt): SizeInt;begin  if Self <> nil then    Result := TTabSetData(FData).FindStop(Column)  else    Result := -1;end;class function TJclTabSet.FromString(const S: string): TJclTabSet;var  cur: PChar;  function ParseNumber: Integer;  var    head: PChar;  begin    StrSkipChars(cur, CharIsWhiteSpace);    head := cur;    while CharIsDigit(cur^) do      Inc(cur);    Result := -1;    if (cur <= head) or not TryStrToInt(Copy(head, 1, cur - head), Result) then      Result := -1;  end;  procedure ParseStops;  var    openBracket, hadComma: Boolean;    num: SizeInt;  begin    StrSkipChars(cur, CharIsWhiteSpace);    openBracket := cur^ = '[';    hadComma := False;    if openBracket then      Inc(cur);    repeat      num := ParseNumber;      if (num < 0) and hadComma then        raise EJclStringError.CreateRes(@RsTabs_StopExpected)      else      if num >= 0 then        Result.Add(num);      StrSkipChars(cur, CharIsWhiteSpace);      hadComma := cur^ = ',';      if hadComma then        Inc(cur);    until (cur^ = #0) or (cur^ = '+') or (cur^ = ']');    if hadComma then      raise EJclStringError.CreateRes(@RsTabs_StopExpected)    else    if openBracket and (cur^ <> ']') then      raise EJclStringError.CreateRes(@RsTabs_CloseBracketExpected);  end;  procedure ParseTabWidth;  var    num: SizeInt;  begin    StrSkipChars(cur, CharIsWhiteSpace);    if cur^ = '+' then    begin      Inc(cur);      StrSkipChars(cur, CharIsWhiteSpace);      num := ParseNumber;      if (num < 0) then        raise EJclStringError.CreateRes(@RsTabs_TabWidthExpected)      else        Result.TabWidth := num;    end;  end;  procedure ParseZeroBasedFlag;  begin    StrSkipChars(cur, CharIsWhiteSpace);    if cur^ = '0' then    begin      Inc(cur);      if CharIsWhiteSpace(cur^) or (cur^ = #0) or (cur^ = '[') then      begin        Result.ZeroBased := True;        StrSkipChars(cur, CharIsWhiteSpace);      end      else        Dec(cur);    end;  end;begin  Result := TJclTabSet.Create;  try    Result.ZeroBased := False;    cur := PChar(S);    ParseZeroBasedFlag;    ParseStops;    ParseTabWidth;  except    // clean up the partially complete instance (to avoid memory leaks)...    Result.Free;    // ... and re-raise the exception    raise;  end;end;function TJclTabSet.GetCount: SizeInt;begin  if Self <> nil then    Result := Length(TTabSetData(FData).FStops)  else    Result := 0;end;function TJclTabSet.GetStops(Index: SizeInt): SizeInt;begin  if Self <> nil then  begin    if (Index < 0) or (Index >= Length(TTabSetData(FData).FStops)) then    begin      raise EJclStringError.CreateRes(@RsArgumentOutOfRange);    end    else      Result := TTabSetData(FData).FStops[Index];  end  else  begin    raise EJclStringError.CreateRes(@RsArgumentOutOfRange);  end;end;function TJclTabSet.GetTabWidth: SizeInt;begin  if Self <> nil then    Result := TTabSetData(FData).FWidth  else    Result := 0;end;function TJclTabSet.GetZeroBased: Boolean;begin  Result := (Self = nil) or TTabSetData(FData).FZeroBased;end;procedure TJclTabSet.OptimalFillInfo(StartColumn, TargetColumn: SizeInt; out TabsNeeded, SpacesNeeded: SizeInt);var  nextTab: SizeInt;begin  if StartColumn < Self.StartColumn then  // starting column less than 1 or 0 (depending on ZeroBased state)    raise ArgumentOutOfRangeException.Create('StartColumn');  if (TargetColumn < StartColumn) then    // target lies before the starting column    raise ArgumentOutOfRangeException.Create('TargetColumn');  TabsNeeded := 0;  repeat    nextTab := TabFrom(StartColumn);    if nextTab <= TargetColumn then    begin      Inc(TabsNeeded);      StartColumn := nextTab;    end;  until nextTab > TargetColumn;  SpacesNeeded := TargetColumn - StartColumn;end;function TJclTabSet.Optimize(const S: string): string;begin  Result := Optimize(S, StartColumn);end;function TJclTabSet.Optimize(const S: string; Column: SizeInt): string;var  sb: TJclStringBuilder;  head: PChar;  cur: PChar;  tgt: SizeInt;  procedure AppendOptimalWhiteSpace(Target: SizeInt);  var    tabCount: SizeInt;    spaceCount: SizeInt;  begin    if cur > head then    begin      OptimalFillInfo(Column, Target, tabCount, spaceCount);      if tabCount > 0 then        sb.Append(#9, tabCount);      if spaceCount > 0 then        sb.Append(' ', spaceCount);    end;  end;begin  if Column < StartColumn then    raise ArgumentOutOfRangeException.Create('Column');  sb := TJclStringBuilder.Create(Length(S));  try    cur := PChar(s);    while cur^ <> #0 do    begin      // locate first whitespace character      head := cur;      while (cur^ <> #0) and not CharIsWhiteSpace(cur^) do        Inc(cur);      // output non whitespace characters      if cur > head then        sb.Append(head, 0, cur - head);      // advance column      Inc(Column, cur - head);      // initialize target column indexer      tgt := Column;      // locate end of whitespace sequence      while CharIsWhiteSpace(cur^) do      begin        if CharIsReturn(cur^) then        begin          // append optimized whitespace sequence...          AppendOptimalWhiteSpace(tgt);          // ...set the column back to the start of the line...          Column := StartColumn;          // ...reset target column indexer...          tgt := Column;          // ...add the line break character...          sb.Append(cur^);        end        else        if cur^ = #9 then          tgt := TabFrom(tgt)       // expand the tab        else          Inc(tgt);                 // a normal whitespace; taking up 1 column        Inc(cur);      end;      AppendOptimalWhiteSpace(tgt); // append optimized whitespace sequence...      Column := tgt;                // ...and memorize the column for the next iteration    end;    Result := sb.ToString;          // convert result to a string  finally    sb.Free;  end;end;procedure TJclTabSet.RemoveAt(Index: SizeInt);begin  if Self <> nil then    TTabSetData(FData).RemoveAt(Index)  else    raise NullReferenceException.Create;end;procedure TJclTabSet.SetStops(Index, Value: SizeInt);begin  if Self <> nil then    TTabSetData(FData).SetStops(Index, Value)  else    raise NullReferenceException.Create;end;procedure TJclTabSet.SetTabWidth(Value: SizeInt);begin  if Self <> nil then  begin    TTabSetData(FData).FWidth := Value;    TTabSetData(FData).CalcRealWidth;  end  else    raise NullReferenceException.Create;end;procedure TJclTabSet.SetZeroBased(Value: Boolean);var  shift: SizeInt;  idx:   SizeInt;begin  if Self <> nil then  begin    if Value <> TTabSetData(FData).FZeroBased then    begin      TTabSetData(FData).FZeroBased := Value;      if Value then        shift := -1      else        shift := 1;      for idx := 0 to High(TTabSetData(FData).FStops) do        TTabSetData(FData).FStops[idx] := TTabSetData(FData).FStops[idx] + shift;    end;  end  else    raise NullReferenceException.Create;end;function TJclTabSet.InternalTabStops: TDynSizeIntArray;begin  if Self <> nil then    Result := TTabSetData(FData).FStops  else    Result := nil;end;function TJclTabSet.InternalTabWidth: SizeInt;begin  if Self <> nil then    Result := TTabSetData(FData).FRealWidth  else    Result := 2;end;function TJclTabSet.NewReference: TJclTabSet;begin  if Self <> nil then    Result := TJclTabSet.Create(FData)  else    Result := nil;end;function TJclTabSet.StartColumn: SizeInt;begin  if GetZeroBased then    Result := 0  else    Result := 1;end;function TJclTabSet.TabFrom(Column: SizeInt): SizeInt;begin  if Column < StartColumn then    raise ArgumentOutOfRangeException.Create('Column');  Result := FindStop(Column);  if Result < 0 then    Result := not Result  else    Inc(Result);  if Result >= GetCount then  begin    if GetCount > 0 then      Result := TTabSetData(FData).FStops[High(TTabSetData(FData).FStops)]    else      Result := StartColumn;    while Result <= Column do      Inc(Result, ActualTabWidth);  end  else    Result := TTabSetData(FData).FStops[Result];end;function TJclTabSet.ToString: string;begin  Result := ToString(TabSetFormatting_Full);end;function TJclTabSet.ToString(FormattingOptions: SizeInt): string;var  sb: TJclStringBuilder;  idx: SizeInt;  function WantBrackets: Boolean;  begin    Result := (TabSetFormatting_SurroundStopsWithBrackets and FormattingOptions) <> 0;  end;  function EmptyBrackets: Boolean;  begin    Result := (TabSetFormatting_EmptyBracketsIfNoStops and FormattingOptions) <> 0;  end;  function IncludeAutoWidth: Boolean;  begin    Result := (TabSetFormatting_AutoTabWidth and FormattingOptions) <> 0;  end;  function IncludeTabWidth: Boolean;  begin    Result := (TabSetFormatting_NoTabWidth and FormattingOptions) = 0;  end;  function IncludeStops: Boolean;  begin    Result := (TabSetFormatting_NoTabStops and FormattingOptions) = 0;  end;begin  sb := TJclStringBuilder.Create;  try    // output the fixed tabulation positions if requested...    if IncludeStops then    begin      // output each individual tabulation position      for idx := 0 to GetCount - 1 do      begin        sb.Append(TabStops[idx]);        sb.Append(',');      end;      // remove the final comma if any tabulation positions where outputted      if sb.Length <> 0 then        sb.Remove(sb.Length - 1, 1);      // bracket the tabulation positions if requested      if WantBrackets and (EmptyBrackets or (sb.Length > 0)) then      begin        sb.Insert(0, '[');        sb.Append(']');      end;    end;    // output the tab width if requested....    if IncludeTabWidth and (IncludeAutoWidth or (TabWidth > 0)) then    begin      // separate the tab width from any outputted tabulation positions with a whitespace      if sb.Length > 0 then        sb.Append(' ');      // flag tab width      sb.Append('+');      // finally, output the tab width      sb.Append(ActualTabWidth);    end;    // flag zero-based tabset by outputting a 0 (zero) as the first character.    if ZeroBased then      sb.Insert(0, string('0 '));    Result := StrTrimCharRight(sb.ToString, ' ');  finally    sb.Free;  end;end;function TJclTabSet.UpdatePosition(const S: string): SizeInt;var  Line: SizeInt;begin  Result := StartColumn;  Line := -1;  UpdatePosition(S, Result, Line);end;function TJclTabSet.UpdatePosition(const S: string; Column: SizeInt): SizeInt;var  Line: SizeInt;begin  if Column < StartColumn then    raise ArgumentOutOfRangeException.Create('Column');  Result := Column;  Line := -1;  UpdatePosition(S, Result, Line);end;function TJclTabSet.UpdatePosition(const S: string; var Column, Line: SizeInt): SizeInt;var  prevChar: Char;  cur:      PChar;begin  if Column < StartColumn then    raise ArgumentOutOfRangeException.Create('Column');  // initialize loop  cur := PChar(S);  // iterate until end of string (the Null-character)  while cur^ <> #0 do  begin    // check for line-breaking characters    if CharIsReturn(cur^) then    begin      // Column moves back all the way to the left      Column := StartColumn;      // If this is the first line-break character or the same line-break character, increment the Line parameter      Inc(Line);      // check if it's the first of a two-character line-break      prevChar := cur^;      Inc(cur);      // if it isn't a two-character line-break, undo the previous advancement      if (cur^ = prevChar) or not CharIsReturn(cur^) then        Dec(cur);    end    else // check for tab character and expand it    if cur^ = #9 then      Column := TabFrom(Column)    else // a normal character; increment column      Inc(Column);    // advance pointer    Inc(cur);  end;  // set the result to the newly calculated column  Result := Column;end;//=== { NullReferenceException } =============================================constructor NullReferenceException.Create;begin  CreateRes(@RsArg_NullReferenceException);end;{$IFNDEF WINSCP}function CompareNatural(const S1, S2: string; CaseInsensitive: Boolean): SizeInt;var  Cur1, Len1,  Cur2, Len2: SizeInt;  function IsRealNumberChar(ch: Char): Boolean;  begin    Result := ((ch >= '0') and (ch <= '9')) or (ch = '-') or (ch = '+');  end;  procedure NumberCompare;  var    IsReallyNumber: Boolean;    FirstDiffBreaks: Boolean;    Val1, Val2:     SizeInt;  begin    Result := 0;    IsReallyNumber := False;    // count leading spaces in S1    while (Cur1 <= Len1) and CharIsWhiteSpace(S1[Cur1]) do    begin      Dec(Result);      Inc(Cur1);    end;    // count leading spaces in S2 (canceling them out against the ones in S1)    while (Cur2 <= Len2) and CharIsWhiteSpace(S2[Cur2]) do    begin      Inc(Result);      Inc(Cur2);    end;    // if spaces match, or both strings are actually followed by a numeric character, continue the checks    if (Result = 0) or ((Cur1 <= Len1) and CharIsNumberChar(S1[Cur1]) and (Cur2 <= Len2) and CharIsNumberChar(S2[Cur2])) then    begin      // Check signed number      if (Cur1 <= Len1) and (S1[Cur1] = '-') and ((Cur2 > Len2) or (S2[Cur2] <> '-')) then        Result := 1      else      if (Cur2 <= Len2) and (S2[Cur2] = '-') and ((Cur1 > Len1) or (S1[Cur1] <> '-')) then        Result := -1      else        Result := 0;      if (Cur1 <= Len1) and ((S1[Cur1] = '-') or (S1[Cur1] = '+')) then        Inc(Cur1);      if (Cur2 <= Len2) and ((S2[Cur2] = '-') or (S2[Cur2] = '+')) then        Inc(Cur2);      FirstDiffBreaks := (Cur1 <= Len1) and (S1[Cur1] = '0') or (Cur2 <= Len2) and (S2[Cur2] = '0');      while (Cur1 <= Len1) and CharIsDigit(S1[Cur1]) and (Cur2 <= Len2) and CharIsDigit(S2[Cur2]) do      begin        IsReallyNumber := True;        Val1 := StrToInt(S1[Cur1]);        Val2 := StrToInt(S2[Cur2]);        if (Result = 0) and (Val1 < Val2) then          Result := -1        else        if (Result = 0) and (Val1 > Val2) then          Result := 1;        if FirstDiffBreaks and (Result <> 0) then          Break;        Inc(Cur1);        Inc(Cur2);      end;      if IsReallyNumber then      begin        if not FirstDiffBreaks then        begin          if (Cur1 <= Len1) and CharIsDigit(S1[Cur1]) then            Result := 1          else          if (Cur2 <= Len2) and CharIsDigit(S2[Cur2]) then            Result := -1;        end;      end;    end;  end;  procedure SetByCompareLength;  var    Remain1: SizeInt;    Remain2: SizeInt;  begin    // base result on relative compare length (spaces could be ignored, so even if S1 is longer than S2, they could be    // completely equal, or S2 could be longer)    Remain1 := Len1 - Cur1 + 1;    Remain2 := Len2 - Cur2 + 1;    if Remain1 < 0 then      Remain1 := 0;    if Remain2 < 0 then      Remain2 := 0;    if Remain1 < Remain2 then      Result := -1    else    if Remain1 > Remain2 then      Result := 1;  end;begin  Cur1 := 1;  Len1 := Length(S1);  Cur2 := 1;  Len2 := Length(S2);  Result := 0;  while (Result = 0) do  begin    if (Cur1 > Len1) or (Cur2 > Len2) then    begin      SetByCompareLength;      Break;    end    else    if (Cur1 <= Len1) and (Cur2 > Len2) then      Result := 1    else    if (S1[Cur1] = '-') and IsRealNumberChar(S2[Cur2]) and (S2[Cur2] <> '-') then      Result := -1    else    if (S2[Cur2] = '-') and IsRealNumberChar(S1[Cur1]) and (S1[Cur1] <> '-') then      Result := 1    else    if (IsRealNumberChar(S1[Cur1]) or CharIsWhiteSpace(S1[Cur1])) and (IsRealNumberChar(S2[Cur2]) or CharIsWhiteSpace(S2[Cur2])) then      NumberCompare    else    begin      if CaseInsensitive then        Result := StrLIComp(PChar(@S1[Cur1]), PChar(@S2[Cur2]), 1)      else        Result := StrLComp(PChar(@S1[Cur1]), PChar(@S2[Cur2]), 1);      Inc(Cur1);      Inc(Cur2);    end;  end;end;function CompareNaturalStr(const S1, S2: string): SizeInt; overload;begin  Result := CompareNatural(S1, S2, False);end;function CompareNaturalText(const S1, S2: string): SizeInt; overload;begin  Result := CompareNatural(S1, S2, True);end;{$ENDIF ~WINSCP}initialization  {$IFNDEF UNICODE_RTL_DATABASE}  LoadCharTypes;  // this table first  LoadCaseMap;    // or this function does not work  {$ENDIF ~UNICODE_RTL_DATABASE}  {$IFDEF UNITVERSIONING}  RegisterUnitVersion(HInstance, UnitVersioning);  {$ENDIF UNITVERSIONING}{$IFDEF UNITVERSIONING}finalization  UnregisterUnitVersion(HInstance);{$ENDIF UNITVERSIONING}end.
 |