Simpler HTML2DOM Ansatz

    • C#

      Simpler HTML2DOM Ansatz

      HTML ist eine sogenannte Auszeichnungssprache, das ist, der Text wird (syntaktisch) so strukturiert, dass sie in erster Linie maschinenlesbar wird, aus der dann' ein Parser ein sogenanntes Document Object Model (DOM) generiert. Das DOM ist dabei hierarchisch aufgebaut (Wurzel, Eltern/Kind-Knoten) und erleichtert weiterführende Operationen wie das Rendern der Webseite, oder der Manipulation (etwa mit Javascript) von Inhalten eben dieser Webseite.

      Im Folgenden soll ein simpler DOM Generator präsentiert werden.

      Der Prozess besteht aus zwei Phasen:

      In der ersten Phase wird der Text tokenisiert (die Zeichen also in logische Einheiten zusammengefasst, im gewissen Sinne kontextualisiert):

      Spoiler anzeigen

      C#-Quellcode

      1. enum TokenType
      2. {
      3. BracketOpen,
      4. BracketClose,
      5. Slash,
      6. String
      7. }
      8. struct Token
      9. {
      10. public TokenType TokenType { get; set; }
      11. public string? Data { get; set; }
      12. }
      13. IEnumerable<Token> Tokenize(string html)
      14. {
      15. Dictionary<char, TokenType> TOKENS = new Dictionary<char, TokenType>()
      16. {
      17. { '<', TokenType.BracketOpen },
      18. { '>', TokenType.BracketClose },
      19. { '/', TokenType.Slash },
      20. };
      21. StringBuilder charAccum = new StringBuilder();
      22. bool bracketOpened = false;
      23. int whiteSpaces = 0;
      24. for (int i = 0; i < html.Length; i++)
      25. {
      26. var c = html[i];
      27. if (TOKENS.ContainsKey(c))
      28. {
      29. if (charAccum.Length > 0)
      30. {
      31. yield return new Token()
      32. {
      33. Data = charAccum.ToString(),
      34. TokenType = TokenType.String
      35. };
      36. charAccum.Clear();
      37. }
      38. yield return new Token()
      39. {
      40. Data = c + "",
      41. TokenType = TOKENS[c]
      42. };
      43. if (c == '<')
      44. bracketOpened = true;
      45. else if (c == '>')
      46. bracketOpened = false;
      47. }
      48. else
      49. {
      50. if (bracketOpened && c == ' ')
      51. {
      52. if (whiteSpaces > 0)
      53. {
      54. continue;
      55. }
      56. else whiteSpaces++;
      57. }else if (bracketOpened && c!=' ')
      58. {
      59. whiteSpaces = 0;
      60. }
      61. charAccum.Append(c);
      62. }
      63. }
      64. }


      Im zweiten Schritt wird' aus den Tokens dann das DOM generiert:
      Spoiler anzeigen

      C#-Quellcode

      1. class HtmlNode
      2. {
      3. private StringBuilder _attributeBuilder = new StringBuilder();
      4. private StringBuilder _contentBuilder = new StringBuilder();
      5. public List<HtmlNode> Children { get; private set; } = new List<HtmlNode>();
      6. public string? Type { get; private set; }
      7. public string Attribute => _attributeBuilder.ToString();
      8. public string Content => _contentBuilder.ToString();
      9. public void PutAttribute(string? data)
      10. {
      11. string[]? args = data?.Split(' ');
      12. if (args == null) return;
      13. Type = args[0];
      14. _attributeBuilder.Append(string.Join(' ', args.Skip(1)));
      15. }
      16. public void PutContent(string? data)
      17. {
      18. _contentBuilder.Append(data);
      19. }
      20. public override string ToString()
      21. {
      22. return $"[{Type}] {Content}";
      23. }
      24. }
      25. HtmlNode Html2DOM(IEnumerable<Token> tokens)
      26. {
      27. HtmlNode root = new HtmlNode();
      28. Stack<HtmlNode> nodeStack = new Stack<HtmlNode>();
      29. var enumerator = tokens.GetEnumerator();
      30. while (enumerator.MoveNext())
      31. {
      32. var token = enumerator.Current;
      33. if (token.TokenType == TokenType.BracketOpen)
      34. {
      35. enumerator.MoveNext();
      36. HtmlNode currentNode = new();
      37. if(enumerator.Current.TokenType == TokenType.Slash)
      38. {
      39. nodeStack.Push(root);
      40. enumerator.MoveNext();
      41. var endingType = enumerator.Current.Data;
      42. var endingTag = nodeStack.Pop();
      43. List<HtmlNode> nodeAccumulator = new List<HtmlNode>();
      44. while (endingTag.Type != null && !endingTag.Type.Equals(endingType))
      45. {
      46. nodeAccumulator.Add(endingTag);
      47. endingTag = nodeStack.Pop();
      48. }
      49. foreach (var child in nodeAccumulator)
      50. {
      51. endingTag.Children.Add(child);
      52. }
      53. root = nodeStack.Pop();
      54. root.Children.Add(endingTag);
      55. }
      56. else
      57. {
      58. nodeStack.Push(root);
      59. currentNode.PutAttribute(enumerator.Current.Data);
      60. root = currentNode;
      61. }
      62. continue;
      63. }
      64. if (token.TokenType == TokenType.BracketClose) continue;
      65. root.PutContent(token.Data);
      66. }
      67. return root;
      68. }



      Möglicher Aufruf:

      C#-Quellcode

      1. var dom = Html2DOM(Tokenize("<html><head><link a=b></head><body><p>Test</p><b>Test</b></body>"));


      Viel Spaß.
      Und Gott alleine weiß alles am allerbesten und besser.