2010-05-17 4 views
4

J'ai une fonction pour convertir des documents dans différents formats, qui appelle ensuite une autre fonction basée sur le document de type. C'est assez simple pour tout ce qui concerne les documents HTML qui nécessitent un peu de nettoyage, et que le nettoyage est différent en fonction d'où il vient. J'ai donc eu l'idée que je pourrais passer une référence à un sous-programme à la fonction de conversion afin que l'appelant ait la possibilité de modifier le HTML, un peu comme ça (je ne suis pas au travail donc ce n'est pas copier-coller) :Comment puis-je modifier une référence scalaire transmise à une référence de sous-programme?

package Converter; 
... 
sub convert 
{ 
    my ($self, $filename, $coderef) = @_; 

    if ($filename =~ /html?$/i) { 
     $self->_convert_html($filename, $coderef); 
    } 
} 

sub _convert_html 
{ 
    my ($self, $filename, $coderef) = @_; 

    my $html = $self->slurp($filename); 
    $coderef->(\$html); #this modifies the html 
    $self->save_to_file($filename, $html); 
} 

qui est alors appelé par:

Converter->new->convert("./whatever.html", sub { s/<html>/<xml>/i }); 

J'ai essayé deux choses différentes le long de ces lignes, mais je continue à obtenir « utilisation de la valeur non initialisée en substitution (s ///) '. Y a-t-il un moyen de faire ce que j'essaie de faire?

Merci

+1

Avant de lire l'une des réponses ci-dessous: vous pouvez essayer d'ajouter des instructions d'impression à chaque niveau de sous-programme, pour voir si ce que vous obtenez comme arguments correspond vraiment à ce que vous pensez devrait être. ** Astuce: l'instruction d'impression à l'intérieur de la substitution coderef devrait vous conduire à la réponse. ** – Ether

Répondre

5

Si elle était moi, j'éviterait de modifier l'arbitre scalaire et juste retourner la valeur modifiée.

sub _convert_html 
{ 
    my ($self, $filename, $coderef) = @_; 

    my $html = $self->slurp($filename); 
    $html = $coderef->($html); #this modifies the html 
    $self->save_to_file($filename, $html); 
} 

Cependant, si vous voulez Pour modifier les arguments d'un sous-marin, il est utile de savoir que tous les arguments sub sont passés en Perl (les éléments de @_ sont aliasés aux arguments de l'appel secondaire).Ainsi, votre sous de conversion peut ressembler à:

sub { $_[0] =~ s/<html>/<xml>/ } 

Mais si vous voulez vraiment fonctionner sur $_, comme vous avez dans votre exemple de code désiré, vous devez faire _convert_html() ressembler à:

sub _convert_html 
{ 
    my ($self, $filename, $coderef) = @_; 

    my $html = $self->slurp($filename); 

    $coderef->() for $html; 

    $self->save_to_file($filename, $html); 
} 

Le for est un moyen facile de localiser correctement $_. Vous pouvez également faire:

sub _convert_html 
{ 
    my ($self, $filename, $coderef) = @_; 

    local $_ = $self->slurp($filename); 

    $coderef->(); 

    $self->save_to_file($filename, $_); 
} 
+0

Merci pour l'explication détaillée :-) – Mark

+2

Cela fait beaucoup de copies: d'abord sur la pile de paramètres, puis changez-la et copiez-la sur la pile de paramètres. Cela peut faire mal si vous devez traiter beaucoup, beaucoup de pages. –

+1

notez que si la vitesse est importante, 'local' est plus rapide que' for' avec un élément –

2

Essayez ceci:

Converter->new->convert("./whatever.html", sub { ${$_[0]} =~ s/<html>/<xml>/i; }); 

Vous obtenez un avertissement de valeur non initialisée parce que la substitution n'est pas rien donné à opérer ($_ n'est pas défini dans son champ d'application). Vous devez lui indiquer où trouver sa valeur (en @_, à titre de référence).

Si vous voulez être de fantaisie que vous pourriez faire le coderef fonctionner sur tous ses args par défaut:

sub { map { $$_ =~ s/<html>/<xml>/i } @_ } 
+0

Pour l'exemple de code de carte, je me demande ce que je voudrais que la valeur de retour soit. :) –

+0

@brian: en effet; normalement j'écrirais un sous-élément de conversion pour retourner la nouvelle valeur, plutôt que d'utiliser des références. – Ether

3

Rappelez-vous qu'un s/// par lui-même fonctionne sur $_, mais votre référence scalaire est transmis dans votre rappel sub comme argument, et est donc dans le tableau @_.

Vous pouvez simplement changer votre sous callback à quelque chose comme ceci:

sub { my ($ref) = @_; $$ref =~ s/<html>/<xml>/i } 

Ou, vous pouvez profiter de la nature crénelage des arguments de sous-programme Perl, et le modifier directement:

sub _convert_html { 
    ... 
    $coderef->($html); 
} 

puis

sub { $_[0] =~ s/<html>/<xml>/i } 

(Cela fait modifier la chaîne d'origine, comme l ong comme argument est une variable scalaire et non une chaîne littérale)

Questions connexes