Kopioi alue Delphi-ruudukosta ja liitä se Wordiin, ja muotoilu yleensä katoaa: pelkkää tekstiä, ei lihavoituja otsikoita, ei reunuksia, ei täyttöjä. HotXLS sulkee tämän aukon TXLSRange.CopyToClipboard-metodilla, joka asettaa CF_HTML-leikepöytäkuorman — Windowsin muodon tyylitellylle HTML:lle tavutarkoilla fragmenttimerkeillä — leikepöydälle tavallisen Unicode-tekstin viereen
Tämä kuulostaa yksinkertaiselta, kunnes katsot, mitä CF_HTML-kuorma todella vaatii. Muoto tarvitsee lyhyen tekstiotsikon, joka nimeää täsmälleen, mistä fragmentti alkaa ja mihin se päättyy suuremman leikepöytäpuskurin sisällä, ja nuo sijainnit ovat tavusiirtymiä, laskettuina läpi minkä tahansa monitavuisen koodauksen, johon HTML lopulta päätyy. Laske aritmetiikka väärin yhdenkin tavun verran, ja kohdesovellus joko nappaa väärän viipaleen merkintäkieltä tai antaa periksi ja palautuu tavalliseen tekstiin, eikä kumpikaan epäonnistuminen näytä bugilta koodissasi — se näyttää Wordilta, joka on Word
Miksi kopiointi-liittäminen Delphi-ruudukosta yleensä menettää muotoilunsa
Oletusarvoinen Windows-leikepöytäkutsu, johon useimmat Delphi-koodit turvautuvat, SetClipboardData CF_TEXT- tai CF_UNICODETEXT-muodolla, kantaa vain koskaan tavallisia merkkejä, joten mikä tahansa lähderuudukkoon sovellettu tyyli ei pääse minnekään. Word, Outlook ja jokainen Chromium-pohjainen selain etsivät rikkaampaa muotoa, kun liität: HTML-esityksen valinnasta, täydellisenä inline-tyyleillä, taulukkorakenteella ja linkeillä. Excel itse luottaa juuri tähän temppuun — kopioi alue Excelissä, ja leikepöytä vastaanottaa hiljaa useita muotoja kerralla, HTML mukaan lukien, joten mihin sovellukseen tahansa liitätkin, se poimii rikkaimman, jota se ymmärtää. Komponentti, joka vain koskaan kirjoittaa CF_UNICODETEXT-muotoa, antaa jokaiselle noista rikkaammista kuluttajista ei mitään työstettävää, ja visuaalinen rikkaus, jonka käyttäjä juuri kopioi, ei yksinkertaisesti ole siellä liitettäväksi
Mikä täsmälleen on CF_HTML-leikepöytämuoto?
CF_HTML ei ole kiinteä järjestelmän leikepöytämuoto kuten CF_TEXT; se on dynaamisesti rekisteröity, pyydetty nimellä funktiolla RegisterClipboardFormat('HTML Format'), ja sen kuorma on lyhyt ASCII-otsikko, jota seuraa HTML-asiakirja tai -fragmentti. Otsikko kantaa viisi kenttää — Version, StartHTML, EndHTML, StartFragment, EndFragment — joissa Version on aina 0.9 ja muut neljä ovat desimaalilukuja kirjoitettuina ASCII-numeroina. StartHTML ja EndHTML rajaavat koko asiakirjan, kuten vastaanottavan sovelluksen pitäisi jäsentää se kontekstia varten, fontit ja tyylit mukaan lukien, kun taas StartFragment ja EndFragment rajaavat kapeamman viipaleen, joka todella päätyy kohdistimeen, tavanomaisesti merkittynä itse merkintäkielessä <!--StartFragment-->- ja <!--EndFragment-->-kommenteilla, jotta rajat säilyvät naiivin uudelleensarjallistuksen läpi
Tavusiirtymät, ei merkkimäärät: klassinen CF_HTML-ansa
CF_HTML:n neljä numeerista otsikkokenttää ovat tavusiirtymiä leikepöydällä olevaan tarkkaan tavusekvenssiin, laskettuina otsikon itsensä aivan ensimmäisestä merkistä — ei merkkimääriä, ei Unicode-koodipisteitä, eikä siirtymiä suhteessa fragmenttiin tai <body>-tagiin. Tuo ero on se, missä käsin rakennetut CF_HTML-toteutukset menevät hiljaa pieleen: Delphin UnicodeString:n Length raportoi UTF-16-koodiyksiköitä, mikä sattuu olemaan sama kuin tavumäärä tavalliselle ASCII-tekstille, joten bugi kulkee puhtaasti läpi minkä tahansa englanninkielisellä esimerkkidatalla kirjoitetun testin ja paljastuu vasta, kun kopioitu solu sisältää ajatusviivan, valuuttamerkin tai aksentillisen merkin — euromerkki on yksi UTF-16-koodiyksikkö mutta kolme tavua UTF-8:ssa, ja jokainen tuon pisteen jälkeen laskettu siirtymä ajautuu sen verran kuin koodaus lisäsi ylimääräisiä tavuja. Sitä seuraava epäonnistuminen ei ole kaatuminen; se on vastaanottava sovellus, joka nappaa täsmälleen sen tavualueen, johon otsikko osoitti, löytää viipaleen merkintäkieltä, joka alkaa tai päättyy kesken tagin, ja joko renderöi roskaa tai antaa periksi ja palautuu mihin tahansa tavalliseen tekstiin, joka istuu sen vieressä leikepöydällä, hiljaa, ilman mitään koodissasi selittämässä miksi — tässä on muoto koodille, joka tuottaa juuri tuon epäonnistumisen:
// Onneton: Length() UnicodeString-arvossa laskee UTF-16-koodiyksiköitä, ei tavuja
var
Header: string;
Fragment: string;
StartFragmentOfs: Integer;
begin
Header := 'Version:0.9'#13#10 + 'StartHTML:0000000000'#13#10 + '...';
StartFragmentOfs := Length(Header) + Pos('<!--StartFragment-->', Fragment);
// Valuuttasymboli, ajatusviiva tai mikä tahansa aksenttimerkki, joka on asetettu
// ennen tätä kohtaa maksaa yhden merkin täällä mutta kaksi tai kolme tavua
// kun dokumentti on UTF-8-koodattu, joten StartFragmentOfs osoittaa nyt
// liian aikaisin verrattuna siihen, mistä fragmentti todella alkaa leikepöydällä
end;
Miten HotXLS pitää otsikon tavutarkkana
HotXLS välttää tämän bugiluokan rakenteellisesti: TXLSRange.CopyToClipboard ja sen alla oleva lxClipboard-yksikkö rakentavat CF_HTML-asiakirjan ja sen otsikon kokonaan AnsiString-muodossa, Delphin tavu-merkkijonotyyppinä, joten Length ja Pos palauttavat jo tavusijainteja kaikkialla laskennassa — erillistä vaihetta ei ole, eikä siis vaihetta unohdettavaksi, jossa Unicode-merkkimäärä täytyisi muuntaa tavumääräksi ennen kuin se menee otsikkoon
On toinenkin, pienempi temppu, joka kannattaa tuntea, jos koskaan rakennat CF_HTML-otsikon käsin. Otsikko kirjoitetaan kahdesti: kerran kymmenellä nollanumerolla kunkin neljän siirtymän paikalla, jotta sen oma tavupituus voidaan mitata, ja vielä kerran todelliset siirtymät paikattuina sisään. Koska jokainen todellinen siirtymä muotoillaan samaan kiinteään kymmenen numeron leveyteen, toinen otsikko tulee ulos tavulleen samanpituisena kuin paikanvaraajaversio, mikä on juuri se, mikä pitää aiemman mittauksen pätevänä uudelleenkirjoituksen jälkeen. Ohita kiinteä leveys, muotoile luku pelkällä IntToStr-funktiolla sen sijaan, ja otsikko voi kutistua tai kasvaa numeron verran kahden kierroksen välillä, mitätöiden hiljaa jokaisen sen jälkeisen siirtymän:
const
Placeholder = '0000000000'; // 10 ASCII-numeroa: kiinteä leveys sisään, kiinteä leveys ulos
var
Header: AnsiString; // AnsiString.Length on tavumäärä, ei merkkimäärä
StartHtmlOfs: Integer;
begin
Header := 'Version:0.9'#13#10 +
'StartHTML:' + Placeholder + #13#10 +
'EndHTML:' + Placeholder + #13#10 +
'StartFragment:' + Placeholder + #13#10 +
'EndFragment:' + Placeholder + #13#10;
StartHtmlOfs := Length(Header); // turvallista mitata kerran etukäteen
// ...laske todelliset siirtymät AnsiString-dokumenttia vasten...
// rakenna sitten Header uudelleen oikeilla luvuilla samaan muotoon
// 10-numeroisella leveydellä, joten sen tavupituus -- ja siten StartHtmlOfs --
// ei koskaan siirry paikkamerkin ja lopullisen vaiheen välillä
end;
Miksi pelkkä tekstikuorma silti kulkee mukana
TXLSRange.CopyToClipboard ei koskaan aseta CF_HTML:ää leikepöydälle yksinään; se kirjoittaa aina CF_UNICODETEXT-muodon samassa kutsussa, koska CF_HTML on rekisteröity muoto eikä yksi niistä kiinteistä CF_*-vakioista, joita jokainen Windows-sovellus jo tietää etsiä — tavallinen tekstieditori, vanha ruudukko tai mikä tahansa, joka ei koskaan tarkistanut 'HTML Format'-muotoa, ei näe sitä lainkaan, ja kopioimasi alue joko saapuu sarkainerotettuna tekstinä tai ei saavu ollenkaan. Tuo sarkainerotettu teksti ei myöskään ole karkea approksimaatio: kaavasolut kopioituvat kaavamerkkijononaan johtavan =-merkin palautettuna, jos tallennettu teksti pudotti sen, vastaten sitä, miten Excelin oma leikepöytäteksti käyttäytyy, tavalliset solut kopioivat FormattedText-arvonsa — merkkijonon sellaisena kuin se näytetään, joten valuuttasolu kopioituu muodossa $1,234.56, ei taustalla olevana 1234.56 — ja mikä tahansa kenttä, joka sisältää sarkaimen, lainausmerkin tai rivinvaihdon, lainataan sisäänrakennetuilla lainausmerkeillä kahdennettuna, samaa käytäntöä, jota CSV käyttää
SaveAsHTML ei ole erillinen renderöintipolku, joka on pultattu vain leikepöytätapausta varten. CopyToClipboard kutsuu täsmälleen samaa HTML-kirjoitinta, joka kuvataan artikkelissa HotXLS:n CSV-, TSV- ja HTML-vienti, ja kääri sitten mitä tahansa tuo kirjoitin tuottaa CF_HTML-kirjekuoreen sen tallentamisen sijaan itsenäisenä tiedostona, joten kaikki, mikä pätee tuohon HTML:ään, kulkee suoraan siihen, mikä päätyy leikepöydälle. Työarkin alueen vetäminen yhteen molempina muotoina yhdellä kutsulla näyttää tältä:
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
// Klassisen TXLSWorkbookin alueet paljastavat saman metodin kuin
// Workbook.Sheets[1].Range['A1', 'F40'].CopyToClipboard
if Book.Sheets[1].Range['A1:F40'].CopyToClipboard then
ShowMessage('Range copied - press Ctrl+V in Word or a browser')
else
ShowMessage('Clipboard was busy; see the retry pattern below');
finally
Book.Free;
end;
end;
Säilyttääkö liitetty alue fonttinsa, värinsä ja yhdistetyt solunsa?
Kyllä, koska kuorman HTML-puoli on täysi renderöinti alueesta, ei paljas datadumppi: fontit, täyttövärit, reunukset, lukumuodot ja yhdistetyt solut kaikki kulkevat läpi inline-tyyleinä ja taulukkorakenteena, samana tyylikoneistona, joka käsitellään artikkelissa HotXLS:n opas ehdolliseen muotoiluun ja rich textiin, koska solun rich text -jaksot ja ehdollisen muotoilun tulos syöttävät molemmat samaa renderöintiä, josta CopyToClipboard lukee. Se, mikä ei säily matkassa, on elävä kaavakäytös: kaavasolun tavallinen tekstimuoto kantaa kaavamerkkijonon, joten laskentataulukkotietoinen liitoskohde voisi periaatteessa laskea sen uudelleen, mutta HTML-muoto kantaa vain viimeisimmän lasketun tuloksen, koska HTML:llä ei ole käsitettä kaavalle, jota selain tai tekstinkäsittelyohjelma arvioisi
Liittämisen vahvistaminen ja varatun leikepöydän käsittely
Kaksi tapaa nappaa useimmat leikepöytäongelmat ennen kuin asiakas tekee sen. Liitä ensin Notepadiin vahvistaaksesi, että CF_UNICODETEXT-varakeino on järkevää sarkainerotettua tekstiä, liitä sitten sama kopio Wordiin tai selaimeen vahvistaaksesi, että tyylitelty versio ilmestyy — kuorma, joka näyttää oikealta yhdessä ja väärältä toisessa, tarkoittaa yleensä, että fragmenttimerkit päätyivät väärään paikkaan. Kohtele sitten CopyToClipboard-metodin palauttamaa totuusarvoa merkityksellisenä, ei koristeellisena: OpenClipboard voi epäonnistua, kun toinen prosessi pitää leikepöytää auki, riittävän yleistä kiireisellä työpöydällä, että yksi tarkistamaton kutsu lopulta liittää tyhjää ilman virhettä selittämässä miksi, mitä alla oleva uudelleenyritys suojaa vastaan:
function TryCopyRangeToClipboard(Workbook: TXLSXWorkbook): Boolean;
var
Attempt: Integer;
begin
Result := False;
for Attempt := 1 to 5 do
begin
Result := Workbook.Sheets[1].Range['A1:F40'].CopyToClipboard;
if Result then
Break;
Sleep(50); // anna leikepöytää pitävälle sovellukselle hetki
end;
if not Result then
raise Exception.Create('Could not take ownership of the clipboard');
end;
Muoto itse ei ole eksoottinen heti, kun otsikko on tavutarkka ja pelkän tekstin varakeino on rehellinen siitä, mitä se sisältää — se on ollut olemassa suurin piirtein muuttumattomana siitä lähtien, kun Internet Explorer ensimmäisenä määritteli sen, ja jokainen suuri Windows-sovellus lukee sen yhä samalla tavalla. CopyToClipboard istuu PasteFromClipboard-metodin, saman vaihdon lukupuolen, vieressä laajemmassa leikepöytä- ja vientipinnassa, joka on dokumentoitu HotXLS-Delphi-komponentin tuotesivulla