HotXLS Delphi Excel Component säilyttää OpenDocument-data-pilottitaulukot ODS:n avaus- ja tallennuskierroksen läpi ottamalla tiedoston content.xml <table:data-pilot-tables>-alipuun sanatarkasti talteen avaushetkellä ja toistamalla sen tallennuksessa, versiosta 2.382.0 lähtien. Versiosta 2.382.1 lähtien fragmentti kantaa myös jokaisen esi-isiensä julistaman XML-nimiavaruussidoksen, joten tallennettu pivot-määritelmä pysyy hyvin muodostettuna kaikille kuluttajille, ei vain HotXLS:lle
Molemmat muutokset pakottanut bugi tuli esiin tiukassa korpusajossa. Näyte official-pivot.ods, jonka on kirjoittanut LibreOffice 6.1:n kehitysbuildi, sisältää yhden pivotin nimeltä DataPilot1, joka lukee alueen Sheet1.A2:E30 ja sijoittaa tuloksensa alueelle Sheet1.G6:J18. Avaa se HotXLS:llä, tallenna muuttamattomana, laske <table:data-pilot-table>-elementit tulosteesta: yksi sisään, nolla ulos, niin Win32- kuin Win64-alustalla. Mikään testissä ei koskenut pivotiin. Ensimmäinen luotauskierros oli verrannut vain soluvakioita ja meni läpi; rakennevarmistus oli se, mikä paljasti katoamisen, mikä muistuttaa, että arvot täsmäävät on heikko määritelmä edestakaiselle uskollisuudelle
Miksi ODS-pivot-taulukko katoaa kirjaston tallennuksessa?
ODS-pivot-taulukko katoaa, koska HotXLS:llä ei ole muistinvaraista mallia OpenDocument-data-pilottitaulukoille ja ODS-kirjoittaja rakentaa content.xml:n kokonaan mallista. Kirjoittaja kokoaa automaattiset tyylit, yhden <table:table>-elementin per laskentataulukko, <table:content-validations>-, <table:named-expressions>- ja <table:database-ranges>-osat, kukin generoituna objekteista, joita työkirja todella sisältää. Pivot-määritelmällä — ODF 1.3 Part 3 §9.6, <table:data-pilot-tables>-säiliö, jossa on yksi <table:data-pilot-table> per pivot, kantaan table:source-cell-range-arvonsa, table:data-pilot-field-lapsensa, table:target-range-address-arvonsa ja table:buttons-rakenteensa — ei ole objektia, jossa elää, joten uudelleen generoitu osa yksinkertaisesti jättää sen pois
Kontrasti XLSX:ään on tarkoituksellinen. HotXLS jäsentää SpreadsheetML-pivot-välimuistit ja pivot-taulukot oikeaan malliin, jota voit rakentaa, laajentaa lasketuilla kentillä ja päivittää Delphistä, joten ne säilyvät tallennuksessa koska ne kirjoitetaan uudelleen eikä kopioida. ODS-pivotit ovat paljon harvinaisempi pyyntö, ja ODF-data-pilot-sanaston mallintaminen pelkän edestakaisen kierroksen takia olisi paljon koodia, jota kukaan ei muokkaa. Pragmaattinen vastaus on sama, jota HotXLS jo soveltaa tuntemattomiin extLst-lohkoihin XLSX:ssä: pidä se mitä et mallinna, tavu tavulta jos voit, tapahtuma tapahtumalta jos et voi
Mikä ensimmäisessä Pos-pohjaisessa talteenotossa meni pieleen?
v2.382.0:n talteenotto leikkasi pivot-määritelmän tiedostosta content.xml pelkkänä merkkijonona, ja leikkeestä puuttuivat nimiavaruusjulistukset, jotka tekivät siitä merkityksellisen. Toteutus oli niin lyhyt kuin miltä kuulostaakin — pura osa WideString-arvoksi, etsi avaustagi Pos-funktiolla, etsi sulkeva tagi sen jälkeen, kopioi väli työkirjan FRawOdsDataPilotTablesXml-kenttään:
// HotXLS v2.382.0 -- korvattu yhden julkaisun myöhemmin
function OdsCaptureDataPilotTablesXml(Stream: TStream): WideString;
const
OpenTag: WideString = '<table:data-pilot-tables';
CloseTag: WideString = '</table:data-pilot-tables>';
var
Text: WideString;
StartPos, ClosePos: Integer;
begin
Result := '';
Text := LoadPartAsWideString(Stream); // koko content.xml muistissa
StartPos := Pos(OpenTag, Text);
if StartPos = 0 then Exit;
ClosePos := Pos(CloseTag, Copy(Text, StartPos, MaxInt));
if ClosePos = 0 then Exit;
Result := Copy(Text, StartPos, ClosePos + Length(CloseTag) - 1);
end;
Laskentaväittämä meni vihreäksi, ja korjaus toimitettiin. Sen nappasi kiinni samana päivänä lisätty toinen, tiukempi tarkistus: jokainen tallennetun paketin XML-osa syötetään HotXLS:n ulkopuoliselle, nimiavaruustietoiselle jäsentimelle, ja tuo jäsennin hylkäsi uuden content.xml:n sitomattoman etuliitteen virheellä. LibreOfficen pivot kantaa tuottajakohtaisia laajennusattribuutteja — loext:ignore-selected-page="true" sivukentässä, calcext:repeat-item-labels="false" joka tasolla — ja leikattu merkkijono sisälsi nuo attribuutit mutta ei xmlns:loext- ja xmlns:calcext-julistuksia, jotka sitoivat ne. Nuo julistukset istuivat lähdetiedoston <office:document-content>-juuressa, niitä oli kolmekymmentäviisi, kahdentuhannen merkin päässä pivotista
W3C Namespaces in XML 1.0 §6.1 määrittelee säännön, joka tekee tästä kovan virheen eikä kosmeettisen: nimiavaruusjulistus on voimassa sen elementin aloitustagista, jossa se esiintyy, kyseisen elementin lopputagiin asti, ja jokainen etuliitteellinen nimi tuon näkyvyysalueen sisällä ratkeaa sitä vasten. Leikkaa alipuu irti asiakirjasta, niin leikkaat sen irti näkyvyysalueesta. HotXLS kirjoittaa oman <office:document-content>-juurensa yhdellätoista julistuksella — office, table, text, style, number, fo, draw, svg, xlink, calcext, tableooo — joten calcext: sattui ratkeamaan, table: sattui ratkeamaan, eikä loext: ratkennut. Nimiavaruustietoinen jäsennin kohtelee sitomatonta etuliitettä hyvinmuodostusrikkeenä, mikä tarkoittaa, että koko osa on lukukelvoton eikä vain yksi attribuutti
Miten HotXLS siirtää esi-isien xmlns-sidokset fragmenttiin?
HotXLS v2.382.1 korvasi merkkijonoleikkeen kierroksella tiedoston content.xml yli oman virtaavan TXMLReader-lukijansa kautta, pitäen pinossa nimiavaruussidoksia, jotka on merkitty sen syvyyden mukaan, jolla kukin julistettiin, ja kopioiden yhä voimassa olevat sidokset fragmentin juurielementtiin heti kun kohde saavutetaan. Lukija ajetaan PreserveWhitespaceText päällä, jotta tekstisolmut palaavat täsmälleen kirjoitetussa muodossaan, ja uudelleen rakennetut tagit käyttävät TXMLReader.RawName- ja TXMLReader.Attribute[I].RawName -arvoja — tiedostossa olevaa etuliitteen kirjoitusasua — eivät niitä kanonisia nimiä, jotka lukija normaalisti ojentaa osajäsentimille. Tässä silmukan ydin:
// Namespaces: TStringList muodossa 'xmlns:p=uri', julistussyvyys Objects[]-listassa
while Reader.Read do
begin
if CaptureDepth >= 0 then
XlsxAppendRawXmlReaderNode(Result, Reader); // elementti, teksti, CDATA, kommentti
if Reader.NodeType = xmlntElement then
begin
for I := 0 to Reader.AttributeCount - 1 do
begin
AttrName := Reader.Attribute[I].RawName;
if (AttrName = 'xmlns') or (Pos(WideString('xmlns:'), AttrName) = 1) then
Namespaces.AddObject(String(AttrName) + '=' + String(Reader.Attribute[I].Value),
TObject(NativeInt(Depth)));
end;
if (CaptureDepth < 0) and (Reader.Name = 'table:data-pilot-tables') then
begin
Opening := XlsxRawXmlReaderOpenTag(Reader); // riisu ensin lopusta '>' tai '/>'
...
// Siirrä voimassa olevat esi-isien sidokset fragmentin juureen.
for I := Namespaces.Count - 1 downto 0 do
begin
AttrName := WideString(Namespaces.Names[I]);
if Seen.IndexOf(String(AttrName)) >= 0 then Continue; // sisin sidos voittaa
Seen.Add(String(AttrName));
if not Reader.HasAttribute(AttrName) then // onko jo julistettu tässä? ohita
Opening := Opening + ' ' + AttrName + '="' +
XlsxEscapeAttr(WideString(Namespaces.ValueFromIndex[I])) + '"';
end;
...
CaptureDepth := Depth;
end;
if not Reader.IsEmptyElement then Inc(Depth);
end
else if Reader.NodeType = xmlntEndElement then
begin
Dec(Depth);
if Depth = CaptureDepth then Exit; // alipuu sulkeutui
end;
if (Reader.NodeType = xmlntEndElement) or
((Reader.NodeType = xmlntElement) and Reader.IsEmptyElement) then
while (Namespaces.Count > 0) and
(NativeInt(Namespaces.Objects[Namespaces.Count - 1]) >= Depth) do
Namespaces.Delete(Namespaces.Count - 1); // poistu näkyvyysalueelta
end;
if CaptureDepth >= 0 then
raise Exception.Create('OpenDocument pivot definition ended inside an element');
Kolme yksityiskohtaa tuossa silmukassa kantavat oikeellisuuden. Pinon läpikäynti sisimmästä sidoksesta ulospäin ja kunkin etuliitteen muistaminen Seen-joukossa toteuttaa varjostuksen: jos lähempi esi-isä sitoo xmlns:table-etuliitteen uudelleen, lähempi arvo voittaa, täsmälleen kuten §6.1 vaatii. Sellaisten etuliitteiden ohittaminen, jotka elementti jo julistaa itse, välttää saman attribuutin tuottamisen kahteen kertaan, mikä olisi eri hyvinmuodostusvirhe. Ja poistosääntö laukeaa lopputageilla ja tyhjillä elementeillä, koska <x/> ei koskaan tuota EndElement-tapahtumaa — sama itsensä sulkeva ansa, joka XLSX:n extLst-talteenoton piti oppia. Kohteen täsmäyttäminen Reader.Name-arvon eikä RawName-arvon perusteella on hiljaisempi voitto: lukija kanonisoi ODF:n taulukkonimiavaruuden URI:n table-etuliitteeksi, joten tuottaja, joka kirjoittaa sen muodossa t:data-pilot-tables, täsmää silti, kun taas tuotettu fragmentti säilyttää sen etuliitteen, jota tuottaja käytti
Silmukka myös kieltäytyy arvaamasta. Jos osa päättyy kesken talteenoton — katkaistu tai virheellinen content.xml — OdsCaptureDataPilotTablesXml nostaa poikkeuksen sen sijaan, että palauttaisi puolikkaan fragmentin, koska puolikas fragmentti kirjoitettaisiin takaisin tallennuksessa ja muuttaisi vahingoittuneen syötteen vahingoittuneeksi tulosteeksi, jossa on kirjaston nimi
Minne fragmentti päätyy tallennetussa content.xml:ssä?
HotXLS kirjoittaa talteen otetun fragmentin <office:spreadsheet>-elementtiin välittömästi sen generoiman <table:named-expressions>-osan jälkeen ja ennen <table:database-ranges>-osaa. ODF 1.3 Part 3:n <office:spreadsheet>-sisältömalli määrää kiinteän järjestyksen noille loppulapsille, joten sanatarkkaa lohkoa ei voi yksinkertaisesti liittää sinne, missä kirjoittaja sattuu olemaan; se on pudotettava tiettyyn paikkaan. Kutsujan puolelta ei ole API:a eikä mitään konfiguroitavaa; määritelmä kulkee mukana tavallisessa avaus- ja tallennuskierroksessa:
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
if Book.OpenODS('official-pivot.ods') <> 1 then
raise Exception.Create('open failed');
Book.Sheets[0].Cells[2, 5].Value := 1250.0; // muokkaus pivotin lähdealueen sisällä
Book.SaveAsODS('official-pivot-out.ods');
// tulosteen content.xml kantaa yhä DataPilot1:n sen
// lähdealueen, kenttien, kohdealueen, painikkeiden ja loext:/calcext:-attribuuttien kanssa
finally
Book.Free;
end;
end;
Kahdennus on tarkoituksellinen ja sen tunteminen kannattaa. Fragmentin juuri toistaa nyt xmlns:table- ja xmlns:calcext-julistukset, vaikka tallennettu asiakirjan juuri julistaa ne myös; Namespaces in XML sallii etuliitteen uudelleenjulistamisen sisäkkäisessä näkyvyysalueessa, joten kaksoiskappaleet ovat vaarattomia. LibreOfficen näytteessä mukaan kulkeva joukko on kaikki kolmekymmentäviisi juurijulistusta, noin kaksi kilotavua 8 357 merkin määritelmän päälle, koska talteenotto ei analysoi, mitä etuliitteitä alipuu todella käyttää. Käytettyjen etuliitteiden skannaus karsisi tuon, ja se voi tulla myöhemmin; oikeellisuus ensin, kompaktius toiseksi
Sääntö alipuiden leikkaamiseen XML:stä sanatarkkaa toistoa varten
Yleinen oppi on, että alipuu on itsenäinen vasta kun olet tehnyt siitä sellaisen, ja nimiavaruuksien näkyvyysalue on ensimmäinen asia, joka rikkoutuu kun sen unohtaa. Tarkistuslista, jota HotXLS nykyään soveltaa kaikkeen sellaiseen talteenottoon, jonka tarkoitus on pitää se mitä emme mallinna:
- Käy asiakirja läpi oikealla lukijalla ja seuraa näkyvyysalueella olevia sidoksia. Merkkijonohaku
Pos-funktiolla ei näe näkyvyysaluetta lainkaan, ja se täsmää väärin myös sisäkkäisissä samannimisissä elementeissä, kommentin taiCDATA-osion sisällä olevassa täsmäävässä merkkijonossa ja attribuuttiarvoissa, jotka sattuvat sisältämään tagin tekstin - Kopioi voimassa olevat sidokset fragmentin juureen, sisimmästä alkaen, kerran per etuliite, ohittaen sen minkä juuri jo julistaa
- Säilytä raaka etuliitteen kirjoitusasu tuotetuissa tageissa; täsmäytä kohde ratkaistun nimiavaruuden eikä kirjaimellisen etuliitteen perusteella
- Säilytä tyhjät tekstisolmut ja muista, että tyhjä elementti sulkee oman näkyvyysalueensa ilman lopputagin tapahtumaa
- Validoi tallennettu osa jäsentimellä, joka ei ole testattava kirjasto itse. Kirjasto lukee mielellään oman tulosteensa uudelleen saman sallivan koodipolun kautta, joka sen kirjoitti
Viimeinen kohta on se, joka todella löysi HXLS-003:n toisella kerralla. v2.382.0:n hyväksyntätarkistus oli säännöllinen lauseke, joka laski data-pilot-table-aloitustagit tallennetusta content.xml:stä, ja säännöllinen lauseke näkee tagin, ei asiakirjaa — se on sokea sille, ovatko tuon tagin etuliitteet sidottuja. v2.382.1:ssä lisätty tiukka korpusajuri jäsentää jokaisen tallennetun paketin XML- ja .rels-osan nimiavaruustietoisella jäsentimellä ja vertaa sitten pivot-puuta — tagi, lajitellut attribuutit, teksti, lapset, rekursiivisesti — alkuperäiseen. Tuo vertailu on nimiavaruuksien suhteen laajennettu, joten etuliitteen uudelleenkirjoitus menisi silti läpi eikä sitomaton etuliite voi mennä
Mihin sanatarkan toiston tae päättyy
Sanatarkka toisto säilyttää määritelmän; se ei ymmärrä sitä, ja rajat seuraavat siitä. HotXLS ei tarjoa API:a ODS-pivotin lukemiseen, muokkaamiseen tai päivittämiseen, joten FRawOdsDataPilotTablesXml on sisäinen kenttä ja ainoa havaittava käytös on, että määritelmä säilyy. Fragmentti serialisoidaan uudelleen lukijan tapahtumista eikä kopioida tavuina: attribuuttien lainausmerkit ja itsensä sulkevat muodot normalisoituvat, kun taas teksti ja tyhjät merkit säilyvät. Talteen otettu XML tuotetaan vain ODS-sisältökirjoittajassa, joten tiedostosta .ods avattu työkirja menettää pivotin tallennettaessa muotoon .xlsx, eikä tiedostosta .xlsx avatulla työkirjalla ole mitään toistettavaa .ods-tallennukseen — ODS-tuonnin ja -viennin polkujen epäsymmetriat pätevät tässä kuten kaikkialla. Ja koska määritelmä on läpinäkymätön, se ei voi seurata muokkauksiasi: nimeä Sheet1 uudelleen tai siirrä lähdedataa HotXLS:ssä, ja tallennettu pivot osoittaa yhä alueeseen Sheet1.A2:E30, jättäen kuluttajan raportoimaan rikkoutuneesta alueesta seuraavalla päivityksellään. Tähän kuuluu myös yksi järjestystä koskeva varoitus: HotXLS tuottaa AutoFilter-alueet <table:database-ranges>-muodossa pivot-fragmentin jälkeen, eikä korpusnäytteessä ole yhtään tietokanta-aluetta, joten työkirja, jossa on sekä suodatin että pivot, kannattaa ajaa ODF-skeemavalidaattorin läpi ennen kuin luotat noiden kahden elementin keskinäiseen järjestykseen
Testaa oman tuottajasi tiedostoilla, älä vain korpusnäytteellä. Nimiavaruuksien siirto käsittelee minkä tahansa etuliitteen, jonka tuottaja julistaa esi-isässä, mutta asiakirja, joka julistaa etuliitteen itse pivot-elementissä tai käyttää taulukkosanastolle oletusnimiavaruutta, harjoittaa ohitus- ja varjostushaaroja, joita LibreOfficen näyte ei harjoita. Molemmat on toteutettu; kummallekaan ei ole vielä näytettä korpuksessa, ja tuo ero on juuri sellaista, jonka changelog-merkintä tapaa hämärtää
Sanatarkka data-pilot-talteenotto versiossa 2.382.0 ja nimiavaruusnäkyvyysalueen korjaus versiossa 2.382.1 toimitetaan nykyisessä HotXLS Delphi Excel Componentissa, jonka tuotesivu luettelee täyden ODS-, XLSX- ja XLS-luku-kirjoituskattavuuden Delphille ja C++Builderille