Article technique

Lister vite les feuilles en Delphi avec GetSheetNames

Parfois la seule question à laquelle une routine de réception doit répondre est structurelle : ce classeur possède-t-il une feuille nommée « Mapping », ou combien d'onglets porte-t-il. Y répondre en appelant Open est la façon coûteuse de s'y prendre. Une ouverture complète déploie la table des chaînes partagées, décode chaque enregistrement de style et parcourt les cellules de chaque feuille, car elle n'a aucun moyen de savoir que vous ne vouliez que la table des matières. Sur un gros fichier, cela représente des centaines de mégaoctets d'allocations et plusieurs secondes de processeur dépensées pour lire une liste qui occupe quelques kilooctets. HotXLS, la bibliothèque tableur native pour Delphi de losLab, vous donne cette liste à part : GetSheetNames rend les noms des feuilles de calcul, dans l'ordre du classeur, sans matérialiser une seule cellule

Pourquoi le catalogue est peu coûteux à lire

Les deux formats de feuille de calcul placent leur table des matières près du début, et c'est ce qui rend un appel de listage rapide plutôt que malin. Un paquet OOXML garde le catalogue des feuilles dans xl/workbook.xml, une partie qui reste petite que le classeur contienne dix lignes ou dix millions. Un .xls BIFF8 range ses enregistrements BoundSheet au début du flux des globales du classeur, avant toute donnée de cellule. Le travail qu'un appel de listage évite n'est donc pas une erreur d'arrondi face à une ouverture complète. C'est l'essentiel du fichier. Lire le catalogue coûte les mêmes quelques kilooctets quel que soit le nombre de lignes, tandis qu'une ouverture complète croît avec les données, et sur un classeur de plusieurs mégaoctets l'écart atteint plusieurs ordres de grandeur, tant en octets touchés qu'en mémoire allouée

GetSheetNames de HotXLS en Delphi lisant uniquement le catalogue de feuilles d'un fichier XLSX ou XLS, alors qu'une ouverture complète parcourt chaque cellule
Le catalogue siège dans workbook.xml ou dans les enregistrements BoundSheet, si bien que le listage coûte quelques kilooctets alors qu'une ouverture complète croît avec les données

Ce coût constant est la propriété autour de laquelle concevoir. Un portail de réception bâti sur GetSheetNames se comporte pareil sur un fichier de 200 lignes et sur un fichier de 200 Mo, si bien que le fichier le plus lent d'un lot ne dicte plus le rythme auquel on décide si un fichier mérite seulement d'être traité

Un seul appel pour .xls, .xlsx et les formats de modèle

Sur la façade XLS, TXLSWorkbook.GetSheetNames lit plus que du .xls. Elle accepte aussi les .xlsx, .xlsm, .xltx et .xltm fondés sur le zip, en ne tirant que workbook.xml de l'archive. Pour une véritable entrée .xls, elle balaie les enregistrements BoundSheet et s'arrête au premier enregistrement EOF du sous-flux des globales, si bien qu'un gros fichier binaire ne coûte encore que ses premiers kilooctets. La façade XLSX porte une garantie qui compte plus pour du code de service au long cours qu'il n'y paraît d'abord : TXLSXWorkbook.GetSheetNames ne réinitialise ni ne remplit l'instance de classeur, si bien qu'une instance qui tient déjà un document ouvert peut sonder d'autres fichiers sans déranger celui qu'elle a en main. GetODSSheetNames applique la même approche aux paquets OpenDocument, et chacun de ces appels possède une surcharge sur flux, ce qui vous permet d'inspecter un dépôt qui n'atterrit jamais sur disque

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

Le même appel fait une bonne boîte de dialogue d'import bureautique. Listez les feuilles, laissez l'utilisateur en choisir une, et ne payez l'ouverture complète qu'après le choix. Avec un classeur de cinquante feuilles, la différence se voit : un sélecteur qui apparaît aussitôt face à un sélecteur qui se fige pendant que tout le fichier se charge derrière lui

Les fichiers .xlsm à macros activées et les formats de modèle se listent exactement comme un .xlsx ordinaire, puisque le catalogue siège dans le même workbook.xml qu'un vbaProject.bin voyage ou non dans le paquet. Un pipeline de réception peut donc énumérer les feuilles d'un classeur à macros pour l'aiguillage, sans jamais toucher la charge utile des macros ni rien faire qui l'exécuterait, et laisser la décision de politique sur les macros à l'étape qui ouvre réellement le fichier

Lire la valeur de retour sans se tromper soi-même

Les conventions de retour ne sont pas uniformes dans HotXLS. Certains appels renvoient 1 en cas de succès, d'autres renvoient un compte, si bien que pour les fonctions de listage le seul contrôle qui tienne consiste à traiter toute valeur inférieure ou égale à zéro comme un échec, avec la liste de chaînes vidée. Résistez à la tentation de lire une liste vide comme « un classeur sans feuilles ». ECMA-376 comme la spécification BIFF8 exigent au moins une feuille dans un classeur valide, si bien que zéro nom signifie toujours que la lecture a échoué, jamais que le fichier est légitimement vide

Un listage en échec est lui-même un signal à conserver. Un fichier .xlsx qui fait échouer l'appel est l'une de quelques choses précises : tronqué, pas vraiment un paquet OOXML (les exports CSV mal étiquetés venus d'autres systèmes se présentent ici sans arrêt), ou un conteneur chiffré. Les distinguer est le travail du contrôle suivant. Journaliser les premiers octets du fichier rejeté à côté de l'échec transforme généralement un fil de support en un seul message

Détecter les conteneurs chiffrés avant l'aiguillage

Un .xlsx chiffré n'est pas un zip. C'est un fichier composé OLE qui enveloppe des flux EncryptionInfo et EncryptedPackage, si bien que GetSheetNames ne peut pas voir à l'intérieur et renvoie un échec comme pour n'importe quel autre fichier illisible. CanReadEncrypted teste cette forme de conteneur, ce qui permet à la réception d'aiguiller un fichier chiffré à dessein au lieu d'avaler une erreur de lecture générique venue du fond d'un travailleur :

Flux de tri de réception en Delphi avec CanReadEncrypted et GetSheetNames de HotXLS aiguillant les dépôts vers mot de passe requis, illisible ou normal
CanReadEncrypted passe en premier parce qu'un fichier OOXML chiffré est un conteneur OLE dans lequel les appels de listage ne peuvent pas regarder
type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // Un OOXML chiffré est un conteneur OLE, pas un zip : contrôlez-le
    // en premier, car les appels de listage ne peuvent pas voir dedans.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

Le chiffrement est l'endroit où HotXLS est délibérément asymétrique, et l'aiguillage doit le respecter. Le chiffrement .xls hérité (RC4, RC4 CryptoAPI, XOR) est lisible : TXLSWorkbook.Open(FileName, Password) déchiffre avec un mot de passe stocké, et ces fichiers peuvent rester sur le chemin automatisé. Les paquets OOXML chiffrés vont dans l'autre sens. HotXLS sait en écrire un avec SaveAsEncrypted, mais il ne sait pas le relire. OpenEncrypted lève EXlsxEncryptionNotImplemented quand on lui remet un paquet chiffré, ce qui explique pourquoi une conception de réception honnête envoie les .xlsx chiffrés à une personne équipée d'Excel et garde dans le code les .xls porteurs de mot de passe

Pour le travail par lots, ce classificateur gagne sa place en passant sur tout un répertoire entrant avant qu'un travailleur ne commence le vrai traitement, puisque chaque sonde coûte environ une ouverture de fichier et quelques kilooctets de lectures. Le placer en tête change le mode de défaillance qui intéresse vraiment l'exploitation. Au lieu d'un travail de 3 heures du matin qui meurt sur le fichier 412 des 600, vous obtenez 412 fichiers en file et 5 rejetés à la réception avec un motif attaché à chacun. Les mêmes appels de bibliothèque, une bien meilleure histoire d'exploitation

Les questions auxquelles un appel de listage ne peut pas répondre

Les noms et l'ordre sont tout ce que vous obtenez. Les appels de listage ne disent rien de la visibilité, si bien que les feuilles masquées et très masquées arrivent dans la liste comme n'importe quelle autre. Ils ne rapportent aucune dimension de plage utilisée, aucun décompte de cellules et aucune propriété de document. La partie docProps/core.xml est petite elle aussi, mais il n'existe pas aujourd'hui de sonde limitée aux propriétés, si bien que les métadonnées d'auteur et de titre coûtent encore un Open complet. La façon propre de vivre avec cela consiste à laisser les faits bon marché aiguiller chaque fichier et à réserver les faits coûteux aux fichiers qui survivent à l'aiguillage. Pour les fichiers qui poursuivent vers une lecture profonde, un balayage en lecture seule d'un gros .xls tourne nettement plus vite avec _DisableGraphics := True, qui saute l'analyse OfficeArt. Mais n'enregistrez jamais depuis cette instance : la couche de dessin qu'elle a sautée a disparu du modèle, et l'enregistrement la retirerait du fichier

Les fichiers qui passent le tri filent d'ordinaire vers une analyse plus profonde. L'atelier d'audit et de conversion de classeurs traite les compteurs par feuille qu'il vaut la peine de collecter une fois une ouverture complète justifiée, et le guide de performance des grands classeurs explique comment garder cette ouverture complète rapide

HotXLS est une bibliothèque tableur native en Object Pascal pour Delphi et C++Builder ; la surface complète de l'API, y compris les appels d'inspection montrés ici, est documentée sur la page produit du HotXLS Delphi Component