Elise LINCKER

Boîte à outils 3 - partie 2

Extraction de relations de dépendances syntaxiques

Objectif

Le deuxième objectif de cette troisième boîte à outils est d'extraire des relations syntaxiques de dépendances. Nous travaillerons sur les données annotées avec UDPipe au format XML, générées par la boîte à outils 2.

Nous allons procéder par différentes méthodes :

Nous nous intéressons aux cas suivants :

Méthode 1 : XQuery

La première méthode consiste à utiliser des requêtes XQuery. Nous utilisons le logiciel BaseX pour lancer les requêtes.

Le code ci-dessous permet d'extraire les relations de type NSUBJ. Pour extraire les dépendances d'une autre relation syntaxique, il suffit de remplacer la valeur de la chaîne de caractères de la condition WHERE, dans la ligne :

where contains($item/a[8]/text(),'nsubj')

Par exemple :

where contains($item/a[8]/text(),'amod') ou where contains($item/a[8]/text(),'obj')

(: on s'intéresse aux relations de dépendances de type NSUBJ
   si vous souhaitez extraire un autre type de relation, modifiez la ligne 15 de ce code
   selon la relation, on voudra aussi modifier l'ordre d'affichage (Dep Gouv ou Gouv Dep) ligne 34 :)
(: dans le fichier de sortie UDPipe reformaté en XML :
   chaque phrase est dans une balise <p>
   chaque token est dans une balise <item>
   une balise <item> contient 10 balises <a>
   la 1ere balise <a> contient l'identifiant du token
   la 2eme balise <a> contient la forme du token (son texte)
   la 7eme balise <a> contient l'identifiant de son gouverneur
   la 8eme balise <a> correspond à la relation de dépendance avec son gouverneur :)

(: pour chaque item dont la 8e balise <a> contient la chaîne de caractères 'obj' :)
for $item in collection("BAO2_sortieUDpipe_3246.txt.xml")//item
where contains($item/a[8]/text(),'nsubj')

(: on récupère dans la variable $formeDep la forme du dépendant
   dans la variable $positionDep l'identifiant du dépendant
   dans la variable $positionGouv l'identifiant du gouverneur :)
let $formeDep:=$item/a[2]/text()
let $positionDep:=$item/a[1]/text()
let $positionGouv:=$item/a[7]/text()

(: on récupère dans la variable $formeGouv la forme du gouverneur,
   en fonction de la position du gouverneur par rapport au dépendant (avant ou après)
   attention il faut convertir le contenu textuel des positions en valeur numérique :)
let $formeGouv:=
   if ( number($positionGouv) < number($positionDep) )
   then ( $item/preceding-sibling::item[number(a[1])=number($positionGouv)]/a[2]/text() )
   else ( $item/following-sibling::item[number(a[1])=number($positionGouv)]/a[2]/text() )

(: on concatène la forme du gouverneur + un espace + la forme du dépendant
   et on stocke la liste de toutes les séquences dans la variable $res :)
let $res:= string-join(($formeDep,$formeGouv)," ")

(: on regroupe les séquences :)
group by $grp:=$res
(: on compte le nombre d'occurrences de chaque séquence et on les trie par ordre décroissant :)
order by count($res) descending

(: on concatène la séquence + une tabulation + le nombre d'occurrences
   et on affiche le tout :)
return string-join(($grp,count($res)),"	")

Cette deuxième requête extrait les triplets (Antécédent, Pronom relatif, Verbe) d'une relative sujet.

(: on s'intéresse aux relatives sujet
   antécédent -[acl:relcl]-> verbe -[nsubj]-> pronom[PronType=Rel]
   l'objectif est d'extraire le pronom relatif sujet, puis le verbe qui le gouverne, puis le nom complété par la relative :)
(: dans le fichier de sortie UDPipe reformaté en XML :
   chaque phrase est dans une balise <p>
   chaque token est dans une balise <item>
   une balise <item> contient 10 balises <a>
   la 1ere balise <a> contient l'identifiant du token
   la 2eme balise <a> contient la forme du token (son texte)
   la 6eme balise <a> contient les marques flexionnelles selon le POS (gender,number,prontype,mood,person,tense...)
   la 7eme balise <a> contient l'identifiant de son gouverneur
   la 8eme balise <a> correspond à la relation de dépendance avec son gouverneur :)

for $item in collection("BAO2_sortieUDpipe_3214.txt.xml")//item
where (contains($item/a[8]/text(),'nsubj') and contains($item/a[6]/text(),'PronType=Rel'))

(: on récupère dans la variable $formePronom la forme du pronom (le dépendant)
   dans la variable $positionPronom l'identifiant du pronom
   dans la variable $positionGouv l'identifiant du gouverneur (le verbe) :)
let $formePronom:=$item/a[2]/text()
let $positionPronom:=$item/a[1]/text()
let $positionGouv:=$item/a[7]/text()

(: on récupère dans la variable $formeGouv la forme du gouverneur du pronom (le verbe),
   et dans la variable $positionGouvGouv la position du gouverneur du gouverneur (l'antécédent, le nom complété par la relative)

   pour cela on récupère dans un premier temps la ligne de l'item correspondant au gouverneur,
   en fonction de sa position par rapport à son dépendant (avant ou après)
   attention il faut convertir le contenu textuel des positions en valeur numérique :)
let $ligneGouv:=
    if ( number($positionGouv) < number($positionPronom) )
    then ( $item/preceding-sibling::item[number(a[1])=number($positionGouv)] )
    else ( $item/following-sibling::item[number(a[1])=number($positionGouv)] )
let $formeGouv:= $ligneGouv/a[2]/text()
let $positionGouvGouv:= $ligneGouv/a[7]/text()

(: on récupère dans la variable $formeGouvGouv la forme du gouverneur du gouverneur (l'antécédent),
   en fonction de sa position par rapport à son dépendant (avant ou après) 
   attention, comme on récupère normalement un nom, on veut éviter de récupérer les articles élidés mal analysés
   alors, on teste si la forme contient une apostrophe :)
let $formeGouvGouv:=
    if ( number($positionGouvGouv) < number($positionGouv) )
    then ( $item/preceding-sibling::item[number(a[1])=number($positionGouvGouv)]/a[2]/text() )
    else ( $item/following-sibling::item[number(a[1])=number($positionGouvGouv)]/a[2]/text() )

(: on concatène la forme de l'antécédent + la forme du pronom + la forme du verbe avec des espaces
   et on stocke la liste de toutes les séquences dans la variable $res :)
let $res:= string-join(($formeGouvGouv,$formePronom,$formeGouv)," ")

(: on regroupe les séquences :)
group by $grp:=$res
(: on compte le nombre d'occurrences de chaque séquence et on les trie par ordre décroissant :)
order by count($res) descending

(: on concatène la séquence + une tabulation + le nombre d'occurrences
   et on affiche le tout :)
return string-join(($grp,count($res)),"	")

Méthode 2 : XSLT

Le code ci-dessous permet d'extraire les relations de type NSUBJ. Pour extraire les dépendances d'une autre relation syntaxique, il suffit de remplacer la valeur du paramètre $relation, dans la ligne :

<xsl:param name="relation">nsubj</xsl:param>.

La feuille de style permet d'afficher les séquences dépendant-gouverneur, mais ne trie pas les résultats. Pour l'extraction et le tri, on lancera la suite de commandes suivantes directement en ligne de commandes :

xsltproc fichier.xsl fichier.xml | sort | uniq -c | sort -nr

Par exemple, pour la rubrique 3244 :

xsltproc bao3_extract_dep_1relation_udpipexml.xsl BAO2_sortieUDpipe_3244.txt.xml | sort | uniq -c | sort -nr > BAO3_sortie_XSLT_NSUBJ_3244.txt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="text" encoding="utf-8"/>

<!-- on s'intéresse aux relations de dépendances de type 'nsubj'
     si vous souhaitez extraire u autre type de relation, modifiez la ligne 19 de ce code
     selon la relation, on voudra aussi modifier l'ordre d'affichage (Dep Gouv ou Gouv Dep) lignes 47 et 52 -->
<!-- dans le fichier de sortie UDPipe reformaté en XML :
     chaque phrase est dans une balise <p>
     chaque token est dans une balise <item>
     une balise <item> contient 10 balises <a>
     la 1ere balise <a> contient l'identifiant du token
     la 2eme balise <a> contient la forme du token (son texte)
     la 7eme balise <a> contient l'identifiant de son gouverneur
     la 8eme balise <a> correspond à la relation de dépendance avec son gouverneur -->

<!-- on crée un paramètre $relation dans lequel on stocke la relation recherchée : ici 'nsubj'-->
<xsl:param name="relation">nsubj</xsl:param>

<!-- template principal -->
<xsl:template match="/">
<xsl:apply-templates select=".//p"/>
</xsl:template>

<!-- règle appliquée à chaque balise <p> càd à chaque phrase -->
<xsl:template match="p">

	<!-- pour chaque item de la phrase dont la 8e balise <a> contient la chaîne de caractères  dans $relation (la relation entrée en paramètre) -->
	<xsl:for-each select="item">

		<xsl:if test="contains(./a[8]/text(),$relation)">

			<!-- on récupère dans la variable $formeDep la forme du dépendant
			     dans la variable $positionDep l'identifiant du dépendant
			     dans la variable $positionGouv l'identifiant du gouverneur -->
			<xsl:variable name="formeDep" select="./a[2]/text()"/>
			<xsl:variable name="positionDep" select="./a[1]/text()"/>
			<xsl:variable name="positionGouv" select="./a[7]/text()"/>
			
			<!-- on récupère dans la variable $formeGouv la forme du gouverneur,
			     en fonction de la position du gouverneur par rapport au dépendant (avant ou après) 
			     et on affiche la forme du gouverneur + un espace + la forme du dépendant + un saut de ligne -->
			<xsl:choose>
				<xsl:when test="$positionGouv < $positionDep">
					<xsl:variable name="formeGouv" select="preceding-sibling::item[a[1]=$positionGouv]/a[2]/text()"/>    
					<xsl:value-of select="$formeDep"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
				</xsl:when>
				<xsl:otherwise>
					<xsl:variable name="formeGouv" select="following-sibling::item[a[1]=$positionGouv]/a[2]/text()"/>    
					<xsl:value-of select="$formeDep"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
				</xsl:otherwise>
			</xsl:choose>
		
		</xsl:if>

	</xsl:for-each>
</xsl:template>


</xsl:stylesheet>

Ce deuxième code permet d'extraire les relatives sujet. De la même façon qu'avec le premier code présenté, on utilisera xsltproc pour trier les résultats :

xsltproc fichier.xsl fichier.xml | sort | uniq -c | sort -nr

Par exemple, pour la rubrique 3244 :

xsltproc bao3_extract_dep_relativesSujet_udpipexml.xsl BAO2_sortieUDpipe_3244.txt.xml | sort | uniq -c | sort -nr > BAO3_sortie_XSLT_relativesSujet_3244.txt

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output method="text" encoding="utf-8"/>

<!-- on s'intéresse aux relatives sujet
     antécédent -[acl:relcl]-> verbe -[nsubj]-> pronom[PronType=Rel]
     l'objectif est d'extraire le pronom relatif sujet, puis le verbe qui le gouverne, puis le nom complété par la relative -->
<!-- dans le fichier de sortie UDPipe reformaté en XML :
     chaque phrase est dans une balise <p>
     chaque token est dans une balise <item>
     une balise <item> contient 10 balises <a>
     la 1ere balise <a> contient l'identifiant du token
     la 2eme balise <a> contient la forme du token (son texte)
     la 6eme balise <a> contient les marques flexionnelles selon le POS (gender,number,prontype,mood,person,tense...)
     la 7eme balise <a> contient l'identifiant de son gouverneur
     la 8eme balise <a> correspond à la relation de dépendance avec son gouverneur -->

<!-- template principal -->
<xsl:template match="/">
<xsl:apply-templates select=".//p"/>
</xsl:template>

<!-- règle appliquée à chaque balise <p> càd à chaque phrase -->
<xsl:template match="p">

	<!-- pour chaque item de la phrase dont la 8e balise <a> contient la chaîne de caractères  'nsubj' (la relation qui gouverne le pronom)
         et dont la 6e balise <a> contient 'PronType=Rel' (il s'agit bien d'un pronom relatif) -->
	<xsl:for-each select="item">

		<xsl:if test="contains(a[8]/text(),'nsubj') and contains(a[6]/text(),'PronType=Rel')">

			<!-- on récupère dans la variable $formePronom la forme du pronom (le dépendant)
			     dans la variable $positionPronom l'identifiant du pronom
			     dans la variable $positionGouv l'identifiant du gouverneur du pronom (verbe) -->
			<xsl:variable name="formePronom" select="./a[2]/text()"/>
			<xsl:variable name="positionPronom" select="./a[1]/text()"/>
			<xsl:variable name="positionGouv" select="./a[7]/text()"/>
			
			<!-- en fonction de la position du gouverneur par rapport au dépendant (avant ou après) 
			     on récupère dans la variable $formeGouv la forme du gouverneur
				 et dans la variable $positionGouvGouv l'identifiant du gouverneur du gouverneur (l'antécédent, le nom complété par la relative) -->
			<!-- puis en fonction de la position du gouverneur du gouverneur par rapport au dépendant (avant ou après)
			     on récupère dans la variable $formeGouvGouv la forme du gouverneur du gouverneur -->
			<!-- puis on affiche : forme de l'antécédent (GouvGouv) + espace + forme du pronom (Dep) + espace + forme du verbe (Gouv) + saut de ligne -->
			<xsl:choose>
				<xsl:when test="$positionGouv < $positionPronom">
					<xsl:variable name="formeGouv" select="preceding-sibling::item[a[1]=$positionGouv]/a[2]/text()"/>
					<xsl:variable name="positionGouvGouv" select="preceding-sibling::item[a[1]=$positionGouv]/a[7]/text()"/>
					<xsl:choose>
						<xsl:when test="$positionGouvGouv < $positionPronom">
							<xsl:variable name="formeGouvGouv" select="preceding-sibling::item[a[1]=$positionGouvGouv]/a[2]/text()"/>
							<xsl:value-of select="$formeGouvGouv"/><xsl:text> </xsl:text><xsl:value-of select="$formePronom"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
						</xsl:when>
						<xsl:otherwise>
							<xsl:variable name="formeGouvGouv" select="following-sibling::item[a[1]=$positionGouvGouv]/a[2]/text()"/>
							<xsl:value-of select="$formeGouvGouv"/><xsl:text> </xsl:text><xsl:value-of select="$formePronom"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
						</xsl:otherwise>
					</xsl:choose>
				</xsl:when>
				<xsl:otherwise>
					<xsl:variable name="formeGouv" select="following-sibling::item[a[1]=$positionGouv]/a[2]/text()"/>
					<xsl:variable name="positionGouvGouv" select="following-sibling::item[a[1]=$positionGouv]/a[7]/text()"/>
						<xsl:choose>
						<xsl:when test="$positionGouvGouv < $positionPronom">
							<xsl:variable name="formeGouvGouv" select="preceding-sibling::item[a[1]=$positionGouvGouv]/a[2]/text()"/>
							<xsl:value-of select="$formeGouvGouv"/><xsl:text> </xsl:text><xsl:value-of select="$formePronom"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
						</xsl:when>
						<xsl:otherwise>
							<xsl:variable name="formeGouvGouv" select="following-sibling::item[a[1]=$positionGouvGouv]/a[2]/text()"/>
							<xsl:value-of select="$formeGouvGouv"/><xsl:text> </xsl:text><xsl:value-of select="$formePronom"/><xsl:text> </xsl:text><xsl:value-of select="$formeGouv"/><xsl:text>
</xsl:text>
						</xsl:otherwise>
					</xsl:choose>
				</xsl:otherwise>
			</xsl:choose>

		</xsl:if>

	</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Méthode 3 : Perl

La troisième méthode consiste à utiliser Perl.

Le premier programme extrait les couples (Gouverneur, Dépendant) de la relation indiquée en ligne de commande. Par exemple, pour extraire les relations sujet du fichier correspondant à la rubrique 3244 :

perl bao3_extract_dep_1relation_udpipexml.pl BAO2_sortieUDpipe_3244.txt.xml "nsubj" > BAO3_sortie_Perl_NSUBJ_3244.txt

Ce script a été enrichi de sorte que l'utilisateur entre deux relations en ligne de commande. Le deuxième script permet ainsi de récupérer les triplets (Dépendant1, Gouverneur, Dépendant2) à partir de 2 relations, pour 1 seul et même gouverneur. Par exemple, afin de récupérer les triplets Sujet-Verbe-Objet sur le fichier de la rubrique 3244, on lancera la commande :

perl bao3_extract_dep_2dep1gouv_udpipexml.pl BAO2_sortieUDpipe_3244.txt.xml "nsubj" "obj" > BAO3_sortie_Perl_SVO_3244.txt

Le troisième programme traite le cas particulier des relatives sujet. Il extrait les triplets (Antécédent, Pronom, Verbe). Il n'y a plus de relation à indiquer en ligne de commande. Pour lancer le programme, il suffit d'indiquer le fichier à parcourir, par exemple :

perl bao3_extract_dep_relativesSujet_udpipexml.pl BAO2_sortieUDpipe_3244.txt.xml > BAO3_sortie_Perl_relativesSujet_3244.txt

Script 1 :

#!/usr/bin/perl
<<DOC; 
Nom -- Elise LINCKER
Date -- AVRIL 2021

Traitement -- extraction de dépendances syntaxiques

Utilisation du programme -- perl bao3_extract_dep_1relation_udpipexml.pl FICHIER_A_TRAITER "RELATION_DE_DEPENDANCE_A_EXTRAIRE"
Exemple d utilisation -- perl bao3_extract_dep_1relation_udpipexml.pl BAO2_sortieUDpipe_3244.txt.xml "nsubj" > ./sorties_dep/BAO3_sortie_Perl_OBJ_3244.txt
Entrée -- le nom de la sortie UDPipe reformatée en XML
          et le nom de la relation syntaxique de dépendance à extraire
Sortie -- la liste triée des couples Gouv, Dep de la relation

Remarque -- selon la relation à extraire, on voudra modifier l ordre d affichage des résultats (Dep Gouv ou Gouv Dep) lignes 58 et 67
DOC

use strict;
use utf8;
binmode STDOUT, ':utf8';

#---------------------------------------------------------------------------------
my $fichier="$ARGV[0]";
my $relation="$ARGV[1]";
my %dicoRelation=();

#---------------------------------------------------------------------------------
#on découpe le texte par phrase (1 phrase par balise <p>)
$/="</p>"; 			
open my $IN ,"<:encoding(utf8)","$fichier";
while (my $phrase=<$IN>) {

	#-----------------------------------------------------------------------------
	# on transforme la phrase récupérée en une liste d'items : 1 ligne = 1 balise <item>
	#il suffit de segmenter les phrases avec le caractère de retour à la ligne car on a déjà 1 item par ligne dans le fichier
	my @LIGNES=split(/\n/,$phrase);

	#pour chaque ligne = item :
	for (my $i=0;$i<=$#LIGNES;$i++) {

		#si la ligne lue contient la relation :
		#on utilise les expressions régulières pour récupérer :
		#	la forme du dépendant dans la variable $formeDep
		#	la position du dépendant dans la variable $positionDep
		#	la position du gouverneur dans la variable $positionGouv
		if ($LIGNES[$i]=~/<item><a>([^<]+)<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>([^<]+)<\/a><a>[^<]*$relation[^<]*<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/i) {
			my $formeDep=$2;
			my $positionDep=$1;
			my $positionGouv=$3;

			#on récupère la forme du gouverneur dans la variable $formeGouv
			#en fonction de la position du gouverneur par rapport au dépendant (avant ou après)
			#et on ajouter à $dicoRelation la concaténation forme du gouverneur + espace + forme du dépendant
			if ($positionDep > $positionGouv) {
				#si le gouverneur est avant le dépendant : on cherche le gouverneur parmi les lignes d'indice entre 0 et i
				for (my $k=0; $k<$i; $k++) {
					if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
						my $formeGouv=$1;
						$dicoRelation{"$formeGouv $formeDep"}++;
					}
				}
			}
			else {
				#si le gouverneur est après le dépendant : on cherche le gouverneur parmi la ligne d'indice i+1 et la fin de la liste
				for (my $k=$i+1; $k<=$#LIGNES; $k++) {
					if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
						my $formeGouv=$1;
						$dicoRelation{"$formeGouv $formeDep"}++;
					}
				}
			}
		}
	}
}
close ($IN);

#on affiche la liste des couples Gouv,Dep et leur nombre d'occurrences
foreach my $relation (sort {$dicoRelation{$b}<=>$dicoRelation{$a}} (keys %dicoRelation)) {
	print "$relation\t$dicoRelation{$relation}\n";
}

Script 2 :

#!/usr/bin/perl
<<DOC; 
Nom -- Elise LINCKER
Date -- AVRIL 2021

Traitement -- extraction de dépendances syntaxiques
              séquences Dep1 Gouv Dep2, pour 2 relations et un même gouverneur
              Dep1 <- Gouv -> Dep2

Utilisation du programme -- perl bao3_extract_dep_2dep1gouv_udpipexml.pl FICHIER_A_TRAITER "RELATION1" "RELATION2"
Exemple d utilisation -- perl bao3_extract_dep_2dep1gouv_udpipexml.pl BAO2_sortieUDpipe_3214.txt.xml "nsubj" "obj" > ./sorties_dep/BAO3_sortie_Perl_SVO_3214.txt
Entrée -- le nom de la sortie UDPipe reformatée en XML
          le nom de la première relation de dépendances
          le nom de la deuxième relation de dépendances
Sortie -- la liste triée des triplets Dep1 Gouv Dep2 de la relation

Remarque -- selon les relations, on voudra changer l ordre d affichage des résultats
            Gouv Dep1 Dep2 / Dep1 Gouv Dep2 / Dep2 Gouv Dep1 ...etc, lignes 76 et 85
DOC

use strict;
use utf8;
binmode STDOUT, ':utf8';

#---------------------------------------------------------------------------------
my $fichier="$ARGV[0]";
my $relation1="$ARGV[1]";
my $relation2="$ARGV[2]";
my %dicoRelation=();

#---------------------------------------------------------------------------------
#on découpe le texte par phrase (1 phrase par balise <p>)
$/="</p>"; 			
open my $IN ,"<:encoding(utf8)","$fichier";
while (my $phrase=<$IN>) {

	#-----------------------------------------------------------------------------
	# on transforme la phrase récupérée en une liste d'items : 1 ligne = 1 balise <item>
	#il suffit de segmenter les phrases avec le caractère de retour à la ligne car on a déjà 1 item par ligne dans le fichier
	my @LIGNES=split(/\n/,$phrase);

	#pour chaque ligne = item :
	for (my $i=0;$i<=$#LIGNES;$i++) {

		#on utilise les expressions régulières pour tester :
		#	si la 8e balise <a> contient la chaîne de caractères dans $relation1
		#et, le cas échéant, pour récupérer :
		#	dans la variable $formeDep1 la forme du dépendant 1
		#	dans la variable $positionDep2 la position du dépendant 1
		#	dans la variable $positionGouv la position du gouverneur
		if ($LIGNES[$i]=~/<item><a>([^<]+)<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>([^<]+)<\/a><a>[^<]*$relation1[^<]*<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/i) {
			my $formeDep1=$2;
			my $positionDep1=$1;
			my $positionGouv=$3;

			#on utilise les expressions régulières pour tester si il existe un autre item dans la phrase pour lequel :
			#	la 8e balise <a> contient la chaîne de caractères dans $relation2
			#	la 7e balise <a> contient le contenu de la variable $positionGouv
			#	<=> l'item est gouverné par une relation obj et son gouverneur est le même que celui de Dep1
			#et, le cas échéant, pour récupérer :
			#	dans la variable $formeDep2 la forme du dépendant 2
			for (my $j=0;$j<=$#LIGNES;$j++) {
				if ($LIGNES[$j]=~/<item><a>[^<]*<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>$positionGouv<\/a><a>[^<]*$relation2[^<]*<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
					my $formeDep2=$1;

					#si on a bien récupéré deux items ayant le même gouverneur
					#alors, en fonction de la position du gouverneur par rapport au dépendant 1 (avant ou après) 
					#on récupère dans la variable $formeGouv la forme du gouverneur
					#puis on ajoute à $dicoRelation la concaténation : forme dep1 + espace + forme gouv + espace + forme dep2
					#!!! cet ordre peut être modifié selon le résultat attendu !!!
					if ($positionDep1 > $positionGouv) {
						#si le gouverneur est avant le dépendant 1 : on cherche le gouverneur parmi les lignes d'indice entre 0 et i
						for (my $k=0; $k<$i; $k++) {
							if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
								my $formeGouv=$1;
								$dicoRelation{"$formeDep1 $formeGouv $formeDep2"}++;
							}
						}
					}
					else {
						#si le gouverneur est après le dépendant 1 : on cherche le gouverneur parmi la ligne d'indice i+1 et la fin de la liste
						for (my $k=$i+1; $k<=$#LIGNES; $k++) {
							if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
								my $formeGouv=$1;
								$dicoRelation{"$formeDep1 $formeGouv $formeDep2"}++;
							}
						}
					}
				}
			}			
		}
	}
}
close ($IN);

#on affiche la liste des triplets Dep1 Gouv Dep2 et leur nombre d'occurrences
foreach my $relation (sort {$dicoRelation{$b}<=>$dicoRelation{$a}} (keys %dicoRelation)) {
	print "$relation\t$dicoRelation{$relation}\n";
}

Script 3 :

#!/usr/bin/perl
<<DOC; 
Nom -- Elise LINCKER
Date -- AVRIL 2021

Traitement -- extraction des triplets (Antécédent Pronom Verbe) de relatives sujet
              antécédent -[acl:relcl]-> verbe -[nsubj]-> pronom[PronType=Rel]

Utilisation du programme -- perl bao3_extract_dep_relativesSujet_udpipexml.pl FICHIER_A_TRAITER
Exemple d utilisation -- perl bao3_extract_dep_relativesSujet_udpipexml.pl BAO2_sortieUDpipe_3244.txt.xml > ./sorties_dep/BAO3_sortie_Perl_relativesSujet_3244.txt
Entrée -- le nom de la sortie UDPipe reformatée en XML
Sortie -- la liste triée des triplets GouvGouv Dep Gouv <=> Antécédent Pronom Verbe des relatives sujet
DOC

use strict;
use utf8;
binmode STDOUT, ':utf8';

#---------------------------------------------------------------------------------
my $fichier="$ARGV[0]";
my %dicoRelation=();

#---------------------------------------------------------------------------------
#on découpe le texte par phrase (1 phrase par balise <p>)
$/="</p>"; 			
open my $IN ,"<:encoding(utf8)","$fichier";
while (my $phrase=<$IN>) {

	#-----------------------------------------------------------------------------
	# on transforme la phrase récupérée en une liste d'items : 1 ligne = 1 balise <item>
	#il suffit de segmenter les phrases avec le caractère de retour à la ligne car on a déjà 1 item par ligne dans le fichier
	my @LIGNES=split(/\n/,$phrase);

	#pour chaque ligne = item :
	for (my $i=0;$i<=$#LIGNES;$i++) {

		#on utilise les expressions régulières pour tester :
		#	si la 8e balise <a> contient la chaîne de caractères  'nsubj' (la relation qui gouverne le pronom)
		#	et la 6e balise <a> contient 'PronType=Rel' (il s'agit bien d'un pronom relatif)
		#et pour récupérer :
		#	dans la variable $formePronom la forme du pronom (le dépendant)
		#	dans la variable $positionPronom la position du pronom 
		#	dans la variable $positionGouv la position du gouverneur du pronom (verbe)
		if ($LIGNES[$i]=~/<item><a>([^<]+)<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]*PronType=Rel[^<]*<\/a><a>([^<]+)<\/a><a>[^<]*nsubj[^<]*<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/i) {
			my $formePronom=$2;
			my $positionPronom=$1;
			my $positionGouv=$3;

			#en fonction de la position du gouverneur par rapport au dépendant (avant ou après) 
			#on récupère dans la variable $formeGouv la forme du gouverneur
			#et dans la variable $positionGouvGouv l'identifiant du gouverneur du gouverneur (l'antécédent, le nom complété par la relative)
			
			#puis en fonction de la position du gouverneur du gouverneur par rapport au dépendant (avant ou après)
			#on récupère dans la variable $formeGouvGouv la forme du gouverneur du gouverneur

			#puis on ajoute à $dicoRelation la concaténation : forme de l'antécédent (GouvGouv) + espace + forme du pronom (Dep) + espace + forme du verbe (Gouv)

			if ($positionPronom > $positionGouv) {
				#si le gouverneur est avant le dépendant : on cherche le gouverneur parmi les lignes d'indice entre 0 et i
				for (my $k=0; $k<$i; $k++) {
					if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
						my $formeGouv=$1;
						my $positionGouvGouv=$2;
						if ($positionPronom > $positionGouvGouv) {
							for (my $k=0; $k<$i; $k++) {
								if ($LIGNES[$k]=~/<item><a>$positionGouvGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
									my $formeGouvGouv=$1;
									$dicoRelation{"$formeGouvGouv $formePronom $formeGouv"}++;
								}
							}
						}
						else {
							for (my $k=$i+1; $k<=$#LIGNES; $k++) {
								if ($LIGNES[$k]=~/<item><a>$positionGouvGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
									my $formeGouvGouv=$1;
									$dicoRelation{"$formeGouvGouv $formePronom $formeGouv"}++;
								}
							}
						}
					}
				}
			}
			else {
				#si le gouverneur est après le dépendant : on cherche le gouverneur parmi la ligne d'indice i+1 et la fin de la liste
				for (my $k=$i+1; $k<=$#LIGNES; $k++) {
					if ($LIGNES[$k]=~/<item><a>$positionGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
						my $formeGouv=$1;
						my $positionGouvGouv=$2;
						if ($positionPronom > $positionGouvGouv) {
							for (my $k=0; $k<$i; $k++) {
								if ($LIGNES[$k]=~/<item><a>$positionGouvGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
									my $formeGouvGouv=$1;
									$dicoRelation{"$formeGouvGouv $formePronom $formeGouv"}++;
								}
							}
						}
						else {
							for (my $k=$i+1; $k<=$#LIGNES; $k++) {
								if ($LIGNES[$k]=~/<item><a>$positionGouvGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
									my $formeGouvGouv=$1;
									$dicoRelation{"$formeGouvGouv $formePronom $formeGouv"}++;
								}
							}
						}
					}
				}
			}
		}
	}
}
close ($IN);

#on affiche la liste des triplets GouvGouv, Dep, Gouv et leur nombre d'occurrences
foreach my $relation (sort {$dicoRelation{$b}<=>$dicoRelation{$a}} (keys %dicoRelation)) {
	print "$relation\t$dicoRelation{$relation}\n";
}

Remarque :

Une autre solution serait d'utiliser des programmes Perl, non pas sur la sortie UDPipe reformatée en XML, mais directement sur la sortie UDPipe elle-même. Cette sortie est au format CONLL. On peut toujours utiliser la même méthode et rechercher les dépendants et gouverneurs à l'aide d'expressions régulières, la différence est qu'il faudra utiliser les tabulations, et non les balises. Les deux méthodes sont équivalentes et produisent les mêmes résultats. Nous avons décidé de ne présenter que la méthode d'extraction depuis les fichiers XML, comme nous l'avons fait avec les méthodes XQuery et XSLT.

Résultats

Fichiers :

RUBRIQUE RELATION NSUBJ SVO RELATIVES SUJETS
SORTIE XQuery SORTIE XSLT SORTIE Perl SORTIE Perl SORTIE XQuery SORTIE XSLT SORTIE Perl
Europe - 3214 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Planète - 3244 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Culture - 3246 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Cinéma - 3476 icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement icone-de-telechargement
Tout icone-de-telechargement

Observations sur les résultats :

Les résultats obtenus avec les trois méthodes sont identiques. La seule différence trouvée concerne la rubrique Planète (3244) pour l'extraction de relatives sujet : avec les méthodes XQuery et XSLT, une séquence de plus est trouvée : "qui réponses". Cette séquence ne correspond pas à ce qu'on recherche : "réponses" n'est pas un verbe et aucun antécédent n'est récupéré. Cette erreur est dûe à un mauvais étiquetage par UDPipe, dans la phrase : « Pourquoi ? Pour qui ? » : nos réponses à 36 questions très partagées sur la crise du Covid.
Pour éviter de récupérer de telles erreurs, peut-être faudrait-il ajouter des critères dans le filtrage avec XSLT et XQuery.

On remarque parmi les extractions des relations SUJET et SUJET-OBJET que la plupart des séquences les plus courantes n'apportent pas ou peu d'informations. On retrouve par exemple "ils racontent", "critiquent présentent", "Matinale propose", "Monde donne parole", "il faut que", "vous suivi l'actualité"...etc. Moins la relation est fréquente, plus elle est spécifique à une actualité de la rubrique. Enfin, on retrouve beaucoup de "qui" en position sujet : "qui a", "qui fait", "qui marqué"...etc., d'où l'intérêt d'observer les relatives sujets pour retrouver l'antécédent du pronom relatif.

D'autre part, en ce qui concerne la rubrique Planète (3244), les trois séquences les plus fréquentes sont liées au COVID-19 : "épidémie qui fait", "coronavirus qui sévit" et "graphiques qui montrent" (en retrouvant cette séquence dans les articles, elle est à chaque fois liée aux conséquences du confinement). Rien d'étonnant, car le COVID-19 est le sujet numéro 1 de l'actualité dans le monde en 2020. Le thème du COVID-19 revient dans toutes les rubriques, dans toutes les relations. Davantage dans la rubrique Planète (3244), puis dans la rubrique Europe (3214) : "cas augmentent", "pays compte cas", "bilan dépasse morts" "Ehpad connaît hécatombe", "coronavirus qui sévit"... (3244) "pandémie progresse", "Covid-19 déferla", "% contract maladie", "virus qui appelle"... (3214). Un peu moins fréquemment mais également dans les rubriques Culture (3246) et Cinéma (3476) : "Covid-19 attaque", "Jean transmit virus", "édition annulée", "pandémie impose confinement"... (3246), "studios reportent sortie", "pandémie joué rôle", "pandémie qui empêche", "cinémas qui rouvert"... (3476).

Il sera intéressant de générer, à partir des patrons et relations, des graphes illustrant les thèmes dégagés. Certains sont présentés dans la partie 3 de cette boîte à outils.