Le sous-flux PivotCache BIFF stocke l'ensemble de données en cache d'un tableau croisé dynamique séparément de la vue qui l'affiche, et HotXLS lit et écrit ce sous-flux en inspectant les corps d'enregistrements plutôt qu'en se fiant aux numéros d'enregistrements. Cette distinction est toute l'histoire : le même numéro d'enregistrement porte deux dispositions de corps incompatibles selon le rédacteur qui a produit le fichier, donc le lecteur décide du cadrage d'après le premier corps d'enregistrement qu'il voit
On rencontre cette couche au moment où un tableau croisé dynamique doit survivre à un aller-retour. Une vue de tableau croisé sans son cache est une coquille, et Excel reconstruira le cache depuis la plage source à l'ouverture du fichier, ce qui va très bien jusqu'au point où la plage source a disparu, où les données ont été collées depuis une requête, ou où le classeur est une clôture archivée qui ne doit pas changer quand quelqu'un l'ouvre
Deux structures, deux endroits dans le fichier
Les données en cache et la définition de cache vivent dans des parties différentes du classeur, et les confondre est la première chose à éviter. Les enregistrements en cache forment leur propre sous-flux, donné dans [MS-XLS] §2.1.7.12 comme PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Notez ce qui est absent : il n'y a pas de BOF à la tête de cette production
La définition siège plutôt dans les globales du classeur, comme PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), placée après les enregistrements de formatage et avant les enregistrements BoundSheet et Country. Ainsi, un seul cache est décrit à deux endroits distants de centaines d'enregistrements, et le lien entre eux est un identifiant de flux qui doit concorder en trois endroits à la fois
Chaque cache appartient dans un flux sous _SX_DB_CUR dont le nom est l'épellation hexadécimale majuscule à quatre chiffres de son identifiant. SXStreamID.idStm, le champ idstm répété dans l'en-tête SXDB, et ce nom de flux doivent tous concorder. Quand vous allouez un nouvel identifiant, réservez d'abord chaque numéro déjà lu dans le fichier, sinon un nouveau cache peut réclamer un numéro qui appartient à un cache plus ancien vers lequel le lecteur n'a pas encore marché
Un autre identifiant attrape les gens. La valeur iCache d'une vue de tableau croisé est la position à base zéro du SXStreamID correspondant dans la séquence globale, pas un identifiant de cache que vous choisissez. À l'écriture, elle doit être projetée depuis l'objet cache vers sa position de sortie réelle, et les vues existantes doivent être renumérotées avec elle, sinon la mise à niveau d'un cache pointe silencieusement une vue vers un autre
var
Book: TXLSWorkbook;
Cache: TXLSPivotCache;
Field: TXLSPivotCacheField;
V: TXLSPivotCacheValue;
begin
Book := TXLSWorkbook.Create(nil);
try
Book.LoadFromFile('sales.xls');
Cache := Book.PivotCaches.Add;
Cache.SourceRangeSheet := 'Data';
Cache.SourceFirstRow := 1; Cache.SourceFirstCol := 1;
Cache.SourceLastRow := 500; Cache.SourceLastCol := 6;
Cache.SourceDataType := 1; // SXVS SHEET, MS-XLS 2.4.317
Cache.RefreshOnLoad := False; // faire confiance aux enregistrements en cache
Cache.SaveData := True;
Field := Cache.AddField('Region', xlpcftString);
V.ValueType := xlpcftString;
V.StrValue := 'North';
Field.FindOrAddItem(V);
Cache.SetRecordCount(0); // nettoyer, puis dimensionner la grille d'enregistrements
Cache.SetRecordCount(500);
Book.StorePivotCaches;
finally
Book.Free;
end;
end;
Le double SetRecordCount n'est pas de la superstition. RecordCount est une simple écriture de propriété qui n'alloue pas, et le chemin de croissance interne n'initialise que les lignes nouvellement ajoutées, si bien qu'un cache dont le compte a été posé par le chemin d'en-tête peut se retrouver avec une grille d'index de longueur nulle. Les écritures dans RecordIndices sont alors jetées sans erreur. Poser le compte à zéro puis revenir rétablit la grille, et cela doit se produire après l'ajout de chaque champ, car la largeur de ligne vient du compte de champs
Pourquoi un numéro d'enregistrement ne peut-il pas vous dire la disposition du corps ?
Parce que les numéros d'enregistrements et les dispositions de corps ont changé à des moments différents, si bien que la correspondance entre eux n'est pas une fonction. Un numéro de l'ensemble historique n'apparaît que dans des fichiers de rédacteurs plus anciens, ce qui en fait un signal fiable dans un sens. Un autre numéro est réellement ambigu : il apparaît aussi bien dans des fichiers corrects que dans une plage de versions intermédiaires qui utilisaient le nouveau numéro avec l'ancienne disposition de corps
Le cadrage doit donc être décidé d'après le corps, et une fois par sous-flux de cache plutôt que par enregistrement. HotXLS verrouille le dialecte d'après la longueur du premier enregistrement SXDBB de chaque sous-flux. Dans le cadrage de la spécification, un SXDBB porte exactement un enregistrement de cache, si bien que sa longueur égale une largeur de ligne. Dans le cadrage packé plus ancien, le premier enregistrement porte autant de lignes que possible, donc pour tout cache de plus d'une ligne il fait au moins deux largeurs de ligne. La comparaison est décisive dès que les deux prédictions diffèrent
Quand elles ne diffèrent pas, le lecteur prend la lecture de la spécification, selon le principe que les fichiers écrits par Excel sont plus nombreux que les fichiers écrits par une construction intermédiaire. Ce point aveugle est étroit par construction et, quand il se produit, le fichier lui-même se rejoue toujours octet pour octet. Seuls les indices typés exposés aux appelants sont affectés
La largeur d'index vit dans un autre enregistrement
SXDBB (§2.4.276) porte un index par champ de cache dont le drapeau de valeurs distinctes est posé, dans l'ordre des champs, et la largeur de chaque index se décide ailleurs : l'enregistrement de champ SXFDB correspondant (§2.4.283) déclare un drapeau d'articles courts, et ce drapeau dit si l'index occupe deux octets ou un. Deux enregistrements, un contrat implicite, et une seule phrase dans la spécification pour les relier
Ce couplage est exactement là où un encodage maison se trompe. Un rédacteur HotXLS antérieur packait chaque champ dans le nombre minimal de bits, en remplissant jusqu'à une frontière d'octet entre les lignes, ce qui se défend isolément et contredit directement la largeur que le même rédacteur venait de déclarer dans SXFDB. Un champ de trois valeurs distinctes était décrit large d'un octet dans un enregistrement et occupait deux bits dans l'autre. La correction n'a pas été de réparer l'arithmétique mais d'extraire la décision de largeur dans une fonction que les deux émetteurs appellent, si bien que les deux enregistrements ne peuvent plus diverger. C'est la même classe de défaut que celle décrite dans la dérive de déclaration de longueur des enregistrements BIFF, où une taille déclarée et un corps réel se séparent
La conséquence de ne pas lire du tout ces enregistrements vaut d'être énoncée, car il est facile de la sous-estimer. Quand le lecteur sautait les indices d'enregistrements, chaque cache chargé depuis un fichier rapportait l'index zéro pour chaque champ de chaque ligne, ce qui signifie que chaque ligne pointait la première valeur de chaque champ. Ce n'est pas simplement une introspection réduite : le chemin d'évaluation du tableau croisé et le chemin de remplissage cache-vers-cellule consomment tous deux cette grille. Et un test aller-retour ne peut pas le détecter, parce qu'un cache encore en rejeu brut est réécrit depuis ses octets originaux
// Les drapeaux de provenance vous disent ce que vous tenez et ce qui peut être réécrit
if Cache.FromRawBlobs then
begin
Writeln('stream id : ', IntToHex(Cache.StreamId, 4));
Writeln('legacy framing : ', Cache.RawFramingIsLegacy);
Writeln('own storage : ', Cache.RawHasStorageStream);
Writeln('model complete : ', Cache.RawModelIsComplete);
// La réémission n'est sans perte que quand chaque enregistrement a un modèle ici
if Cache.CanUpgradeFraming then
Writeln('safe to rewrite with the current emitters');
end;
Quand la réécriture d'un cache est-elle sans perte ?
Seulement quand trois conditions se tiennent ensemble, et CanUpgradeFraming est l'unique propriété qui répond à la question. Le cache doit encore être en rejeu brut, le sous-flux doit être dans l'un des cadrages que cette bibliothèque a précédemment écrits incorrectement, et le lecteur doit avoir bâti un modèle typé complet de chaque enregistrement qu'il contient. Un cache écrit par Excel ne se qualifie jamais, car son sous-flux porte des enregistrements pour lesquels HotXLS n'a pas de modèle, et réémettre depuis le modèle les perdrait
Le test de complétude est plus strict qu'il y paraît. Un enregistrement que le lecteur n'a gardé qu'en octets opaques marque le modèle incomplet. Il en va de même d'un compte déclaré d'enregistrements de formules que l'émetteur ne peut pas reproduire, car réémettre réécrirait une déclaration de plusieurs enregistrements de formules en une déclaration d'aucun, et une valeur du fichier qui ne peut pas être reproduite équivaut à un enregistrement qui ne peut pas l'être
Un conservatisme délibéré traverse aussi le rédacteur. Les indices sont bornés dans la plage légale plutôt qu'encodés comme un sentinel hors bande, parce que la spécification définit un index dans la séquence de valeurs distinctes et rien d'autre, et une cellule vide est elle-même une valeur de cette séquence. Un corps d'enregistrement de cache dépassant le plafond d'enregistrement BIFF n'est pas écrit du tout, ce qui exigerait des milliers de champs de cache et reste de toute façon inatteignable dans la limite de colonnes BIFF8 ; le repli est qu'Excel rafraîchit depuis la plage source, ce qui est un comportement défini plutôt qu'un fichier corrompu
Les dates portent la dernière dépendance entre enregistrements. La conversion série-vers-date dépend du système de dates du classeur, et l'émetteur d'enregistrements ne peut pas voir le classeur, si bien que le choix de date de base est passé en paramètre qui prend par défaut le système 1900 et est fourni par le chemin de sauvegarde au niveau du classeur. Sous le système 1900, le numéro de série est la valeur directement ; le système 1904 en diffère de 1462 jours. Le traitement plus large des séries de dates se trouve dans les séries de dates, le système 1904 et les formats de nombre
Si vous travaillez à la couche vue plutôt qu'à la couche cache, les enregistrements qui décrivent le tableau croisé visible sont couverts dans l'ensemble d'enregistrements PivotTable BIFF8, et le comportement côté calcul dans les champs calculés, les éléments calculés et le rafraîchissement. Les trois couches sont livrées avec le composant tableur Delphi HotXLS, ce qui rend possible de charger un classeur historique, d'inspecter ce que son cache contient réellement, et de décider si le réécrire est sûr avant de le faire