2010-07-27 5 views
1

J'essaie d'extraire le contenu d'un élément de date de nombreux documents sgml mal formés. Par exemple, le document peut contenir un élément de date simple commePerl RegEx: Limiter le motif à la première occurrence d'un caractère

<DATE>4th July 1936</DATE> 

ou

<DATE blaAttrib="89787adjd98d9">4th July 1936</DATE> 

mais peut aussi poilu que:

<DATE blaAttrib="89787adjd98d9">4th July 1936 
<EM>spanned across multiple lines and EM element inside DATE</EM></DATE> 

Le but est d'obtenir le « 4ème Juillet 1936 ". Puisque les fichiers ne sont pas gros, j'ai choisi de lire tout le contenu dans une variable et de faire l'expression rationnelle. Ce qui suit est l'extrait de mon code Perl:

{ 
    local $/ = undef; 
    open FILE, "$file" or die "Couldn't open file: $!"; 
    $fileContent = <FILE>; 
    close FILE; 

    if ($fileContent =~ m/<DATE(.*)>(.*)<\/DATE>/) 
    { 
     # $2 should contain the "4th July 1936" but it did not. 
    } 
} 

Malheureusement, le regex ne fonctionne pas pour l'exemple poilu. C'est parce qu'à l'intérieur du <DATE> il y a un élément <EM> et il s'étend également sur plusieurs lignes.

Est-ce que n'importe quelle âme peut me donner des indications, des directions ou des indices?

Merci beaucoup!

+1

[Les amis ne permettent pas aux amis d'analyser HTML avec des expressions régulières!] (http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contenu -tags/1732454 # 1732454) Utilisez un analyseur. – Ether

+0

[Ne pas analyser HTML avec Regex utilisent HTML :: Parser] (http://perldoc.perl.org/perlfaq6.html#How-do-I-match-XML%2c-HTML%2c-or-other- méchant% 2c-laid-choses-avec-un-regex% 3f) Utilisez également [3 arguments ouverts et lexical filehandles] (http://perldoc.perl.org/functions/open.html) – xenoterracide

+0

aussi queston ... vous dire mal formé ... voulez-vous dire/pas/bien formé? ce qui signifie quelque chose comme arrive? – xenoterracide

Répondre

3

Use an XML parser if you can.

Mais votre exemple, probablement vous pouvez essayer

if ($fileContent =~ m/<DATE[^>]*>([^<]+)/) { 
    # use $1 here 
    # you may need to strip new lines 
} 
+1

Bonjour Ken. Merci pour la regex, certainement travaillé. La raison pour laquelle je n'ai pas utilisé d'analyseur XML est qu'il y a environ 20 000 fichiers SGML que je dois vérifier. Leur taille environ 50K chacun. Si je dois les analyser, je pense que c'est une surcharge et sera lent. Je pourrais peut-être utiliser l'analyseur basé sur le sax mais je ne suis pas un expert de Perl alors essayez simplement de faire cette tâche dès que possible et passez à autre chose. – Gilbeg

-4

Il n'y a pas moyen d'utiliser regex sur plusieurs lignes, mais vous pouvez utiliser une petite astuce. Si les fichiers ne sont pas trop gros, comme vous l'avez mentionné, vous pouvez d'abord remplacer tous les caractères \ n par des valeurs (NEW_LINE ou quelque chose comme ça), ou vous pouvez les supprimer et ensuite utiliser votre modèle.

+4

Il y a. Il fait 'local $/= undef; 'qui fait juste cela (bien, il lit tout le fichier à la fois). Lire sur les expressions rationnelles Perl dans 'perldoc perlre'. – MvanGeest

3

Utilisez un analyseur HTML.

Utilisez un analyseur HTML.

Veuillez utiliser un analyseur HTML.

Mais pour une expression régulière, je vais essayer

<DATE(.*?)>(.*)<\/DATE> 

qui devrait être plus rapide que l'alternative de KennyTM ... D'ailleurs, pourquoi capturez vous que le deuxième groupe?

+0

Downvote car la question indique que ce n'est pas XML. – daxim

+0

Ah, je n'avais pas remarqué ça. Pourtant, il existe des parseurs très résilients qui peuvent gérer un énorme gâchis. – MvanGeest

+0

Il y a des parseurs HTML qui feraient bien ce travail. – Ether

3

Si le format de date est fixée, vous pouvez utiliser quelque chose comme ceci:.

m/<DATE(.*)>([0-9]+(st|nd|rd|th)\s(January|February|March|April|May|June|July|August|September|October|November|December)\s[0-9]+)(.*)<\/DATE>/ 
3

au lieu de chercher *, vous devez faire correspondre « tout ce qui est pas un point d'ancrage »

à savoir:


if($string =~ /^<DATE[^>]*>([^<]+)</){ 
il

, 1 $ est votre date

+0

Merci beaucoup ... vous avez raison comme Kenny l'a suggéré. Merci! – Gilbeg

2

Vous devez utiliser une correspondance non gourmande et le modificateur s à faire.correspondre newline

my @l = (
'<DATE>4th July 1936</DATE>', 
'<DATE blaAttrib="89787adjd98d9">4th July 1936</DATE>', 
'<DATE blaAttrib="89787adjd98d9">4th July 1936 
<EM>spanned across multiple lines and EM element inside DATE</EM></DATE>' 
); 

foreach(@l) { 
    /^<DATE.*?>(.*?)</s && print $1; 
} 

sortie:

4th July 1936 
4th July 1936 
4th July 1936 
0

Même votre exemple "poilu" peut être réduite à un type similaire. Si vous allez toujours avoir 1) la date réelle sur la même ligne que l'étiquette de début - et 2) c'est tout ce que vous voulez - peu importe où l'étiquette de fin est.

$fileContent =~ m/<DATE([^>]*)>\s*(\d+\p{Alpha}+\s+\p{Alpha}+\s+\d{4})/ 

va toujours fonctionner. (Si vous ne trouvez pas '>' dans l'étiquette, alors c'est une bonne idée de ne pas causer trop de retours en arrière après que .* ait mangé toute votre ligne, ait fait échouer l'expression et ait ensuite redonné et vérifié, redonné et check, ...)

Questions connexes