Indentation HTML5 et Analyse attributs balises HTML

Partagez votre expérience de PureBasic avec les autres utilisateurs.
boddhi
Messages : 604
Inscription : lun. 26/avr./2010 16:14
Localisation : S 48° 52' 31'' / O 123° 23' 33''

Indentation HTML5 et Analyse attributs balises HTML

Message par boddhi »

Contenu du post effacé le 30/01/23 à la suite d'une mauvaise manip et reconstitué à l'à peu près.

Code permettant l'indentation d'un fichier HTML 5 afin d'en rendre la lecture analytique plus aisée.

N'ayant pas trouvé sur le Net de RegEX concluant, les uns ou les autres échouant toujours à un moment ou à un autre, j'ai donc dû élaborer mon propre ReGex qui, je crois après des tests sur de nombreuses pages HTML, fonctionne parfaitement.
Sauf que, parce qu'il faillait bien un 'Sauf que', exception (de taille, hélas !!!), les pages qui contiennent certains catégories de caractères Unicode (codés sur 3 ou 4 octets). Ceci est dû à une mauvaise gestion des données Unicode récupérées par PB. Un post a été créé dans la section 'Bugs' du forum US.

Je travaille néanmoins, grâce à STARGATE et Demivec, à trouver une solution pour palier à cet 'inconvénient'. Je la posterai dès que possible.

Source : Indentation d'un fichier HTML5

Code : Tout sélectionner

EnableExplicit

#IHTML_FICHIERHTML=0
#REGEX_HTMLBAL=0
#IHTML_REGEXHTMLBAL="</?\w+:?\w*((\s+(\w+-?+)+:?\w?+(\s*=\s*(?:"+#DQUOTE$+".*?"+#DQUOTE$+"|'.*?'|[^'"+#DQUOTE$+">\s]+))?)+\s*|\s*)/?>"
#IHTML_NBESPACES=2
#IHTML_FINDELIGNE=#LF$

Procedure   Pc_Indentation_ChaineHTML(ArgChaineHTML.s)
  Protected.i AncPosition=1       ; Position précédente dans la variable TexteFichier
  Protected.i NouvPosition        ; Position actuelle dans la variable TexteFichier après appel RegEx
  Protected.i NouvLongChaine      ; Longueur de la chaine textuelle entre deux balises après traitemeent Unicode
  Protected.i LongChaine          ; Longueur de la chaine textuelle entre deux balises
  Protected.i NiveauBalise        ; Niveau d'indentation
  Protected.a Commentaire         ; Commentaire HTML en cours de traitement (Booléen)
  Protected.s TexteHTML           ; Contenu final indenté
  Protected.s ChaineFichier       ; Contenu balise HTML ou Contenu entre deux balises
  Protected.s ChaineModifiee      ; ChaineFichier expurgée des espaces
  Protected.s Balise              ; Nom de la balise en cours
  Protected.s ChaineCommentaire   ; Commentaire HTML

  ; Suppression des LF, CR & TAB
  ArgChaineHTML=ReplaceString(ReplaceString(ReplaceString(ArgChaineHTML,Chr(10),""),Chr(13),""),Chr(9),"")
  ArgChaineHTML=Trim(ArgChaineHTML)
  ; Test entête fichier HTML
  If UCase(Left(ArgChaineHTML,14))<>"<!DOCTYPE HTML"
    MessageRequester("Analyse Balises et attributs HTML","Le fichier ne semble pas être un fichier HTML",#PB_MessageRequester_Error)
    ProcedureReturn
  EndIf
  ; Boucle lecture des balises
  If CreateRegularExpression(#REGEX_HTMLBAL,#IHTML_REGEXHTMLBAL)
    If ExamineRegularExpression(#REGEX_HTMLBAL,ArgChaineHTML)
      While NextRegularExpressionMatch(#REGEX_HTMLBAL)
        NouvPosition=RegularExpressionMatchPosition(#REGEX_HTMLBAL)

        ; Analyse du contenu entre deux balises ou commentaires HTML "<!-- blabla -->"
        If AncPosition<>NouvPosition                                            ; Texte ou commentaire
          LongChaine=NouvPosition-AncPosition
          ChaineFichier=Mid(ArgChaineHTML,AncPosition,LongChaine)
          If ChaineFichier<>Space(LongChaine)
            If Left(LTrim(ChaineFichier),4)="<!--"                              ; Balise début commentaire
              If Right(RTrim(ChaineFichier),3)<>"-->"                           ; Commentaire encadrant:"<!-- blabla > <blabla> blabla <!-->"
                Commentaire=#True
                ChaineCommentaire=LTrim(ChaineFichier)
              Else                                                              ; Commentaire non encadrant:<!--blabla-->
                TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)+Trim(ChaineFichier)+#IHTML_FINDELIGNE
              EndIf
            ElseIf Right(RTrim(ChaineFichier),3)="-->"                          ; Balise fin commentaire encadrant
              TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)+ChaineCommentaire+RTrim(ChaineFichier)+#IHTML_FINDELIGNE
              Commentaire=#False
              ChaineCommentaire=""
            Else                                                                ; Texte entre deux balises
              While FindString(ChaineFichier,Space(2))
                ChaineFichier=ReplaceString(ChaineFichier,Space(2)," ")
              Wend
              If Right(TexteHTML,Len(#IHTML_FINDELIGNE)+1)=">"+#IHTML_FINDELIGNE
                ChaineFichier=LTrim(ChaineFichier)
              EndIf
              TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)+ChaineFichier+#IHTML_FINDELIGNE
            EndIf
          EndIf
        EndIf

        ; Analyse balise HTML
        LongChaine=RegularExpressionMatchLength(#REGEX_HTMLBAL)
        ChaineFichier=RegularExpressionMatchString(#REGEX_HTMLBAL)
        ChaineModifiee=ReplaceString(ChaineFichier," ","")
        If Commentaire                                                          ; Balise contenue dans un commentaire encadrant
          ChaineCommentaire+ChaineFichier
        ElseIf Left(ChaineModifiee,3)="<br" Or                                  ; Balise ouvrante avec balise fermante non obligatoire (Possibilité d'en ajouter pour les cas non prévus et il y en a surement!)
               Left(ChaineModifiee,3)="<hr" Or
               (Left(ChaineModifiee,4)="<col" And Left(ChaineModifiee,9)<>"<colgroup") Or
               Left(ChaineModifiee,4)="<img" Or
               Left(ChaineModifiee,5)="<base" Or
               Left(ChaineModifiee,5)="<area" Or
               Left(ChaineModifiee,5)="<link" Or
               Left(ChaineModifiee,5)="<meta" Or
               Left(ChaineModifiee,6)="<frame" Or
               Left(ChaineModifiee,6)="<input" Or
               Left(ChaineModifiee,6)="<param" Or
               Left(ChaineModifiee,6)="<track" Or
               Left(ChaineModifiee,7)="<keygen" Or
               Left(ChaineModifiee,7)="<source" Or
               Left(ChaineModifiee,8)="<bgsound" Or
               (Left(ChaineModifiee,2)<>"</" And Right(ChaineModifiee,2)="/>")  ; Balise non encdrante (Par ex.:"<br/>","<a blabla />")
          If Left(ChaineModifiee,3)="<br" And Right(TexteHTML,Len(#IHTML_FINDELIGNE)+1)<>">"+#IHTML_FINDELIGNE
            TexteHTML=Left(TexteHTML,Len(TexteHTML)-Len(#IHTML_FINDELIGNE))
          Else
            TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)
          EndIf
          TexteHTML+ChaineFichier+#IHTML_FINDELIGNE
        Else                                                                    ; Balise encadrante
          If Mid(Left(ChaineModifiee,2),2)<>"/"                                 ; Balise encadrante ouvrante
             ;Obtenir le nom de la balise ouvrante
             ;Balise=Trim(Mid(ChaineFichier,2))                                   ; Important : Ne pas modifier l'ordre des 3 lignes suivantes ni les "factoriser"
             ;Balise=Trim(Left(Balise,Len(Balise)-1))
             ;Balise=StringField(Balise,1," ")
            TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)+ChaineFichier+#IHTML_FINDELIGNE
            NiveauBalise+1
          ElseIf Mid(Left(ChaineModifiee,2),2)="/"                              ; Balise encadrante fermante
             ;Obtenir le nom de la balise fermante
             ;Balise=Trim(Mid(ChaineFichier,2))                                   ; Important : Ne pas modifier l'ordre des 4 lignes suivantes ni les "factoriser"
             ;Balise=Trim(Mid(Balise,2))
             ;Balise=Trim(Left(Balise,Len(Balise)-1))
             ;Balise=StringField(Balise,1," ")
            NiveauBalise-Bool(NiveauBalise>0)                                   ; Usage de Bool():On évite une erreur en cas de code HTML mal construit
            TexteHTML+Space(NiveauBalise*#IHTML_NBESPACES)+ChaineFichier+#IHTML_FINDELIGNE
          EndIf
        EndIf
        AncPosition=NouvPosition+LongChaine
      Wend
      If TexteHTML
        Debug TexteHTML
      EndIf
    EndIf
  EndIf
EndProcedure
Procedure   Pc_Indentation_FichierHTML(ArgFichierHTML.s)
  Protected.s TexteFichier        ; Contenu du fichier HTML lu

  If Trim(ArgFichierHTML)=""
    MessageRequester("Analyse Balises et attributs HTML","Argument ArgFichierHTML invalide",#PB_MessageRequester_Error)
    ProcedureReturn
  EndIf
  ; Ouverture du fichier
  If ReadFile(#IHTML_FICHIERHTML,ArgFichierHTML,#PB_UTF8)
    TexteFichier=ReadString(#IHTML_FICHIERHTML,#PB_File_IgnoreEOL)
    CloseFile(#IHTML_FICHIERHTML)
    Pc_Indentation_ChaineHTML(TexteFichier)
  Else
    MessageRequester("Analyse Balises et attributs HTML","Erreur lors de l'accès au fichier",#PB_MessageRequester_Error)
  EndIf
EndProcedure

Define.s FichierHTML
FichierHTML=OpenFileRequester("Sélectionner un fichier HTML","C:\","Fichiers HTML (*.htm*)|*.htm*",0)
If FichierHTML
  Pc_Indentation_FichierHTML(FichierHTML)
EndIf



De même, j'ai élaboré un RegEx permettant de récupérer les attributs des balises HTML :

Ajout 1 : Analyse des attributs des balises d'un fichier HTML

Code : Tout sélectionner

EnableExplicit

Enumeration RegEX
  #REGEX_HTMLBAL
  #REGEX_HTMLATT
EndEnumeration

#IHTML_FICHIERHTML=0
#IHTML_REGEXHTMLBAL="</?\w+:?\w*((\s+(\w+-?+)+:?\w?+(\s*=\s*(?:"+#DQUOTE$+".*?"+#DQUOTE$+"|'.*?'|[^'"+#DQUOTE$+">\s]+))?)+\s*|\s*)/?>"
#IHTML_REGEXHTMLATT="(\S+)=["+#DQUOTE$+"']?((?:.(?!["+#DQUOTE$+"']?\s+(?:\S+)=|[>"+#DQUOTE$+"']))+.)["+#DQUOTE$+"']?"

Procedure   Pc_Analyse_BalisesEtAttributesHTML(ArgFichierHTML.s)
  Protected.i AncPosition=1       ; Position précédente dans la variable TexteFichier
  Protected.i NouvPosition        ; Position actuelle dans la variable TexteFichier après appel RegEx
  Protected.i LongChaine          ; Longueur de la chaine textuelle entre deux balises
  Protected.i NbAttributs         ; Nombre d'attributs d'une balise HTML
  Protected.i Compteur            ; Variable boucle For/Next
  Protected.a Commentaire         ; Commentaire HTML en cours de traitement (Booléen)
  Protected.s TexteFichier        ; Contenu du fichier HTML lu
  Protected.s ChaineFichier       ; Contenu balise HTML ou Contenu entre deux balises
  Protected.s NomAttribut         ; Nom de l'attribut HTML
  Protected.s ValeurAttribut      ; Valeur de l'attribut HTML
  Dim Attributs.s(0)

  If Trim(ArgFichierHTML)=""
    MessageRequester("Analyse Balises et attributs HTML","Argument ArgFichierHTML invalide",#PB_MessageRequester_Error)
    ProcedureReturn
  EndIf
  ; Ouverture du fichier
  If ReadFile(#IHTML_FICHIERHTML,ArgFichierHTML,#PB_UTF8)
    TexteFichier=ReadString(#IHTML_FICHIERHTML,#PB_File_IgnoreEOL)
    CloseFile(#IHTML_FICHIERHTML)
    ; Suppression des LF, CR & TAB
    ;TexteFichier=ReplaceString(ReplaceString(ReplaceString(TexteFichier,Chr(10),""),Chr(13),""),Chr(9),"")
    ReplaceString(TexteFichier,Chr(10)," ",#PB_String_InPlace)
    ReplaceString(TexteFichier,Chr(13)," ",#PB_String_InPlace)
    ReplaceString(TexteFichier,Chr(9)," ",#PB_String_InPlace)
    TexteFichier=Trim(TexteFichier)
    ; Test entête fichier HTML
    If UCase(Left(TexteFichier,14))<>"<!DOCTYPE HTML"
      MessageRequester("Analyse Balises et attributs HTML","Le fichier ne semble pas être un fichier HTML",#PB_MessageRequester_Error)
      ProcedureReturn
    EndIf
    ; Boucle lecture des balises
    If CreateRegularExpression(#REGEX_HTMLBAL,#IHTML_REGEXHTMLBAL) And CreateRegularExpression(#REGEX_HTMLATT,#IHTML_REGEXHTMLATT)
      If ExamineRegularExpression(#REGEX_HTMLBAL,TexteFichier)
        While NextRegularExpressionMatch(#REGEX_HTMLBAL)
          NouvPosition=RegularExpressionMatchPosition(#REGEX_HTMLBAL)

          ; Analyse du contenu entre deux balises ou commentaires HTML "<!-- blabla -->"
          If AncPosition<>NouvPosition                                            ; Texte ou commentaire
            LongChaine=NouvPosition-AncPosition
            ChaineFichier=Mid(TexteFichier,AncPosition,LongChaine)
            If ChaineFichier<>Space(LongChaine)
              If Left(LTrim(ChaineFichier),4)="<!--" And Right(RTrim(ChaineFichier),3)<>"-->"  ; Balise début commentaire encadrant:"<!-- blabla > <blabla> blabla <!-->"
                Commentaire=#True
              ElseIf Right(RTrim(ChaineFichier),3)="-->"                          ; Balise fin commentaire encadrant
                Commentaire=#False
              EndIf
            EndIf
          EndIf
          ; Analyse balise HTML
          LongChaine=RegularExpressionMatchLength(#REGEX_HTMLBAL)
          ChaineFichier=RegularExpressionMatchString(#REGEX_HTMLBAL)
          If Not Commentaire                                                    ; Balise autre qu'un commentaire
            If Left(Trim(LTrim(ChaineFichier,"<")),1)<>"/"                      ; On exclut les balises fermantes
              Debug "Balise HTML : "+ChaineFichier
              ; Analyse attributs
              NbAttributs=ExtractRegularExpression(#REGEX_HTMLATT,ChaineFichier,Attributs())
              If NbAttributs
                Debug "  Nombre d'attributs : "+NbAttributs
                Debug "  Attributs : "
                NbAttributs-1
                ReDim Attributs(NbAttributs)
                For Compteur=0 To NbAttributs
                  NomAttribut=StringField(Attributs(Compteur),1,"=")
                  ValeurAttribut=StringField(Attributs(Compteur),2,"=")
                  ;ValeurAttribut=Trim(StringField(Attributs(Compteur),2,"="),Chr(34))  ; Valeur de l'attribut sans les guillements
                  Debug "    • "+NomAttribut+" : "+ValeurAttribut
                Next
              Else
                Debug "  Aucun attribut"
              EndIf
              Debug ""
            EndIf
          EndIf
          AncPosition=NouvPosition+LongChaine
        Wend
      EndIf
    EndIf
  Else
    MessageRequester("Analyse Balises et attributs HTML","Erreur lors de l'accès au fichier",#PB_MessageRequester_Error)
  EndIf
EndProcedure

Define.s FichierHTML
FichierHTML=OpenFileRequester("Sélectionner un fichier HTML","C:\","Fichiers HTML (*.htm*)|*.htm*",0)
If FichierHTML
  Pc_Analyse_BalisesEtAttributesHTML(FichierHTML)
EndIf
Dernière modification par boddhi le mar. 31/janv./2023 1:04, modifié 7 fois.
Marc56
Messages : 2197
Inscription : sam. 08/févr./2014 15:19

Re: Indentation HTML5

Message par Marc56 »

Beau boulot, ça me rappelle l'une des fonctions de HTML Tidy :wink:

PS. Perso, j'ai abandonné les +#DQUOTE$+ pour utiliser les chaines d'échappement disponibles depuis PB 5.40
(c'est juste pour en faire moins et je trouve ça plus lisible (quoiqu'une RegEx ça ressemble toujours à de l'uuencodage :mrgreen: )

Code : Tout sélectionner

#IHTML_REGEXHTMLBAL =  "</?\w+:?\w*((\s+(\w+-?+)+:?\w?+(\s*=\s*(?:"+#DQUOTE$+".*?"+#DQUOTE$+""

#IHTML_REGEXHTMLBAL = ~"</?\\w+:?\\w*((\\s+(\\w+-?+)+:?\\w?+(\\s*=\\s*(?:\".*?\""
boddhi
Messages : 604
Inscription : lun. 26/avr./2010 16:14
Localisation : S 48° 52' 31'' / O 123° 23' 33''

Re: Indentation HTML5

Message par boddhi »

@Marc56 Merci :wink:
PS. Perso, j'ai abandonné les +#DQUOTE$+ pour utiliser les chaines d'échappement disponibles depuis PB 5.40
(c'est juste pour en faire moins et je trouve ça plus lisible (quoiqu'une RegEx ça ressemble toujours à de l'uuencodage :mrgreen: )
Franchement, perso, je suis de la vieille école :oops: . Dans mon code originel, c'était des Chr(34) en lieu et place !
Mais comme beaucoup utilisent les #LF$, #DQUOTES$, etc..., je ne voulais pas trahir mon grand âge ! :D

Quant aux chaînes d'échappement, ça m'arrive de les utiliser, surtout lors de traitements XML mais comme je suis parti d'un vieux source que j'avais élaboré il y a presque vingt ans (sans les RegEx et nettement nettement plus lourd !), j'en ai gardé qq reliques :D .

Important ! J'ai apporté une modif dans le code du 1er post. J'avais en effet oublié, lors de la retranscription du source, de décommenter la ligne 118.
boddhi
Messages : 604
Inscription : lun. 26/avr./2010 16:14
Localisation : S 48° 52' 31'' / O 123° 23' 33''

Re: Indentation HTML5

Message par boddhi »

Ajout de l'analyse des attributs HTML (Voir Ajout 1)
boddhi
Messages : 604
Inscription : lun. 26/avr./2010 16:14
Localisation : S 48° 52' 31'' / O 123° 23' 33''

Re: Indentation HTML5 et Analyse attributs balises HTML

Message par boddhi »

Ajout du traitement de la balise <hr> dans la procédure Pc_Indentation_HTML() du code 1
Avatar de l’utilisateur
Kwai chang caine
Messages : 6989
Inscription : sam. 23/sept./2006 18:32
Localisation : Isere

Re: Indentation HTML5 et Analyse attributs balises HTML

Message par Kwai chang caine »

Ahhh!!! le traitement / l'analyse du HTML un de mes vieux rêve de logiciel
Mais contrairement aux premières impressions, c'est pas aussi simple que ça y parait :oops:
Merci du partage, ces codes pourront m'être surement utile quand je me replongerais dans cet enfer :wink:
Encore plus avec l'utilisation des REGEX, encore un domaine qui est resté dans mes rêves d'accessibilité un jour :oops:
ImageLe bonheur est une route...
Pas une destination

PureBasic Forum Officiel - Site PureBasic
boddhi
Messages : 604
Inscription : lun. 26/avr./2010 16:14
Localisation : S 48° 52' 31'' / O 123° 23' 33''

Re: Indentation HTML5 et Analyse attributs balises HTML

Message par boddhi »

@KCC
Sans dire trop de conneries, je crois, c'est vrai que traiter le HTML peut se révéler ardu surtout lorsqu'il faut tenir compte de plusieurs versions.

Pour avoir analysé nombre de codes, c'est parfois franchement le bordel et atteste d'un manque de rigueur de certains développeurs
Au contraire des langages de prog ou d'autres métalangages (le XML que pour ne citer qu'un exemple) qui sont structurés et ne laissent que peu de liberté à la syntaxe (et c'est tant mieux), le HTML, ou tout du moins, les navigateurs sont assez permissifs en la matière.
Entre les balises ouvrantes qui ne trouvent jamais leurs balises fermantes, ceux qui codent des balises d'une manière sur une ligne et d'une autre à la ligne d'en dessous, les balises mal imbriquées, la dépréciation des nombreuses balises dont il faut quand même tenir compte.

Avec un langage de prog, un For/IF/... qui ne rencontre pas son Next/Endif/... au bon endroit et c'est le Syntax Error assuré !

Quant au RegEx, c'est très puissant mais, en ce qui me concerne, d'une assez grande difficulté de rédaction et de lisibilité qui n'aide pas au débogage.Je ne l'utilise que dans de très rares cas et parfois de simples instructions If sont plus faciles à mettre en oeuvre.

A toutes fins utiles, j'utilise, entre autres, ce site assez pratique pour m'aider.
Avatar de l’utilisateur
Kwai chang caine
Messages : 6989
Inscription : sam. 23/sept./2006 18:32
Localisation : Isere

Re: Indentation HTML5 et Analyse attributs balises HTML

Message par Kwai chang caine »

Merci de ta réponse et bon courage :wink:
ImageLe bonheur est une route...
Pas une destination

PureBasic Forum Officiel - Site PureBasic
Répondre