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:
// Fragile: Length() on a UnicodeString counts UTF-16 code units, not bytes
var
Header: string;
Fragment: string;
StartFragmentOfs: Integer;
begin
Header := 'Version:0.9'#13#10 + 'StartHTML:0000000000'#13#10 + '...';
StartFragmentOfs := Length(Header) + Pos('<!--StartFragment-->', Fragment);
// A currency symbol, an em dash, or any accented character placed
// before this point costs one character here but two or three bytes
// once the document is UTF-8 encoded, so StartFragmentOfs now points
// short of where the fragment actually begins on the real clipboard
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 digits: fixed width in, fixed width out
var
Header: AnsiString; // AnsiString.Length is a byte count, not a char count
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); // safe to measure once, up front
// ...compute the real offsets against the AnsiString document...
// then rebuild Header with the real numbers formatted to the same
// 10-digit width, so its byte length -- and therefore StartHtmlOfs --
// never moves between the placeholder pass and the final one
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');
// Classic TXLSWorkbook ranges expose the identical method as
// 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); // give whichever app is holding the clipboard a moment
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-komponentin tuotesivulla