[GELÖST]HTML Parser fehlverhalten

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Warf
Beiträge: 2122
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

[GELÖST]HTML Parser fehlverhalten

Beitrag von Warf »

Hey ho,

tut mir leid für die herzlichst beschissene Überschrift, mir ist nichts besseres eingefallen.
Im Zuge eines Projekts habe ich basierend auf einem alten HTML Parser für D6 (HTML Parser v1.2. von hier)
Dieser Verarbeitet HTML inhalt zu einer Liste mit Objekten die entweder THTMLTag's (alles was mit < > umschlossen ist und nicht in die anderen gruppen gehört) THtmlText(plaintext) THTMLServerScript (<?php ?>) und THTMLComment's (<!-- -->).

Damit habe ich dann ein Nodesystem gebastelt (da mich immer nur einzelne nodes interresieren), doch leider verschachtelt das die nodes im body teil komplett falsch.

Hier ist die Parse Methode (komplettes test projekt liegt im Anhang anbei)

Code: Alles auswählen

procedure THTMLDocument.Parse(htm: string);
 
  function CheckTag(t: string): boolean;
  var
    i: integer;
  begin
    Result := True;
    for i := 0 to 12 do
      if LowerCase(t) = NonClosing[i] then
      begin
        Result := False;
        break;
      end;
  end;
 
  function ParseTag(var curr: integer; const l: TList): TNode;
  begin
    Result := TNode.Create(TObject(l[curr]) as THTMLTag);
    Inc(curr);
    // If container Element and isn't closed in the same line (e.g. <div />)
    if CheckTag(Result.Name) and (Result.Param['/'] = nil) then
      while curr < l.Count do
      begin
        if (TObject(l[curr]) is THTMLTag) then
        begin // Is an html tag
          if (TObject(l[curr]) as THTMLTag).Name[1] = '/' then
          begin // starts with a / (closing tag>
            if LowerCase((TObject(l[curr]) as THTMLTag).Name) = '/' +
              LowerCase(Result.Name) then
            begin // is the closing tag to our current tag
              Inc(curr);
              break;
            end;
          end
          else
            Result.AddNode(ParseTag(curr, l));
        end
        else if (TObject(l[curr]) is THTMLText) then
          Result.Content := Result.Content + (TObject(l[curr]) as THTMLText).Text +
            LineEnding;
        Inc(curr);
      end;
  end;
 
var
  i: integer;
begin
  Parser.Memory.Clear;
  Parser.Memory.Write(htm[1], Length(htm));
  Parser.Execute;
  i := 0;
  while not (TObject(Parser.parsed[i]) is THTMLTag) do
    Inc(i);
  FRoot := ParseTag(i, Parser.parsed);
end;
Und ich stehe grade völlig auf dem schlauch, erkennt einer von euch den Fehler, oder kennt einer von euch einen guten HTML parser? (DOM und XMLRead funktioniert nicht da HTML syntaktisch mehr zulässt als striktes XML, und ich somit bei einigen Websites einfach fehler bekomme). Ist hauptsächlich zum finden lesen einzelner Nodes, ihrer properties und ihres Inhaltes (also z.B. gehe zu Element mit id = X, gehe in die subnode 2, und gebe text dieser node aus).


EDIT:
Habe es gelöst, sollte es jemanden interresieren habe ich die Lösung (über BeniBela InternetTools, nicht mehr mit der alten D6 unit) als anhang hochgeladen


Grüße
Fred
Dateianhänge
Test.zip
(56.85 KiB) 83-mal heruntergeladen
Zuletzt geändert von Warf am Do 1. Okt 2015, 17:50, insgesamt 2-mal geändert.

Benutzeravatar
theo
Beiträge: 10873
Registriert: Mo 11. Sep 2006, 19:01

Re: HTML Parser fehlverhalten

Beitrag von theo »

Deine Demo kann man auf 64bit nicht kompilieren wegen ASM.
FPC hat einen Parser dabei:
uses FastHTMLParser
Vielleicht geht's damit.

EDIT hab's mal kurz getestet. Sieht recht stabil aus.
Folgendes gibt alle IDs aus:

Code: Alles auswählen

uses FastHTMLParser, HTMLUtil;   
...
procedure TForm1.Button1Click(Sender: TObject);
var FH:THTMLParser;
  var SL:TStringList;
begin
  SL:=TStringList.Create;
  SL.LoadFromFile('/home/theo/public_html/testen.html');
  FH:=THTMLParser.Create(SL.Text);
  FH.OnFoundTag:=@FHFoundTag;
  FH.OnFoundText:=@FHFoundText;
  Memo1.Lines.BeginUpdate;
  FH.Exec;
  Memo1.Lines.EndUpdate;
  FH.free;
  SL.Free;
end;
 
procedure TForm1.FHFoundTag(NoCaseTag, ActualTag: string);
var S:String;
begin
  S:=GetVal(ActualTag,'ID');
  if S<>'' then Memo1.Lines.Add(S);
end;
 
procedure TForm1.FHFoundText(AText: string);
begin
 // Memo1.Lines.Add(AText);
end;       

BeniBela
Beiträge: 320
Registriert: Sa 21. Mär 2009, 17:31
OS, Lazarus, FPC: Linux (Lazarus SVN, FPC 2.4)
CPU-Target: 64 Bit

Re: HTML Parser fehlverhalten

Beitrag von BeniBela »

Wenn man HTML wirklich zuverlässig parsen will, muss man den Algorithmus von HTML 5 verwenden: http://www.w3.org/TR/html5/syntax.html#parsing

Der ist dort eindeutig beschrieben, eine Art Automat mit mehreren Zuständen und Stack, und je nach gelesenem Zeichen gibt es unterschiedliche Zustandübergänge. Und wann bereits gelesene HTML-Elemente umsortiert werden müssen. (oft gibt es <x><y>, dass dann aber als <y><x> geparst werden muss)

Ich habe hier einen geschrieben, der ist aber auch älter HTML 5 (aus D4 Zeiten) und müsste eigentlich mit dem HTML5-Algorithmus neugeschrieben werden, parst aber alle Seiten, die ich kenne.

Nur <?php ?> könnte problematisch werden

Warf
Beiträge: 2122
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: HTML Parser fehlverhalten

Beitrag von Warf »

Zwar habe ich jetzt den Fehler gefunden (Curr an den falschen Stellen erhöht) aber dass die unit nicht AIF 64 Bit läuft ist problematisch. Die fpc United kann ich leider nicht verwenden da ich delphi Kompatibilität sicher stellen will.

Den kompletten HTML5 Algorithmus brauche ich ja auch nicht, mich interessiert nur die baumstruktur, ob da jetzt ein doctype steht oder in welcher Reihenfolge Text und Tags kommen sind mir recht egal, deshalb tut es auch so ein kleiner parser.

Aber ich denke ich steige dann mal auf deine simplehtmlparser unit um, die sieht relativ gut aus, und scheint auch nicht unnötig überladen zu sein, perfekt das was ich suche

Antworten