Introduction à l’analyse textuelle avec R et le tidytext
Rappel : il existe plusieurs façons de faire de l’analyse textuelle, nous ne verrons ici qu’une approche rudimentaire mais plutôt efficace, le bag of words (sac de mots).
On charge les différents paquets nécessaires pour cette séance.
library(tidyverse)
library(dplyr)
library(tidytext) # Pour manipuler les données textuelles
library(stopwords) # Mots "inutiles"
library(SnowballC) # Lemmatisation / stemmatisation
Chargeons la base de données entretiens.
entretiens <- read_csv(here::here('data', 'entretiens','entretiens.csv'))
glimpse(entretiens)
Observations: 18
Variables: 5
$ binome [3m[38;5;246m<chr>[39m[23m "A_GIBOIRE_PFISTER.txt", "B_BRILLAUD_CHANNAC_OLIVIER_Maison_…
$ commerce_nom [3m[38;5;246m<chr>[39m[23m "Les Récupérables", "Maison Château Rouge", "Sap & Co", "Tem…
$ commerce_type [3m[38;5;246m<chr>[39m[23m "vente de vêtements fabriqués à partir de tissus recyclés", …
$ sexe [3m[38;5;246m<chr>[39m[23m "Femme", "Homme", "Homme", "Homme", "Homme", "Homme", "Homme…
$ texte [3m[38;5;246m<chr>[39m[23m "Euh alors les filles ça va pas être possible parce que c es…
Que remarque-t-on ? Comment cette base est-elle construite ?
Étape 1 : recodage (“tokenization”)
Dans l’approche “sac de mots” (bag of words), on divise notre texte en unités de texte plus petites. Par exemple, on peut faire des “sacs” de 1 mot, 2 mots, …, \(n\) mots. C’est ce qu’on appelle des \(n\)-grams. Pour l’instant, on se contente de faire des sacs d’un seul mot (\(1\)-gram).
On veut donc passer d’une base large (une colonne très longue, avec tout le texte) vers une base longue (beaucoup de lignes). En fait, on veut changer d’individu statistique : du texte vers le sac de mots, ou dans notre cas, vers le mot.
C’est donc une opération de recodage. Nous pourrions le faire avec les outils du tidyverse, mais comme c’est une opération commune dans l’analyse textuelle, nous allons utiliser directement une fonction issue du paquet tidytext, unnest_tokens().
mots <- entretiens %>%
unnest_tokens(mot, texte)
Regardons quels sont les 10 mots les plus utilisés :
mots %>%
count(mot, sort = TRUE) %>%
head(10)
A priori, ce ne sont pas trop les mots qui nous intéressent… Comment faire ?
Étape 2 : enlever les “mots vides” (stop words)
Pour enlever les mots qui nous intéressent, nous pourrions nous-mêmes construire une liste de mots que nous voulons exclure de notre base. Mais ce serait un peu fastidieux, nous allons donc plutôt utiliser une liste déjà toute faite, contenue dans le paquet stopwords.
Les stop words, appelés “mots vides” en français, sont les mots qu’on juge “vides de sens”, du moins dans une approche sac de mots. Il faut ici user de prudence et ne pas utiliser ces listes sans vérifier quels termes elles contiennent. En effet, on pourrait très bien s’intéresser à ces “mots vides” selon notre question de recherche, même si ce n’est pas notre cas ici.
Voyons les stopwords en question :
glimpse(stopwords('fr', source="stopwords-iso"))
chr [1:689] "a" "abord" "absolument" "afin" "ah" "ai" "aie" "aient" "aies" ...
Nous pouvons filtrer notre liste de termes de plusieurs façons. Nous allons ici utiliser une procédure propre à la manipulation des bases de données, les jointures. Les jointures permettent de combiner plusieurs bases entre elles. Ici, nous aller créer une base de stop words, avec une variable mot, qui va correspondre exactement à la variable mot dans notre base de mots. Ainsi, nous allons pouvoir réaliser une anti-jointure, c’est-à-dire exclure de la base mots toutes les lignes où la variable mot correspond à la variable mot dans notre base de stop words.
# Crétaion d'une base de mots vides
mots_vides <- tibble(mot = stopwords('fr', source="stopwords-iso"))
# Création d'une base de mots
mots <- entretiens %>%
unnest_tokens(mot, texte) %>%
anti_join(mots_vides)
Remarquez au passage le message qu’affiche dplyr pour nous signaler comment s’effectue la jointure.
Maintenant, regardons à nouveau quels sont les 25 mots les plus utilisés :
mots %>%
count(mot, sort = TRUE) %>%
head(25) %>%
print(n = 25)
C’est déjà bien mieux ! Mais nous avons toujours quelques soucis : choses et chose pourraient ainsi être regroupés. Comment y parvenir ?
Étape 3 : lemmatisation ou stemmatisation
Encore une fois, nous pourrions ici “manuellement” nous servir du tidyverse pour regrouper tous les mots d’une même famille ensemble, mais ce serait à nouveau une opération fastidieuse. Évidemment, les paquets d’analyse textuelle proposent des outils pour y parvenir. Nous pouvons distinguer en gros deux méthodes :
- stemmatisation : on coupe les mots pour en obtenir la racine (stem veut dire “tige”, “souche” en anglais). Les mots “choses” et “chose” deviennent ainsi “chose”.
- lemmatisation : cette opération est plus complexe, mais prend en compte certains cas que la stemmatisation laissera de côté, par exemple le verbe “essayer” se transformera en “essaie”, qu’on pourra plus facilement agréger.
En français, la stemmatisation donne des résultats limités, nous allons donc plutôt lemmatiser notre base à l’aide du paquet SnowballC et de sa fonction wordStem(). Regardons comment elle fonctionne :
wordStem('essayer', language = "fr")
[1] "essai"
Nous allons créer une nouvelle variable lemme pour chaque mot, afin de ne pas perdre les mots originaux.
Combinons toutes nos commandes pour produire la base de mots souhaitée :
mots <- entretiens %>%
unnest_tokens(mot, texte) %>%
anti_join(mots_vides) %>%
mutate(lemme = wordStem(mot, language = "fr"))
Joining, by = "mot"
Et maintenant, regardons quels sont les 25 mots les plus employés :
top25 <- mots %>%
count(lemme, sort = TRUE) %>%
head(25) %>%
print(n = 25)
Le résultat est bien meilleur.
Présenter et exporter ses résultats
Nous avons maintenant un premier résultat, un tri à plat des 25 mots les plus employés dans notre corpus. Admettons qu’on veuille le présenter dans un document. Nous avons deux possibilités :
- Présenter nos données sous forme numérique
- Présenter nos données sous forme graphique
Exporter un tableau vers un traitement de texte
Pour exporter un tableau vers un traitement de texte traditionnel (LibreOffice, par exemple, je n’en vois pas d’autres, non, vraiment), on peut suivre la méthode suivante :
- On écrit le tableau dans un format spécial depuis la console (ou bien on peut l’exporter directement dans un nouveau fichier)
- On copie et on colle le texte obtenu dans le traitement de texte
- On sélectionne le texte collé, et on se rend dans le menu Table > Convert et on sélectionne les réglages appropriés.
Par exemple :
top25 %>%
write.table(row.names = FALSE, quote = FALSE, sep = ",")
lemme,n
oui,432
ouais,405
quarti,374
fair,281
vrai,243
chos,218
sais,206
an,196
commerc,176
viennent,175
heu,170
fin,151
travaill,147
produit,141
part,129
ru,129
client,126
temp,123
paris,122
coup,115
arriv,113
déjà,112
faut,110
mond,109
truc,103
Faire un graphique avec ggplot2
Nous allons voir ici comment réaliser des graphiques avec ggplot2, ce sera une introduction très brève à un vaste sujet.
La visualisation de données pourrait faire l’objet d’un cours à part. Voici quelques conseils généraux :
- Une représentation graphique n’est pas une représentation objective de nos données, au sens où elle donnerait un accès direct aux observations. Au contraire, un graphique convie une information et un point de vue sur ces informations. Quand vous réalisez un graphique, ne cherchez pas à faire un graphique plaisant mais plutôt un graphique qui a du sens et qui convie au mieux le message que vous voulez faire passer.
- Le principe de Tufte : le meilleur graphique est celui qui optimise le ratio encre / information. L’idée est d’utiliser le moins d’éléments graphiques possibles pour convier l’information souhaitée.
- Les éléments graphiques ou esthétiques doivent être mis au service de la sémantique des données.
Le paquet ggplot2 est directement intégré au paquet tidyverse, le gg signifie “Grammar of Graphics”, littéralement la grammaire des graphiques. L’idée de ggplot est relativement simple : c’est de nous fournir un langage qui permette de produire des graphiques en distinguant les principes sémantiques du graphique de sa mise en forme en suivant des étapes claires.
Voyons comment cela marche en pratique.
Étape 1 : data, sélectionner des données
Tout d’abord, un graphique a besoin de données préalablement préparées. C’est notre cas, on veut utiliser top25 pour en faire un graphique.
On peut fournir les données à ggplot2 de deux façons :
ggplot(top25) # Approche de base dans R

# Approche Tidyverse
top25 %>%
ggplot()

J’utiliserai la deuxième approche par la suite.
Que peut-on remarquer dans RStudio ?
Étape 2 : aes (aesthetics), faire correspondre données et esthétiques
C’est peut-être le concept le plus important de ggplot2. L’idée est qu’un graphique dispose d’un certain nombre de caractéristiques esthétiques, auxquelles nous pourrons faire correspondre des variables. Voici quelques aesthetics :
x, y : axes des abscisses et des ordonnées
fill : remplissage (des colonnes d’un histogramme, d’un diagramme en barre)
colour : couleur (d’un point, d’une courbe, etc.)
shape : forme (d’un point, etc.)
Le concept des esthétiques est particulièrement puissant et élégant, comme nous le verrons par la suite.
Pour l’instant, nous allons simplement faire correspondre l’axe des x à la variable qualitative mot et l’axe des y au nombre d’occurrences de ces mots, n.
top25 %>%
ggplot(aes(x = lemme, y = n))

Que peut-on remarquer dans RStudio ?
Étape 3 : geom (geometry), le choix d’une représentation graphique
Une fois que nous avons choisi des esthétiques, nous devons ajouter à notre graphique une représentation visuelle de ces esthétiques. Or, nous pouvons utiliser plusieurs représentations graphiques pour des mêmes esthétiques.
Dans ggplot2, les représentations graphiques s’appellent des geometries, des “géométries”. Ici, nous pouvons utiliser au moins deux représentations graphiques des mêmes données :
geom_point : selon les données, on obtient soit un nuage de points (scattre plot) ou un graphe de Cleveland (Cleveland dot plot).
geom_bar : un diagramme en barres (barplot) à distinguer d’un histogramme, qui a d’autres caractéristiques. Visuellement, on reconnaît le diagramme en barres par les espaces entre les barres. De plus, l’histogramme se prête mieux à la représentation de données quantitatives continues
Pour ce faire, on ajoute littéralement à notre graphique notre géométrie avec l’opérateur +.
Voici un graphe de Cleveland :
top25 %>%
ggplot(aes(x = lemme, y = n)) +
geom_point()

Et voici les mêmes données en diagramme en barres :
top25 %>%
ggplot(aes(x = lemme, y = n)) +
geom_bar(stat = "identity")

Par la suite, je me limite au diagramme en barres.
Étape 4 : coord, scales, réglages des coordonnées et des échelles
Après avoir fait un choix de représentation graphique, nous pouvons régler différents paramètres, plus précisément en agissant sur les coordonnées ou les échelles.
Par exemple, ici, on voudrait inverser les coordonnées pour mieux lire les mots.
top25 %>%
ggplot(aes(x = lemme, y = n)) +
geom_bar(stat = "identity") +
coord_flip()

Étape 5 : personnalisation des éléments graphiques
Notez que jusqu’à présent, nous n’avons fait que des choix sémantiques et non pas des choix esthétiques. Nous n’avons pas choisi de couleurs par exemple. Ces choix arrivent en dernière partie.
top25 %>%
ggplot(aes(x = lemme, y = n)) +
geom_bar(stat = "identity") +
coord_flip() +
labs(x = "",
y = "Occurences du lemme",
title = "Les 25 mots les plus présents dans les entretiens",
caption = "Données : entretiens jardins CPES, lemmatisée et nettoyée des mots vides.") +
theme_minimal() # Changer l'apparence du graphique en choisissant des thèmes

Il est aussi possible de choisir un thème une bonne fois pour toute :
theme_set(theme_minimal())
Nous utiliserons le thème minimal par la suite.
Étape 6 : exporter le graphique
Nous pouvons ici utiliser l’interface de RStudio directement pour enregistrer notre graphique.
Une astuce : trier les modalités
Pour rendre notre graphique encore plus lisible, nous pouvons trier les modalités par ordre décroissant. Ceci n’est en fait pas une modification purement visuelle, mais bien sémantique, parce que nous voulons souligner le fait que la variable qualitative “lemme” est ordonnée.
Par conséquent, nous devons agir soit sur les données, soit sur les esthétiques.
top25 %>%
ggplot(aes(x = reorder(lemme, n), y = n)) +
geom_bar(stat = "identity") +
coord_flip() +
labs(x = "",
y = "Occurences du lemme",
title = "Les 25 mots les plus présents dans les entretiens",
caption = "Données : entretiens jardins CPES, lemmatisée et nettoyée des mots vides.")

Croiser des variables
Nous avons déjà effectué et représenté un traitement statistique simple, le tri à plat − c’est-à-dire un simple comptage, attaché à une seule variable qualitative.
Évidemment, nous voulons aller un peu plus loin et étudier les interactions entre plusieurs variables. Dans notre exemple, nous allons croiser deux variables qualitatives : les mots utilisés et le sexe. Ce sont deux variables qualitatives, comme souvent en sciences humaines et sociales. Pour ce faire, on peut utiliser un tableau croisé ou ses représentations graphiques.
Regardons comment procéder dans R.
Préparer des données
Première solution : count()
Nous avons déjà rencontré la fonction count() dans le cours. Elle prend en argument le nom des variables qui vont nous servir à grouper les observations. Par exemple, nous avons déjà fait un tri à plat en comptant seulement les lemmes :
mots %>%
count(lemme, sort = TRUE)
L’argument sort permet de trier par observations décroissantes.
Pour croiser deux variables, l’idée est de grouper à la fois par lemme ET par sexe :
mots %>%
count(sexe, lemme, sort = TRUE)
Deuxième solution : group_by() et summarise()
Le paquet dplyr() nous donne une deuxième solution, un peu plus puissante car elle est plus flexible. Elle se fait en deux étapes : nous groupons notre base par sexe et par lemme (comme avec count()), puis nous produisons une information synthétique à partir de ces groupes, autrement dit, nous produisons un résumé (fonction summarise()).
mots %>%
group_by(sexe, lemme) %>%
summarise(n_sexe = n())
Cela donne le même résultat que count(). Nous verrons plus tard que summarise() s’applique à davantage de cas.
Représenter graphiquement une interaction entre deux variables qualitatives
Nous avons plusieurs façons de représenter graphiquement l’interaction entre deux variables qualitatives (les représentations diffèrent selon les types de variable en jeu).
Nous allons voir deux représentations qui représentent au mieux l’information que nous voulons convier : la répartition genrée de l’emploi des 25 mots les plus courants dans notre corpus.
Préparer les données
Commençons par créer les données nécessaires à notre graphique. La première étape est de trouver les 25 mots les plus fréquents dans l’ensemble (hommes et femmes confondus), ce qu’on sait déjà faire : c’est un tri à plat très simple. Nous allons juste renommer la colonne n en total :
total <- mots %>%
count(lemme) %>%
rename(total = n)
Nous allons à nouveau utiliser les jointures, cette fois pour coller les données d’ensemble produites dans la variable top25 aux données qui croisent les lemmes avec le genre.
mots %>%
count(sexe, lemme) %>%
left_join(total)
Joining, by = "lemme"
Nous pouvons maintenant sélectionner les 25 mots les plus employés dans l’ensemble et les stocker dans une base qui nous servira à faire notre graphique :
g1_data <- mots %>%
count(sexe, lemme) %>%
left_join(total) %>%
arrange(desc(total)) %>% # Tri par ordre décroissant sur la colonne total
head(50)
Joining, by = "lemme"
g1_data
Remarquez qu’on prend les 50 premières lignes, donc les 25 mots les plus fréquents, puisque chaque mot est sur deux lignes, une pour les hommes, une pour les femmes.
Le diagramme en barres : barres colorées
Utilisons les esthétiques de ggplot2, cette fois en ajoutant une deuxième dimension (le sexe) en remplissage, donc fill.
g1_data %>%
ggplot(aes(x = lemme, y = n)) +
geom_bar(aes(fill = sexe), stat = "identity")

On peut ensuite utiliser ce qu’on connait déjà pour produire un graphique plus clair encore :
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_bar(aes(fill = sexe), stat = "identity") +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
fill = "Sexe",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins.")

Ce graphique est plutôt satisfaisant. Toutefois, les barres colorées ne sont pas une solution optimale : la répartition par mot est claire, mais par forcément l’utilisation des mots par sexe (difficile de comparer si les femmes disent plus “fair” ou “chos”).
Le diagramme en barres : barres juxtaposées
Nous pouvons essayer autre chose : juxtaposer les barres au lieu de colorer à l’intérieur de chaque barre, en ajoutant position = "dodge".
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_bar(aes(fill = sexe), stat = "identity", position = "dodge") +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
fill = "Sexe",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins.")

Techniquement, ce graphe est plus lisible car chaque barre commence au même niveau. Malheureusement, il est parfois difficile de distinguer quelles barres correspondent à quel lemme. De manière générale, les barres colorées ou juxtaposées ne sont pas optimales, en particulier si nous avons plus de deux modalités. De plus, nous avons perdu l’ensemble (le total quelque soit le sexe).
Nous pouvons aller encore plus loin : produire deux graphiques, l’un pour les hommes, l’autre pour les femmes, et les coller.
Le diagramme en barres : les facettes
Pour ce faire, nous allons utiliser un nouveau concept de ggplot, les facettes :
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_bar(stat = "identity") +
facet_wrap(~ sexe) +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins.")

C’est déjà beaucoup mieux. Ici, les couleurs n’apportent pas d’information, mais elles peuvent rendre l’ensemble plus lisible.
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_bar(aes(fill = sexe), stat = "identity") +
facet_wrap(~ sexe) +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
fill = "Sexe",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins.")

Ce graphe est presque parfait. Il lui manque juste une information : par rapport au premier graphique, nous avons perdu les données d’ensemble. Heureusement, grâce à ggplot, nous pouvons facilement le rajouter en utilisant un geom supplémentaire, avec une esthétique y = total :
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_bar(aes(y = total), stat = "identity", fill = "gray70") +
geom_bar(aes(fill = sexe), stat = "identity") +
facet_wrap(~ sexe) +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
fill = "Sexe",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins. L'ensemble est en gris.")

Nous ne pouvons guère plus améliorer ce diagramme en barres. Il s’adaptera aussi très bien aux croisements avec une variable qui compte plus de deux modalités (classe d’âge, professions).
Les diagrammes en barres ont toutefois des inconvénients presque intrinsèques : l’aire des barres peut nous induire en erreur alors que concrètement, seule la position de la barre compte. Une représentation moins courante, mais plus efficace que le diagramme en barres est le graphe de Cleveland.
Le graphe de Cleveland.
Le graphe de Cleveland respecte le principe de Tufte (optimiser le ratio encre / information), tout en permettant de comparer très exactement les populations hommes et femmes.
g1_data %>%
ggplot(aes(x = reorder(lemme, total), y = n)) +
geom_point(aes(colour = sexe), stat = "identity") +
coord_flip() +
labs(x = "",
y = "Occurrence des lemmes",
colour = "Sexe",
title = "Les 25 lemmes les plus utilisés selon le sexe",
caption = "Données : entretiens jardins.")

Faire un tableau croisé
Nous pouvons maintenant passer à une autre forme de représentation graphique : le tableau croisé.
Première solution : utiliser table()
On peut utiliser la fonction de base table(), que nous connaissons déjà :
table(mots$sexe, mots$mot)
Cette fonction marche parfaitement bien pour des tableaux qui demandent peu de nettoyage et de recodage. Ce n’est pas le cas ici, nous avons beaucoup trop de mots. Nous pourrions nettoyer nos données au préalable et ensuite faire le tableau.
Le problème de la fonction table() est qu’elle retourne un tableau et non pas un tibble, c’est-à-dire une base de données manipulable. C’est pourquoi nous verrons maintenant une autre façon de produire ce tableau, cette fois avec dplyr.
Deuxième solution : utiliser spread()
Une autre façon de procéder est de prendre nos données g1_data et de considérer que faire un tableau croisé, c’est procéder à un recodage : nous passons d’une base où l’observation est le “mot-sexe” à une autre base où chaque observation serait un mot, et le décompte pour hommes et femmes serait en colonnes.
Pour ce faire, nous pouvons utiliser la fonction spread() :
g1_data %>%
spread(sexe, n)
Le premier argument donne la variable chacune des modalités deviendra une colonne à part, et le deuxième indique le contenu de ces colonnes.
Nous pouvons produire un tableau plus ordonné :
g1_data %>%
spread(sexe, n) %>%
select(lemme, Femme, Homme, total) %>%
arrange(desc(total))
Les mots et leurs fréquences
Jusqu’ici, nous n’avons considéré les mots que dans leurs occurrences, c’est-à-dire leurs effectifs. Quel problème cela peut-il poser à l’interprétation ?
D’une part, nous avons plus de femmes que d’hommes dans notre échantillon :
entretiens %>%
count(sexe)
Et donc, les femmes ont utilisés plus de mots dans l’ensemble :
mots %>%
group_by(sexe) %>%
summarise(n = n())
Mais aussi plus de mots différents, quoique moins en proportion :
mots %>%
group_by(sexe) %>%
summarise(nb_mots = n(),
nb_mots_diff = n_distinct(mot))
NB: remarquez ici l’utilité de la fonction summarise(), qui nous a permis de résumer non seulement le nombre de mots utilisés mais aussi les mots différents mobilisés en une seule commande.
Essayons de voir, pour chaque entretien, combien de mots sont utilisés et combien de mots différents sont mobilisés, puis de croiser ces variables avec le sexe. Pour ce faire, nous devons refaire la base de mots en rajoutant un identifiant à chaque entretien.
mots <- entretiens %>%
mutate(id = 1:nrow(entretiens)) %>%
unnest_tokens(mot, texte) %>%
anti_join(mots_vides) %>%
mutate(lemme = wordStem(mot, language = "fr"))
Joining, by = "mot"
mots %>%
group_by(sexe, id) %>%
summarise(mots = n(),
mots_diff = n_distinct(lemme)) %>%
group_by(sexe) %>%
summarise(mots_mean = mean(mots),
mots_diff_mean = mean(mots_diff),
effectifs = n_distinct(id))
Remarquez à nouveau l’utilité de la fonciton summarise(), qui nous permet de produire en une commande des moyennes.
Ce tableau présente poru haque sexe, le nombre moyen de mots utilisés par entretiens, le nombre moyen de mots différents mobilisés, et enfin le nombre de personnes dans chaque groupe. Que peut-on en déduire ?
Cette brève mise en bouche nous apprend plusieurs cohes :
- D’une part, les effectifs ne suffisent pas : il faut les accompagner de fréquences, des pourcentages, qui mettent en contexte ces effectifs pour hcaque groupe.
- D’autre part, dans l’analyse textuelle, plusieurs types de fréquences sont à prendre en considération : rapporter au nombre de mots total utilisés, ou bien au nombre de mots différents employés.
Présentation des indicateurs de fréquence textuelle
Nous avons trois indicateurs, du plus simple au plus subtil :
- La fréquence des termes (term frequency, TF) : il s’agit de l’approche la plus évidente, c’est à dire diviser le nombre d’occurrences d’un terme par le total des mots utilisés dans un document (ici, un entretien). Cette métrique va toutefois privilégier le mots les plus courants, et comme nous l’avons vu, les mots les plus courants sont souvent inintéressants car comme toute le monde les utilise beaucoup, ils perdent de leur caractère distinctif.
- La fréquence inverse de documents (inverse document frequency, IDF) : pour pallier ce problème, nous pouvons créer une autre métrique qui caractérise la spécificité d’un terme dans l’ensemble du corpus, en donnant plus de poids aux mots rarement employés dans l’ensemble du corpus qui seraient plus distinctifs. C’est la fréquence inverse puisque plus cette métrique est important, moins le terme est courant à l’échelle du corpus.
- TF-IDF : c’est la combinaison des deux, on multiplie les deux indicateurs précédents pour tenir compte à la fois de la fréquence des termes et de leur rareté relative.
Dans le paquet tidytext, nous avons heureusement une fonction prête à l’emploi :
mots_freq <- mots %>%
count(id, sexe, lemme) %>%
bind_tf_idf(lemme, id, n)
mots_freq
Explorons un peu cette base. Quels sont les termes avec la plus grande TF ?
mots_freq %>%
arrange(desc(tf))
Quels termes ont une IDF nulle ?
mots_freq %>%
filter(idf == 0)
Quels termes ont la TF-IDF la plus élevée ?
mots_freq %>%
arrange(desc(tf_idf))
Exemples d’analyse : mots fréquents typiquement féminins
Créons une base de données des mots que les femmes ont utilisés et trions-les par TF-IDF décroissant.
Faisons la même chose pour les hommes.
Fusionnons ces deux bases, en ne gardant que les mots communs entre elles inner_join() et en créant une nouvelle variable qui calcule la différence de TF-IDF entre hommes et femmes :
On passe maintenant au graphique :
mots_fh %>%
arrange(desc(diff)) %>%
head(25) %>%
gather("sexe", "tf_idf", starts_with('tf_idf')) %>%
mutate(sexe = str_extract(sexe, '(homme|femme)')) %>%
ggplot(aes(x = reorder(lemme, diff), y = tf_idf)) +
geom_point(aes(colour = sexe)) +
coord_flip() +
labs(x = "",
y = "TF-IDF moyenne",
colour = "Sexe",
title = "Les 25 mots les plus spécifiques aux femmes, par rapport aux hommes",
caption = "Données : entretiens jardin. Les lemmes sont organisés par
écart décroissant entre hommes et femmes.")

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRpZHl0ZXh0IgphdXRob3I6ICJHYWJyaWVsIEFsY2FyYXMiCmRhdGU6ICJtYXJzIDIwMjAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKc3VidGl0bGU6IENQRVMgMiAtIFRlY2huaXF1ZXMgcXVhbnRpdGF0aXZlcwotLS0KCiMgSW50cm9kdWN0aW9uIMOgIGwnYW5hbHlzZSB0ZXh0dWVsbGUgYXZlYyBSIGV0IGxlIHRpZHl0ZXh0CgpSYXBwZWwgOiBpbCBleGlzdGUgcGx1c2lldXJzIGZhw6dvbnMgZGUgZmFpcmUgZGUgbCdhbmFseXNlIHRleHR1ZWxsZSwgbm91cyBuZQp2ZXJyb25zIGljaSBxdSd1bmUgYXBwcm9jaGUgcnVkaW1lbnRhaXJlIG1haXMgcGx1dMO0dCBlZmZpY2FjZSwgbGUgKmJhZyBvZgp3b3JkcyogKHNhYyBkZSBtb3RzKS4KCk9uIGNoYXJnZSBsZXMgZGlmZsOpcmVudHMgcGFxdWV0cyBuw6ljZXNzYWlyZXMgcG91ciBjZXR0ZSBzw6lhbmNlLgoKYGBge3IgbG9hZCBwYWNrYWdlcywgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkcGx5cikKCmxpYnJhcnkodGlkeXRleHQpICMgUG91ciBtYW5pcHVsZXIgbGVzIGRvbm7DqWVzIHRleHR1ZWxsZXMKbGlicmFyeShzdG9wd29yZHMpICMgTW90cyAiaW51dGlsZXMiCmxpYnJhcnkoU25vd2JhbGxDKSAjIExlbW1hdGlzYXRpb24gLyBzdGVtbWF0aXNhdGlvbgpgYGAKCkNoYXJnZW9ucyBsYSBiYXNlIGRlIGRvbm7DqWVzIGBlbnRyZXRpZW5zYC4KCmBgYHtyIGRhdGEsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQplbnRyZXRpZW5zIDwtIHJlYWRfY3N2KGhlcmU6OmhlcmUoJ2RhdGEnLCAnZW50cmV0aWVucycsJ2VudHJldGllbnMuY3N2JykpCmdsaW1wc2UoZW50cmV0aWVucykKYGBgCgpRdWUgcmVtYXJxdWUtdC1vbiA/IENvbW1lbnQgY2V0dGUgYmFzZSBlc3QtZWxsZSBjb25zdHJ1aXRlID8KCiMjIMOJdGFwZSAxIDogcmVjb2RhZ2UgKCJ0b2tlbml6YXRpb24iKQoKRGFucyBsJ2FwcHJvY2hlICJzYWMgZGUgbW90cyIgKCpiYWcgb2Ygd29yZHMqKSwgb24gZGl2aXNlIG5vdHJlIHRleHRlIGVuIHVuaXTDqXMKZGUgdGV4dGUgcGx1cyBwZXRpdGVzLiBQYXIgZXhlbXBsZSwgb24gcGV1dCBmYWlyZSBkZXMgInNhY3MiIGRlIDEgbW90LCAyIG1vdHMsCi4uLiwgJG4kIG1vdHMuIEMnZXN0IGNlIHF1J29uIGFwcGVsbGUgZGVzICRuJC1ncmFtcy4gUG91ciBsJ2luc3RhbnQsIG9uIHNlCmNvbnRlbnRlIGRlIGZhaXJlIGRlcyBzYWNzIGQndW4gc2V1bCBtb3QgKCQxJC1ncmFtKS4KCk9uIHZldXQgZG9uYyBwYXNzZXIgZCd1bmUgYmFzZSBsYXJnZSAodW5lIGNvbG9ubmUgdHLDqHMgbG9uZ3VlLCBhdmVjIHRvdXQgbGUKdGV4dGUpIHZlcnMgdW5lIGJhc2UgbG9uZ3VlIChiZWF1Y291cCBkZSBsaWduZXMpLiBFbiBmYWl0LCBvbiB2ZXV0IGNoYW5nZXIKZCdpbmRpdmlkdSBzdGF0aXN0aXF1ZSA6IGR1IHRleHRlIHZlcnMgbGUgc2FjIGRlIG1vdHMsIG91IGRhbnMgbm90cmUgY2FzLCB2ZXJzCmxlIG1vdC4KCkMnZXN0IGRvbmMgdW5lIG9ww6lyYXRpb24gZGUgcmVjb2RhZ2UuIE5vdXMgcG91cnJpb25zIGxlIGZhaXJlIGF2ZWMgbGVzIG91dGlscwpkdSBgdGlkeXZlcnNlYCwgbWFpcyBjb21tZSBjJ2VzdCB1bmUgb3DDqXJhdGlvbiBjb21tdW5lIGRhbnMgbCdhbmFseXNlCnRleHR1ZWxsZSwgbm91cyBhbGxvbnMgdXRpbGlzZXIgZGlyZWN0ZW1lbnQgdW5lIGZvbmN0aW9uIGlzc3VlIGR1IHBhcXVldApgdGlkeXRleHRgLCBgdW5uZXN0X3Rva2VucygpYC4KCmBgYHtyIHVubmVzdH0KbW90cyA8LSBlbnRyZXRpZW5zICU+JQogIHVubmVzdF90b2tlbnMobW90LCB0ZXh0ZSkKYGBgCgpSZWdhcmRvbnMgcXVlbHMgc29udCBsZXMgMTAgbW90cyBsZXMgcGx1cyB1dGlsaXPDqXMgOgoKYGBge3IgdG9wIDEwIHNpbXBsZX0KbW90cyAlPiUKICBjb3VudChtb3QsIHNvcnQgPSBUUlVFKSAlPiUKICBoZWFkKDEwKQpgYGAKCipBIHByaW9yaSosIGNlIG5lIHNvbnQgcGFzIHRyb3AgbGVzIG1vdHMgcXVpIG5vdXMgaW50w6lyZXNzZW50Li4uIENvbW1lbnQgZmFpcmUgPwoKIyMgw4l0YXBlIDIgOiBlbmxldmVyIGxlcyAibW90cyB2aWRlcyIgKCpzdG9wIHdvcmRzKikKClBvdXIgZW5sZXZlciBsZXMgbW90cyBxdWkgbm91cyBpbnTDqXJlc3NlbnQsIG5vdXMgcG91cnJpb25zIG5vdXMtbcOqbWVzCmNvbnN0cnVpcmUgdW5lIGxpc3RlIGRlIG1vdHMgcXVlIG5vdXMgdm91bG9ucyBleGNsdXJlIGRlIG5vdHJlIGJhc2UuIE1haXMgY2UKc2VyYWl0IHVuIHBldSBmYXN0aWRpZXV4LCBub3VzIGFsbG9ucyBkb25jIHBsdXTDtHQgdXRpbGlzZXIgdW5lIGxpc3RlIGTDqWrDoCB0b3V0ZQpmYWl0ZSwgY29udGVudWUgZGFucyBsZSBwYXF1ZXQgYHN0b3B3b3Jkc2AuCgpMZXMgKnN0b3Agd29yZHMqLCBhcHBlbMOpcyAibW90cyB2aWRlcyIgZW4gZnJhbsOnYWlzLCBzb250IGxlcyBtb3RzIHF1J29uIGp1Z2UKInZpZGVzIGRlIHNlbnMiLCBkdSBtb2lucyBkYW5zIHVuZSBhcHByb2NoZSBzYWMgZGUgbW90cy4gSWwgZmF1dCBpY2kgdXNlciBkZQpwcnVkZW5jZSBldCBuZSBwYXMgdXRpbGlzZXIgY2VzIGxpc3RlcyBzYW5zIHbDqXJpZmllciBxdWVscyB0ZXJtZXMgZWxsZXMKY29udGllbm5lbnQuIEVuIGVmZmV0LCBvbiBwb3VycmFpdCB0csOocyBiaWVuIHMnaW50w6lyZXNzZXIgw6AgY2VzICJtb3RzIHZpZGVzIgpzZWxvbiBub3RyZSBxdWVzdGlvbiBkZSByZWNoZXJjaGUsIG3Dqm1lIHNpIGNlIG4nZXN0IHBhcyBub3RyZSBjYXMgaWNpLgoKVm95b25zIGxlcyBzdG9wd29yZHMgZW4gcXVlc3Rpb24gOgoKYGBge3Igc3RvcHdvcmRzfQpnbGltcHNlKHN0b3B3b3JkcygnZnInLCBzb3VyY2U9InN0b3B3b3Jkcy1pc28iKSkKYGBgCgpOb3VzIHBvdXZvbnMgZmlsdHJlciBub3RyZSBsaXN0ZSBkZSB0ZXJtZXMgZGUgcGx1c2lldXJzIGZhw6dvbnMuIE5vdXMgYWxsb25zIGljaQp1dGlsaXNlciB1bmUgcHJvY8OpZHVyZSBwcm9wcmUgw6AgbGEgbWFuaXB1bGF0aW9uIGRlcyBiYXNlcyBkZSBkb25uw6llcywgbGVzCmpvaW50dXJlcy4gTGVzIGpvaW50dXJlcyBwZXJtZXR0ZW50IGRlIGNvbWJpbmVyIHBsdXNpZXVycyBiYXNlcyBlbnRyZSBlbGxlcy4KSWNpLCBub3VzIGFsbGVyIGNyw6llciB1bmUgYmFzZSBkZSBzdG9wIHdvcmRzLCBhdmVjIHVuZSB2YXJpYWJsZSBgbW90YCwgcXVpIHZhCmNvcnJlc3BvbmRyZSAqZXhhY3RlbWVudCogw6AgbGEgdmFyaWFibGUgYG1vdGAgZGFucyBub3RyZSBiYXNlIGRlIG1vdHMuIEFpbnNpLApub3VzIGFsbG9ucyBwb3V2b2lyIHLDqWFsaXNlciB1bmUgYW50aS1qb2ludHVyZSwgYydlc3Qtw6AtZGlyZSBleGNsdXJlIGRlIGxhIGJhc2UKYG1vdHNgIHRvdXRlcyBsZXMgbGlnbmVzIG/DuSBsYSB2YXJpYWJsZSBgbW90YCBjb3JyZXNwb25kIMOgIGxhIHZhcmlhYmxlIGBtb3RgCmRhbnMgbm90cmUgYmFzZSBkZSBzdG9wIHdvcmRzLgoKYGBge3Igcm0gc3RvcHdvcmRzLCBtZXNzYWdlID0gRkFMU0V9CiMgQ3LDqXRhaW9uIGQndW5lIGJhc2UgZGUgbW90cyB2aWRlcwptb3RzX3ZpZGVzIDwtIHRpYmJsZShtb3QgPSBzdG9wd29yZHMoJ2ZyJywgc291cmNlPSJzdG9wd29yZHMtaXNvIikpCgojIENyw6lhdGlvbiBkJ3VuZSBiYXNlIGRlIG1vdHMKbW90cyA8LSBlbnRyZXRpZW5zICU+JQogIHVubmVzdF90b2tlbnMobW90LCB0ZXh0ZSkgJT4lCiAgYW50aV9qb2luKG1vdHNfdmlkZXMpCmBgYAoKUmVtYXJxdWV6IGF1IHBhc3NhZ2UgbGUgbWVzc2FnZSBxdSdhZmZpY2hlIGBkcGx5cmAgcG91ciBub3VzIHNpZ25hbGVyIGNvbW1lbnQgcydlZmZlY3R1ZSBsYSBqb2ludHVyZS4KCk1haW50ZW5hbnQsIHJlZ2FyZG9ucyDDoCBub3V2ZWF1IHF1ZWxzIHNvbnQgbGVzIDI1IG1vdHMgbGVzIHBsdXMgdXRpbGlzw6lzIDoKCmBgYHtyIHRvcCAyNSBybSBzdG9wd29yZHN9Cm1vdHMgJT4lCiAgY291bnQobW90LCBzb3J0ID0gVFJVRSkgJT4lCiAgaGVhZCgyNSkgJT4lCiAgcHJpbnQobiA9IDI1KQpgYGAKCkMnZXN0IGTDqWrDoCBiaWVuIG1pZXV4ICEgTWFpcyBub3VzIGF2b25zIHRvdWpvdXJzIHF1ZWxxdWVzIHNvdWNpcyA6IGBjaG9zZXNgIGV0CmBjaG9zZWAgcG91cnJhaWVudCBhaW5zaSDDqnRyZSByZWdyb3Vww6lzLiBDb21tZW50IHkgcGFydmVuaXIgPwoKIyMgw4l0YXBlIDMgOiBsZW1tYXRpc2F0aW9uIG91IHN0ZW1tYXRpc2F0aW9uCgpFbmNvcmUgdW5lIGZvaXMsIG5vdXMgcG91cnJpb25zIGljaSAibWFudWVsbGVtZW50IiBub3VzIHNlcnZpciBkdSBgdGlkeXZlcnNlYApwb3VyIHJlZ3JvdXBlciB0b3VzIGxlcyBtb3RzIGQndW5lIG3Dqm1lIGZhbWlsbGUgZW5zZW1ibGUsIG1haXMgY2Ugc2VyYWl0CsOgIG5vdXZlYXUgdW5lIG9ww6lyYXRpb24gZmFzdGlkaWV1c2UuIMOJdmlkZW1tZW50LCBsZXMgcGFxdWV0cyBkJ2FuYWx5c2UKdGV4dHVlbGxlIHByb3Bvc2VudCBkZXMgb3V0aWxzIHBvdXIgeSBwYXJ2ZW5pci4gTm91cyBwb3V2b25zIGRpc3Rpbmd1ZXIgZW4KZ3JvcyBkZXV4IG3DqXRob2RlcyA6CgorICpzdGVtbWF0aXNhdGlvbiogOiBvbiBjb3VwZSBsZXMgbW90cyBwb3VyIGVuIG9idGVuaXIgbGEgcmFjaW5lICgqc3RlbSogdmV1dAogIGRpcmUgInRpZ2UiLCAic291Y2hlIiBlbiBhbmdsYWlzKS4gTGVzIG1vdHMgImNob3NlcyIgZXQgImNob3NlIgogIGRldmllbm5lbnQgYWluc2kgImNob3NlIi4KKyAqbGVtbWF0aXNhdGlvbiogOiBjZXR0ZSBvcMOpcmF0aW9uIGVzdCBwbHVzIGNvbXBsZXhlLCBtYWlzIHByZW5kIGVuIGNvbXB0ZQogIGNlcnRhaW5zIGNhcyBxdWUgbGEgc3RlbW1hdGlzYXRpb24gbGFpc3NlcmEgZGUgY8O0dMOpLCBwYXIgZXhlbXBsZSBsZSB2ZXJiZQogICJlc3NheWVyIiBzZSB0cmFuc2Zvcm1lcmEgZW4gImVzc2FpZSIsIHF1J29uIHBvdXJyYSBwbHVzIGZhY2lsZW1lbnQgYWdyw6lnZXIuCgpFbiBmcmFuw6dhaXMsIGxhIHN0ZW1tYXRpc2F0aW9uIGRvbm5lIGRlcyByw6lzdWx0YXRzIGxpbWl0w6lzLCBub3VzIGFsbG9ucyBkb25jCnBsdXTDtHQgbGVtbWF0aXNlciBub3RyZSBiYXNlIMOgIGwnYWlkZSBkdSBwYXF1ZXQgYFNub3diYWxsQ2AgZXQgZGUgc2EgZm9uY3Rpb24KYHdvcmRTdGVtKClgLiBSZWdhcmRvbnMgY29tbWVudCBlbGxlIGZvbmN0aW9ubmUgOgoKYGBge3IgbGVtbWUgdGVzdH0Kd29yZFN0ZW0oJ2Vzc2F5ZXInLCBsYW5ndWFnZSA9ICJmciIpCmBgYAoKTm91cyBhbGxvbnMgY3LDqWVyIHVuZSAqbm91dmVsbGUgdmFyaWFibGUqIGBsZW1tZWAgcG91ciBjaGFxdWUgYG1vdGAsIGFmaW4gZGUgbmUKcGFzIHBlcmRyZSBsZXMgbW90cyBvcmlnaW5hdXguCgpDb21iaW5vbnMgdG91dGVzIG5vcyBjb21tYW5kZXMgcG91ciBwcm9kdWlyZSBsYSBiYXNlIGRlIG1vdHMgc291aGFpdMOpZSA6CgpgYGB7ciBtb3RzIGZpbmFsfQptb3RzIDwtIGVudHJldGllbnMgJT4lCiAgdW5uZXN0X3Rva2Vucyhtb3QsIHRleHRlKSAlPiUKICBhbnRpX2pvaW4obW90c192aWRlcykgJT4lCiAgbXV0YXRlKGxlbW1lID0gd29yZFN0ZW0obW90LCBsYW5ndWFnZSA9ICJmciIpKQpgYGAKCkV0IG1haW50ZW5hbnQsIHJlZ2FyZG9ucyBxdWVscyBzb250IGxlcyAyNSBtb3RzIGxlcyBwbHVzIGVtcGxvecOpcyA6CgpgYGB7ciB0b3AgMjUgZmluYWx9CnRvcDI1IDwtIG1vdHMgJT4lCiAgY291bnQobGVtbWUsIHNvcnQgPSBUUlVFKSAlPiUKICBoZWFkKDI1KSAlPiUKICBwcmludChuID0gMjUpCmBgYAoKTGUgcsOpc3VsdGF0IGVzdCBiaWVuIG1laWxsZXVyLgoKIyBQcsOpc2VudGVyIGV0IGV4cG9ydGVyIHNlcyByw6lzdWx0YXRzCgpOb3VzIGF2b25zIG1haW50ZW5hbnQgdW4gcHJlbWllciByw6lzdWx0YXQsIHVuIHRyaSDDoCBwbGF0IGRlcyAyNSBtb3RzIGxlcyBwbHVzCmVtcGxvecOpcyBkYW5zIG5vdHJlIGNvcnB1cy4gQWRtZXR0b25zIHF1J29uIHZldWlsbGUgbGUgcHLDqXNlbnRlciBkYW5zIHVuCmRvY3VtZW50LiBOb3VzIGF2b25zIGRldXggcG9zc2liaWxpdMOpcyA6CgorIFByw6lzZW50ZXIgbm9zIGRvbm7DqWVzIHNvdXMgZm9ybWUgbnVtw6lyaXF1ZQorIFByw6lzZW50ZXIgbm9zIGRvbm7DqWVzIHNvdXMgZm9ybWUgZ3JhcGhpcXVlCgojIyBFeHBvcnRlciB1biB0YWJsZWF1IHZlcnMgdW4gdHJhaXRlbWVudCBkZSB0ZXh0ZQoKUG91ciBleHBvcnRlciB1biB0YWJsZWF1IHZlcnMgdW4gdHJhaXRlbWVudCBkZSB0ZXh0ZSB0cmFkaXRpb25uZWwgKExpYnJlT2ZmaWNlLApwYXIgZXhlbXBsZSwgamUgbidlbiB2b2lzIHBhcyBkJ2F1dHJlcywgbm9uLCB2cmFpbWVudCksIG9uIHBldXQgc3VpdnJlIGxhCm3DqXRob2RlIHN1aXZhbnRlIDoKCjEuIE9uIMOpY3JpdCBsZSB0YWJsZWF1IGRhbnMgdW4gZm9ybWF0IHNww6ljaWFsIGRlcHVpcyBsYSBjb25zb2xlIChvdSBiaWVuIG9uCiAgIHBldXQgbCdleHBvcnRlciBkaXJlY3RlbWVudCBkYW5zIHVuIG5vdXZlYXUgZmljaGllcikKMi4gT24gY29waWUgZXQgb24gY29sbGUgbGUgdGV4dGUgb2J0ZW51IGRhbnMgbGUgdHJhaXRlbWVudCBkZSB0ZXh0ZQozLiBPbiBzw6lsZWN0aW9ubmUgbGUgdGV4dGUgY29sbMOpLCBldCBvbiBzZSByZW5kIGRhbnMgbGUgbWVudSBUYWJsZSA+IENvbnZlcnQgZXQKICAgb24gc8OpbGVjdGlvbm5lIGxlcyByw6lnbGFnZXMgYXBwcm9wcmnDqXMuCgpQYXIgZXhlbXBsZSA6CgpgYGB7ciBleHBvcnQgdG9wMjV9CnRvcDI1ICU+JQogIHdyaXRlLnRhYmxlKHJvdy5uYW1lcyA9IEZBTFNFLCBxdW90ZSA9IEZBTFNFLCBzZXAgPSAiLCIpCmBgYAoKIyMgRmFpcmUgdW4gZ3JhcGhpcXVlIGF2ZWMgYGdncGxvdDJgCgpOb3VzIGFsbG9ucyB2b2lyIGljaSBjb21tZW50IHLDqWFsaXNlciBkZXMgZ3JhcGhpcXVlcyBhdmVjIGBnZ3Bsb3QyYCwgY2Ugc2VyYSB1bmUKaW50cm9kdWN0aW9uIHRyw6hzIGJyw6h2ZSDDoCB1biB2YXN0ZSBzdWpldC4KCkxhIHZpc3VhbGlzYXRpb24gZGUgZG9ubsOpZXMgcG91cnJhaXQgZmFpcmUgbCdvYmpldCBkJ3VuIGNvdXJzIMOgIHBhcnQuIFZvaWNpCnF1ZWxxdWVzIGNvbnNlaWxzIGfDqW7DqXJhdXggOgoKKyBVbmUgcmVwcsOpc2VudGF0aW9uIGdyYXBoaXF1ZSBuJ2VzdCBwYXMgdW5lIHJlcHLDqXNlbnRhdGlvbiBvYmplY3RpdmUgZGUgbm9zCiAgZG9ubsOpZXMsIGF1IHNlbnMgb8O5IGVsbGUgZG9ubmVyYWl0IHVuIGFjY8OocyBkaXJlY3QgYXV4IG9ic2VydmF0aW9ucy4gQXUKICBjb250cmFpcmUsIHVuIGdyYXBoaXF1ZSBjb252aWUgdW5lIGluZm9ybWF0aW9uIGV0IHVuIHBvaW50IGRlIHZ1ZSBzdXIgY2VzCiAgaW5mb3JtYXRpb25zLiBRdWFuZCB2b3VzIHLDqWFsaXNleiB1biBncmFwaGlxdWUsIG5lIGNoZXJjaGV6IHBhcyDDoCBmYWlyZSB1bgogIGdyYXBoaXF1ZSBwbGFpc2FudCBtYWlzIHBsdXTDtHQgdW4gZ3JhcGhpcXVlIHF1aSBhIGR1IHNlbnMgZXQgcXVpIGNvbnZpZSBhdQogIG1pZXV4IGxlIG1lc3NhZ2UgcXVlIHZvdXMgdm91bGV6IGZhaXJlIHBhc3Nlci4KKyBMZSBwcmluY2lwZSBkZSBUdWZ0ZSA6IGxlIG1laWxsZXVyIGdyYXBoaXF1ZSBlc3QgY2VsdWkgcXVpIG9wdGltaXNlIGxlIHJhdGlvCiAgZW5jcmUgLyBpbmZvcm1hdGlvbi4gTCdpZMOpZSBlc3QgZCd1dGlsaXNlciBsZSBtb2lucyBkJ8OpbMOpbWVudHMgZ3JhcGhpcXVlcwogIHBvc3NpYmxlcyBwb3VyIGNvbnZpZXIgbCdpbmZvcm1hdGlvbiBzb3VoYWl0w6llLgorIExlcyDDqWzDqW1lbnRzIGdyYXBoaXF1ZXMgb3UgZXN0aMOpdGlxdWVzIGRvaXZlbnQgw6p0cmUgbWlzIGF1IHNlcnZpY2UgZGUgbGEKICBzw6ltYW50aXF1ZSBkZXMgZG9ubsOpZXMuCgpMZSBwYXF1ZXQgYGdncGxvdDJgIGVzdCBkaXJlY3RlbWVudCBpbnTDqWdyw6kgYXUgcGFxdWV0IHRpZHl2ZXJzZSwgbGUgYGdnYApzaWduaWZpZSAiR3JhbW1hciBvZiBHcmFwaGljcyIsIGxpdHTDqXJhbGVtZW50IGxhIGdyYW1tYWlyZSBkZXMgZ3JhcGhpcXVlcy4KTCdpZMOpZSBkZSBnZ3Bsb3QgZXN0IHJlbGF0aXZlbWVudCBzaW1wbGUgOiBjJ2VzdCBkZSBub3VzIGZvdXJuaXIgdW4gbGFuZ2FnZSBxdWkKcGVybWV0dGUgZGUgcHJvZHVpcmUgZGVzIGdyYXBoaXF1ZXMgZW4gZGlzdGluZ3VhbnQgbGVzICpwcmluY2lwZXMgc8OpbWFudGlxdWVzKgpkdSBncmFwaGlxdWUgZGUgc2EgKm1pc2UgZW4gZm9ybWUqIGVuIHN1aXZhbnQgZGVzICrDqXRhcGVzKiBjbGFpcmVzLgoKVm95b25zIGNvbW1lbnQgY2VsYSBtYXJjaGUgZW4gcHJhdGlxdWUuCgojIyMgw4l0YXBlIDEgOiBgZGF0YWAsIHPDqWxlY3Rpb25uZXIgZGVzIGRvbm7DqWVzCgpUb3V0IGQnYWJvcmQsIHVuIGdyYXBoaXF1ZSBhIGJlc29pbiBkZSBkb25uw6llcyBwcsOpYWxhYmxlbWVudCBwcsOpcGFyw6llcy4gQydlc3QKbm90cmUgY2FzLCBvbiB2ZXV0IHV0aWxpc2VyIGB0b3AyNWAgcG91ciBlbiBmYWlyZSB1biBncmFwaGlxdWUuCgpPbiBwZXV0IGZvdXJuaXIgbGVzIGRvbm7DqWVzIMOgIGBnZ3Bsb3QyYCBkZSBkZXV4IGZhw6dvbnMgOgoKYGBge3IgZ2cgZGF0YSwgZmlnLnNob3cgPSAiaGlkZSIgfQpnZ3Bsb3QodG9wMjUpICMgQXBwcm9jaGUgZGUgYmFzZSBkYW5zIFIKCiMgQXBwcm9jaGUgVGlkeXZlcnNlCnRvcDI1ICU+JQogIGdncGxvdCgpCmBgYAoKSid1dGlsaXNlcmFpIGxhIGRldXhpw6htZSBhcHByb2NoZSBwYXIgbGEgc3VpdGUuCgpRdWUgcGV1dC1vbiByZW1hcnF1ZXIgZGFucyBSU3R1ZGlvID8KCiMjIyDDiXRhcGUgMiA6IGBhZXNgICgqYWVzdGhldGljcyopLCBmYWlyZSBjb3JyZXNwb25kcmUgZG9ubsOpZXMgZXQgZXN0aMOpdGlxdWVzCgpDJ2VzdCBwZXV0LcOqdHJlIGxlIGNvbmNlcHQgbGUgcGx1cyBpbXBvcnRhbnQgZGUgYGdncGxvdDJgLiBMJ2lkw6llIGVzdCBxdSd1bgpncmFwaGlxdWUgZGlzcG9zZSBkJ3VuIGNlcnRhaW4gbm9tYnJlIGRlIGNhcmFjdMOpcmlzdGlxdWVzIGVzdGjDqXRpcXVlcywKYXV4cXVlbGxlcyBub3VzIHBvdXJyb25zIGZhaXJlIGNvcnJlc3BvbmRyZSBkZXMgdmFyaWFibGVzLiBWb2ljaSBxdWVscXVlcyBgYWVzdGhldGljc2AgOgoKKyBgeGAsIGB5YCA6IGF4ZXMgZGVzIGFic2Npc3NlcyBldCBkZXMgb3Jkb25uw6llcworIGBmaWxsYCA6IHJlbXBsaXNzYWdlIChkZXMgY29sb25uZXMgZCd1biBoaXN0b2dyYW1tZSwgZCd1biBkaWFncmFtbWUgZW4gYmFycmUpCisgYGNvbG91cmAgOiBjb3VsZXVyIChkJ3VuIHBvaW50LCBkJ3VuZSBjb3VyYmUsIGV0Yy4pCisgYHNoYXBlYCA6IGZvcm1lIChkJ3VuIHBvaW50LCBldGMuKQoKTGUgY29uY2VwdCBkZXMgZXN0aMOpdGlxdWVzIGVzdCBwYXJ0aWN1bGnDqHJlbWVudCBwdWlzc2FudCBldCDDqWzDqWdhbnQsIGNvbW1lIG5vdXMKbGUgdmVycm9ucyBwYXIgbGEgc3VpdGUuCgpQb3VyIGwnaW5zdGFudCwgbm91cyBhbGxvbnMgc2ltcGxlbWVudCBmYWlyZSBjb3JyZXNwb25kcmUgbCdheGUgZGVzIHggw6AgbGEKdmFyaWFibGUgcXVhbGl0YXRpdmUgYG1vdGAgZXQgbCdheGUgZGVzIHkgYXUgbm9tYnJlIGQnb2NjdXJyZW5jZXMgZGUgY2VzIG1vdHMsCmBuYC4KCmBgYHtyIGFlcywgZmlnLnNob3cgPSAiaGlkZSIgfQp0b3AyNSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsZW1tZSwgeSA9IG4pKQpgYGAKClF1ZSBwZXV0LW9uIHJlbWFycXVlciBkYW5zIFJTdHVkaW8gPwoKIyMjIMOJdGFwZSAzIDogYGdlb21gICgqZ2VvbWV0cnkqKSwgbGUgY2hvaXggZCd1bmUgcmVwcsOpc2VudGF0aW9uIGdyYXBoaXF1ZQoKVW5lIGZvaXMgcXVlIG5vdXMgYXZvbnMgY2hvaXNpIGRlcyBlc3Row6l0aXF1ZXMsIG5vdXMgZGV2b25zIGFqb3V0ZXIgw6Agbm90cmUKZ3JhcGhpcXVlIHVuZSByZXByw6lzZW50YXRpb24gdmlzdWVsbGUgZGUgY2VzIGVzdGjDqXRpcXVlcy4gT3IsIG5vdXMgcG91dm9ucwp1dGlsaXNlciBwbHVzaWV1cnMgcmVwcsOpc2VudGF0aW9ucyBncmFwaGlxdWVzIHBvdXIgZGVzIG3Dqm1lcyBlc3Row6l0aXF1ZXMuCgpEYW5zIGBnZ3Bsb3QyYCwgbGVzIHJlcHLDqXNlbnRhdGlvbnMgZ3JhcGhpcXVlcyBzJ2FwcGVsbGVudCBkZXMgYGdlb21ldHJpZXNgLApkZXMgImfDqW9tw6l0cmllcyIuIEljaSwgbm91cyBwb3V2b25zIHV0aWxpc2VyIGF1IG1vaW5zIGRldXggcmVwcsOpc2VudGF0aW9ucwpncmFwaGlxdWVzIGRlcyBtw6ptZXMgZG9ubsOpZXMgOgoKKyBgZ2VvbV9wb2ludGAgOiBzZWxvbiBsZXMgZG9ubsOpZXMsIG9uIG9idGllbnQgc29pdCB1biBudWFnZSBkZSBwb2ludHMKICAoKnNjYXR0cmUgcGxvdCopIG91IHVuIGdyYXBoZSBkZSBDbGV2ZWxhbmQgKCpDbGV2ZWxhbmQgZG90IHBsb3QqKS4KKyBgZ2VvbV9iYXJgIDogdW4gZGlhZ3JhbW1lIGVuIGJhcnJlcyAoKmJhcnBsb3QqKSDDoCBkaXN0aW5ndWVyIGQndW4KICBoaXN0b2dyYW1tZSwgcXVpIGEgZCdhdXRyZXMgY2FyYWN0w6lyaXN0aXF1ZXMuIFZpc3VlbGxlbWVudCwgb24gcmVjb25uYcOudCBsZQogIGRpYWdyYW1tZSBlbiBiYXJyZXMgcGFyIGxlcyBlc3BhY2VzIGVudHJlIGxlcyBiYXJyZXMuIERlIHBsdXMsIGwnaGlzdG9ncmFtbWUKICBzZSBwcsOqdGUgbWlldXggw6AgbGEgcmVwcsOpc2VudGF0aW9uIGRlIGRvbm7DqWVzIHF1YW50aXRhdGl2ZXMgY29udGludWVzCgpQb3VyIGNlIGZhaXJlLCBvbiAqYWpvdXRlIGxpdHTDqXJhbGVtZW50KiDDoCBub3RyZSBncmFwaGlxdWUgbm90cmUgZ8Opb23DqXRyaWUgYXZlYwpsJ29ww6lyYXRldXIgYCtgLgoKVm9pY2kgdW4gZ3JhcGhlIGRlIENsZXZlbGFuZCA6CgpgYGB7ciBnZW9tX3BvaW50IDF9CnRvcDI1ICU+JQogIGdncGxvdChhZXMoeCA9IGxlbW1lLCB5ID0gbikpICsKICBnZW9tX3BvaW50KCkKYGBgCgpFdCB2b2ljaSBsZXMgbcOqbWVzIGRvbm7DqWVzIGVuIGRpYWdyYW1tZSBlbiBiYXJyZXMgOgoKYGBge3IgYmFycGxvdCAxfQp0b3AyNSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsZW1tZSwgeSA9IG4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpCmBgYAoKUGFyIGxhIHN1aXRlLCBqZSBtZSBsaW1pdGUgYXUgZGlhZ3JhbW1lIGVuIGJhcnJlcy4KCiMjIyDDiXRhcGUgNCA6IGBjb29yZGAsIGBzY2FsZXNgLCByw6lnbGFnZXMgZGVzIGNvb3Jkb25uw6llcyBldCBkZXMgw6ljaGVsbGVzCgpBcHLDqHMgYXZvaXIgZmFpdCB1biBjaG9peCBkZSByZXByw6lzZW50YXRpb24gZ3JhcGhpcXVlLCBub3VzIHBvdXZvbnMgcsOpZ2xlcgpkaWZmw6lyZW50cyBwYXJhbcOodHJlcywgcGx1cyBwcsOpY2lzw6ltZW50IGVuIGFnaXNzYW50IHN1ciBsZXMgY29vcmRvbm7DqWVzIG91IGxlcwrDqWNoZWxsZXMuCgpQYXIgZXhlbXBsZSwgaWNpLCBvbiB2b3VkcmFpdCBpbnZlcnNlciBsZXMgY29vcmRvbm7DqWVzIHBvdXIgbWlldXggbGlyZSBsZXMgbW90cy4KCmBgYHtyIGJhcnBsb3QgY29vcmRfZmxpcH0KdG9wMjUgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbGVtbWUsIHkgPSBuKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKIyMjIMOJdGFwZSA1IDogcGVyc29ubmFsaXNhdGlvbiBkZXMgw6lsw6ltZW50cyBncmFwaGlxdWVzCgpOb3RleiBxdWUganVzcXUnw6AgcHLDqXNlbnQsIG5vdXMgbidhdm9ucyBmYWl0IHF1ZSBkZXMgY2hvaXggc8OpbWFudGlxdWVzIGV0IG5vbgpwYXMgZGVzIGNob2l4IGVzdGjDqXRpcXVlcy4gTm91cyBuJ2F2b25zIHBhcyBjaG9pc2kgZGUgY291bGV1cnMgcGFyIGV4ZW1wbGUuIENlcwpjaG9peCBhcnJpdmVudCBlbiBkZXJuacOocmUgcGFydGllLgoKYGBge3IgYmFycGxvdCB0aGVtaW5nfQp0b3AyNSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsZW1tZSwgeSA9IG4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICIiLAogICAgICAgeSA9ICJPY2N1cmVuY2VzIGR1IGxlbW1lIiwKICAgICAgIHRpdGxlID0gIkxlcyAyNSBtb3RzIGxlcyBwbHVzIHByw6lzZW50cyBkYW5zIGxlcyBlbnRyZXRpZW5zIiwKICAgICAgIGNhcHRpb24gPSAiRG9ubsOpZXMgOiBlbnRyZXRpZW5zIGphcmRpbnMgQ1BFUywgbGVtbWF0aXPDqWUgZXQgbmV0dG95w6llIGRlcyBtb3RzIHZpZGVzLiIpICsKICB0aGVtZV9taW5pbWFsKCkgIyBDaGFuZ2VyIGwnYXBwYXJlbmNlIGR1IGdyYXBoaXF1ZSBlbiBjaG9pc2lzc2FudCBkZXMgdGjDqG1lcwpgYGAKCklsIGVzdCBhdXNzaSBwb3NzaWJsZSBkZSBjaG9pc2lyIHVuIHRow6htZSB1bmUgYm9ubmUgZm9pcyBwb3VyIHRvdXRlIDoKCmBgYHtyIHNldF90aGVtZX0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgpOb3VzIHV0aWxpc2Vyb25zIGxlIHRow6htZSBgbWluaW1hbGAgcGFyIGxhIHN1aXRlLgoKIyMjIMOJdGFwZSA2IDogZXhwb3J0ZXIgbGUgZ3JhcGhpcXVlCgpOb3VzIHBvdXZvbnMgaWNpIHV0aWxpc2VyIGwnaW50ZXJmYWNlIGRlIFJTdHVkaW8gZGlyZWN0ZW1lbnQgcG91ciBlbnJlZ2lzdHJlcgpub3RyZSBncmFwaGlxdWUuCgojIyMgVW5lIGFzdHVjZSA6IHRyaWVyIGxlcyBtb2RhbGl0w6lzCgpQb3VyIHJlbmRyZSBub3RyZSBncmFwaGlxdWUgZW5jb3JlIHBsdXMgbGlzaWJsZSwgbm91cyBwb3V2b25zIHRyaWVyIGxlcwptb2RhbGl0w6lzIHBhciBvcmRyZSBkw6ljcm9pc3NhbnQuIENlY2kgbidlc3QgZW4gZmFpdCBwYXMgdW5lIG1vZGlmaWNhdGlvbgpwdXJlbWVudCB2aXN1ZWxsZSwgbWFpcyBiaWVuIHPDqW1hbnRpcXVlLCBwYXJjZSBxdWUgbm91cyB2b3Vsb25zIHNvdWxpZ25lciBsZQpmYWl0IHF1ZSBsYSB2YXJpYWJsZSBxdWFsaXRhdGl2ZSAibGVtbWUiIGVzdCBvcmRvbm7DqWUuCgpQYXIgY29uc8OpcXVlbnQsIG5vdXMgZGV2b25zIGFnaXIgc29pdCBzdXIgbGVzIGRvbm7DqWVzLCBzb2l0IHN1ciBsZXMgZXN0aMOpdGlxdWVzLgoKYGBge3IgYmFycGxvdCByZW9yZGVyfQp0b3AyNSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCBuKSwgeSA9IG4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICIiLAogICAgICAgeSA9ICJPY2N1cmVuY2VzIGR1IGxlbW1lIiwKICAgICAgIHRpdGxlID0gIkxlcyAyNSBtb3RzIGxlcyBwbHVzIHByw6lzZW50cyBkYW5zIGxlcyBlbnRyZXRpZW5zIiwKICAgICAgIGNhcHRpb24gPSAiRG9ubsOpZXMgOiBlbnRyZXRpZW5zIGphcmRpbnMgQ1BFUywgbGVtbWF0aXPDqWUgZXQgbmV0dG95w6llIGRlcyBtb3RzIHZpZGVzLiIpCmBgYAoKIyBDcm9pc2VyIGRlcyB2YXJpYWJsZXMKCk5vdXMgYXZvbnMgZMOpasOgIGVmZmVjdHXDqSBldCByZXByw6lzZW50w6kgdW4gdHJhaXRlbWVudCBzdGF0aXN0aXF1ZSBzaW1wbGUsICBsZQp0cmkgw6AgcGxhdMKg4oiSIGMnZXN0LcOgLWRpcmUgdW4gc2ltcGxlIGNvbXB0YWdlLCBhdHRhY2jDqSDDoCB1bmUgc2V1bGUgdmFyaWFibGUKcXVhbGl0YXRpdmUuCgrDiXZpZGVtbWVudCwgbm91cyB2b3Vsb25zIGFsbGVyIHVuIHBldSBwbHVzIGxvaW4gZXQgw6l0dWRpZXIgbGVzIGludGVyYWN0aW9ucwplbnRyZSBwbHVzaWV1cnMgdmFyaWFibGVzLiBEYW5zIG5vdHJlIGV4ZW1wbGUsIG5vdXMgYWxsb25zIGNyb2lzZXIgZGV1eAp2YXJpYWJsZXMgcXVhbGl0YXRpdmVzIDogbGVzIG1vdHMgdXRpbGlzw6lzIGV0IGxlIHNleGUuIENlIHNvbnQgZGV1eCB2YXJpYWJsZXMKcXVhbGl0YXRpdmVzLCBjb21tZSBzb3V2ZW50IGVuIHNjaWVuY2VzIGh1bWFpbmVzIGV0IHNvY2lhbGVzLiBQb3VyIGNlIGZhaXJlLCBvbgpwZXV0IHV0aWxpc2VyIHVuIHRhYmxlYXUgY3JvaXPDqSBvdSBzZXMgcmVwcsOpc2VudGF0aW9ucyBncmFwaGlxdWVzLgoKUmVnYXJkb25zIGNvbW1lbnQgcHJvY8OpZGVyIGRhbnMgUi4KCiMjIFByw6lwYXJlciBkZXMgZG9ubsOpZXMKCiMjIyBQcmVtacOocmUgc29sdXRpb24gOiBgY291bnQoKWAKCk5vdXMgYXZvbnMgZMOpasOgIHJlbmNvbnRyw6kgbGEgZm9uY3Rpb24gYGNvdW50KClgIGRhbnMgbGUgY291cnMuIEVsbGUgcHJlbmQgZW4KYXJndW1lbnQgbGUgbm9tIGRlcyB2YXJpYWJsZXMgcXVpIHZvbnQgbm91cyBzZXJ2aXIgw6AgZ3JvdXBlciBsZXMgb2JzZXJ2YXRpb25zLgpQYXIgZXhlbXBsZSwgbm91cyBhdm9ucyBkw6lqw6AgZmFpdCB1biB0cmkgw6AgcGxhdCBlbiBjb21wdGFudCBzZXVsZW1lbnQgbGVzCmxlbW1lcyA6CgpgYGB7ciB0cmkgw6AgcGxhdH0KbW90cyAlPiUKICBjb3VudChsZW1tZSwgc29ydCA9IFRSVUUpCmBgYAoKTCdhcmd1bWVudCBgc29ydGAgcGVybWV0IGRlIHRyaWVyIHBhciBvYnNlcnZhdGlvbnMgZMOpY3JvaXNzYW50ZXMuCgpQb3VyIGNyb2lzZXIgZGV1eCB2YXJpYWJsZXMsIGwnaWTDqWUgZXN0IGRlIGdyb3VwZXIgw6AgbGEgZm9pcyBwYXIgbGVtbWUgRVQgcGFyIHNleGUgOgoKYGBge3IgY291bnQgdHJpIMOgIHBsYXR9Cm1vdHMgJT4lCiAgY291bnQoc2V4ZSwgbGVtbWUsIHNvcnQgPSBUUlVFKQpgYGAKCiMjIyBEZXV4acOobWUgc29sdXRpb24gOiBgZ3JvdXBfYnkoKWAgZXQgYHN1bW1hcmlzZSgpYAoKTGUgcGFxdWV0IGBkcGx5cigpYCBub3VzIGRvbm5lIHVuZSBkZXV4acOobWUgc29sdXRpb24sIHVuIHBldSBwbHVzIHB1aXNzYW50ZSBjYXIKZWxsZSBlc3QgcGx1cyBmbGV4aWJsZS4gRWxsZSBzZSBmYWl0IGVuIGRldXggw6l0YXBlcyA6IG5vdXMgZ3JvdXBvbnMgbm90cmUgYmFzZQpwYXIgc2V4ZSBldCBwYXIgbGVtbWUgKGNvbW1lIGF2ZWMgYGNvdW50KClgKSwgcHVpcyBub3VzIHByb2R1aXNvbnMgdW5lCmluZm9ybWF0aW9uIHN5bnRow6l0aXF1ZSDDoCBwYXJ0aXIgZGUgY2VzIGdyb3VwZXMsIGF1dHJlbWVudCBkaXQsIG5vdXMgcHJvZHVpc29ucwp1biByw6lzdW3DqSAoZm9uY3Rpb24gYHN1bW1hcmlzZSgpYCkuCgpgYGB7ciBncm91cF9ieSBzdW1tYXJpc2V9Cm1vdHMgJT4lCiAgZ3JvdXBfYnkoc2V4ZSwgbGVtbWUpICU+JQogIHN1bW1hcmlzZShuX3NleGUgPSBuKCkpCmBgYAoKQ2VsYSBkb25uZSBsZSBtw6ptZSByw6lzdWx0YXQgcXVlIGBjb3VudCgpYC4gTm91cyB2ZXJyb25zIHBsdXMgdGFyZCBxdWUKYHN1bW1hcmlzZSgpYCBzJ2FwcGxpcXVlIMOgIGRhdmFudGFnZSBkZSBjYXMuCgojIyBSZXByw6lzZW50ZXIgZ3JhcGhpcXVlbWVudCB1bmUgaW50ZXJhY3Rpb24gZW50cmUgZGV1eCB2YXJpYWJsZXMgcXVhbGl0YXRpdmVzCgpOb3VzIGF2b25zIHBsdXNpZXVycyBmYcOnb25zIGRlIHJlcHLDqXNlbnRlciBncmFwaGlxdWVtZW50IGwnaW50ZXJhY3Rpb24gZW50cmUKZGV1eCB2YXJpYWJsZXMgKnF1YWxpdGF0aXZlcyogKGxlcyByZXByw6lzZW50YXRpb25zIGRpZmbDqHJlbnQgc2Vsb24gbGVzIHR5cGVzIGRlCnZhcmlhYmxlIGVuIGpldSkuCgpOb3VzIGFsbG9ucyB2b2lyIGRldXggcmVwcsOpc2VudGF0aW9ucyBxdWkgcmVwcsOpc2VudGVudCBhdSBtaWV1eCBsJ2luZm9ybWF0aW9uCnF1ZSBub3VzIHZvdWxvbnMgY29udmllciA6IGxhIHLDqXBhcnRpdGlvbiBnZW5yw6llIGRlIGwnZW1wbG9pIGRlcyAyNSBtb3RzIGxlcwpwbHVzIGNvdXJhbnRzIGRhbnMgbm90cmUgY29ycHVzLgoKIyMjIFByw6lwYXJlciBsZXMgZG9ubsOpZXMKCkNvbW1lbsOnb25zIHBhciBjcsOpZXIgbGVzIGRvbm7DqWVzIG7DqWNlc3NhaXJlcyDDoCBub3RyZSBncmFwaGlxdWUuIExhIHByZW1pw6hyZQrDqXRhcGUgZXN0IGRlIHRyb3V2ZXIgbGVzIDI1IG1vdHMgbGVzIHBsdXMgZnLDqXF1ZW50cyBkYW5zIGwnZW5zZW1ibGUgKGhvbW1lcyBldApmZW1tZXMgY29uZm9uZHVzKSwgY2UgcXUnb24gc2FpdCBkw6lqw6AgZmFpcmUgOiBjJ2VzdCB1biB0cmkgw6AgcGxhdCB0csOocyBzaW1wbGUuCk5vdXMgYWxsb25zIGp1c3RlIHJlbm9tbWVyIGxhIGNvbG9ubmUgYG5gIGVuIGB0b3RhbGAgOgoKYGBge3IgdG9wMjUgdG90YWx9CnRvdGFsIDwtIG1vdHMgJT4lCiAgY291bnQobGVtbWUpICU+JQogIHJlbmFtZSh0b3RhbCA9IG4pCmBgYAoKTm91cyBhbGxvbnMgw6Agbm91dmVhdSB1dGlsaXNlciBsZXMgam9pbnR1cmVzLCBjZXR0ZSBmb2lzIHBvdXIgY29sbGVyIGxlcwpkb25uw6llcyBkJ2Vuc2VtYmxlIHByb2R1aXRlcyBkYW5zIGxhIHZhcmlhYmxlIGB0b3AyNWAgYXV4IGRvbm7DqWVzIHF1aSBjcm9pc2VudApsZXMgbGVtbWVzIGF2ZWMgbGUgZ2VucmUuCgpgYGB7ciBjb3VudCBsZWZ0X2pvaW59Cm1vdHMgJT4lCiAgY291bnQoc2V4ZSwgbGVtbWUpICU+JQogIGxlZnRfam9pbih0b3RhbCkKYGBgCgpOb3VzIHBvdXZvbnMgbWFpbnRlbmFudCBzw6lsZWN0aW9ubmVyIGxlcyAyNSBtb3RzIGxlcyBwbHVzIGVtcGxvecOpcyBkYW5zCmwnZW5zZW1ibGUgZXQgbGVzIHN0b2NrZXIgZGFucyB1bmUgYmFzZSBxdWkgbm91cyBzZXJ2aXJhIMOgIGZhaXJlIG5vdHJlCmdyYXBoaXF1ZSA6CgpgYGB7ciBnMV9kYXRhfQpnMV9kYXRhIDwtIG1vdHMgJT4lCiAgY291bnQoc2V4ZSwgbGVtbWUpICU+JQogIGxlZnRfam9pbih0b3RhbCkgJT4lCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkgJT4lICMgVHJpIHBhciBvcmRyZSBkw6ljcm9pc3NhbnQgc3VyIGxhIGNvbG9ubmUgdG90YWwKICBoZWFkKDUwKQpnMV9kYXRhCmBgYAoKUmVtYXJxdWV6IHF1J29uIHByZW5kIGxlcyA1MCBwcmVtacOocmVzIGxpZ25lcywgZG9uYyBsZXMgMjUgbW90cyBsZXMgcGx1cwpmcsOpcXVlbnRzLCBwdWlzcXVlIGNoYXF1ZSBtb3QgZXN0IHN1ciBkZXV4IGxpZ25lcywgdW5lIHBvdXIgbGVzIGhvbW1lcywgdW5lCnBvdXIgbGVzIGZlbW1lcy4KCiMjIyBMZSBkaWFncmFtbWUgZW4gYmFycmVzIDogYmFycmVzIGNvbG9yw6llcwoKVXRpbGlzb25zIGxlcyBlc3Row6l0aXF1ZXMgZGUgYGdncGxvdDJgLCBjZXR0ZSBmb2lzIGVuIGFqb3V0YW50IHVuZSBkZXV4acOobWUKZGltZW5zaW9uIChsZSBzZXhlKSBlbiByZW1wbGlzc2FnZSwgZG9uYyBgZmlsbGAuCgpgYGB7ciBiYXJwbG90IGZpbGx9CmcxX2RhdGEgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbGVtbWUsIHkgPSBuKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gc2V4ZSksIHN0YXQgPSAiaWRlbnRpdHkiKQpgYGAKCk9uIHBldXQgZW5zdWl0ZSB1dGlsaXNlciBjZSBxdSdvbiBjb25uYWl0IGTDqWrDoCBwb3VyIHByb2R1aXJlIHVuIGdyYXBoaXF1ZSBwbHVzCmNsYWlyIGVuY29yZSA6CgpgYGB7ciBiYXJwbG90IGZpbGwgZmluYWx9CmcxX2RhdGEgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihsZW1tZSwgdG90YWwpLCB5ID0gbikpICsKICBnZW9tX2JhcihhZXMoZmlsbCA9IHNleGUpLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIk9jY3VycmVuY2UgZGVzIGxlbW1lcyIsCiAgICAgICBmaWxsID0gIlNleGUiLAogICAgICAgdGl0bGUgPSAiTGVzIDI1IGxlbW1lcyBsZXMgcGx1cyB1dGlsaXPDqXMgc2Vsb24gbGUgc2V4ZSIsCiAgICAgICBjYXB0aW9uID0gIkRvbm7DqWVzIDogZW50cmV0aWVucyBqYXJkaW5zLiIpCmBgYAoKQ2UgZ3JhcGhpcXVlIGVzdCBwbHV0w7R0IHNhdGlzZmFpc2FudC4gVG91dGVmb2lzLCBsZXMgYmFycmVzIGNvbG9yw6llcyBuZSBzb250CnBhcyB1bmUgc29sdXRpb24gb3B0aW1hbGUgOiBsYSByw6lwYXJ0aXRpb24gcGFyIG1vdCBlc3QgY2xhaXJlLCBtYWlzIHBhcgpmb3Jjw6ltZW50IGwndXRpbGlzYXRpb24gZGVzIG1vdHMgcGFyIHNleGUgKGRpZmZpY2lsZSBkZSBjb21wYXJlciBzaSBsZXMgZmVtbWVzCmRpc2VudCBwbHVzICJmYWlyIiBvdSAiY2hvcyIpLgoKIyMjIExlIGRpYWdyYW1tZSBlbiBiYXJyZXMgOiBiYXJyZXMganV4dGFwb3PDqWVzCgpOb3VzIHBvdXZvbnMgZXNzYXllciBhdXRyZSBjaG9zZSA6IGp1eHRhcG9zZXIgbGVzIGJhcnJlcyBhdSBsaWV1IGRlIGNvbG9yZXIKw6AgbCdpbnTDqXJpZXVyIGRlIGNoYXF1ZSBiYXJyZSwgZW4gYWpvdXRhbnQgYHBvc2l0aW9uID0gImRvZGdlImAuCgpgYGB7ciBiYXJwbG90IGRvZGdlfQpnMV9kYXRhICU+JQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIobGVtbWUsIHRvdGFsKSwgeSA9IG4pKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBzZXhlKSwgc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIk9jY3VycmVuY2UgZGVzIGxlbW1lcyIsCiAgICAgICBmaWxsID0gIlNleGUiLAogICAgICAgdGl0bGUgPSAiTGVzIDI1IGxlbW1lcyBsZXMgcGx1cyB1dGlsaXPDqXMgc2Vsb24gbGUgc2V4ZSIsCiAgICAgICBjYXB0aW9uID0gIkRvbm7DqWVzIDogZW50cmV0aWVucyBqYXJkaW5zLiIpCmBgYAoKVGVjaG5pcXVlbWVudCwgY2UgZ3JhcGhlIGVzdCBwbHVzIGxpc2libGUgY2FyIGNoYXF1ZSBiYXJyZSBjb21tZW5jZSBhdSBtw6ptZQpuaXZlYXUuIE1hbGhldXJldXNlbWVudCwgaWwgZXN0IHBhcmZvaXMgZGlmZmljaWxlIGRlIGRpc3Rpbmd1ZXIgcXVlbGxlcyBiYXJyZXMKY29ycmVzcG9uZGVudCDDoCBxdWVsIGxlbW1lLiBEZSBtYW5pw6hyZSBnw6luw6lyYWxlLCBsZXMgYmFycmVzIGNvbG9yw6llcyBvdQpqdXh0YXBvc8OpZXMgbmUgc29udCBwYXMgb3B0aW1hbGVzLCBlbiBwYXJ0aWN1bGllciBzaSBub3VzIGF2b25zIHBsdXMgZGUgZGV1eAptb2RhbGl0w6lzLiBEZSBwbHVzLCBub3VzIGF2b25zIHBlcmR1IGwnZW5zZW1ibGUgKGxlIHRvdGFsIHF1ZWxxdWUgc29pdCBsZQpzZXhlKS4KCk5vdXMgcG91dm9ucyBhbGxlciBlbmNvcmUgcGx1cyBsb2luIDogcHJvZHVpcmUgZGV1eCBncmFwaGlxdWVzLCBsJ3VuIHBvdXIgbGVzCmhvbW1lcywgbCdhdXRyZSBwb3VyIGxlcyBmZW1tZXMsIGV0IGxlcyBjb2xsZXIuCgojIyMgTGUgZGlhZ3JhbW1lIGVuIGJhcnJlcyA6IGxlcyBmYWNldHRlcwoKUG91ciBjZSBmYWlyZSwgbm91cyBhbGxvbnMgdXRpbGlzZXIgdW4gbm91dmVhdSBjb25jZXB0IGRlIGBnZ3Bsb3RgLCBsZXMKZmFjZXR0ZXMgOgoKYGBge3IgYmFycGxvdCBmYWNldH0KZzFfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCB0b3RhbCksIHkgPSBuKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgZmFjZXRfd3JhcCh+IHNleGUpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICIiLAogICAgICAgeSA9ICJPY2N1cnJlbmNlIGRlcyBsZW1tZXMiLAogICAgICAgdGl0bGUgPSAiTGVzIDI1IGxlbW1lcyBsZXMgcGx1cyB1dGlsaXPDqXMgc2Vsb24gbGUgc2V4ZSIsCiAgICAgICBjYXB0aW9uID0gIkRvbm7DqWVzIDogZW50cmV0aWVucyBqYXJkaW5zLiIpCmBgYAoKQydlc3QgZMOpasOgIGJlYXVjb3VwIG1pZXV4LiBJY2ksIGxlcyBjb3VsZXVycyBuJ2FwcG9ydGVudCBwYXMgZCdpbmZvcm1hdGlvbiwKbWFpcyBlbGxlcyBwZXV2ZW50IHJlbmRyZSBsJ2Vuc2VtYmxlIHBsdXMgbGlzaWJsZS4KCmBgYHtyIGJhcnBsb3QgZmFjZXQgZmlsbH0KZzFfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCB0b3RhbCksIHkgPSBuKSkgKwogIGdlb21fYmFyKGFlcyhmaWxsID0gc2V4ZSksIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgZmFjZXRfd3JhcCh+IHNleGUpICsKICBjb29yZF9mbGlwKCkgKwogIGxhYnMoeCA9ICIiLAogICAgICAgeSA9ICJPY2N1cnJlbmNlIGRlcyBsZW1tZXMiLAogICAgICAgZmlsbCA9ICJTZXhlIiwKICAgICAgIHRpdGxlID0gIkxlcyAyNSBsZW1tZXMgbGVzIHBsdXMgdXRpbGlzw6lzIHNlbG9uIGxlIHNleGUiLAogICAgICAgY2FwdGlvbiA9ICJEb25uw6llcyA6IGVudHJldGllbnMgamFyZGlucy4iKQpgYGAKCkNlIGdyYXBoZSBlc3QgcHJlc3F1ZSBwYXJmYWl0LiBJbCBsdWkgbWFucXVlIGp1c3RlIHVuZSBpbmZvcm1hdGlvbiA6IHBhcgpyYXBwb3J0IGF1IHByZW1pZXIgZ3JhcGhpcXVlLCBub3VzIGF2b25zIHBlcmR1IGxlcyBkb25uw6llcyBkJ2Vuc2VtYmxlLgpIZXVyZXVzZW1lbnQsIGdyw6JjZSDDoCBgZ2dwbG90YCwgbm91cyBwb3V2b25zIGZhY2lsZW1lbnQgbGUgcmFqb3V0ZXIgZW4KdXRpbGlzYW50IHVuIGBnZW9tYCBzdXBwbMOpbWVudGFpcmUsIGF2ZWMgdW5lIGVzdGjDqXRpcXVlIGB5ID0gdG90YWxgIDoKCmBgYHtyIGJhcnBsb3QgZmFjZXQgZmlsbCB0b3RhbH0KZzFfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCB0b3RhbCksIHkgPSBuKSkgKwogIGdlb21fYmFyKGFlcyh5ID0gdG90YWwpLCBzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJncmF5NzAiKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBzZXhlKSwgc3RhdCA9ICJpZGVudGl0eSIpICsKICBmYWNldF93cmFwKH4gc2V4ZSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIk9jY3VycmVuY2UgZGVzIGxlbW1lcyIsCiAgICAgICBmaWxsID0gIlNleGUiLAogICAgICAgdGl0bGUgPSAiTGVzIDI1IGxlbW1lcyBsZXMgcGx1cyB1dGlsaXPDqXMgc2Vsb24gbGUgc2V4ZSIsCiAgICAgICBjYXB0aW9uID0gIkRvbm7DqWVzIDogZW50cmV0aWVucyBqYXJkaW5zLiBMJ2Vuc2VtYmxlIGVzdCBlbiBncmlzLiIpCmBgYAoKTm91cyBuZSBwb3V2b25zIGd1w6hyZSBwbHVzIGFtw6lsaW9yZXIgY2UgZGlhZ3JhbW1lIGVuIGJhcnJlcy4gSWwgcydhZGFwdGVyYQphdXNzaSB0csOocyBiaWVuIGF1eCBjcm9pc2VtZW50cyBhdmVjIHVuZSB2YXJpYWJsZSBxdWkgY29tcHRlIHBsdXMgZGUgZGV1eAptb2RhbGl0w6lzIChjbGFzc2UgZCfDomdlLCBwcm9mZXNzaW9ucykuCgpMZXMgZGlhZ3JhbW1lcyBlbiBiYXJyZXMgb250IHRvdXRlZm9pcyBkZXMgaW5jb252w6luaWVudHMgcHJlc3F1ZSBpbnRyaW5zw6hxdWVzIDoKbCdhaXJlIGRlcyBiYXJyZXMgcGV1dCBub3VzIGluZHVpcmUgZW4gZXJyZXVyIGFsb3JzIHF1ZSBjb25jcsOodGVtZW50LCBzZXVsZSBsYQpwb3NpdGlvbiBkZSBsYSBiYXJyZSBjb21wdGUuIFVuZSByZXByw6lzZW50YXRpb24gbW9pbnMgY291cmFudGUsIG1haXMgcGx1cwplZmZpY2FjZSBxdWUgbGUgZGlhZ3JhbW1lIGVuIGJhcnJlcyBlc3QgbGUgZ3JhcGhlIGRlIENsZXZlbGFuZC4KCiMjIyBMZSBncmFwaGUgZGUgQ2xldmVsYW5kLgoKTGUgZ3JhcGhlIGRlIENsZXZlbGFuZCByZXNwZWN0ZSBsZSBwcmluY2lwZSBkZSBUdWZ0ZSAob3B0aW1pc2VyIGxlIHJhdGlvIGVuY3JlCi8gaW5mb3JtYXRpb24pLCB0b3V0IGVuIHBlcm1ldHRhbnQgZGUgY29tcGFyZXIgdHLDqHMgZXhhY3RlbWVudCBsZXMgcG9wdWxhdGlvbnMKaG9tbWVzIGV0IGZlbW1lcy4KCmBgYHtyIGNsZXZlbGFuZCBkb3QgcGxvdH0KZzFfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCB0b3RhbCksIHkgPSBuKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IHNleGUpLCBzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIk9jY3VycmVuY2UgZGVzIGxlbW1lcyIsCiAgICAgICBjb2xvdXIgPSAiU2V4ZSIsCiAgICAgICB0aXRsZSA9ICJMZXMgMjUgbGVtbWVzIGxlcyBwbHVzIHV0aWxpc8OpcyBzZWxvbiBsZSBzZXhlIiwKICAgICAgIGNhcHRpb24gPSAiRG9ubsOpZXMgOiBlbnRyZXRpZW5zIGphcmRpbnMuIikKYGBgCgojIyBGYWlyZSB1biB0YWJsZWF1IGNyb2lzw6kKCk5vdXMgcG91dm9ucyBtYWludGVuYW50IHBhc3NlciDDoCB1bmUgYXV0cmUgZm9ybWUgZGUgcmVwcsOpc2VudGF0aW9uIGdyYXBoaXF1ZcKgOgpsZSB0YWJsZWF1IGNyb2lzw6kuCgojIyMgUHJlbWnDqHJlIHNvbHV0aW9uIDogdXRpbGlzZXIgYHRhYmxlKClgCgpPbiBwZXV0IHV0aWxpc2VyIGxhIGZvbmN0aW9uIGRlIGJhc2UgYHRhYmxlKClgLCBxdWUgbm91cyBjb25uYWlzc29ucyBkw6lqw6AgOgoKYGBge3IgdGFibGUsIGV2YWwgPSBGQUxTRX0KdGFibGUobW90cyRzZXhlLCBtb3RzJG1vdCkKYGBgCgpDZXR0ZSBmb25jdGlvbiBtYXJjaGUgcGFyZmFpdGVtZW50IGJpZW4gcG91ciBkZXMgdGFibGVhdXggcXVpIGRlbWFuZGVudCBwZXUgZGUKbmV0dG95YWdlIGV0IGRlIHJlY29kYWdlLiBDZSBuJ2VzdCBwYXMgbGUgY2FzIGljaSwgbm91cyBhdm9ucyBiZWF1Y291cCB0cm9wIGRlCm1vdHMuIE5vdXMgcG91cnJpb25zIG5ldHRveWVyIG5vcyBkb25uw6llcyBhdSBwcsOpYWxhYmxlIGV0IGVuc3VpdGUgZmFpcmUgbGUgdGFibGVhdS4KCkxlIHByb2Jsw6htZSBkZSBsYSBmb25jdGlvbiBgdGFibGUoKWAgZXN0IHF1J2VsbGUgcmV0b3VybmUgdW4gdGFibGVhdSBldCBub24gcGFzCnVuIGB0aWJibGVgLCBjJ2VzdC3DoC1kaXJlIHVuZSBiYXNlIGRlIGRvbm7DqWVzIG1hbmlwdWxhYmxlLiBDJ2VzdCBwb3VycXVvaSBub3VzCnZlcnJvbnMgbWFpbnRlbmFudCB1bmUgYXV0cmUgZmHDp29uIGRlIHByb2R1aXJlIGNlIHRhYmxlYXUsIGNldHRlIGZvaXMgYXZlYwpgZHBseXJgLgoKIyMjIERldXhpw6htZSBzb2x1dGlvbiA6IHV0aWxpc2VyIGBzcHJlYWQoKWAKClVuZSBhdXRyZSBmYcOnb24gZGUgcHJvY8OpZGVyIGVzdCBkZSBwcmVuZHJlIG5vcyBkb25uw6llcyBgZzFfZGF0YWAgZXQgZGUKY29uc2lkw6lyZXIgcXVlIGZhaXJlIHVuIHRhYmxlYXUgY3JvaXPDqSwgYydlc3QgcHJvY8OpZGVyIMOgIHVuIHJlY29kYWdlIDogbm91cwpwYXNzb25zIGQndW5lIGJhc2Ugb8O5IGwnb2JzZXJ2YXRpb24gZXN0IGxlICJtb3Qtc2V4ZSIgw6AgdW5lIGF1dHJlIGJhc2Ugb8O5CmNoYXF1ZSBvYnNlcnZhdGlvbiBzZXJhaXQgdW4gbW90LCBldCBsZSBkw6ljb21wdGUgcG91ciBob21tZXMgZXQgZmVtbWVzIHNlcmFpdAplbiBjb2xvbm5lcy4KClBvdXIgY2UgZmFpcmUsIG5vdXMgcG91dm9ucyB1dGlsaXNlciBsYSBmb25jdGlvbiBgc3ByZWFkKClgIDoKCmBgYHtyIHNwcmVhZH0KZzFfZGF0YSAlPiUKICBzcHJlYWQoc2V4ZSwgbikKYGBgCgpMZSBwcmVtaWVyIGFyZ3VtZW50IGRvbm5lIGxhIHZhcmlhYmxlIGNoYWN1bmUgZGVzIG1vZGFsaXTDqXMgZGV2aWVuZHJhIHVuZQpjb2xvbm5lIMOgIHBhcnQsIGV0IGxlIGRldXhpw6htZSBpbmRpcXVlIGxlIGNvbnRlbnUgZGUgY2VzIGNvbG9ubmVzLgoKTm91cyBwb3V2b25zIHByb2R1aXJlIHVuIHRhYmxlYXUgcGx1cyBvcmRvbm7DqSA6CgpgYGB7ciBzcHJlYWQgZmluYWx9CmcxX2RhdGEgJT4lCiAgc3ByZWFkKHNleGUsIG4pICU+JQogIHNlbGVjdChsZW1tZSwgRmVtbWUsIEhvbW1lLCB0b3RhbCkgJT4lCiAgYXJyYW5nZShkZXNjKHRvdGFsKSkKYGBgCgojIExlcyBtb3RzIGV0IGxldXJzIGZyw6lxdWVuY2VzCgpKdXNxdSdpY2ksIG5vdXMgbidhdm9ucyBjb25zaWTDqXLDqSBsZXMgbW90cyBxdWUgZGFucyBsZXVycyBvY2N1cnJlbmNlcywKYydlc3Qtw6AtZGlyZSBsZXVycyAqZWZmZWN0aWZzKi4gUXVlbCBwcm9ibMOobWUgY2VsYSBwZXV0LWlsIHBvc2VyCsOgIGwnaW50ZXJwcsOpdGF0aW9uwqA/CgpEJ3VuZSBwYXJ0LCBub3VzIGF2b25zIHBsdXMgZGUgZmVtbWVzIHF1ZSBkJ2hvbW1lcyBkYW5zIG5vdHJlIMOpY2hhbnRpbGxvbsKgOgoKYGBge3IgZWZmZWN0aWZzIHNleGV9CmVudHJldGllbnMgJT4lCiAgY291bnQoc2V4ZSkKYGBgCgpFdCBkb25jLCBsZXMgZmVtbWVzIG9udCB1dGlsaXPDqXMgcGx1cyBkZSBtb3RzIGRhbnMgbCdlbnNlbWJsZcKgOgoKYGBge3IgZWZmZWN0aWZzIG1vdHN9Cm1vdHMgJT4lCiAgZ3JvdXBfYnkoc2V4ZSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCkpCmBgYAoKTWFpcyBhdXNzaSBwbHVzIGRlIG1vdHMgZGlmZsOpcmVudHMsIHF1b2lxdWUgbW9pbnMgZW4gcHJvcG9ydGlvbsKgOgoKYGBge3IgZWZmZWN0aWZzIG1vdHMgZGlmZn0KbW90cyAlPiUKICBncm91cF9ieShzZXhlKSAlPiUKICBzdW1tYXJpc2UobmJfbW90cyA9IG4oKSwKICAgICAgICAgICAgbmJfbW90c19kaWZmID0gbl9kaXN0aW5jdChtb3QpKQpgYGAKCipOQjogcmVtYXJxdWV6IGljaSBsJ3V0aWxpdMOpIGRlIGxhIGZvbmN0aW9uIGBzdW1tYXJpc2UoKWAsIHF1aSBub3VzIGEgcGVybWlzIGRlCnLDqXN1bWVyIG5vbiBzZXVsZW1lbnQgbGUgbm9tYnJlIGRlIG1vdHMgdXRpbGlzw6lzIG1haXMgYXVzc2kgbGVzIG1vdHMgZGlmZsOpcmVudHMKbW9iaWxpc8OpcyBlbiB1bmUgc2V1bGUgY29tbWFuZGUqLgoKRXNzYXlvbnMgZGUgdm9pciwgcG91ciBjaGFxdWUgZW50cmV0aWVuLCBjb21iaWVuIGRlIG1vdHMgc29udCB1dGlsaXPDqXMgZXQKY29tYmllbiBkZSBtb3RzIGRpZmbDqXJlbnRzIHNvbnQgbW9iaWxpc8OpcywgcHVpcyBkZSBjcm9pc2VyIGNlcyB2YXJpYWJsZXMKYXZlYyBsZSBzZXhlLiBQb3VyIGNlIGZhaXJlLCBub3VzIGRldm9ucyByZWZhaXJlIGxhIGJhc2UgZGUgbW90cyBlbiByYWpvdXRhbnQKdW4gaWRlbnRpZmlhbnQgw6AgY2hhcXVlIGVudHJldGllbi4KCmBgYHtyIGVudHJldGllbnMgc2V4ZX0KbW90cyA8LSBlbnRyZXRpZW5zICU+JQogIG11dGF0ZShpZCA9IDE6bnJvdyhlbnRyZXRpZW5zKSkgJT4lCiAgdW5uZXN0X3Rva2Vucyhtb3QsIHRleHRlKSAlPiUKICBhbnRpX2pvaW4obW90c192aWRlcykgJT4lCiAgbXV0YXRlKGxlbW1lID0gd29yZFN0ZW0obW90LCBsYW5ndWFnZSA9ICJmciIpKQoKbW90cyAlPiUKICBncm91cF9ieShzZXhlLCBpZCkgJT4lCiAgc3VtbWFyaXNlKG1vdHMgPSBuKCksCiAgICAgICAgICAgIG1vdHNfZGlmZiA9IG5fZGlzdGluY3QobGVtbWUpKSAlPiUKICBncm91cF9ieShzZXhlKSAlPiUKICBzdW1tYXJpc2UobW90c19tZWFuID0gbWVhbihtb3RzKSwKICAgICAgICAgICAgbW90c19kaWZmX21lYW4gPSBtZWFuKG1vdHNfZGlmZiksCiAgICAgICAgICAgIGVmZmVjdGlmcyA9IG5fZGlzdGluY3QoaWQpKQpgYGAKClJlbWFycXVleiDDoCBub3V2ZWF1IGwndXRpbGl0w6kgZGUgbGEgZm9uY2l0b24gYHN1bW1hcmlzZSgpYCwgcXVpIG5vdXMgcGVybWV0IGRlCnByb2R1aXJlIGVuIHVuZSBjb21tYW5kZSBkZXMgbW95ZW5uZXMuCgpDZSB0YWJsZWF1IHByw6lzZW50ZSBwb3J1IGhhcXVlIHNleGUsIGxlIG5vbWJyZSBtb3llbiBkZSBtb3RzIHV0aWxpc8OpcyBwYXIKZW50cmV0aWVucywgbGUgbm9tYnJlIG1veWVuIGRlIG1vdHMgZGlmZsOpcmVudHMgbW9iaWxpc8OpcywgZXQgZW5maW4gbGUgbm9tYnJlIGRlCnBlcnNvbm5lcyBkYW5zIGNoYXF1ZSBncm91cGUuIFF1ZSBwZXV0LW9uIGVuIGTDqWR1aXJlwqA/CgpDZXR0ZSBicsOodmUgbWlzZSBlbiBib3VjaGUgbm91cyBhcHByZW5kIHBsdXNpZXVycyBjb2hlc8KgOgoKKyBEJ3VuZSBwYXJ0LCBsZXMgZWZmZWN0aWZzIG5lIHN1ZmZpc2VudCBwYXPCoDogaWwgZmF1dCBsZXMgYWNjb21wYWduZXIgZGUKICAqZnLDqXF1ZW5jZXMqLCBkZXMgcG91cmNlbnRhZ2VzLCBxdWkgbWV0dGVudCBlbiBjb250ZXh0ZSBjZXMgZWZmZWN0aWZzIHBvdXIKICBoY2FxdWUgZ3JvdXBlLgorIEQnYXV0cmUgcGFydCwgZGFucyBsJ2FuYWx5c2UgdGV4dHVlbGxlLCBwbHVzaWV1cnMgdHlwZXMgZGUgZnLDqXF1ZW5jZXMgc29udAogIMOgIHByZW5kcmUgZW4gY29uc2lkw6lyYXRpb27CoDogcmFwcG9ydGVyIGF1IG5vbWJyZSBkZSBtb3RzIHRvdGFsIHV0aWxpc8Opcywgb3UKICBiaWVuIGF1IG5vbWJyZSBkZSBtb3RzIGRpZmbDqXJlbnRzIGVtcGxvecOpcy4KCiMjIFByw6lzZW50YXRpb24gZGVzIGluZGljYXRldXJzIGRlIGZyw6lxdWVuY2UgdGV4dHVlbGxlCgpOb3VzIGF2b25zIHRyb2lzIGluZGljYXRldXJzLCBkdSBwbHVzIHNpbXBsZSBhdSBwbHVzIHN1YnRpbMKgOgoKKyAqKkxhIGZyw6lxdWVuY2UgZGVzIHRlcm1lcyoqICgqdGVybSBmcmVxdWVuY3kqLCBURinCoDogaWwgcydhZ2l0IGRlIGwnYXBwcm9jaGUKICBsYSBwbHVzIMOpdmlkZW50ZSwgYydlc3Qgw6AgZGlyZSBkaXZpc2VyIGxlIG5vbWJyZSBkJ29jY3VycmVuY2VzIGQndW4gdGVybWUgcGFyCiAgbGUgdG90YWwgZGVzIG1vdHMgdXRpbGlzw6lzIGRhbnMgdW4gZG9jdW1lbnQgKGljaSwgdW4gZW50cmV0aWVuKS4gQ2V0dGUKICBtw6l0cmlxdWUgdmEgdG91dGVmb2lzIHByaXZpbMOpZ2llciBsZSBtb3RzIGxlcyBwbHVzIGNvdXJhbnRzLCBldCBjb21tZSBub3VzCiAgbCdhdm9ucyB2dSwgbGVzIG1vdHMgbGVzIHBsdXMgY291cmFudHMgc29udCBzb3V2ZW50IGluaW50w6lyZXNzYW50cyBjYXIgY29tbWUKICB0b3V0ZSBsZSBtb25kZSBsZXMgdXRpbGlzZSBiZWF1Y291cCwgaWxzIHBlcmRlbnQgZGUgbGV1ciBjYXJhY3TDqHJlCiAgZGlzdGluY3RpZi4KKyAqKkxhIGZyw6lxdWVuY2UgaW52ZXJzZSBkZSBkb2N1bWVudHMqKiAoKmludmVyc2UgZG9jdW1lbnQgZnJlcXVlbmN5KiwgSURGKcKgOgogIHBvdXIgcGFsbGllciBjZSBwcm9ibMOobWUsIG5vdXMgcG91dm9ucyBjcsOpZXIgdW5lIGF1dHJlIG3DqXRyaXF1ZSBxdWkKICBjYXJhY3TDqXJpc2UgbGEgc3DDqWNpZmljaXTDqSBkJ3VuIHRlcm1lIGRhbnMgbCdlbnNlbWJsZSBkdSBjb3JwdXMsIGVuIGRvbm5hbnQKICBwbHVzIGRlIHBvaWRzIGF1eCBtb3RzIHJhcmVtZW50IGVtcGxvecOpcyBkYW5zIGwnZW5zZW1ibGUgZHUgY29ycHVzIHF1aQogIHNlcmFpZW50IHBsdXMgZGlzdGluY3RpZnMuIEMnZXN0IGxhIGZyw6lxdWVuY2UgaW52ZXJzZSBwdWlzcXVlIHBsdXMgY2V0dGUKICBtw6l0cmlxdWUgZXN0IGltcG9ydGFudCwgbW9pbnMgbGUgdGVybWUgZXN0IGNvdXJhbnQgw6AgbCfDqWNoZWxsZSBkdSBjb3JwdXMuCisgKipURi1JREYqKsKgOiBjJ2VzdCBsYSBjb21iaW5haXNvbiBkZXMgZGV1eCwgb24gbXVsdGlwbGllIGxlcyBkZXV4IGluZGljYXRldXJzCiAgcHLDqWPDqWRlbnRzIHBvdXIgdGVuaXIgY29tcHRlIMOgIGxhIGZvaXMgZGUgbGEgZnLDqXF1ZW5jZSBkZXMgdGVybWVzIGV0IGRlIGxldXIKICByYXJldMOpIHJlbGF0aXZlLgoKRGFucyBsZSBwYXF1ZXQgYHRpZHl0ZXh0YCwgbm91cyBhdm9ucyBoZXVyZXVzZW1lbnQgdW5lIGZvbmN0aW9uIHByw6p0ZQrDoCBsJ2VtcGxvacKgOgoKYGBge3IgdGRfaWRmfQptb3RzX2ZyZXEgPC0gbW90cyAlPiUKICBjb3VudChpZCwgc2V4ZSwgbGVtbWUpICU+JQogIGJpbmRfdGZfaWRmKGxlbW1lLCBpZCwgbikKbW90c19mcmVxCmBgYAoKRXhwbG9yb25zIHVuIHBldSBjZXR0ZSBiYXNlLiBRdWVscyBzb250IGxlcyB0ZXJtZXMgYXZlYyBsYSBwbHVzIGdyYW5kZSBURsKgPwoKYGBge3IgdGZ9Cm1vdHNfZnJlcSAlPiUKICBhcnJhbmdlKGRlc2ModGYpKQpgYGAKClF1ZWxzIHRlcm1lcyBvbnQgdW5lIElERiBudWxsZcKgPwoKYGBge3IgaWRmfQptb3RzX2ZyZXEgJT4lCiAgZmlsdGVyKGlkZiA9PSAwKQpgYGAKClF1ZWxzIHRlcm1lcyBvbnQgbGEgVEYtSURGIGxhIHBsdXMgw6lsZXbDqWXCoD8KCmBgYHtyIHRvcCB0ZmlkZn0KbW90c19mcmVxICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQpgYGAKCiMjIEV4ZW1wbGVzIGQnYW5hbHlzZcKgOiBtb3RzIGZyw6lxdWVudHMgdHlwaXF1ZW1lbnQgZsOpbWluaW5zCgpDcsOpb25zIHVuZSBiYXNlIGRlIGRvbm7DqWVzIGRlcyBtb3RzIHF1ZSBsZXMgZmVtbWVzIG9udCB1dGlsaXPDqXMgZXQgdHJpb25zLWxlcwpwYXIgVEYtSURGIGTDqWNyb2lzc2FudC4KCmBgYHtyIHRvcCBmZW1tZXN9Cm1vdHNfZiA8LSBtb3RzX2ZyZXEgJT4lCiAgZmlsdGVyKHNleGUgPT0gIkZlbW1lIikgJT4lCiAgZ3JvdXBfYnkobGVtbWUpICU+JQogIHN1bW1hcmlzZShuID0gc3VtKG4pLAogICAgICAgICAgICB0Zl9pZGZfbSA9IG1lYW4odGZfaWRmKSkgJT4lCiAgYXJyYW5nZShkZXNjKHRmX2lkZl9tKSkKbW90c19mCmBgYAoKRmFpc29ucyBsYSBtw6ptZSBjaG9zZSBwb3VyIGxlcyBob21tZXMuCgpgYGB7ciB0b3AgaG9tbWVzfQptb3RzX2ggPC0gbW90c19mcmVxICU+JQogIGZpbHRlcihzZXhlID09ICJIb21tZSIpICU+JQogIGdyb3VwX2J5KGxlbW1lKSAlPiUKICBzdW1tYXJpc2UobiA9IHN1bShuKSwKICAgICAgICAgICAgdGZfaWRmX20gPSBtZWFuKHRmX2lkZikpICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGZfbSkpCm1vdHNfaApgYGAKCkZ1c2lvbm5vbnMgY2VzIGRldXggYmFzZXMsIGVuIG5lIGdhcmRhbnQgcXVlIGxlcyBtb3RzIGNvbW11bnMgZW50cmUgZWxsZXMKYGlubmVyX2pvaW4oKWAgZXQgZW4gY3LDqWFudCB1bmUgbm91dmVsbGUgdmFyaWFibGUgcXVpIGNhbGN1bGUgbGEgZGlmZsOpcmVuY2UgZGUKVEYtSURGIGVudHJlIGhvbW1lcyBldCBmZW1tZXPCoDoKCmBgYHtyIGlubmVyX2pvaW59Cm1vdHNfZmggPC0gbW90c19mICU+JQogIGlubmVyX2pvaW4obW90c19oLCBieSA9IGMoImxlbW1lIiksIHN1ZmZpeCA9IGMoIl9mZW1tZXMiLCAiX2hvbW1lcyIpKSAlPiUKICBtdXRhdGUoZGlmZiA9IHRmX2lkZl9tX2ZlbW1lcy10Zl9pZGZfbV9ob21tZXMpCm1vdHNfZmgKYGBgCgpPbiBwYXNzZSBtYWludGVuYW50IGF1IGdyYXBoaXF1ZcKgOgoKYGBge3IgZ3JhcGggZmVtbWVzfQptb3RzX2ZoICU+JQogIGFycmFuZ2UoZGVzYyhkaWZmKSkgJT4lCiAgaGVhZCgyNSkgJT4lCiAgZ2F0aGVyKCJzZXhlIiwgInRmX2lkZiIsIHN0YXJ0c193aXRoKCd0Zl9pZGYnKSkgJT4lCiAgbXV0YXRlKHNleGUgPSBzdHJfZXh0cmFjdChzZXhlLCAnKGhvbW1lfGZlbW1lKScpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKGxlbW1lLCBkaWZmKSwgeSA9IHRmX2lkZikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBzZXhlKSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gIiIsCiAgICAgICB5ID0gIlRGLUlERiBtb3llbm5lIiwKICAgICAgIGNvbG91ciA9ICJTZXhlIiwKICAgICAgIHRpdGxlID0gIkxlcyAyNSBtb3RzIGxlcyBwbHVzIHNww6ljaWZpcXVlcyBhdXggZmVtbWVzLCBwYXIgcmFwcG9ydCBhdXggaG9tbWVzIiwKICAgICAgIGNhcHRpb24gPSAiRG9ubsOpZXPCoDogZW50cmV0aWVucyBqYXJkaW4uIExlcyBsZW1tZXMgc29udCBvcmdhbmlzw6lzIHBhcgogICAgICAgw6ljYXJ0IGTDqWNyb2lzc2FudCBlbnRyZSBob21tZXMgZXQgZmVtbWVzLiIpCmBgYAo=