Projektowanie stron WWW od podszewki

Artykuły na każdy temat

[PHP] Zaawansowana wyszukiwarka z funkcją wyświetlania i zamieniania wyników

Dodano 20.02.2011r. o 23:52
Na początku wyleje swoje żale do internetu. Mój kochaniutki dysk parę dni temu, po raz pierwszy, poważnie odmówił mi posłuszeństwa. Poleciała mi partycja systemowa zawierająca 10GB ważnych danych, które przepadły bezpowrotnie. Bardzo ładny gest ze strony twardziela. Niestety odzew musiał być adekwatny do sytuacji. Pięć minut sam na sam w piekarniku dało do myślenia opornym talerzom dysku. Po tym zdarzeniu o dziwo zaczął działać prawidłowo. Oczywiście z piekarnikiem żartuje ale kusiło mnie Wink W końcu trzeba iść do przodu, a nie cofać się.

Wracając do wątku, który będę redagował ponownie. Jakiś czas temu pewna osoba zainspirowała mnie do napisania, a raczej przyspieszenia prac nad skryptem, którego zadaniem było przeszukiwanie, wyświetlanie i podmienianie odpowiednich fraz ze zwróconych wyników. W związku z tym siadłem sobie na parę godzin przy edytorze i klepnąłem ową wyszukiwarkę plików. Zacznę od omówienia konfiguracji skryptu, która wygląda następująco:
Kod:
<?php
// Konfiguracja
$config['step'] = 1;
$config['glob_path'] = array('D:\xampp\htdocs\search');
$config['glob_flags'] = null;
$conifg['glob_pattern'] = '*.txt';
$config['search_file'] = array();
$config['search_term'] = array('#(test)#');
$config['remove_floppy_from_disks_list'] = true;
$config['show_source'] = true;
$config['show_empty_results'] = false;
$config['str_replace'] = null;
$config['preg_replace'] = null;
?>
Pierwsza zmienna, czyli $config['step'] oznacza krok, który ma zostać wykonany przez aplikację.

Lista kroków jest następująca:
  1. Wyszukiwanie plików
  2. Wyświetlanie wyników
  3. Zamienianie określonych fraz w określonych wynikach

Zmienna $config['glob_path'] odpowiedzialna jest za ścieżkę lub ścieżki, w których ma zacząć wyszukiwanie określonych plików. Wyszukiwanie opiera się na użyciu funkcji glob(), stąd też można zdefiniować flagi dla tej funkcji, podając je w zmiennej $config['glob_flags']. Następna zmienna ($conifg['glob_pattern']) jest bardzo istotna i mówi funkcji glob(), jakie pliki maja zostać znalezione. Oczywiście format powinien być zgodny z tym, jaki używa się w wyżej wymienionej funkcji. Jeżeli życzymy sobie bardziej zaawansowanego przeszukiwania po nazwach pliku, wtedy możemy wrzucić wzorce wyrażeń regularnych do tablicy $config['search_file']. Połączenie odpowiedniej konfiguracji $conifg['glob_pattern'] oraz $config['search_file'] może stanowczo przyspieszyć prace wyszukiwania. Następne 4 zmienne odpowiedzialne są za wyświetlanie wyników. Przypuśćmy, że chcemy, aby wyświetlało wyniki (zawartości plików) z szukaną przez nas frazą. W takim przypadku analogicznie tworzymy kolejne elementy tablicy $config['search_term'] z odpowiednimi wartościami. Jeżeli posiadamy system Windows i chcemy, aby przeszukało wszystkie dyski z wyjątkiem stacji dyskietek, wtedy ustawiamy $config['remove_floppy_from_disks_list'] na true i ustawiamy $config['glob_path'] na puste, czyli null. Jeżeli życzymy sobie wyświetlania zawartości znalezionych plików, wtedy zmienna $config['show_source'] powinna mieć wartość true. Gdyby zależało nam tylko i wyłącznie na wydobyciu nazw plików wtedy ustawiamy wyżej wymieniona zmienną na false. Ponadto wyniki zapisane są w pliku results.txt więc można nimi łatwo manipulować. Następna zmienna $config['show_empty_results'] odpowiedzialna jest za wyświetlanie zawartości plików nie pasujących do wzorca. Czyli jeżeli plik nie jest pusty i zmienna ma wartość true, to i tak zostanie wyświetlony, mimo, że może się tak zdarzyć, że szukanej frazy w nim nie ma. Trzecia część konfiguracji odpowiedzialna jest za kod, który zamienia odpowiednie fragmenty kodu na inne fragmenty kodu. Brzmi ciężko, ale jest łatwe. Zmienna $config['str_replace'] odpowiada, za podmianę przy pomocy funkcji str_replace() natomiast zmienna $config['preg_replace'] jak jej nazwa wskazuje służy do wstawienia wzorców wyrażeń regularnych do funkcji preg_replace(). Owa funkcjonalność ma tą przewagę nad innymi edytorami, że pozwala na wstawienie np. \n i innych dziwnych znaków, których normalnie pole typu TextBox w edytorze nie zezwala. Właśnie ta funkcja była pożądana przez osobę, która inspirowała mnie do napisania tego skryptu. Pozwala ona na masową zmianę określonych plików, z zachowaniem odpowiednich reguł. Przykładowy wygląd tablic dla zamieniania metoda str_replace() i preg_replace() wygląda następująco:
Kod:
<?php
$config['str_replace'] = array(array('test''foo'), array('foo''bar'));
$config['preg_replace'] = array(array('#(bar)#'), array('\\1 jest emo ')); // Nie, nie mam nic do emo ;)
?>
Powyższy przykład zamienia najpierw ciąg test na foo, a następnie foo na bar. W rezultacie tam gdzie wystąpiła fraza test pojawi się magiczny bar. Używanie preg_replace() ma o tyle wyższość nad innymi metodami, że możemy wrzucać dość zawiłe wzorce, nawet z odwołaniem się do innych funkcji PHP. Skala i możliwości użycia regex są na tyle duże, że nie sposób zliczyć, w jakich celach mogą zostać użyte. Pamiętajcie tylko, że w pierwszej tablicy mamy szukane elementy, a w drugiej ich zamienniki. Oczywiście możecie sobie rozszerzyć to o wiele wariatów tworząc tablice zagnieżdżone.

Swoją drogą starałem się jak najbardziej zoptymalizować skrypt, dlatego też powinien być wystarczająco szybki, aby przelecieć dysk szybciej aniżeli defaultowa wyszukiwarka windowsowska. Oczywiście dopuszczam do głowy myśl, że gdzieś mogłem popełnić błąd, stad prośba do osób wykorzystujących skrypt, aby znalezione bugi raportowali. Najlepiej w komentarzu pod artykułem bądź via private message. Moim zdaniem skrypt jest rozwojowy i można go rozszerzyć o kilka potrzebnych funkcjonalności. Kwestia czasu i chęci. Możliwe, że sam będę chętny, aby to zrobić, o ile pomysły na jego rozbudowę będą dostatecznie dobre.

Ponadto starałem się, aby skrypt był uniwersalny, dlatego też powinien działać na Window'sie, Linux'ie, Mac'u oraz systemach typu UNIX. Zapewne nie będzie on szybszy niż polecenia/aplikacje konsolowo-systemowe, jednak jego działanie powinno zadowolić nie jedna osobę. Przykładowy rezultat wyszukiwania:
miniatura-search-results.png
Cały skrypt z plikami testowymi znajduje się w dziale skrypty pod tytułem Zaawansowana wyszukiwarka plików. Jeżeli wybierasz opcje numer dwa, to możesz skopiować całość z poniższego listingu:
Kod:
<style type="text/css">
body { width: 98%; font-family: "Lucida Console"; font-size: 12px }
code { width: 100%; margin: 10px 0; padding: 5px; overflow: auto; display: block; border: 1px solid red }
em.search { background: #CEC }
</style>
<?php
/**
 * @version: 1.00.00
 * @author: CapaciousCore
 */

// Konfiguracja
$config['step'] = 1;
$config['glob_path'] = array('D:\xampp\htdocs\search');
$config['glob_flags'] = null;
$conifg['glob_pattern'] = '*.txt';
$config['search_file'] = array();
$config['search_term'] = array('#(test)#');
$config['remove_floppy_from_disks_list'] = true;
$config['show_source'] = true;
$config['show_empty_results'] = false;
$config['str_replace'] = null;
$config['preg_replace'] = null;

// Mała zmiana ustawień
if($config['step'] == || $config['step'] == || $config['step'] == 3)
{
 set_time_limit(0);
 ini_set('memory_limit''32M');
}

// Wyszukiwanie
if($config['step'] == 1)
{
 $start_time get_time();
 $files = array();
 $targets = (!empty($config['glob_path']) ? $config['glob_path'] : get_disks($config['remove_floppy_from_disks_list']));

 if(!empty($targets))
 {
  if(!empty($config['search_file']))
  {
   $counter_filters_for_file_names count($config['search_file']);
  }

  for($h 0$how count($targets); $h $how; ++$h)
  {
   $files array_merge($filessearch_files($targets[$h], ($conifg['glob_pattern'] ? $conifg['glob_pattern'] : '*'), $config['search_file'], $counter_filters_for_file_names$config['glob_flags']));
  }

  if(@file_put_contents('results.txt'serialize($files)))
  {
   echo 'Zapisano wyniki wyszukiwania (znaleziono: '.count($files).' plików, szukano '.round((get_time() - $start_time), 8).' sekund)';
  }
  else
  {
   echo 'Nie udało się zapisać wyników wyszukiwania';
  }
 }
 else
 {
  echo 'Brak punktu lub punktów wejścia';
 }
}
// Wyświetlanie wyników
else if($config['step'] == 2)
{
 $data unserialize(@file_get_contents('results.txt'));

 if(!empty($data))
 {
  $counter_search_terms count($config['search_term']);

  for($h 0$how count($data); $h $how; ++$h)
  {
   $content = @file_get_contents($data[$h]);

   if($content || $config['show_source'])
   {
    echo '<b>'.$data[$h].'</b><br>';

    if($config['show_source'] && !empty($content))
    {
     if($counter_search_terms 0)
     {
      for($i 0$i $counter_search_terms; ++$i)
      {
       $content preg_replace($config['search_term'], "<em>\\1</em>"$content, -1$counter_replaced);
      }

      if($counter_replaced || $config['show_empty_results'])
      {
       echo '<pre><code>'.str_replace(array('&lt;em&gt;''&lt;/em&gt;''</em><em>'), array('<em class="search">''</em>'''), htmlspecialchars($content)).'</code></pre>';
      }

      unset($counter_replaced);
     }
     else
     {
      echo '<pre><code>'.htmlspecialchars($content).'</code></pre>';
     }
    }
   }
  }
 }
 else
 {
  echo 'Problem z odczytem wyników wyszukiwania';
 }
}
// Zastąpienie tresci
else if($config['step'] == 3)
{
 $data unserialize(@file_get_contents('results.txt'));

 if(!empty($data))
 {
  $how_changed 0;
  $how count($data);

  for($h 0$h $how; ++$h)
  {
   $content = @file_get_contents($data[$h]);

   if($content)
   {
    if(!is_null($config['str_replace']))
    {
     $new_content str_replace($config['str_replace'][0], $config['str_replace'][1], $content);
    }

    if(!is_null($config['preg_replace']) || (!empty($config['preg_replace'][0]) && !empty($config['preg_replace'][1])))
    {
     $new_content preg_replace($config['preg_replace'][0], $config['preg_replace'][1], (!is_null($config['str_replace']) ? $new_content $content));
    }

    if((!is_null($config['str_replace']) || !is_null($config['preg_replace'])) && $new_content !== $content)
    {
     if(@file_put_contents($data[$h], $new_content))
     {
      ++$how_changed;
     }
    }
   }
  }

  echo 'Spośród '.$how.' plików przetworzono '.$how_changed;
 }
 else
 {
  echo 'Problem z odczytem wyników wyszukiwania';
 }
}
else
{
 echo 'Nieznany krok';
}

// Funkcje pomocnicze
function search_files($path$pattern$search_file_names$counter_filters_for_file_names$flags null)
{
 $files = array();
 $path .= (substr($path, -1) != DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR '');
 $objects glob($path.($pattern $pattern '*'), $flags);

 foreach($objects as $object)
 {
  // Plik
  if(is_file($object))
  {
   if($counter_filters_for_file_names)
   {
    for($h 0$h $counter_filters_for_file_names; ++$h)
    {
     if(preg_match($search_file_names[$h], basename($object)))
     {
      $files[] = $object;

      break;
     }
    }
   }
   else
   {
    $files[] = $object;
   }
  }

  /*
  // Folder
  else if($pattern != '*')
  {
   // @todo: Tutaj powinien być segment odpowiedzialny za filtrowanie nazw folderów lub ścieżek - jak kto woli Razz

   $new_files = search_files($object, $pattern, $search_file_names, $counter_filters_for_file_names, $flags);

   if(!empty($new_files))
   {
    $files = array_merge($files, $new_files);
   }
  }
  */
 }

 // Przeszukiwanie folderu głównego i podfolderów
 foreach(glob($path.'*'GLOB_ONLYDIR) as $subdir)
 {
  $new_files search_files($subdir$pattern$search_file_names$counter_filters_for_file_names$flags);
  $files array_merge($files$new_files);
 }

 return (is_array($files) ? $files : array());
}

function get_disks($remove_floppy false)
{
 // Windows
 if(php_uname('s') == 'Windows NT')
 {
  $disks = `fsutil fsinfo drives`;
  $disks str_word_count($disks1);

  if($disks[0] != 'Drives' && $disks[0] != 'Dyski')
  {
   return false;
  }

  unset($disks[0]); $disks[0] = 'A';

  if($remove_floppy)
  {
   $catch[0] = array_search('A'$disks);
   $catch[1] = array_search('B'$disks);

   if(is_int($catch[0]))
   {
    unset($disks[$catch[0]]);
   }

   if(is_int($catch[1]))
   {
    unset($disks[$catch[1]]);
   }
  }

  $disks array_values($disks);

  if(!empty($disks))
  {
   foreach($disks as $key => $disk)
   {
    $disks[$key] = $disk.':\\';
   }
  }
 }
 // Mac, Linux, Unix
 else // if(php_uname('s') == 'Darwin' || php_uname('s') == 'Linux' || php_uname('s') == 'Unix')
 {
  $disks glob('/*');
 }

 return $disks;
}

function get_time()
{
 list($usec$sec) = explode(' 'microtime());

 return ((float)$usec + (float)$sec);
}
?>

Lista rzeczy do zrobienia

  • Przyjazny interface
  • Nazwy wyszukanych plików mogą być linkami do nich
  • Możliwość wyłączenia przeszukiwania podfolderów (z uwzględnieniem ukrytych i systemowych)
  • Uwzględnienie daty stworzenia i modyfikacji pliku przy wyszukiwaniu
  • Uwzględnienie rozmiaru pliku
  • Przeszukiwanie archiwów
  • Inne, których teraz nie pamiętam i mi poleciały z partycją

Komentarze

Publikowane komentarze są prywatnymi opiniami użytkowników serwisu. Serwis nie ponosi odpowiedzialności za treść opinii. W trosce o zachowanie poziomu dyskusji wszystkie komentarze podlegają akceptacji przed ich publikacją dlatego proszę cierpliwie czekać aż komentarz zostanie opublikowany.

Todziu

Dodano 16.03.2016r. o 13:47
A mam pytanie jak zrobić aby do pliku results.txt zapisywał skrypt tylko ścieżki do plików wraz z ich nazwą ?

Dzieki

kSledzikowski

Dodano 04.08.2012r. o 10:50
Świetne! Sam robiłem kiedyś coś podobnego, ale było to mniej zaawansowane. Zacnie, zacnie. Very Happy

CapaciousCore

Dodano 20.04.2011r. o 01:23
@Masaj zrób lepsze Wink

Masaj

Dodano 19.04.2011r. o 23:35
Cienkie to

Dodaj komentarz

Zostaw komentarz jeżeli możesz! Nie bądź przysłowiowym botem! Nie bądź obojętny! Ciebie to nic nie kosztuje, a mi sprawi uśmiech na twarzy.
Zezwolono używać: BBCode
Zabroniono używać:
znaczników HTML

(Wymagany)

(Wymagany, niepublikowany)

(Nie wymagana)

Token:

Obrazek dla bota

(Przepisz tylko cyfry!)

(Wymagana)