Μαζικές αντικαταστάσεις αρχείων (utility)

Κώδικας, πληροφορίες, ερωτήσεις και απαντήσεις σχετικές με την JavaScript.

Συντονιστές: WebDev Moderators, Super-Moderators

Απάντηση
Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Μαζικές αντικαταστάσεις αρχείων (utility)

Δημοσίευση από skeftomilos » 26 Δεκ 2005 02:09

Έστω ότι είχατε ένα φάκελο γεμάτο αρχεία, και θέλατε να αντικαταστήσετε σε όλα τα αρχεία τη λέξη «ModelNumber» με «ProductCode», τι θα κάνατε; Μάλλον δε θα θέλατε να κάνετε μία-μία τις μεταβολές με το χέρι, ούτε να ανοίγετε ένα-ένα αρχείο και να κάνετε "ReplaceAll". Πιθανόν να έχετε κάποιο πρόγραμμα που να μπορεί να κάνει μαζική αντικατάσταση σε όλα τα αρχεία ταυτόχρονα, όπως για παράδειγμα το freeware BK ReplaceEm. Μπορεί ακόμα να σας παρέχει αυτή τη δυνατότητα ο αγαπημένος σας editor (Dreamweaver, Visual Studio). Αν πάντως για διάφορους λόγους δε σας ικανοποιεί καμία από αυτές τις λύσεις, μπορεί να αποφασίσετε να πάρετε την κατάσταση τα χέρια σας, γράφοντας ένα script όπως το παρακάτω:

Replacements.js

Κώδικας: Επιλογή όλων

var VALID_EXTENSIONS = 'php,inc,js'

var REPLACEMENTS = [
  ['2005', '2006'],
  [/PHP4/gi, 'PHP5'],
]

// Μετατροπή των έγκυρων καταλήξεων σε Set.
VALID_EXTENSIONS = arrayToSet(VALID_EXTENSIONS.split(/\W/))

// Διαγραφή τυχόν κενού στοιχείου στο τέλος του array.
if (!REPLACEMENTS[REPLACEMENTS.length - 1]) REPLACEMENTS = REPLACEMENTS.slice(0, REPLACEMENTS.length - 1)

// Έλεγχος και μετατροπή των strings σε Regular expressions.
for &#40;var i = 0; i < REPLACEMENTS.length; i++&#41; &#123;
  var findItem = REPLACEMENTS&#91;i&#93;&#91;0&#93;
  if &#40;typeof findItem == 'string'&#41; &#123;
    REPLACEMENTS&#91;i&#93;&#91;0&#93; = new RegExp&#40;encodeRE&#40;findItem&#41;, 'g'&#41;
  &#125;
&#125;

var fso = WScript.CreateObject&#40;'Scripting.FileSystemObject'&#41;

var folderName = fso.GetParentFolderName&#40;WScript.ScriptFullName&#41;
var folder = fso.GetFolder&#40;folderName&#41;

var changedFiles = &#91;&#93;

// Αντικαταστάσεις.
for &#40;var e = new Enumerator&#40;folder.Files&#41;; !e.atEnd&#40;&#41;; e.moveNext&#40;&#41;&#41; &#123;
  var file = e.item&#40;&#41;
  // Έλεγχος αν πρόκειται για το ίδιο το script.
  if &#40;file.name == WScript.ScriptName&#41; continue
  var extension = fso.GetExtensionName&#40;file.Name&#41;
  if &#40;VALID_EXTENSIONS&#91;extension&#93;&#41; &#123;
    var stream = file.OpenAsTextStream&#40;1, 0&#41; // ForReading, ASCII.
    var initialData = stream.ReadAll&#40;&#41;
    stream.Close&#40;&#41;
    var data = initialData
    for &#40;var i = 0; i < REPLACEMENTS.length; i++&#41; &#123;
      data = data.replace&#40;REPLACEMENTS&#91;i&#93;&#91;0&#93;, REPLACEMENTS&#91;i&#93;&#91;1&#93;&#41;
    &#125;
    if &#40;data != initialData&#41; &#123;
      var stream = file.OpenAsTextStream&#40;2, 0&#41; // ForWriting, ASCII.
      stream.Write&#40;data&#41;
      stream.Close&#40;&#41;
      changedFiles.push&#40;file.Name&#41;
    &#125;
  &#125;
&#125;

var msg = 'Μεταβολή ' + changedFiles.length + ' αρχείων'
if &#40;changedFiles.length&#41; msg += '\n- ' + changedFiles.join&#40;'\n- '&#41;

WScript.Echo&#40;msg&#41;

function encodeRE&#40;s&#41; &#123;
  return s.replace&#40;/&#40;&#91;\^\$\.\*\?\+\&#123;\&#125;\&#40;\&#41;\&#91;\&#93;\/\|\\&#93;&#41;/g, '\\$1'&#41;
&#125;

function arrayToSet&#40;array&#41; &#123;
  var o = &#123;&#125;
  for &#40;var i = 0; i < array.length; i++&#41; o&#91;array&#91;i&#93;&#93; = true
  return o
&#125;
Το script αυτό ενεργεί πάνω στα αρχεία του φακέλου στον οποίο βρίσκεται το ίδιο. Δηλαδή αν το βάλετε στο φάκελο D:\My Documents\PHP θα επηρεάσει τα αρχεία αυτού του φακέλου, όχι άλλων φακέλων παρακάτω ή παραπάνω. Μπορείτε να περιορίσετε τη δράση του σε αρχεία με συγκεκριμένα extensions, π.χ. txt και html. Μπορείτε ακόμα να προκαλέσετε περισσότερες από μία αντικαταστάσεις ταυτόχρονα. Τέλος μπορείτε να χρησιμοποιήσετε Regular Expressions, για ακόμα περισσότερο έλεγχο στο τι θα αντικατασταθεί και τι όχι.

Κώδικας: Επιλογή όλων

var VALID_EXTENSIONS = 'php,inc,js'
Αυτή είναι η εντολή όπου ορίζονται τα επιτρεπτά extensions. Γράφονται όλα σε ένα string, χωρισμένα με κόμμα.

Κώδικας: Επιλογή όλων

var REPLACEMENTS = &#91;
  &#91;'2005', '2006'&#93;,
  &#91;/PHP4/gi, 'PHP5'&#93;,
&#93;
Εδώ γράφονται οι λέξεις προς αντικατάσταση, και οι αντικαταστάτες τους. Η μεταβλητή REPLACEMENTS είναι ένα array που περιέχει arrays δύο στοιχείων. Στο συγκεκριμένο παράδειγμα θα γίνουν δύο αντικαταστάσεις. Σύμφωνα με το πρώτο ζέυγος θα αντικατασταθεί η λέξη '2005' με την '2006'. Το δεύτερο ζέυγος περιέχει μία Regular Expression. Η έκφραση /PHP4/gi σημαίνει ότι η αναζήτηση για τη λέξη PHP4 θα είναι case insensitive, δηλαδή θα αντικατασταθούν και οι λέξεις php4, Php4 κ.λπ. αν υπάρχουν. Αυτές είναι όλες κι όλες οι ρυθμίσεις που χρειάζονται, μπορείτε να το κάνετε copy-paste από κατάλογο σε κατάλογο, και νά κάνετε τις κατάλληλες ρυθμίσεις κατά περίπτωση.


Δύο σημεία του script έχουν ιδιαίτερο ενδιαφέρον, οπότε ας τους ρίξουμε μια ματιά. Δεν πρόκειται για ειδικά χαρακτηριστικά της JScript, αλλά αφορούν τον πυρήνα της JavaScript. Το πρώτο είναι η συνάρτηση encodeRE().

Κώδικας: Επιλογή όλων

function encodeRE&#40;s&#41; &#123;
  return s.replace&#40;/&#40;&#91;\^\$\.\*\?\+\&#123;\&#125;\&#40;\&#41;\&#91;\&#93;\/\|\\&#93;&#41;/g, '\\$1'&#41;
&#125;
Σκοπός αυτής της συνάρτησης είναι να μετατρέψει ένα string σε έκφραση Regular Expression. Οι χαρακτήρες ^ $ . * ? + { } ( ) [ ] / | \ έχουν ειδικό νόημα στις Regular Expressions, οπότε πρέπει να προστεθεί μπροστά τους μια ανάποδη κάθετος. Η ρουτίνα αυτή χρησιμοποιείται στη γραμμή...

Κώδικας: Επιλογή όλων

if &#40;typeof findItem == 'string'&#41; &#123;
  REPLACEMENTS&#91;i&#93;&#91;0&#93; = new RegExp&#40;encodeRE&#40;findItem&#41;, 'g'&#41;
&#125;
...όπου μετατρέπονται τα strings σε Regular Expressions. Η αντικατάσταση είναι αναγκαία παρόλο που η JavaScript επιτρέπει τη χρήση string στη μέθοδο String.replace(). Για παράδειγμα η έκφραση...

Κώδικας: Επιλογή όλων

'aaa'.replace&#40;'a', 'b'&#41;
...είναι έγκυρη αλλά επιστρέφει το string 'baa' αντί για το επιθυμητό 'bbb'. Το σωστό είναι:

Κώδικας: Επιλογή όλων

'aaa'.replace&#40;/a/g, 'b'&#41;
Το g στο τέλος σημαίνει global, δηλαδή οι αντικαταστάσεις δε θα σταματήσουν μετά το πρώτο match.

Το δεύτερο σημείο που αξίζει προσοχής είναι η συνάρτηση arrayToSet().

Κώδικας: Επιλογή όλων

function arrayToSet&#40;array&#41; &#123;
  var o = &#123;&#125;
  for &#40;var i = 0; i < array.length; i++&#41; o&#91;array&#91;i&#93;&#93; = true
  return o
&#125;
Αυτό που κάνει δεν είναι τόσο προφανές. Μετατρέπει ένα array σε object, έτσι ώστε οι τιμές του array να γίνουν ονόματα ιδιοτήτων του object. Δηλαδή αν δώσουμε το array ['a', '?', 12] θα πάρουμε το αντικείμενο {'a':true, '?':true, '12':true}. Αναρωτιέστε τι νόημα έχει αυτό; Η απάντηση είναι -> Performance!

Τα arrays της JavaScript είναι από τη φύση τους κάπως βραδύστροφα, και συμφέρει να αποφεύγουμε όσο μπορούμε να διατρέχουμε τα στοιχεία τους με loop. Στο script λοιπόν προκύπτει ένα array με την εντολή...

Κώδικας: Επιλογή όλων

VALID_EXTENSIONS.split&#40;/\W/&#41;
Το string 'php,inc,js' μετατράπηκε σε array ['php', 'inc', 'js'] και τώρα πρέπει να βρούμε ένα τρόπο να ελέγχουμε αν μία κατάληξη ενός αρχείου ταιριάζει με κάποιο από τα στοιχεία του array. Αυτό θα μπορούσε να γίνει κάπως έτσι:

Κώδικας: Επιλογή όλων

fuction arrayContains&#40;array, item&#41;
  for &#40;var i = 0; i < array.length; i++&#41; &#123;
    if &#40;array&#91;i&#93; == item&#41; return true
  &#125;
  return false
&#125;
Δηλαδή θα μπούμε σε ένα loop για κάθε αναζήτηση που πρόκειται να κάνουμε. Not good! Η λύση έρχεται από τα generic JavaScript objects, που στην ουσία είναι name-value bags. Σχεδόν σε όλες τις υλοποιήσεις της JavaScript τα objects υλοποιούνται ως hash-tables, ώστε η πρόσβαση στις ιδιότητές τους να γίνεται με αστραπιαία ταχύτητα. Επιπλέον υπάρχει και το ενδιαφέρον χαρακτηριστικό ότι τα objects δεν διαμαρτύρονται αν ζητήσουμε μια ανύπαρκτη ιδιότητα, αλλά επιστρέφουν undefined. Άρα θα κάνουμε το εξής. Αντί να κάνουμε loop στο array για να δούμε αν υπάρχει μια τιμή, θα κάνουμε ένα μόνο έλεγχο ζητώντας μια ιδιότητα από ένα object! Αυτού του είδους το αντικείμενο ονομάζεται Set. Έτσι για να εξετάσουμε το array για την τιμή 'config' αντί για...

Κώδικας: Επιλογή όλων

if &#40;arrayContains&#40;VALID_EXTENSIONS, 'config'&#41;&#41; &#123; ...
...θα γράψουμε...

Κώδικας: Επιλογή όλων

if &#40;VALID_EXTENSIONS&#91;'config'&#93;&#41; &#123; ...
...με την προϋπόθεση βέβαια ότι η VALID_EXTENSIONS από array έχει γίνει Set:

Κώδικας: Επιλογή όλων

VALID_EXTENSIONS = arrayToSet&#40;VALID_EXTENSIONS&#41;

Επιστρέφοντας στο script για την αντικατάσταση, παρατηρούμε ότι έχει κάποιες ελλείψεις. Η αντικατάσταση ξεκινά αμέσως μόλις αρχίσει να τρέχει το script, χωρίς να μας δίνει κάποιες πληροφορίες για το τι αλλαγές πρόκειται να κάνει στα αρχεία μας. Μια προτιμοτερη μορφή του script που εμφανίζει μηνύματα και επιτρέπει την ακύρωση των αλλαγών: replacements.zip.

Εικόνα

Μια άλλη ψιλοατέλεια είναι ότι τόσο τα δεδομένα για τις αντικαταστάσεις όσο και ο κώδικας του script βρίσκονται στο ίδιο αρχείο. Αν αρχίσετε να κάνετε copy-paste το script από φάκελο σε φάκελο θα δημιουργήσετε πολλαπλά αντίγραφα του ίδιου κώδικα. Δεν είναι το τέλος του κόσμου, αλλά αν θέλετε να το αποφύγετε υπάρχει λύση: τα πακέτα wsf. Ένα αρχείο wsf συνενώνει κώδικα από διάφορα άλλα αρχεία. Για παράδειγμα το...

Κώδικας: Επιλογή όλων

<job>
  <script language="JScript" src="a.js"/>
  <script language="JScript" src="b.js"/>
</job>
...θα φορτώσει και θα τρέξει με τη σειρά τα scripts a.js και b.js. Στην περίπτωσή μας θα κάνουμε τα εξής. Θα μεταφέρουμε τον κώδικα του script σε ένα σταθερό και αμετάβλητο σημείο του δίσκου, π.χ. στη θέση D:\Scripts\replacements-core.js. Θα αφαιρέσουμε από το αρχείο τις πρώτες γραμμές που περιέχουν τα δεδομένα, και θά γίνει έτσι:

replacements-core.js

Κώδικας: Επιλογή όλων

var VALID_EXTENSIONS, REPLACEMENTS

if &#40;!VALID_EXTENSIONS || !REPLACEMENTS&#41; &#123;
  WScript.Echo&#40;'Το script δεν τρέχει stand-alone.\nΠρέπει να γίνει included σε ένα wsf αρχείο.'&#41;
  WScript.Quit&#40;&#41;
&#125;

// Μετατροπή των έγκυρων καταλήξεων σε Set.
VALID_EXTENSIONS = arrayToSet&#40;VALID_EXTENSIONS.split&#40;/\W/&#41;&#41;
...
...
Και το αρχείο wsf θα πάρει την παρακάτω μορφή:

Replacements.wsf

Κώδικας: Επιλογή όλων

<job>
  <script language="JScript">
    var VALID_EXTENSIONS = 'php,inc,js'

    var REPLACEMENTS = &#91;
      &#91;'2005', '2006'&#93;,
      &#91;/PHP4/gi, 'PHP5'&#93;,
    &#93;
  </script>
  <script language="JScript" src="D&#58;\Scripts\replacements-core.js"/>
</job>
Το αρχείο αυτό θα το βάλουμε στο φάκελο που μας ενδιαφέρει να γίνει η αντικατάσταση, και θα το κάνουμε copy-paste όταν χρειάζεται.

Enjoy :)
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Άβαταρ μέλους
ThyClub
Honorary Member
Δημοσιεύσεις: 5312
Εγγραφή: 17 Νοέμ 2003 00:21
Τοποθεσία: Hell's Kitchen
Επικοινωνία:

Μαζικές αντικαταστάσεις αρχείων (utility)

Δημοσίευση από ThyClub » 26 Δεκ 2005 14:59

Έγινε βοήθημα. Thank you :wink:

Άβαταρ μέλους
cordis
Administrator, [F|H]ounder, [C|S]EO
Δημοσιεύσεις: 27622
Εγγραφή: 09 Οκτ 1999 03:00
Τοποθεσία: Greece
Επικοινωνία:

Μαζικές αντικαταστάσεις αρχείων (utility)

Δημοσίευση από cordis » 26 Δεκ 2005 16:41

πολύ καλό κόλπο! ;)
Δεν απαντάω σε προσωπικά μηνύματα με ερωτήσεις που καλύπτονται από τις ενότητες του forum. Για ο,τι άλλο είμαι εδώ για εσάς.
- follow me @twitter


Απάντηση

Επιστροφή στο “JavaScript και Frameworks”

Μέλη σε σύνδεση

Μέλη σε αυτήν τη Δ. Συζήτηση: Δεν υπάρχουν εγγεγραμμένα μέλη και 0 επισκέπτες