Christian_: Fehlerhafte (nicht abgeschlossene) HTML Tags entfernen

Hallo,

ich bin auf der Suche nach einem Skript, dass fehlerhafte HTML Tags aus einem String entfernt. Zur Verdeutlichung ein Beispiel:

Aus dem String 'ABC, 123, <img src="images/bild.'

soll werden 'ABC, 123, '

Im Internet habe ich diverse Skripte gefunden, die nicht geschlossene Tags abschließen, in diesem Fall hilft mir das jedoch wenig, da ich nicht möchte, dass beispielsweise, fehlerhafte Bilder angezeigt werden.

Mein gedanklicher Ansatz ist folgender:

1. In dem String muss der erste offene Tag gesucht werden.
2. Es muss überprüft werden ob nach diesem ersten offenen Tag weitere Tags geöffnet wurden bevor der erste Tag geschlossen wurde.
2.1. Ist das der Fall wird erst nach dem zweiten offenen Tag gesucht und geschaut ob nach dem weitere Tags geöffnet werden bevor es geschlossen wird. (Das wird ein rekursiver Aufruf)
Wird dann ein offener Tag geschlossen, beendet sich ein rekursiver Aufruf und das Skript geht wieder einen Tag zurück und überprüft ob der Tag geschlossen wird.
Sollte der ursprüngliche Tag nicht geschlossen werden, wird alles ab dort gelöscht.
3. Es wird nach dem zweiten offenen Tag gesucht, nachdem der erste geschlossen wurde.

usw...

Ich hoffe mir kann dabei jemand helfen.

Liebe Grüße
Christian

  1. Hallo nochmal,

    anscheinend habe ich nicht genau genug gesucht, ich habe doch mehrere Ergebnisse im Netz gefunden. Leider hat keiner dazu eine passende Lösung gefunden. Hier ein paar Aufzählung von Links die sich mit dem Thema beschäftigen:

    http://www.easy-coding.de/text-logisch-abschneiden-t4268.html
    http://forum.de.selfhtml.org/archiv/2009/2/t182831/
    http://www.php.de/php-einsteiger/43376-text-kuerzen-kein-problem-aber-mit-html.html <- hier wird sogar ein Parser vorgestellt, der aber leider nicht richtig funktioniert.

    Liebe Grüße
    Christian

  2. Hallo Christian,

    Dein Problem lässt sich relativ einfach mit den üblichen String-Funktionen beheben. Erst teilst Du den String mit explode() bei jedem '<' auf. Du erhältst ein array, welches Du mit einer Schleife und strpos() nach '>' durchsuchen kannst. Ist es nicht enthalten, wird das Array-Element gelöscht.

    Dieses recht simple Vorgehen hat allerdings auch zwei Schönheitsfehler. Zum einen schlägt das ganze bei Kommentaren, die ihrerseits Elemente umschließen (so z. B. <!-- <br/> -->), fehl. Zum anderen kannst Du ohne erheblichen Mehraufwand nicht feststellen, wo das fehlerhaft notierte Element endet, was nach der obigen Vorgehensskizze und einem String '<img src="images/bild. ABC, 123, ' zum Verlust von Daten führt (in dem Fall ' ABC, 123, ').

    Gruß aus Berlin!
    eddi

  3. @@Christian_:

    nuqneH

    ich bin auf der Suche nach einem Skript, dass fehlerhafte HTML Tags aus einem String entfernt.

    Was ist dein Problem, das du damit lösen willst?

    Qapla'

    --
    Alle Menschen sind klug. Die einen vorher, die anderen nachher. (John Steinbeck)
  4. Hallo,

    danke Edgar, ich habe mal etwas versucht. Leider funktioniert es noch nicht ganz. Was mache ich falsch?

      
    function checktags($string, $index = 0) {  
      
    	$opentagpos   = strpos($string,'<', $index);  
    	$index		  = $opentagpos + 1;  
    	  
    	$closetag1pos = strpos($string,'</',$index);  
    	$closetag2pos = strpos($string,'>',$closetag1pos);  
    	  
    	if($closetag1pos !== false and $closetag2pos !== false) {  
    		$inneropentag = strpos($string, '<', $index);  
    		if(($inneropentag < $closetag1pos)) {  
    			$array = checktags($string, $inneropentag);  
    			$closetag1pos = strpos($array[0],'</',$array[1]);  
    			$closetag2pos = strpos($array[0],'>',$closetag1pos);  
    			$inneropentag = strpos($array[0], '<', $array[1]);  
    			if($closetag1pos === false and $closetag2pos === false)  
    				return array(substr($array[0],$opentagpos-1),$opentagpos-1);  
    			elseif($inneropentag === false)  
    				return array($array[0]);  
    			else  
    				checktags($array[0],$inneropentag);  
    		} else {  
    			return array($string, $closetag2pos);  
    		}  
    	} else  
    		return array(substr($string,$opentagpos-1),$opentagpos-1);  
    }  
      
    $string = '<a><b></b></a>';  
      
    $array = checktags($string);  
    echo $array[0];  
    
    
    1. Hi!

      ich habe mal etwas versucht. Leider funktioniert es noch nicht ganz. Was mache ich falsch?

      *seufz* Zwei Dinge werden ewig bestehen bleiben: "Funktioniert nicht"-Fehlerbeschreibungen und unkommentierter Code.

      Was konkret erwartest du bei welchen Eingabedaten, was erhältst du stattdessen als Ergebnis und an welcher Stelle genau haben welche Variablen andere Werte (oder Ausdrücke andere Ergebnisse) als von dir vorgesehen?

      Lo!

      1. Hallo,

        auch wenn es eine umständlcieh Lösung ist, sie funktioniert:

          
        // Der Index bestimmt die aktuelle Prüfposition im String  
        function checktags($string, $index = 0) {  
        	// Position des ersten Tags '<' Bestimmen  
        	$opentagpos   = strpos($string,'<', $index);  
        	  
        	// Wenn ein öffnendes Tag vorhanden ist  
        	if($opentagpos !== false) {  
        		$index		  = $opentagpos + 1;  
        		  
        		// Position der ersten Schluss Tags '</' und '>' bestimmen  
        		$closetag1pos = strpos($string,'</',$index);  
        		$closetag2pos = strpos($string,'>',$closetag1pos);  
        		  
        		// Position des ersten selbstschließenden Schlusstags bestimmen  
        		$selfclosetag = strpos($string,'/>',$index);  
        		  
        		// Wenn ein Schlusstag vorhanden ist  
        		if($selfclosetag !== false or (($closetag1pos !== false) and ($closetag2pos !== false))) {  
        			  
        			// Position des nächsten offenen Tags '<' bestimmen  
        			$inneropentag = strpos($string, '<', $index);  
        			  
        			// Abfrage nach erstem Schluss-Tag: '>' oder '/>'  
        			if($selfclosetag !== false and $selfclosetag < $closetag2pos)  
        				$firstclosedtag = $selfclosetag;  
        			else // firstclosedtag wird auf das Erste schließende Tag gesetzt	  
        				$firstclosedtag = $closetag2pos;  
        			  
        			  
        			// Position des nächsten offenen Tags nach dem ersten schließenden Tag bestimmen  
        			$nexttag	  = strpos($string,'<',$firstclosedtag);  
        			  
        			// Wenn das nächste offene Tag noch vor dem ersten schließenden Tag vorkommt. Bsp: <a><b></b></a> oder <a><img /></a>  
        			//																					  ^					  ^	  
        			if(($inneropentag < $closetag1pos and $firstclosedtag == $closetag2pos) or ($inneropentag < $selfclosetag and $firstclosedtag == $selfclosetag)) {  
        				$array 		  = checktags($string, $inneropentag);		// Dann erst das verschachtelte offene Tag überprüfen -> Rekursiver Selbstaufruf  
        				$closetag1pos = strpos($array[0],'</',$array[1]);		// Anschließend überprüfen ob das umfassende Tag geschlossen ist  
        				$closetag2pos = strpos($array[0],'>',$closetag1pos);  
        				$nexttag	  = strpos($array[0], '<',$closetag2pos);	// Position des nächsten Tags nach dem Schluss-Tag des umfassenden Tags bestimmen  
        				  
        				if($closetag1pos === false or $closetag2pos === false)	// Sollte das umfassende Tag nicht geschlossen sein wird der String ab der Position  
        					if($opentagpos == 0)								// des umfassenden Tags gekürzt  
        						return array('',0);  
        					else  
        						return array(substr($array[0],0,$opentagpos),$opentagpos-1);  
        				elseif($nexttag !== false and $nexttag!== strpos($array[0],'</',$closetag2pos)) // Sollte ein weiteres Tag nach dem Schluss-Tag folgen  
        					return checktags($array[0],$nexttag);										// wird es überprüft -> Rekursion	  
        				else  
        					return array($array[0], $closetag2pos);										// Sonst wird der String ausgegeben  
        					  
        			//	Sollte kein verschachteltes Tag vorkommen, wird überprüft ob nach dem ersten Schlusstag ein weiteres offenes Tag folgt	  
        			} elseif($nexttag !== false and $nexttag !== strpos($string,'</',$firstclosedtag))  
        				return checktags($string,$nexttag); 		// Falls ja, wird es überprft  
        			  else  
        			  	return array($string, $firstclosedtag+1);	// Sonst wird der String zürckgegeben  
          
        		// Sollte es kein Schluss-Tag geben wird der String ab der Position des offenen Tags gelöscht.  
        		} elseif($opentagpos == 0)  
        			return array('',0);  
        		  else  
        			return array(substr($string,0,$opentagpos),$opentagpos-1);  
        	// Wenn kein öffnendes Tag vorhanden ist -> String ausgeben ohne Änderung  
        	} else  
        		return array($string);  
        }  
        
        

        Ich hoffe es ist ausreichend kommentiert.

        Gruß
        Christian

    2. Hallo Christian,

      function checktags($string, $index = 0) {

      $opentagpos   = strpos($string,'<', $index);
      $index       = $opentagpos + 1;

      $closetag1pos = strpos($string,'</',$index);
      $closetag2pos = strpos($string,'>',$closetag1pos);

        
      bereits hier machst Du Dir unnötige Umstände. Es reicht aus, die erste und zweite Postion von '<' zu ermitteln und nur diese Teilzeichenkette zu betrachen:  
        
      `$segment=substr($string,($p=strpos($string,'<')+1),strpos($string,'<',$p)-$p);`{:.language-php}  
        
      In `$segment`{:.language-php} muss denn nur noch nach einem '>' gesucht werden.  
      So würde man dann Teilzeichenkette für Teilzeichenkette abarbeiten, nur warum sollte man das tun? explode() nimmt einem diese Arbeit ganz komfortabel ab:  
        
      ~~~php
      $array=explode('<',$string);  
        
      foreach($array as $v)  
         if(strpos($v,'>')===false)  
            echo "fehlerhaften Tag gefunden in '$v'\n";
      
      if($closetag1pos !== false and $closetag2pos !== false) {  
      
        $inneropentag = strpos($string, '<', $index);  
        if(($inneropentag < $closetag1pos)) {  
        	$array = checktags($string, $inneropentag);  
        	$closetag1pos = strpos($array[0],'</',$array[1]);  
        	$closetag2pos = strpos($array[0],'>',$closetag1pos);  
        	$inneropentag = strpos($array[0], '<', $array[1]);  
        	if($closetag1pos === false and $closetag2pos === false)  
        		return array(substr($array[0],$opentagpos-1),$opentagpos-1);  
        	elseif($inneropentag === false)  
        		return array($array[0]);  
        	else  
        		checktags($array[0],$inneropentag);
      
        
      Bei Deinem String `'<a><b></b></a>'`{:.language-php} greift Deine erste Bedingung `($closetag1pos === false and $closetag2pos === false)`{:.language-php} nicht. Auch Deine zweite Bedingung `($inneropentag === false)`{:.language-php} greift nicht. Es wird also getan, was unter `else`{:.language-php} angegeben ist. Vermutlich muss es da `return checktags($array[0],$inneropentag);`{:.language-php} heißen. So jedenfalls wird nichts ausgegeben. Testausgaben, so z. B. mit `print_r(array($closetag1pos,$closetag2pos,$inneropentag));`{:.language-php}, vor den Bedingungen hätten Dir hier helfen können, den Flüchtigkeitsfehler zu finden.  
        
      Aber wie geschrieben, halte ich Deine Ansatz für umständlich und auch nicht wirklich zielführend, wenn ich mir die Ausgabe mit dem eingefügten `return`{:.language-php} ansehe. Nutze lieber explode()!  
        
        
      Gruß aus Berlin!  
      eddi