Tobias Pelster: Verzeichnisarray sortieren

Hallo nochmal!
Anknüpfend an diesen Thread habe ich es nun hinbekommen, daß meine Funktion die Verzeichnisse als Array ausgibt. So weit so gut. Aber wenn ich nun sort($array) darüber laufen lasse, wird natürlich zum einen nur die erste Ebene sortiert, und zum anderen berücksichtigt die Sortierung auch den Wert "Array"... Somit kommt die Ausgabe:

  
Array  
(  
    [0] => /Testordner  
    [1] => /zzzzz  
    [2] => Array  
        (  
            [0] => /zzzzz/dsfdsfgdsfg  
            [1] => /zzzzz/asdasd  
        )  
  
    [3] => /Ordnerggg  
    [4] => Array  
        (  
            [0] => /Ordnerggg/lala  
            [1] => Array  
                (  
                    [0] => /Ordnerggg/lala/aaa  
                    [1] => /Ordnerggg/lala/lala2  
                )  
  
        )  
  
)  

Mit array_multisort ist es ebenfalls so, daß die Unterordner nach unten gepackt werden (da ja der / vor dem "Array" kommt).

Wie kann ich das Array sortieren, so daß die Unterordner direkt unter dem Ursprungsordner angezeigt werden?

Eine andere Frage ist, wie ich nach Erfolg das Array gescheit als Liste ausgebe. Aber erstmal kleine Schritte machen ;-)

Hat jemand eine Idee?
Tobi

  1. hi,

    Wie kann ich das Array sortieren, so daß die Unterordner direkt unter dem Ursprungsordner angezeigt werden?

    Da du eine rekursive Struktur aufgebaut hast, solltest du m.E. auch das Sortieren rekursiv durchführen.

    Eine andere Frage ist, wie ich nach Erfolg das Array gescheit als Liste ausgebe.

    Rekursion, erneut.

    gruß,
    wahsaga

    --
    /voodoo.css:
    #GeorgeWBush { position:absolute; bottom:-6ft; }
    1. Hi wahsaga!

      Ja stimmt. Natürlich muß ich das Teil rekursiv durchlaufen. Aber selbst

        
      array_multisort($array, SORT_ASC);  
      
      

      berücksichtigt anscheinend dieses "Array". Es kommt die Ausgabe

        
      Array  
      (  
          [0] => /Ordnerggg  
          [1] => /Testordner  
          [2] => /zzzzz  
          [3] => Array  
              (  
                  [0] => /Ordnerggg/lala  
                  [1] => Array  
                      (  
                          [0] => /Ordnerggg/lala/aaa  
                          [1] => /Ordnerggg/lala/lala2  
                      )  
        
              )  
        
          [4] => Array  
              (  
                  [0] => /zzzzz/dsfdsfgdsfg  
                  [1] => /zzzzz/asdasd  
              )  
        
      )  
      
      

      Mein anderer Ansatz war, alle Werte in ein nicht-rekursives Array zu packen, zu sortieren

        
      Array  
      (  
          [0] => /Ordnerggg  
          [1] => /Ordnerggg/lala  
          [2] => /Ordnerggg/lala/aaa  
          [3] => /Ordnerggg/lala/lala2  
          [4] => /Testordner  
          [5] => /zzzzz  
          [6] => /zzzzz/asdasd  
          [7] => /zzzzz/dsfdsfgdsfg  
      )  
      
      

      und es dann mit foreach(array as element) --> while(strtok($element, "/") ) ... wieder zu zerlegen. Nur dann denke ich ist es schwierig, mit HTML-Listen zu arbeiten, denn woher soll das einzelne Element wissen, das es nicht noch ein zweites in dieser Ebene gibt und das <ul> auf/ oder zumachen soll... :-/

      Mein Ansatz für die Zerlegung

        
       $schubidu = "jetzt gehts los <br />";  
       foreach ( $tree as $element ) {  
        $schubidu .= "neues Element: ";  
        $tok = strtok($element, "/");  
        while ($tok !== false) {  
         $schubidu .= "Token=$tok, ";  
         $tok = strtok("/");  
        }  
        $schubidu .= "<br />";  
       }  
       $schubidu .= "Nu is aber schluß<br />";  
      
      

      Was ist denn besser geeignet? in einer Array-ebene bleiben oder es rekursiv anpacken?
      Tobi

      1. *snif* Keiner eine weitere Idee?
        Vielleicht einen Link zu einer fertigen Funktion? Die, die ich selbst gefunden habe, waren entweder nicht rekursiv oder interessieren sich nicht um die Sortierung, sondern hauen die Einträge direkt raus.
        Tobi

        1. Hallo Tobias Pelster,

          *snif* Keiner eine weitere Idee?

          Was mir an deinem Ergebnis-Array nicht gefällt, ist seine "Kompliziertheit". Da die Keys in PHP-Arrays ja bekanntlich Strings enthalten, eignen sie sich prima, um Datei- oder Verzeichnisnamen zu speichern. Ich denke mir das jetzt mal so:

          Die Values enthalten entweder nichts (oder einen Dummy-Eintrag), wenn sie ein "Nicht-Verzeichnis" darstellen sollen -- also meistens Dateien.

          Die Values enthalten ein (Unter-)Array, wenn sie ein Verzeichnis darstellen sollen.

          Das ist einfacher als dein Entwurf (IMHO). Außerdem sortiere ich die Einträge gleich, wenn sie eingelesen wurden und nicht erst bei der Ausgabe.

          Sollte dir die Sortierreihenfolge nicht gefallen, kannst du ja statt ksort() uksort benutzen und dir die passende Vergleichsfunktion selbst basteln.

          Vielleicht einen Link zu einer fertigen Funktion? Die, die ich selbst gefunden habe, waren entweder nicht rekursiv oder interessieren sich nicht um die Sortierung, sondern hauen die Einträge direkt raus.

          Kein Link, sondern was aus meiner PHP-Code-Restekiste:

            
          function scanr_dir(  
            $dir_name  
          ) {  
            // Notfalls den letzen Verzeichnistrenner entfernen  
            // (außer wir haben '/' übergeben bekommen)  
            $dir_name = preg_replace('/[^\A]'.preg_quote(DIRECTORY_SEPARATOR, '/').'\Z/', '', $dir_name);  
            $dh = @opendir($dir_name);  
            if(FALSE === $dh) exit( sprintf('Verzeichnis "%s" gibts gar nicht!', $dir_name) );  
            // einlesen  
            $dir_list = array(); // Falls das Verzeichnis mal leer ist  
            while($dir_entry = @readdir($dh) ) {  
              if('.' == $dir_entry || '..' == $dir_entry) continue;  
              if( is_dir($dir_name.DIRECTORY_SEPARATOR.$dir_entry) ) $dir_list[$dir_entry] = array();  
              else $dir_list[$dir_entry] = 'file';  
              // statt 'file' kannst du hier auch Informationen zur Datei zwischenlagern  
            }  
            
            //Nach den Namen sortieren  
            ksort($dir_list);  
            
            foreach($dir_list as $key => $val) {  
              printf('k(%s) v(%s)<br />', $key, $val);  
              // Rekursion:  
              if( is_array($val) )  
                $dir_list[$key] = scanr_dir( $dir_name.DIRECTORY_SEPARATOR.$key );  
            }  
            return($dir_list);  
          }  
          
          

          Die Ausgabe, oder noch besser: "Das Rendering in HTML" B-), erfolgt in
          einer separaten Funktion.

          Und weil wir hier bei SELFHTML sind, hab ich dir noch eine Aufgabe übriggelassen: Es werden nämlich im Moment nur die Teilpfade angezeigt.
          Willst du für jeden Eintrag den ganzen Pfad anzeigen, musst du noch ein wenig basteln -- z.B. Der Funktion noch einen zusätzlichen Parameter
          $aktueller_basispfad mitgeben ...)

            
          function html_make_dir_list(  
            $dir_list  
          ) {  
            $html = '<ul>';  
            foreach($dir_list as $key => $val) {  
              if( is_array($val) ) $html .= sprintf('<li>[%s] %s</li>', $key, html_make_dir_list($val) );  
              else $html .= sprintf('<li>%s</li>', $key);  
            }  
            $html .= '</ul>';  
            return($html);  
          }  
          
          

          Das ganze in Aktion (zur Probe):

            
          $dirs = scanr_dir('irgendein_pfad');  
          // Die Liste als "rekursives" Array  
          printf('<pre>%s</pre>', print_r($dirs, 1) );  
            
          // Der HTML-Output:  
          printf('<hr />%s', html_make_dir_list($dirs) );  
          
          

          MffG
          EisFuX

          --
          Auch meine Hosenträger sind intelligent, in dem Sinne, dass man sie regulieren kann. Sie besitzen ein adaptives Verhalten.
          Stanisław Lem
          1. Kleiner Nachtrag:

            ...
              foreach($dir_list as $key => $val) {
                printf('k(%s) v(%s)<br />', $key, $val);
                // Rekursion:
                if( is_array($val) )
                  $dir_list[$key] = scanr_dir( $dir_name.DIRECTORY_SEPARATOR.$key );
              }
            ...

            Das printf() hat natürlich in dieser Schleife nichts zu suchen -- ich sollte wirklich mal einen Debugger benutzen ...

            Besser ist das so:

              
            ...  
              foreach($dir_list as $key => $val) {  
                // Rekursion:  
                if( is_array($val) )  
                  $dir_list[$key] = scanr_dir( $dir_name.DIRECTORY_SEPARATOR.$key );  
              }  
            ...  
            
            

            MffG
            EisFuX

            --
            Auch meine Hosenträger sind intelligent, in dem Sinne, dass man sie regulieren kann. Sie besitzen ein adaptives Verhalten.
            Stanisław Lem
            1. Hey super. Danke, EisFuX. Werds gleich nach meiner Nachmittagspause mal testen und über das Ergebnis berichten.

              Bin aber zuversichtlich :-D
              Danke nochmal.
              Tobi

              1. Lieber Tobias,

                ich verwende exakt für Dein Prpblem eine andere Array-Struktur, die sich wunderbar leicht sortieren lässt.

                Mein Verzeichnisarray sieht so aus:

                Array(  
                 [ordner1] => Array(  
                               [unterordner1] => Array(  
                                                  [Datei1.dat] =>  
                                                  [Datei2.dat] =>  
                                                 )  
                               [Datei3.dat] =>  
                               [Datei4.dat] =>  
                              )  
                 [ordner_leer] => Array ()  
                 [ordner3] => Array(  
                               [Datei5.dat] =>  
                              )  
                 [Datei6.dat] =>  
                )
                

                Das Prinzip lässt sich in etwa so zusammenfassen: Datei-/Ordnername steht im Key eines Array-Elements, bei Ordnern steht der Inhalt als neues Array im Value, bei Dateien bleibt der Value leer. Die Unterscheidung zwischen Datei und Verzeichnis ist anhand des Values möglich: Ist Value leer, so ist es eine Datei, ist Value ein Array, so ist es ein Ordner; ist es weder noch, so liegt ein Fehler vor.

                Eine Sortierfunktion ist auch schnell gemacht: Sie prüft in einem Array (Verzeichnis) die Values zuerst auf is_array(), denn diese wandern nach oben, dann erst auf strnatcmp().

                Liebe Grüße aus Ellwangen,

                Felix Riesterer.

                1. Hallo nochmal!
                  Ich habe es nun gemacht wie ihr (und fur allem EisFuX) es empfohlen habt. Nach ein paar Anpassungen läuft es nun auch wunderbar. Vielen herzlichen Dank!.

                  Hier noch mal für Interessierte der Code.
                  (Und vielleicht findet der eine oder andere ja noch einen Fehler oder hat ne Anregung. Dann nur her damit.:-))

                    
                  /**  
                  * scanr_dir()  
                  * Erzeugt rekursiv ein Array mit allen in dem übergebenden  
                  * Ordner enthaltenden Elementen.  
                  * Dabei ist jeder Schlüsselname der Dateiname eines Elementes.  
                  * Ordner haben als Wert den Ordnerinhalt als Array,  
                  * Dateien haben als Wert die Kennung "file"  
                  * @param dir_name Pfad zum auslesen  
                  * @return Array mit dem Ordnerinhalt  
                  */  
                  function scanr_dir( $dir_name ) {  
                   // Notfalls den letzen Verzeichnistrenner entfernen  
                   // (außer es wurde '/' übergeben)  
                   $dir_name = preg_replace( '/[^\A]'.preg_quote('/', '/').'\Z/', '', $dir_name );  
                   $dh = @opendir( $dir_name );  
                   if( FALSE === $dh ) exit( sprintf( 'Verzeichnis "%s" gibts gar nicht!', $dir_name ) );  
                   // einlesen  
                   $dir_list = array(); // Falls das Verzeichnis mal leer ist  
                   while( $dir_entry = @readdir( $dh ) ) {  
                    if( '.' == $dir_entry || '..' == $dir_entry ) continue;  
                    if( is_dir( $dir_name.DIRECTORY_SEPARATOR.$dir_entry ) )  
                     $dir_list[ $dir_entry ] = array();  
                    else  
                     $dir_list[ $dir_entry ] = 'file';  
                   }  
                   //Nach den Namen sortieren  
                   ksort( $dir_list );  
                    
                   foreach( $dir_list as $key => $val ) {  
                    // Rekursion:  
                    if( is_array( $val ) )  
                     $dir_list[ $key ] = scanr_dir( $dir_name.'/'.$key );  
                   }  
                   return( $dir_list );  
                  }  
                    
                  /**  
                  * html_make_dir_list()  
                  * Erzeugt aus einem Array mit Verzeichnisinformationen  
                  * rekursiv einen Verzeichnisbaum in Form einer  
                  * unsortierten Liste.  
                  * @param dir_list Array mit dem Verzeichnisbaum  
                  * @param $currPath Hilfsvariable  
                  * @return HTML-Code eines Verzeichnisbaums  
                  */  
                  function html_make_dir_list( $dir_list, $currPath = '' ) {  
                   if ( $currPath == '' ) // Wird Funktion zum 1. Mal aufgerufen: Erzeuge Root-Link  
                    $html = '<ul><li><a href="http://...?action=list&path=">'.LANG_DOCROOT.'</a><ul>';  
                   else  
                    $html = '<ul>';  
                   foreach( $dir_list as $key => $val ) { // Pro Array-Eintrag  
                    if( is_array( $val ) ) {  
                     $html .= sprintf( '<li><a href="http://...?action=list&path=%s">%s</a> %s</li>', ( $currPath.'/'.$key ), $key, html_make_dir_list( $val, $currPath.'/'.$key ) );  
                    } else {  
                     //$html .= sprintf('<li>%s</li>', $key); // Dateien wollen wir nicht anzeigen  
                    }  
                   }  
                   if ( $currPath == '' )  
                    $html .= '</ul></li></ul>';  
                   else  
                    $html .= '</ul>';  
                   if ( $html == '<ul></ul>' )  
                    return '';  
                   else  
                    return $html;  
                  }  
                    
                  // Aufruf:  
                  $dirs = scanr_dir( CONFIG_DOCS_PATH );  
                  $dir2ul = html_make_dir_list( $dirs );  
                  return $dir2ul;  
                  
                  

                  Ich mach jetzt Feierabend. Und morgen geht's daran, die Liste mit JavaScript optisch aufzupeppen (Auf- und Zuklappen der Ordner usw.)
                  Vielen Dank nochmal an alle!
                  Tobi

                  1. Hi,

                    Und morgen geht's daran, die Liste mit JavaScript optisch aufzupeppen (Auf- und Zuklappen der Ordner usw.)

                    etwa so? ;-)

                    freundliche Grüße
                    Ingo

                    1. Lieber Ingo,

                      vielleicht dann doch eher so. Immerhin soll es ein Verzeichnis-Listing werden.

                      Liebe Grüße aus Ellwangen,

                      Felix Riesterer.

                      1. Guten Morgen ihr beiden!
                        Eher so.  Läuft auch schon recht gut. Mein Problem ist nur, daß dieses Script die aktuelle URL mit den Links im Verzeichnisbaum vergleicht und so entscheidet, welcher Ordner als "aktiv" markiert werden soll. Das ist natürlich einerseits ganz geschickt gemacht. Aber meine URLs sehen in etwa so aus:
                        http://...php?path=/Ordnerggg&action=list
                        Da sich der Action-Wert ändert, ist die URI dann nicht mehr gleich dem Verzeichnislink und das Script klappt alles wieder ein. Ich muß also ne möglichkeit finden, dem Script zu sagen er soll nur den GET-Parameter 'path' beachten.
                        Ich denke ich muß im Jscript an dieser Stelle was verändern:

                          
                            /** Prüfen zweier Links auf Übereinstimmung */  
                            function is_active_link(strUrl)  
                            {  
                                arrUrls = new Array(strUrl,String(eval(objTargetWindow + '.location')));  
                                for(u = 0; u < arrUrls.length; ++u)  
                                    {  
                                    if (blnIgnoreAnchor)  
                                        {  
                                        arrUrls[u] = arrUrls[u].replace(/#.*?$/, '');  
                                        }  
                                    if (blnIgnoreQuery)  
                                        {  
                                        arrUrls[u]=arrUrls[u].replace(/\?[^#]*/g, '');  
                                        }  
                                    }  
                                return(arrUrls[0]==arrUrls[1]);  
                            }  
                        
                        

                        Doch von JavaScript habe ich so gar keine Ahnung. Gibt es überhaupt GET-Parameter unter Jscript? :-D
                        Grüße
                        Tobi

                        1. hi,

                          Doch von JavaScript habe ich so gar keine Ahnung. Gibt es überhaupt GET-Parameter unter Jscript? :-D

                          Nein, es gibt nur location.search - darin findest du den Querystring inklusive dem einleitenden Fragezeichen.
                          (Es gibt auch noch document.URL, da stünde der komplette URL drin - aber das ist ja noch mehr, als du brauchst.)

                          Wenn du also

                          http://...php?path=/Ordnerggg&action=list

                          hast, enthält location.search den Wert "?path=/Ordnerggg&action=list" - und aus dem musst du dir das gewünschte extrahieren. Stringfunktionen bieten sich an, ggf. lässt sich mit split() etwas erreichen.

                          Mein Problem ist nur, daß dieses Script die aktuelle URL mit den Links im Verzeichnisbaum vergleicht und so entscheidet, welcher Ordner als "aktiv" markiert werden soll.

                          Musst du diese Entscheidung denn überhaupt Javascript-seitig treffen?
                          Kannst du nicht schon serverseitig das Listenelement, welches das aktuelle Verzeichnis darstellt, entsprechend auszeichnen - z.B. mit einer ID oder Klasse?

                          gruß,
                          wahsaga

                          --
                          /voodoo.css:
                          #GeorgeWBush { position:absolute; bottom:-6ft; }
                          1. Hi wahrsaga!

                            Musst du diese Entscheidung denn überhaupt Javascript-seitig treffen?
                            Kannst du nicht schon serverseitig das Listenelement, welches das aktuelle Verzeichnis darstellt, entsprechend auszeichnen - z.B. mit einer ID oder Klasse?

                            Hm. Stimmt, würde auch gehen. Doch mir gefällt die Idee, ein wenig Rechenzeit an den User abzudrücken ;-). Und es dürfe doch an sich gleich schwierig sein, ob ich nun unter Jscript die URL nach dem Pfadnamen durchforste (das mit split schau ich mir mal an) oder die Liste (mit getelementbyid).. Wie gesagt, Javascript ist nicht so meine Welt...
                            Mittlerweile bin ich auch aus der geposteten Funktion schlauer geworden: blnIgnoreAnchor und blnIgnoreQuery sind konfigurationsvariablen zum entscheiden ob URL-Anker oder GET-Parameter ignoriert werden; und die eh false sind. somit wird das innere der if-schleifen nie aufgerufen. Aber da drin steht ja schon der Ansatz. Da bei mir "path" wenn überhaupt am Anfang der Parameter steht, muß ich nur dafür sorgen daß die Funktion den Rest der URL ab dem "&"-Zeichen abschneidet....und dann...
                            ...ich muss weg... :-D bis gleich
                            Tobi

                            1. Geil, das klappt!

                              Zugegeben, ein wenig unsicher ist die Funktion schon,denn nun muss immer der Phad als erstes in der URL angegeben sein. Aber wenn ich das beachte, klappt wunderbar....

                              Ich hab einfach in der Funktion noch eine weitere Aktion

                                
                              if (blnIgnoreAfterAmp)  
                                {  
                                arrUrls[u]=arrUrls[u].replace(/\&[^#]*/g, '');  
                              }  
                              
                              

                              eingefügt, welche alles nach dem & abschnibbelt. So läufts.
                              Ha. Mehr wollte ich gar nicht. Danke für die Anregung!!
                              Tobi

                              1. Lieber Tobias,

                                wenn Du Dir den Download-Ordner unserer Schulwebsite einmal ohne Javascript angesehen hättest, dann wüßtest Du mehr darüber, denn er ist barrierefrei! User ohne verfügbares Javascript können ihn uneingeschränkt nutzen.

                                Der Baum ist als verschachtelte <ul> aufgebaut. Probiere einmal diese Seite ohne CSS aus!

                                Liebe Grüße aus Ellwangen,

                                Felix Riesterer.

      2. Hi,

        Mein anderer Ansatz war, alle Werte in ein nicht-rekursives Array zu packen, zu sortieren

        fast gut... nimm doch hierzu im ersten Schritt einfach nur die Namen - egal ob Datei oder Verzeichnis - der aktuellen Ebene. Die sortierst Du dann und wenn Du die Verzeichnisse zu oberst haben möchtest, dann stelle ihnen ein Leerzeichen oder "!" voran, das Du nach dem Sortieren wieder entfernst.
        Im nächsten Schritt erstellst Du für jedes Verzeichnis ein neues Array, sortierst dieses wie gehabt und fügst es in das "große" Array ein.

        freundliche Grüße
        Ingo

        1. Hi Ingo!
          Danke für deine Anregungen. Aber....Hm, so ganz kann ich dir noch nicht folgen. Aber ich probier's mal :-D
          Also, zuerst ein Array erstellen von der aktuellen Ebene. Dabei kann ich mir die Dateien ja direkt mit is_dir() vom Halse schaffen...

            
          Array  
          (  
              [0] => Ordnerggg  
              [1] => Testordner  
              [2] => zzzzz  
          )  
          
          

          NACHDEM (und nicht währenddessen) das Array erstellt wurde, wird es natürlich sortert, und für jeden einzelnen Wert nochmal ein Array erzeigt. Ich bekomme ein Unter-Array zurück, welches ich in das Hauptarray einfüge. Als Beispiel:

            
          HauptArray  
          (  
              [0] => Ordnerggg  
              [1] => Testordner  
              [2] => zzzzz  
          )  
            
          NebenArray  
          (  
              [0] => UnterOrdner1vonTestordner  
              [1] => UnterOrdner2vonTestordner  
              [2] => zumdidumm  
          )  
          
          

          So weit so gut. Nur: Wie füge ich das Array nun hinter den Wert "Testordner" ein? Wenn ich $hauptarray[] = $nebenarray mache, dann habe ich ja nix gewonnen, da das Teil wieder am Ende klebt.

          Und wieso soll ich die übergeordneten Ordner nicht mitnehmen? Für's sortieren ist's doch egal, da alle Werte in einem Nebenarray eh den gleichen Anfang haben. (Den Anfang dran zu lassen hätte den forteil, daß ich mir die einzelnen Ordner nicht im Array "zusammensuchen" muss)

          Tobi

          1. Hi,

            Dabei kann ich mir die Dateien ja direkt mit is_dir() vom Halse schaffen...

            eben nicht! Die Verzeichnisnamen brauchst Du doch in diesem Array.

            Nur: Wie füge ich das Array nun hinter den Wert "Testordner" ein? Wenn ich $hauptarray[] = $nebenarray mache, dann habe ich ja nix gewonnen, da das Teil wieder am Ende klebt.

            genau deshalb brauchst Du dieses Element im Haupt-Array, wobei ich auch vorschlagen würde, die Namen in den keys zu speichern. Das könnte dann so aussehen:

            Array  
            (  
                [Ordnerggg] => 0  
                [Testordner] => Array  
                                (  
                                    [UnterOrdner1vonTestordner] => 0  
                                    [UnterOrdner1vonTestordner] => 0  
                                    [zumdidumm] => 0  
                                 )  
                [zzzzz] => zzzzz  
            )
            

            freundliche Grüße
            Ingo