Taulukko sisältää asiakkaiden nimien sarakkeen. Jotkut ovat kiinaksi, jotkut kyrillisillä kirjaimilla, muutamissa on saksalaisia umlautteja tai ranskalaisia aksentteja. Viet sen CSV-muotoon ja avaat tuloksen, ja jokainen merkki on ehjä. Viet saman työkirjan RTF-muotoon osoitetarrojen mallipohjaa varten, avaat sen tekstinkäsittelyohjelmassa, ja ei-ASCII-nimet ovat romahtaneet kysymysmerkkien riveiksi. Tiedot eivät koskaan muuttuneet. Se mikä muuttui, on kirjoittamasi tiedostomuodon koodaussopimus, ja jokaisella vientipolulla on erilainen sopimus.
Tämä on se ansa, joka odottaa kirjastoa, joka näyttää täysin Unicode-tietoiselta pinnalla. Solun teksti pidetään sisäisesti WideString-tyyppinä, joten malli ei koskaan menetä merkkiä. Menetys tapahtuu rajalla, kirjoittimessa, jonka on serialisoitava kyseinen teksti tiedostomuotoon, jolla on omat sääntönsä siitä, mitkä tavut ovat laillisia ja miten kaikki laillisen alueen ulkopuolella oleva on koodattava. Tee yksi kirjoitin oikein, ja voit silti toimittaa toisen, joka turmelee saman tekstin. Korjaus ei ole globaali kytkin. Se on erillinen, oikea päätös jokaisella polulla.
RTF on suunnittelultaan 7-bittisesti turvallinen muoto
Rich Text Format predates Unicode ja se määriteltiin selviämään siirroista, jotka välittävät vain tulostettavaa ASCII-tekstiä. RTF-dokumentti ilmoittaa koodisivun otsikossaan, ja kaikki merkit, joita kirjoitin ei voi edustaa kyseisellä koodisivulla, on vietävä ohjauskoodina raa'an tavun sijaan. Asiaankuuluva ohjauskoodi on \u, joka kantaa etumerkillistä 16-bittistä koodiyksikköä, jota seuraa ASCII-korvausmerkki lukijoille, jotka ovat liian vanhoja ymmärtämään itse ohjauskoodia.
HotXLS kirjoittaa RTF:ää tällä tavalla. Dokumentin otsikko avautuu ilmoittamalla koodisivun muodossa \ansi\ansicpg1252\uc1, ja lxRTF-yksikön kirjoitin käy läpi jokaisen merkkijonon vieden minkä tahansa tavallisen ASCII:n yläpuolella olevan merkin \u-ohjauskoodina, jotta tavuvirta pysyy 7-bittisenä ilmoitetusta koodisivusta riippumatta. Koodipisteestä kuten U+4E2D tulee kirjaimellinen sarja 3?, ei raaka tavu, jota lukija yrittäisi tulkita minkä tahansa olettamansa koodisivun kautta. Ilman tätä kurinalaisuutta kaikella ilmoitetun koodisivun ulkopuolella olevalla ei ole laillista tavuesitystä, ja raa'an arvon vievä kirjoitin tuottaa kysymysmerkit, joista tämä artikkeli alkoi.
Yksityiskohta, joka on pidettävä mielessä, on se, että ilmoitettu koodisivu ja ohjauskoodit ovat saman sopimuksen kaksi puoliskoa. Pelkän koodisivun ilmoittaminen ei auta sen ulkopuolella olevaa tekstiä. Ohjauskoodien vieminen ilman ilmoitettua koodisivua jättää korvausmerkit epäselviksi. Molempien on oltava oikein yhdessä, minkä vuoksi vain toisen niistä käsittelevä kirjoitin epäonnistuu edelleen ensimmäisessä monikielisessä työkirjassa.
HTML-koodaus on muutakin kuin kulmasulkeita
HTML-vienti tuottaa monilehtisen dokumentin, jonka navigointikehykset kantavat taulukoiden nimiä näkyvänä tekstinä. Nämä nimet ovat tekijän hallitsemia merkkijonoja, jotka voivat sisältää mitä tahansa merkkejä, mukaan lukien merkinnän kannalta merkityksellisiä merkkejä. Taulukon, jonka nimi on kirjaimellisesti Q1 & Q2 <draft>, on saavutettava sivu suojattuina entiteetteinä, tai kulmasulkeet avaavat haamutagin ja ampersand aloittaa entiteettiviitteen, jota ei koskaan tarkoitettu. Tämä on tavallista HTML-koodausta, ja sen ohittaminen taulukon nimessä on sellainen laiminlyönti, joka läpäisee jokaisen vain ASCII-nimillä rakennetun testin.
Koodauskysymys sijaitsee yhtä tasoa sen alapuolella. Kun ei-ASCII-merkit päätyvät kontekstiin, jota ei taata tarjoiltavan UTF-8-muodossa, turvallinen esitys on numeerinen merkkiviite, joten U+00E9 kirjoitetaan muodossa é raa'an tavun sijaan, jonka merkitys riippuu vastauksen merkistöstä. Tämän säännön peilikuva pätee matkalla sisään. XLSX:stä takaisin luettu työkirja kantaa jaettuja merkkijonoja, joissa merkki voi ja olla tallennettu numeerisena XML-entiteettinä, ja kyseinen entiteetti on dekoodattava yhdeksi kokonaiseksi merkiksi ennen kuin se saapuu solumalliin. Dekoodaa se huolimattomasti jakaen koodipisteen erillisiksi tavuiksi, ja yksittäinen merkki nousee uudelleen esiin kahtena mojibaken palasena, joita mikään myöhempi vienti ei voi korjata.
XLSX-säiliö on ZIP, ja ZIP-tiedostolla on oma nimikoodauksensa
XLSX-tiedosto on ZIP-arkisto, ja arkisto tallentaa nimen jokaiselle jäsenelleen. ZIP on niin vanha, että sen alkuperäinen spesifikaatio ei sanonut mitään näiden nimien koodauksesta, joten lukija, joka ei löydä signaalia, olettaa arkiston paikallisen koodisivun. Tämä oletus on väärä heti, kun jäsenen nimi sisältää ei-ASCII-merkin, mikä tapahtuu lokalisoiduilla taulukon osien nimillä ja upotetuilla mediatiedostoilla, joiden tiedostonimet kantavat aksentteja tai ei-latinalaisia kirjoitusjärjestelmiä.
Korjaus on yksi bitti. Yleiskäyttöinen bitti 11 kussakin paikallisessa tiedosto-otsikossa ilmoittaa, että jäsenen nimi on koodattu UTF-8-muodossa. HotXLS tarkistaa juuri tämän bitin lukeessaan arkistoa testaten yleiskäyttöisiä lippuja maskia $0800 vasten, ja lukija tai kirjoittaja, joka jättää sen huomiotta, lukee väärin nimen, jonka oikea toteutus tallensi UTF-8-muodossa. Bitti on halpa asettaa ja halpa kunnioittaa, ja se on koko ero jäsenen nimen välillä, joka selviää edestakaisesta matkasta, ja sellaisen välillä, joka saapuu korruptoituneena ennen kuin taulukon sisältöä on edes jäsennetty.
Kirjainkoon muunnos ja numeroiden skannaus sisältävät saman vaaran
Kaavan arviointi on se kohta, jossa Unicode-turvallisuus lakkaa olemasta serialisointia ja muuttuu vertailuksi. SEARCH-funktio on epäherkkä kirjainkoolle, mikä tarkoittaa, että sen on muutettava kirjainkoko ennen kuin se etsii alimerkkijonoa. Väärä tapa muuntaa on tehdä se ANSI-koodisivun kautta, koska ei-ASCII-tekstin muuntaminen kyseisellä tavalla reitittää merkit kapean koodisivun läpi ja turmelee kaiken sen ulkopuolella olevan. Oikea tapa on laaja-alainen kirjainkoon muunnos, joka säilyttää täyden UTF-16-alueen. HotXLS muuntaa funktion WideUpperCase avulla juuri tästä syystä, joten haku aksentoidulle tai ei-latinalaiselle tekstille täsmää samoihin merkkeihin kuin mitä sille annettiin raakakoodisivun sijaan.
Kaavatokenisoija kantaa vastaavaa velvollisuutta, jolla ei ole mitään tekemistä kirjainten kanssa ja kaikella sen kanssa, mihin token päättyy. Tieteellinen merkintätapa, kuten 1E3 tai 2.5E-3, on yksi numeerinen literaali, ja skannerin on tunnistettava E, valinnainen etumerkki ja sitä seuraavat numerot osana lukua sen sijaan, että se rikkoisi syötteen nimeksi, jota seuraa erillinen luku. Skanneri, joka käsittelee tämän väärin, muuttaa täysin kelvollisen vakion jäsennysvirheeksi tai, mikä pahempaa, hiljaa vääräksi lausekkeeksi. Se kuuluu samaan keskusteluun, koska molemmissa tapauksissa on kyse siitä, että lukija tekee oikean merkkitasoisen päätöksen: toinen siitä, miten merkki taitetaan vertailua varten, toinen siitä, jatkaako merkki nykyistä tokenia.
Monikielisen työkirjan rakentaminen ja vienti
Julkinen API ei pyydä sinua ajattelemaan mitään tästä. Rakennat työkirjan WideString-soluarvoista ja kutsut haluamaasi viennin aloituspistettä. Koodauspäätökset tapahtuvat jokaisen kirjoittimen sisällä. Alla oleva esimerkki kylvää taulukkoon tekstiä useilla kirjoitusjärjestelmillä ja kirjoittaa sitten sekä RTF-tiedoston että HTML-tiedoston samasta työkirjasta, joten molemmat polut ajetaan identtistä syötettä vasten.
uses
lxHandle;
procedure ExportMultilingualWorkbook;
var
Book: IXLSWorkbook;
Sheet: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add('Customers');
Sheet.Cells[1, 1].Value := 'Name';
Sheet.Cells[1, 2].Value := 'City';
// Cell text is held as WideString, so every script survives the model.
Sheet.Cells[2, 1].Value := '王伟'; // Chinese
Sheet.Cells[2, 2].Value := '北京';
Sheet.Cells[3, 1].Value := 'Müller'; // German umlaut
Sheet.Cells[3, 2].Value := 'Köln';
Sheet.Cells[4, 1].Value := 'Иванов'; // Cyrillic
Sheet.Cells[4, 2].Value := 'Москва';
Sheet.Cells[5, 1].Value := 'Désirée'; // French accents
Sheet.Cells[5, 2].Value := 'Montréal';
// RTF: the lxRTF writer declares the code page and emits every
// non-ASCII character as a \u escape, keeping the file 7-bit clean.
Book.SaveAsRTF('Customers.rtf');
// HTML: sheet names are HTML-escaped and non-ASCII text is written
// so it does not depend on a guessed response charset.
Book.SaveAsHTML('Customers.html');
finally
Book := nil;
end;
end;
Molemmat kutsut palauttavat Integer-tilan, ja molemmat kuluttavat samaa muistissa olevaa tekstiä. Mikään kutsuvassa koodissa ei ilmoita koodisivua tai koodaumeeriset viitteet, koska vastuu kuuluu kirjoittimelle, joka tuntee oman tiedostomuotonsa. Työkirjatason SaveAsCSV noudattaa samaa muotoa, jos tarvitset erotellun viennin identtisestä lähteestä.
// Same workbook, a third export path with its own encoding rules.
Book.SaveAsCSV('Customers.csv');
Unicode-turvallisuus on polkukohtaista, ei kirjastokohtaista
Opetus, joka kannattaa ottaa mukaan, on se, että ei ole olemassa yhtä paikkaa olla Unicode-turvallinen. RTF tarvitsee ilmoitetun koodisivun plus \u-ohjauskoodit. HTML tarvitsee entiteettikoodauksen merkinnän kannalta merkityksellisille merkeille ja numeeriset viitteet sinne, missä merkistöä ei taata, plus jaettuihin merkkijonoihin saapuvien entiteettien oikean dekoodauksen. ZIP-säiliö tarvitsee yleiskäyttöisen bitin 11 asetettuna, jotta UTF-8-jäsenen nimi luetaan UTF-8-muodossa. Kaavan arviointi tarvitsee laaja-alaisen kirjainkoon muunnoksen ja tokenisoijan, joka pitää tieteellisen merkintätavan yhtenä kappaleena. Jokainen näistä on erilainen sopimus, ja kirjasto voi täyttää yhden rikkoen samalla hiljaa toista. Se on syy siihen, miksi työkalu, joka saa CSV:n oikein, voi silti antaa sinulle RTF:n täynnä kysymysmerkkejä.
Jos vientisi tukeutuu eroteltuihin muotoihin, niiden väliset kompromissit käsitellään artikkelissamme CSV-, TSV- ja HTML-viennistä, ja kun lähde on tulosjoukko käsin rakennetun taulukon sijaan, kaavat artikkelissa tietokantojen viennistä Delphi-raportteja varten pariutuvat luonnollisesti tässä kuvattujen koodaussääntöjen kanssa. Kaikki tämä toimitetaan osana Delphin ja C++Builderin HotXLS-komponenttia lukemis-, kaava- ja muotoilu-API:en rinnalla, joita käsitellään muualla tässä blogissa.