Lire un fichier Excel (.xlsx)

Partagez votre expérience de PureBasic avec les autres utilisateurs.
boby
Messages : 261
Inscription : jeu. 07/juin/2007 22:54

Lire un fichier Excel (.xlsx)

Message par boby »

Bonjour,

Suite à une demande de "moulinette" pour faire des recherches croisés entre plusieurs fichier Excel qui m'a été fait, j'en ai profité pour faire les choses à peut près proprement.
Si ça peut servire à quelqu'un...

Code : Tout sélectionner

EnableExplicit
DeclareModule Excel
  Declare Open(File$) ; Returns the new generated number if the file was opened successfully and zero if there was an error
  Declare Close(File) ; Returns nonzero if the operation was successful or zero if it failed. 
  Declare CountSheets(File) ; Returns amount of sheets in the workbook, zero if can't count.
  Declare.s GetSheetName(File,Sheet=1) ; Returns sheet name of the #sheet, "" if it failed.
  Declare CountRow(File,Sheet=0) ; Returns amount of row in the #sheet, zero if can't count.
  Declare CountCol(File,Sheet=0) ; Returns amount of col in the #sheet, zero if can't count.
  Declare LoadSheet(File,Sheet) ; Returns nonzero if the operation was successful or zero if it failed. 
  Declare.s ReadCell(File,Row,Col,Sheet=0) ; Returns cell value, " " if empty, "" if it failed.
EndDeclareModule

Module Excel
  UseZipPacker()
  Structure file
    File.i
    SharedString.i
    Sheet.i
    WorkBook.i
  EndStructure
  
  Procedure Open(File$)
    *pack.file = AllocateMemory(SizeOf(file))
    If Not GetExtensionPart(File$) = "xlsx" : ProcedureReturn #False : EndIf
    *Pack\File = OpenPack(#PB_Any,File$)
    If *pack\File
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File))
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\_rels")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\docProps")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\xl")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\xl\_rels")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\xl\printerSettings")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\xl\theme")
      CreateDirectory(GetTemporaryDirectory()+Str(*Pack\File)+"\xl\worksheets")
      ExaminePack(*Pack\File)
      While NextPackEntry(*Pack\File)
        UncompressPackFile(*Pack\File,GetTemporaryDirectory()+Str(*Pack\File)+"\"+PackEntryName(*pack\File))
      Wend
      ClosePack(*Pack\File)
      *pack\WorkBook = LoadXML(#PB_Any,GetTemporaryDirectory()+Str(*Pack\File)+"\xl\workbook.xml")
      If Not XMLStatus(*pack\WorkBook) = #PB_XML_Success : ProcedureReturn #False : EndIf
      *pack\SharedString = LoadXML(#PB_Any,GetTemporaryDirectory()+Str(*Pack\File)+"\xl\sharedStrings.xml")
      If Not XMLStatus(*pack\SharedString) = #PB_XML_Success : ProcedureReturn #False : EndIf
      ProcedureReturn *Pack
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure Close(*File.file)
    If Not *File : ProcedureReturn #False : EndIf
    If IsXML(*File\SharedString) : FreeXML(*File\SharedString) : EndIf
    If IsXML(*File\Sheet) : FreeXML(*File\Sheet) : EndIf
    If IsXML(*File\WorkBook) : FreeXML(*File\WorkBook) : EndIf
    If DeleteDirectory(GetTemporaryDirectory()+Str(*File\File),"*.*",#PB_FileSystem_Recursive)
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure CountSheets(*File.file)
    If Not *File : ProcedureReturn #False : EndIf
    Protected node
    node = MainXMLNode(*file\WorkBook)
    node = ChildXMLNode(node,4)
    ProcedureReturn XMLChildCount(node)
  EndProcedure
  
  Procedure.s GetSheetName(*File.file,Sheet=1)
    If Not *File : ProcedureReturn  "" : EndIf
    Protected node
    node = MainXMLNode(*File\WorkBook)
    node = ChildXMLNode(node,4)
    While Not GetXMLNodeName(node) = "sheets"
      node = NextXMLNode(node)
      If node = 0 : ProcedureReturn "" : EndIf
    Wend
    node = ChildXMLNode(node,Sheet)
    If node
      ProcedureReturn GetXMLAttribute(node,"name")
    Else
      ProcedureReturn ""
    EndIf
  EndProcedure
  
  Procedure CountRow(*File.file,Sheet=0) ; If sheet is set to 0, the current (or 1st if not current) Sheet will be read.
    If Not *File : ProcedureReturn #False : EndIf
    Protected node, Row$
    If Sheet = 0 
      If IsXML(*File\Sheet) = 0
        If Not LoadSheet(*File,1) : ProcedureReturn #False : EndIf
      EndIf
    Else
      If Not LoadSheet(*File,Sheet) : ProcedureReturn #False : EndIf
    EndIf
    node = MainXMLNode(*File\Sheet)
    node = ChildXMLNode(node)
    While GetXMLNodeName(node) <> "dimension"
      node = NextXMLNode(node)
    Wend
    row$ = ReverseString(((ReverseString(StringField(GetXMLAttribute(node,"ref"),2,":")))))
    Repeat
      If Asc(row$) < 48 Or Asc(row$) > 57
        row$ = RemoveString(row$,Chr(Asc(row$)))
      Else
        Break
      EndIf
    ForEver    
    ProcedureReturn Val(Row$)
  EndProcedure
  
  Procedure CountCol(*File.File,Sheet=0) ; If sheet is set to 0, the current (or 1st if not current) Sheet will be read.
    If Not *File : ProcedureReturn #False : EndIf
    Protected node, col, size
    If Sheet = 0 
      If IsXML(*File\Sheet) = 0
        If Not LoadSheet(*File,1) : ProcedureReturn #False : EndIf
      EndIf
    Else
      If Not LoadSheet(*File,Sheet) : ProcedureReturn #False : EndIf
    EndIf
    node = MainXMLNode(*File\Sheet)
    node = ChildXMLNode(node)
    While GetXMLNodeName(node) <> "dimension"
      node = NextXMLNode(node)
    Wend
    size = Len(StringField(GetXMLAttribute(node,"ref"),2,":")) - Len(Str(Val((ReverseString(StringField(GetXMLAttribute(node,"ref"),2,":"))))))
    If size = 1
      ProcedureReturn Asc(Left(StringField(GetXMLAttribute(node,"ref"),2,":"),1))-64
    ElseIf size = 2
      ProcedureReturn (Asc(Left(StringField(GetXMLAttribute(node,"ref"),2,":"),1))-64)*26 + Asc(Left(Right(StringField(GetXMLAttribute(node,"ref"),2,":"),Len(StringField(GetXMLAttribute(node,"ref"),2,":"))-1),1))-64
    EndIf
  EndProcedure
  
  Procedure LoadSheet(*File.file,Sheet)
    If Not *File : ProcedureReturn #False : EndIf
    If IsXML(*File\Sheet) : FreeXML(*File\Sheet) : EndIf
    *File\Sheet = LoadXML(#PB_Any,GetTemporaryDirectory()+Str(*File\File)+"\xl\worksheets\sheet"+Sheet+".xml")
    If XMLStatus(*File\Sheet) = #PB_XML_Success
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndProcedure
  
  Procedure.s ReadCell(*File.file,Row,Col,Sheet=0) ; If sheet is set to 0, the current (or 1st if not current) Sheet will be read.
    If Not *File : ProcedureReturn "" : EndIf
    Protected node, node2
    If Sheet = 0
      If IsXML(*File\Sheet) = 0
        If Not LoadSheet(*File,1) : ProcedureReturn "" : EndIf
      EndIf
    Else
      If Not LoadSheet(*File,Sheet) : ProcedureReturn "" : EndIf
    EndIf
    node = MainXMLNode(*File\Sheet)
    node2 = MainXMLNode(*File\SharedString)
    node = ChildXMLNode(node)
    While Not GetXMLNodeName(node) = "sheetData"
      node = NextXMLNode(node)
    Wend
    node = ChildXMLNode(node)
    While  Not Val(GetXMLAttribute(node,"r")) = row
      node = NextXMLNode(node)
      If node = 0 : ProcedureReturn " " : EndIf
    Wend
    If ChildXMLNode(node,col)
      node = ChildXMLNode(node,col)
    Else
      ProcedureReturn " "
    EndIf
    If GetXMLAttribute(node,"t") = "s"
      If ChildXMLNode(node)
        node = ChildXMLNode(node)
      Else
        ProcedureReturn " "
      EndIf
      node2 = ChildXMLNode(node2,Val(GetXMLNodeText(node))+1)
      If node2
        node2 = ChildXMLNode(node2)
        ProcedureReturn GetXMLNodeText(node2)
      Else
        ProcedureReturn ""
      EndIf
    ElseIf GetXMLAttribute(node,"t") = ""
      If ChildXMLNode(node)
        node = ChildXMLNode(node)
        ProcedureReturn GetXMLNodeText(node)
      Else
        ProcedureReturn " "
      EndIf
    ElseIf GetXMLAttribute(node,"t") = "e"
      If ChildXMLNode(node)
        node = ChildXMLNode(node)
        While Not GetXMLNodeName(node) = "v"
          node = NextXMLNode(node)
          If node = 0 : ProcedureReturn "" : EndIf
        Wend
        ProcedureReturn GetXMLNodeText(node)
      Else
        ProcedureReturn " "
      EndIf
    EndIf
      
  EndProcedure
  
EndModule

;=================================================Example of use=================================================
; Define xml, row, loop
; xml = Excel::Open(OpenFileRequester("",GetCurrentDirectory(),"Excel | *.xlsx",0))
; Debug Excel::GetSheetName(xml)
; row = Excel::CountRow(xml)
; Debug Excel::CountCol(xml)
; For loop = 1 To row
;   Debug Excel::ReadCell(xml,loop,1)
;   Debug Excel::ReadCell(xml,loop,2)
;   Debug Excel::ReadCell(xml,loop,7)
; Next
; Excel::Close(xml)
;================================================================================================================
C'est trés basique et ne fonctionne qu'avec des fichier Excel simple, mais c'est tout ce dont j'avais besoin.

Edit : certaines valeurs de cellules sont par fois dans le "sheet.xml" et rajout de la lecture du résultat des formules.

Edit : MAJ, il y avais un petit but sur la fonction CountRow
Dernière modification par boby le jeu. 24/août/2017 17:29, modifié 3 fois.
Marc56
Messages : 2146
Inscription : sam. 08/févr./2014 15:19

Re: Lire un fichier Excel (.xlsx)

Message par Marc56 »

Excellent, merci.

On oublie souvent effectivement que le format XLSX est juste un lot de fichiers XML et texte, le tout zippé.
(Un xlsx s'ouvre très bien avec 7zip)

Comme PB gère le format zip et les nœuds XML on peut facilement extraire des données des fichiers xlsx
(plus facilement que l'ancien format xls)

:wink:
Avatar de l’utilisateur
Kwai chang caine
Messages : 6962
Inscription : sam. 23/sept./2006 18:32
Localisation : Isere

Re: Lire un fichier Excel (.xlsx)

Message par Kwai chang caine »

Ouaih depuis l'abandon du format propriétaire XLS qui obligeait à passer soit par un moteur, soit par COMATE pour le lire avec PB
XLSX simplifie beaucoup la vie, et encore plus quand un copain le code pour vous :lol:
Ca pourra m'etre surement utile, car au boulot, ils savent pas prononcer une phrase sans qu'elle contienne le mot EXCEL dedans :?
Merci beaucoup BOBY du partage et soit le bienvenu (surtout pour ce genre de trucs utilitaires :mrgreen:), car je ne crois pas te l'avoir souhaité auparavant 8)
ImageLe bonheur est une route...
Pas une destination

PureBasic Forum Officiel - Site PureBasic
Avatar de l’utilisateur
Fig
Messages : 1176
Inscription : jeu. 14/oct./2004 19:48

Re: Lire un fichier Excel (.xlsx)

Message par Fig »

Très utile ! :D
Il y a deux méthodes pour écrire des programmes sans erreurs. Mais il n’y a que la troisième qui marche.
Version de PB : 6.00LTS - 64 bits
Avatar de l’utilisateur
omega
Messages : 617
Inscription : sam. 26/nov./2011 13:04
Localisation : Alger

Re: Lire un fichier Excel (.xlsx)

Message par omega »

Bonsoir à tous
Bizarre mais chez moi ça ne marche pas, voici le message d'erreur qui s'affiche puis fermeture du programme.
  • Le fichier #XML n'est pas initialisé
Win7 (x64) 64 bits Pb 5.72
Marc56
Messages : 2146
Inscription : sam. 08/févr./2014 15:19

Re: Lire un fichier Excel (.xlsx)

Message par Marc56 »

omega a écrit :Bonsoir à tous
Bizarre mais chez moi ça ne marche pas, voici le message d'erreur qui s'affiche puis fermeture du programme.
  • Le fichier #XML n'est pas initialisé
Message étonnant, car il n'y a pas de constante #XML dans l'exemple donné par boby

As-tu ?
- Créé un fichier xlsx simple (xlsx = Fichier provenant d'Excel 2007 ou +)
- Copié/collé tout le code de bobby
- Désactivé l'exemple (partie entre ;=====)
- Copié strictement le message d'erreur ?

:wink:
Avatar de l’utilisateur
omega
Messages : 617
Inscription : sam. 26/nov./2011 13:04
Localisation : Alger

Re: Lire un fichier Excel (.xlsx)

Message par omega »

Je reprends le message tel qu'il est affiché:

[17 :37 :25]
[17 :38 :59] Attente du démarrage du programme...
[17 :38 :59] Type d'exécutable: Windows - x86 (32bit, Unicode)
[17 :38 :59] Exécutable démarré.
[17 :39 :06] [ERREUR] testExcel1.pb (Ligne: 43)
[17 :39 :06] [ERREUR] Le #XML spécifié n'est pas initialisé.


et en ligne 43:

Code : Tout sélectionner

If Not XMLStatus(*pack\SharedString) = #PB_XML_Success : ProcedureReturn #False : EndIf
Win7 (x64) 64 bits Pb 5.72
boby
Messages : 261
Inscription : jeu. 07/juin/2007 22:54

Re: Lire un fichier Excel (.xlsx)

Message par boby »

Un XLSX est un fichier compressé contenant plusieurs XML, pas besoin de constante, c'est l'ouverture du XML "sharedStrings.xml" qui déconne dans ton cas.

test tu l'exemple avec un vrai fichier xlsx créé par excel ? ce fichier est-il lisible par excel ?
Si oui, ça dit quoi si tu colle

Code : Tout sélectionner

      If Not XMLStatus(*pack\WorkBook) = #PB_XML_Success : ProcedureReturn #False : EndIf
      Debug FileSize(GetTemporaryDirectory()+Str(*Pack\File)+"\xl\sharedStrings.xml")
      *pack\SharedString = LoadXML(#PB_Any,GetTemporaryDirectory()+Str(*Pack\File)+"\xl\sharedStrings.xml")
?
Avatar de l’utilisateur
Fig
Messages : 1176
Inscription : jeu. 14/oct./2004 19:48

Re: Lire un fichier Excel (.xlsx)

Message par Fig »

Rien à voir avec la discussion en cours, mais...

Code : Tout sélectionner

ElseIf GetXMLAttribute(node,"t") = "e" or GetXMLAttribute(node,"t") = "str"
Cette ligne à compléter pour prendre en compte ce cas là. 8)
Il y a deux méthodes pour écrire des programmes sans erreurs. Mais il n’y a que la troisième qui marche.
Version de PB : 6.00LTS - 64 bits
Répondre