Html-Code Parser und Renderer

    • Beta
    • Open Source

    Es gibt 41 Antworten in diesem Thema. Der letzte Beitrag () ist von nikeee13.

      Der Lexer geht erstmal durch den ganzen Code durch und filtert sogenannte Tokens raus, also z.B. /, <, >, =,keyword,text,number. Diese schiebt er in eine Liste, oder ggf.auch natürlich über yield(nicht die beste performance), was automatisch eine StateMachine erzeugt und lexer werden häufig als StateMachine implementiert, das ganze selbst als StateMachine(oder auch auf andere Art, gibt ein paar Möglichkeiten) zu implementieren ist natürlich performanter, aber die Syntax ist denke ich ganz nett über eine Coroutine...
      Und diese Tokens werden anschließend erst von dem parser durchgegangen und analysiert, erst dann findet also tatsächliche Syntax Analyse und DOM aufbau statt. Das ganze logisch auf diese Art zu trennen vereinfacht die logische Strukturierung, die Erweiterbarkeit(neuer HTML Standard z.B. muss eben nicht alles neu gemacht werden) und die Fehlererkennung(und Ignorierung) dürfte auch vereinfacht werden.

      Das ist auch die Standardherangehensweise für die meisten Parser und Compiler. Ob dies für HTML genauso gilt wie für sonstige Sprachen kann ich dir nicht sicher sagen(weiß nicht was die Browser machen), aber ich denke es ist trotzdem sinnvoll und gehe zumindest davon aus, dass es für HTML gilt wie für alles andere halt auch^^
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---

      jvbsl schrieb:

      und die Fehlererkennung(und Ignorierung)

      Kannst du das konkretisieren? Wie sähe das im Bezug auf HTML aus? Mir fiele da nur das Fehlen der End-Tags ein.

      Lieben Dank.
      Und Gott alleine weiß alles am allerbesten und besser.
      Fehlen von end tags oder endtag ohne starttag bei Anführungszeichen..viel mehr fällt mir im Moment nicht wirklich ein. Das merkst du evtl mit der Zeit beim testen von Webseiten, was da nötig ist
      Edit: oder natürlich >< ohne Bedeutung
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Also die obige Implementierung hat keine Probleme mit dem Fehlen von End-Tags:

      Quellcode

      1. <style>#lol{color:red;}</style><h1 id="lol">a<p>b


      Wird erfolgreich geparst:


      Vergleich das mal mit Webkit: Ist fast identisch.

      jvbsl schrieb:

      oder endtag ohne starttag bei Anführungszeichen

      Beispiel?

      jvbsl schrieb:

      oder natürlich >< ohne Bedeutung

      Beispiel?

      Lieben Dank.
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      Quellcode

      1. </head><head>bla<body>
      2. <h1 id="lol><a src="...
      3. <head><><<</head>

      So in die Richtung..

      Und ich behaupte nicht, dass es nicht möglich ist, das auf einmal zu parsen. Ist sogar sehr gut möglich. Früher war das Normal das parsing direkt zu machen
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      eigt. meinte ich title statt head aber sonst prinzipiell schon. Und waren nur Dinge die mir eingefallen sind, wie die dann von jeweiligen Engines geparst werden bzw. was die "richtige" Herangehensweise ist: Keine Ahnung - schließlich ist die Syntax ansich einfach nur falsch^^
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Hier mal ein Tokenizer:

      Spoiler anzeigen

      C#-Quellcode

      1. public class HtmlTokenizer
      2. {
      3. public StringBuilder RawHtmlSource { get; private set; }
      4. public List<Token> Tokens { get; private set; }
      5. public HtmlTokenizer(string rawHtmlSrc)
      6. {
      7. RawHtmlSource = new StringBuilder("<html>" + rawHtmlSrc + "</html>;");
      8. Tokens = new List<Token>();
      9. }
      10. public void Tokenize()
      11. {
      12. bool possibleTagFound = false;
      13. bool foundString = false;
      14. bool letterFound = true;
      15. bool endFound = false;
      16. Token currentToken = new Token();
      17. for (int i = 0; i < RawHtmlSource.Length - 1; i++)
      18. {
      19. char c = RawHtmlSource[i];
      20. char c1 = RawHtmlSource[i + 1];
      21. if (c == '"')
      22. foundString = !foundString;
      23. switch (possibleTagFound)
      24. {
      25. case true:
      26. currentToken.Content.Append(c);
      27. if (!foundString && c == '/')
      28. endFound = true;
      29. if (!foundString && char.IsLetter(c))
      30. letterFound = true;
      31. if (endFound && c == '>' && letterFound)
      32. currentToken.Type = Token.TokenType.EndTag;
      33. else if (!endFound && !foundString && letterFound && c == '>')
      34. currentToken.Type = Token.TokenType.BeginTag;
      35. if (currentToken.Type == Token.TokenType.Content || currentToken.Type == Token.TokenType.Default)
      36. continue;
      37. Tokens.Add(currentToken);
      38. currentToken = new Token()
      39. {
      40. Type = Token.TokenType.Content
      41. };
      42. possibleTagFound = false;
      43. letterFound = false;
      44. endFound = false;
      45. break;
      46. case false:
      47. currentToken.Content.Append(c);
      48. if (!foundString && c == '<')
      49. {
      50. if (c1 != '/' && !char.IsLetter(c1))
      51. continue;
      52. if (currentToken.Type == Token.TokenType.Content && (char.IsLetter(c1) || c1 == '/') && currentToken.Content.Length > 0)
      53. {
      54. currentToken.Content.Remove(currentToken.Content.Length - 1, 1);
      55. if (currentToken.Content.Length > 0)
      56. Tokens.Add(currentToken);
      57. currentToken = new Token();
      58. currentToken.Content.Append("<");
      59. }
      60. possibleTagFound = true;
      61. }
      62. break;
      63. }
      64. }
      65. if (currentToken.Content.Length > 0)
      66. Tokens.Add(currentToken);
      67. }
      68. }


      C#-Quellcode

      1. public class Token
      2. {
      3. public enum TokenType
      4. {
      5. BeginTag,
      6. Content,
      7. EndTag,
      8. Default
      9. };
      10. public TokenType Type { get; set; }
      11. public StringBuilder Content { get; set; }
      12. public Token()
      13. {
      14. Type = TokenType.Default;
      15. Content = new StringBuilder();
      16. }
      17. }





      Aufruf:

      C#-Quellcode

      1. class Program
      2. {
      3. static void Main(string[] args)
      4. {
      5. string html = "<html id=\"lol\">x<p><lol>x</lol><<<>>></html><<><<>>><><><html><title>";
      6. HtmlTokenizer tokenizer = new HtmlTokenizer(html);
      7. tokenizer.Tokenize();
      8. foreach (var token in tokenizer.Tokens)
      9. Console.WriteLine(token);
      10. Console.Read();
      11. }
      12. }


      Ergebnis:


      In der Tat ist das sehr hilfreich.
      So können im Quelltext auch Sonderzeichen stehen..

      Dein Beispiel ergibt folgende Anordnung:


      Das deckt sich mit Google Chrome:


      Beachte das der Tokenizer auch "bla<><<" als Content kennzeichnet.

      _
      Und Gott alleine weiß alles am allerbesten und besser.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „φConst“ ()

      Ich kenns zwar eher so, dass der lexer wirklich noch nichts HTML spezifisches großartig erkennt und das eher der Parser macht, also so in diese Richtung:
      codereview.stackexchange.com/q…ml-xhtml-xml-tokenization
      es werden nur <,>, /, = und ähnliches als tokens erkannt.
      Aber ansonsten schon ganz nette Arbeit.
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      deins ist immernoch ein Tokenizer(Lexer), der aber schon Dinge macht, die man meist gerne dem Parser überlässt.
      Wie gesagt falsch ists nicht.

      Nur ist der Lexer/Tokenizer meist wirklich sehr einfach und allein am Tokenizer erkennt man nicht unbedingt die Sprache.
      Auch ein C Tokenizer hätte <, >, ", / als Tokens, zwar mit einer anderen Bedeutung, aber das würde dann vom Parser interpretiert werden
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      ich wüsste nicht das man soetwas macht, denn das würde nur sehr vieles verkomplizieren und bringt einem vermutlich auch nicht so viel geschwindigkeit...
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Mir fiele ein wo man parallelisieren könnte:

      Die Token-Klasse besteht aus
      TokenTyp und Content.

      Um additionale Informationen wie Tag-Name, Tag-Attribute und ähnliches zu extrahieren, könnte man eine Methode .ProcessToken(), im neuen Thread ausführen, die eben
      den Tag prozessiert.

      _
      Und Gott alleine weiß alles am allerbesten und besser.
      Ee gibt halt trotzdem token, die von zwei zeichen abhängig sind. Das könnte mit threading probleme machen. Für js z.b. == ist nicht dasselbe wie = =

      Also was man eher parallelisiert sind unterschiedliche Dokumente. Z.b. die ganzen css und js...
      Ich wollte auch mal ne total überflüssige Signatur:
      ---Leer---
      Hier mal ein Tokenizer + DOM-Repräsentation:
      Tokenizer.zip

      Funktioniert eigentlich_souverän.

      HTML-Quellcode

      1. string html = "<p>LOL<p>rofl</p><html>TEST!<>><<<>>><<</html>";

      ergibt:

      p
      __->_LOL
      _____p
      ______->rofl
      _____html
      _________->TEST!<>><<<>>><<<

      _
      Und Gott alleine weiß alles am allerbesten und besser.

      Fehlerkorrektur Alpha

      Durch die Methode
      Spoiler anzeigen

      C#-Quellcode

      1. public void Analyze()
      2. {
      3. Dictionary<string, int> knownTags = new Dictionary<string, int>();
      4. Token endToken = null;
      5. for (int i = 0; i < Tokens.Count; i++)
      6. {
      7. Token currentToken = Tokens[i];
      8. if (currentToken.Type == Token.TokenType.Content || currentToken.Type == Token.TokenType.Default) continue;
      9. string name = currentToken.HtmlData.Name;
      10. switch (currentToken.Type)
      11. {
      12. case Token.TokenType.BeginTag:
      13. if (endToken != null)
      14. {
      15. Tokens.Insert(i, endToken);
      16. endToken = null;
      17. continue;
      18. }
      19. if (Declarations.UniqueTags.Contains(name))
      20. {
      21. endToken = new Token()
      22. {
      23. Type = Token.TokenType.EndTag,
      24. Content = new StringBuilder("</" + name + ">;"),
      25. HtmlData = new HtmlData(endToken)
      26. {
      27. NameBuilder = new StringBuilder(name)
      28. }
      29. };
      30. continue;
      31. }
      32. else if (!knownTags.ContainsKey(name))
      33. knownTags.Add(name, 1);
      34. else knownTags[name]++;
      35. break;
      36. case Token.TokenType.EndTag:
      37. if (endToken != null)
      38. if (name.Equals(endToken.HtmlData.Name))
      39. endToken = null;
      40. if (endToken != null)
      41. {
      42. Tokens.Insert(i, endToken);
      43. endToken = null;
      44. continue;
      45. }
      46. if (!knownTags.ContainsKey(name))
      47. {
      48. Tokens.RemoveAt(i);
      49. i--;
      50. continue;
      51. }
      52. if (knownTags[name] <= 0)
      53. {
      54. Tokens.RemoveAt(i);
      55. i--;
      56. }
      57. knownTags[name]--;
      58. break;
      59. }
      60. }
      61. }


      werden nun überflüssige Tags entfernt und einige End-Tags automatisch gesetzt.

      Die Eingabe

      Quellcode

      1. <div><div><p>hallo</rofl></div></div></div></div></div></div></div></div></div>


      ergibt die Ausgabe:
      [BEGIN]html
      [BEGIN]div
      [BEGIN]div
      [BEGIN]p
      [CONTENT]hallo
      [END]p
      [END]div
      [END]div
      [END]html

      _
      Und Gott alleine weiß alles am allerbesten und besser.
      -1-
      Die HTML-Dokumente werden nun in zwei Schritten analysiert:
      Zunächst wird es in die korrespondierenden Komponenten zerlegt (id est: Start/End/Special-Tag, Content) und abschließend als DOM repräsentiert.

      Funktioniert_relativ schnell und solide.

      Git ist aktualisiert.

      Der Tokenizer unter "Analysis"-Namespace zu finden: github.com/NET-D3v3l0p3r/HTMLR…LRenderer/Parser/Analysis
      _
      Und Gott alleine weiß alles am allerbesten und besser.