Wie auch einige andere hier behandelte Verfahren hat die Sentimentanalyse ihre Wurzeln in der Computerlinguistik und Informatik, wird aber seit einigen Jahren auch zunehmend in den Sozialwissenschaften angewandt, um ganz unterschiedliche Texte automatisch zu klassifizieren, etwa Parlamentsdebatten, Freitextantworten in Befragungen, oder Social Media-Diskurse. Ziel der Sentimentanalyse ist die Bestimmung der Polarität eines Textes, womit gemeint ist, ob die darin zum Ausdruck gebrachten Emotionen eher positiv oder negativ sind. Dies geschieht häufig durch Wortlisten und über das Auszählen von Begriffen, die zuvor den Kategorien positiv oder negativ zugeordnet wurden. In vielen Verfahren wird das Resultat anachließend skaliert, oder es werden andere Schritte angewandt, um fehlerhafte Klassifizierungen zu vermeiden. Diese treten vor allem dann auf, wenn Negation oder Ironie verwendet werden, aber auch, wenn der Gegenstand der positiven oder negativen Ausdrücke wechselt oder unklar ist. Unsprünglich wurde die Sentimentanalyse auf Produktbewertungen auf E-Commerence-Plattformen wie Amazon.com getestet, wo diese Probleme eine relativ geringe Rolle spielen. Bei Pressetexten oder Diskursen in den sozialen Medien hingegen, ist oft schwerer zu bewerten, auf was sich eine Sentimentbewertung bezieht, oder welches Sentimenniveau etwa als ‘normal’ betrachtet werden sollte. So kommen beispielsweise in Pressetexten generell wenig Emotionen zum Ausdruck, und die negativen Begriffe überwiegen häufig, ohne dass dies notwendigerweise auf einen schlechten Zustand der Welt zurückzuführen wäre. Schließlich sollte man sich vor Augen führen, dass die Sentimentanalyse ein heuristisches Verfahren ist, dass immer auch fehlerhafte Einzelklassifikationen produziert, was aber idealerweise nicht zu stark ins Gewicht fällt, wenn man etwas Veränderungen im Sentimentverlauf über die Zeit untersucht.

Was die technische Umsetzung angeht, so gehören dieser Abschnitt und das nächste zu spezialisierten Lexika insofern zusammen, als dass es sich bei beiden Ansätzen um ganz ähnliche Verfahren handelt. In beiden Fällen wird ein Lexikon (‘sentiment/topic dictionary’) verwendet, um eine Reihe von Einzelbegriffen in einer Kategorie zusammenzufassen.

Wir verwenden in diesem Kapitel sechs unterschiedliche Sentimentlexika, davon vier in englischer Sprache und zwei für Deutsch:

Diese Lexika sind lediglich Listen von Wörtern, welche wie oben beschrieben den Kategorien postiv oder negativ zugeordnet sind. Zum Teil existiert auch noch eine dritte Kategorie neutral, desweiteren können Begriffe auch mehreren Kategorien zugeordnet sein, oder neben einer Zuordnung der Polarität auch noch eine Sentimentstärke zugeschrieben bekommen. Die hier vorgestellte Technik ist vergleichsweise primitiv, weil sie lediglich Wörter auszählt, allerdings lassen sich die Verfahren leicht noch verfeinern (vgl. bspw. diesen Beitrag von Christian Rauh zur Validierung politischer Sentiment-Lexika). Auch Verfahren die gewichten oder andere Kniffe für die Verringerung der Fehlerrate einsetzen, funktionieren so — die Sentimentanalye ist effektiv, aber auch alles andere als Hexenwerk.

Diese Lexika wenden wir folgend auf fünf Datensätze an: das bereits bekannte Sherlock Holmes—Korpus, einen Datensatz aus Tweets von Donald Trump und Hillary Clinten, einen Kommentar-Korpus aus der Diskussionsplattform Reddit, ein Korpus Schweizer Tageszeitugen mit Artikeln zur Finanzkrise, die zwischen 2007 und 2012 verfasst wurden, und schließlich noch einen Debattenkorpus des 18. Deutschen Bundestags (2013 bis 2017). Auf die Zusammenstellung der Korpora gehen wir später noch ein.

Installation und Laden der benötigten R-Bibliotheken, Laden des Korpus

Zunächst werden wieder die notwendigen Bibliotheken geladen. Neu ist die Bibliothek scales die bei der Normalisierung von Sentiment—Scores zum Einsatz kommt. Dann wird in einem zweiten Schritt das Sherlock-Korpus geladen, welches wir ja bereits zuvor nebst Metadaten im RData-Format gespeichert haben.

if(!require("quanteda")) {install.packages("quanteda"); library("quanteda")}
if(!require("readtext")) {install.packages("readtext"); library("readtext")}
if(!require("tidyverse")) {install.packages("tidyverse"); library("tidyverse")}
if(!require("scales")) {install.packages("scales"); library("scales")}
theme_set(theme_minimal())
load("daten/sherlock/sherlock.korpus.RData")

Erstellung eines Lexikons in quanteda

Wir beginnen zunächst mit einer Sentimentanalyse der Sherlock Holmes-Erzählungen, um bei einem bereits aus Kapitel 1 und 2 vertrauten Korpus zu bleiben. In einem ersten Schritt erstellen wir ein sehr einfaches Ad hoc-Lexikon aus nur sechs Begriffen, um die Struktur eines Lexikons in quanteda zu illustrieren. Dies geschieht mit dem quanteda-Befehl dictionary. Dictionary() akzeptiert eine Reihe von Standardformaten (dazu später noch mehr), aber auch Vektoren, welche die Begriffe enthalten, die eine abstrakte Kategorie operationalisieren. Beliebig viele Kategorien können so definiert und dann mit tausenden von Begriffen ‘befüllt’ werden. Auch Kategorien mit mehreren hierarchischen Ebenen sind möglich — dazu im nächsten Kapitel noch etwas mehr.

test.lexikon <- dictionary(list(posititive.begriffe = c("glück", "freude", "licht"), negative.begriffe = c("trauer", "wut", "dunkelheit")))
test.lexikon
Dictionary object with 2 key entries.
- [posititive.begriffe]:
  - glück, freude, licht
- [negative.begriffe]:
  - trauer, wut, dunkelheit

Erste Sentiment-Analyse mit dem Sherlock Holmes-Korpus

Mit diesem Lexikon können wir mit unserem englischsprachigen Korpus wenig konkretes anfangen, daher wechseln wir besser zu einem echten Sentimentlexikon. In einem zweiten Schritt lesen wir mit dem Befehl scan das Bing Liu Sentiment Lexikon in R ein. Dieses Lexikon umfasst über 6.700 englischsprachige Begriffe die in zwei einfachenn Textdateien abgelegt sind, die jeweils ein Wort je Zeile enthalten. Wir überspringen mit dem Argument skip die ersten 35 Zeilen, da diese Metainformationen über das Lexikon enthalten. Das Argument quiet verhindet die Ausgabe einer Statusmeldung.

positive.woerter.bl <- scan("lexika/bingliu-positive-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)
negative.woerter.bl <- scan("lexika/bingliu-negative-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)

Nun erstellen wir das Lexikon mithilfe der gerade eingelesenen Textvektoren. Dies erfolgt wieder mit der Funktion dictionary(), diesmal mit den gerade eingelesenen Vektoren als Argument.

sentiment.lexikon <- dictionary(list(positive = positive.woerter.bl, negative = negative.woerter.bl))
str(sentiment.lexikon)
Formal class 'dictionary2' [package "quanteda"] with 2 slots
  ..@ .Data:List of 2
  .. ..$ :List of 1
  .. .. ..$ : chr [1:2006] "a+" "abound" "abounds" "abundance" ...
  .. ..$ :List of 1
  .. .. ..$ : chr [1:4783] "2-faced" "2-faces" "abnormal" "abolish" ...
  ..@ meta :List of 3
  .. ..$ system:List of 5
  .. .. ..$ package-version:Classes 'package_version', 'numeric_version'  hidden list of 1
  .. .. .. ..$ : int [1:3] 2 1 2
  .. .. ..$ r-version      :Classes 'R_system_version', 'package_version', 'numeric_version'  hidden list of 1
  .. .. .. ..$ : int [1:3] 4 0 2
  .. .. ..$ system         : Named chr [1:3] "Darwin" "x86_64" "cp"
  .. .. .. ..- attr(*, "names")= chr [1:3] "sysname" "machine" "user"
  .. .. ..$ directory      : chr "/Users/cp/Documents/GitHub/inhaltsanalyse-mit-r.de"
  .. .. ..$ created        : Date[1:1], format: "2021-01-23"
  .. ..$ object:List of 2
  .. .. ..$ valuetype: chr "glob"
  .. .. ..$ separator: chr " "
  .. ..$ user  : list()

Wie man sieht, sind nun mehrere tausend Begriffe den beiden Kategorien des Lexikons zugeordnet worden. Jetzt können wir eine DFM berechnen, welche das erstellte Lexikon auf das Korpus anwendet.

meine.dfm.sentiment <- dfm(korpus, dictionary = sentiment.lexikon)
meine.dfm.sentiment
Document-feature matrix of: 12 documents, 2 features (0.0% sparse) and 1 docvar.
                              features
docs                           positive negative
  A Scandal in Bohemia              245      201
  The Red-headed League             272      216
  A Case of Identity                198      194
  The Boscombe Valley Mystery       194      293
  The Five Orange Pips              148      213
  The Man with the Twisted Lip      191      297
[ reached max_ndoc ... 6 more documents ]

Was ist geschehen? Alle tatsächlich vorkommenden Nennungen der rund 6.700 im Bing Liu-Lexikon enthaltenen Begriffe in den zwölf Sherlock Holmes—Romanen sind jeweils durch die ihnen zugeordnete Kategorie ersetzt worden. Sämtliche Begriffe, die nicht im Lexikon vorkommen, fallen dabei einfach weg. Dadurch bleibt eine Tabelle zurück, die nur noch zwei Spalten enthält — die Summe aller positiven und negativen Begriffe pro Roman. Wir werden darauf später noch im Detail zu sprechen kommen, aber vielleicht haben Sie schon bemerkt, dass mittels dictionary die Spalten einer DFM zusammengefasst werden (also die Wörter), während das Argument group der Funktion dfm() die Zeilen zusammenfasst (also die Texte). Diese dimensionale Reduzierung gehört zu den nützlichsten Eigenschaften von quanteda.

Das folgende Plot zeigt die Sentiment-Verteilung in den zwölf Sherlock Holmes-Erzählungen.

sentiment <- convert(meine.dfm.sentiment, "data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Wörter") %>% 
  mutate(doc_id = as_factor(doc_id)) %>% 
  rename(Roman = doc_id)
ggplot(sentiment, aes(Roman, Wörter, colour = Polarität, group = Polarität)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  ggtitle("Sentiment-Scores in zwölf Sherlock Holmes-Romanen")

Gewichtung von Sentiment-Scores

Erinnert man sich an die im vorherigen Kapitel adressierten Probleme absoluter Wortfrequenzen, so möchte man vielleicht lieber relative Frequenzen berechnen. Das bedeutet beim Einsatz von Lexika in der Regel nicht nur, dass man das Auftreten der Lexikon-Begriffe relativ zur Gesamtwortfrequenz misst, sondern deren Anteil relativ zu einander (also das Verhältnis positiver und negativer Begriffe). Dies hat den Vorteil, dass man die große Zahl aller Begriffe, die weder positiv noch negativ sind, unberücksichtigt lassen kann, was durchaus Sinn ergibt, wenn man sich eben nur für das Sentiment interessiert.

Das folgenden Beispiel verdeutlicht dieses Vorgehen.

meine.dfm.sentiment.prop <- dfm_weight(meine.dfm.sentiment, scheme = "prop")
meine.dfm.sentiment.prop
Document-feature matrix of: 12 documents, 2 features (0.0% sparse) and 1 docvar.
                              features
docs                            positive  negative
  A Scandal in Bohemia         0.5493274 0.4506726
  The Red-headed League        0.5573770 0.4426230
  A Case of Identity           0.5051020 0.4948980
  The Boscombe Valley Mystery  0.3983573 0.6016427
  The Five Orange Pips         0.4099723 0.5900277
  The Man with the Twisted Lip 0.3913934 0.6086066
[ reached max_ndoc ... 6 more documents ]

Auch diese DFM lässt sich natürlich leicht plotten.

sentiment <- convert(meine.dfm.sentiment.prop, "data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Sentiment") %>% 
  mutate(doc_id = as_factor(doc_id)) %>% 
  rename(Roman = doc_id)
ggplot(sentiment, aes(Roman, Sentiment, colour = Polarität, group = Polarität)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  ggtitle("Sentiment-Scores in zwölf Sherlock Holmes-Romanen (relativ)")

Verrechnung und Skalierung positiver und negativer Sentiment-Anteile

Die Darstellung von Sentimentanteilen innerhalb der zwölf Erzählungen lässt sich noch verbessern, indem wir darauf verzichten, beide Polaritäten darszustellen. Da bei dieser Anwendungen die negative Polarität schlicht die Invertierung des positiven Sentiments ergibt, reicht dies aus. Zudem skalieren wir die Werte mittels rescale neu, so dass sie zwischen –1 und +1 liegen.

sentiment <- convert(meine.dfm.sentiment.prop, "data.frame") %>%
  rename(Roman = doc_id, Sentiment = positive) %>%
  select(Roman, Sentiment) %>%
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
  mutate(Roman = as_factor(Roman))
ggplot(sentiment, aes(Roman, Sentiment, group = 1)) + 
  geom_line(size = 1) + 
  geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") + 
  theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) + 
  ggtitle("Verrechnete Sentiment-Scores in zwölf Sherlock Holmes-Romanen")

Wir halten fest: Die Romane zu Beginn des Sherlock Holmes-Zylus sind etwas positiver, während es in der Mitte düsterer zugeht. Zum Schluss hebt sich die Stimmung aber - jedenfalls im Vergleich - wieder. Wenn man diese Darstellung mit dem ersten Plot kontrastiert wird klar, warum verrechnete Frequenzen oftmals einen guten Ansatz darstellen. Andererseits darf man aber auch nicht der Annahme auf den Leim gehen, das Sentiment in ‘The Adventure of the Speckled Band’ sei ausschließlich negativ, also ‘zu 0% positiv’, denn dies ist ein Artefakt unserer proportionalen Skalierung. Es ist lediglich anteilig negativer als in den anderen elf Erzählungen.

Sentimentanalyse mit Twitter-Daten von Donald Trump und Hillary Clinton

Wenden wir uns jetzt einem etwas aktuelleren Beispiel zu, nämlich der Analyse des Sentiments in den Tweets von Donald Trump und Hillary Clinton vor, während, und nach dem US-Präsidentschaftswahlkampf von 2016.

Zunächst laden wie die Trump- und Clinton-Twitter-Datensätze (bereits in einem Korpus-Objekt zusammengefasst und als RData-File gespeichert). Diese Daten wurden aus verschiedenen Online–Archiven und durch die Twitter API zusammengestellt. Ich gehe hier nicht genauer auf die Erstellung des Korpus ein, dies wird aber später noch erläutert.

load("daten/twitter/trumpclinton.korpus.RData")
korpus.stats.monat <- ungroup(korpus.stats.monat)
korpus.stats.monat

Die Tabelle zeigt die bereits monatsweise aggregierten Tweet-Zahlen (oder genauer, die Anzahl der Types/Tokens/Sätze). Hier einige konkrete Beispiele für Tweets der beiden Kandidaten (wieder kann man mit dem Pfeil–Icon nach rechts/links scrollen):

trumpclinton.sample <- corpus_sample(korpus, size = 20)
bind_cols(text = texts(trumpclinton.sample), docvars(trumpclinton.sample))

Zunächst plotten wir die Wörter (oder genauer der Tokens) pro Monat für Hillary Clinton und Donald Trump im Zeitraum von April 2015 bis April 2017, um uns einen Eindruck ihrer Aktivität zu verschaffen. Dies entspricht relativ gut der Anzahl der Tweets, da die Variation bei der Länge nicht allzu stark ausfällt.

Achtung: Hier zeigt die Farbe den Kandidaten an, nicht das Sentiment.

ggplot(korpus.stats.monat, aes(date.print, Tokens, group = Kandidat, col = Kandidat)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + xlab("Monat") + 
  ggtitle("Trump vs. Clinton: Tokens pro Monat (2015-2017)")

Es lässt sich beobachten, dass Hillary Clinton in den sechs Monaten vor der Wahl im November 2016 deutlich aktiver wurde, während Donald Trump im direkten Vergleich zu seiner bereits seit 2009 anhaltenden Präsenz bei Twitter eher etwas weniger Beiträge verfasste.

Wir erstellen nun zunächst eine DFM für jeden der Kandidaten und wenden erneut das Bing Liu Sentiment-Lexikon an. Wir beginnen mit Donald Trump und filtern zunächst das Korpus nach seinen Tweets, die wir anschließend mit Hilfe des Arguments groups nach Monat und Jahr aggregieren (sonst erhält man das Sentiment-Ergebnis für jeden einzelnen Tweet, was bei rund 20.000 Tweets nicht unbedingt interpretierbar ist). Anschließend erstellen wir mittels convert einen data frame, welchen wir noch etwas bearbeiten, um ihn besser plotten zu können.

korpus.trump <- corpus_subset(korpus, Kandidat == "Trump")
meine.dfm.trump <- dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon)
sentiment.trump <- convert(meine.dfm.trump, "data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Wörter") %>% 
  mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>% 
  filter(Datum >= "2015-04-01" & Datum <= "2017-04-01")
ggplot(sentiment.trump, aes(Datum, Wörter, colour = Polarität, group = Polarität)) + 
  geom_line(size = 1) + 
  scale_colour_brewer(palette = "Set1") + 
  scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") + 
  ggtitle("Sentiment-Scores für Donald Trump") + xlab("Monat") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Den exakt gleich Prozess wiederholen wir nun für Hillary Clinton. Wir filtern ihre Tweets aus dem Gesamtkorpus heraus, Gruppieren eine DFM nach Monat und Jahr unter Anwendung des Lexikons, und plotten dann das Ergebnis.

korpus.clinton <- corpus_subset(korpus, Kandidat == "Clinton")
meine.dfm.clinton <- dfm(korpus.clinton, groups = c("monat", "jahr"), dictionary = sentiment.lexikon)
sentiment.clinton <- convert(meine.dfm.clinton, "data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Wörter") %>% 
  mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>% 
  filter(Datum >= "2015-04-01" & Datum <= "2017-04-01")
ggplot(sentiment.clinton, aes(Datum, Wörter, colour = Polarität, group = Polarität)) + 
  geom_line(size = 1) + scale_colour_brewer(palette = "Set1") + 
  scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") + 
  ggtitle("Sentiment-Scores für Hillary Clinton") + xlab("Monat") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Im direkten Vergleich twittern beide Kandidaten eher positiv als negativ, was vielleicht überrascht. Allerdings ist der Abstand zwischen positivem und negativem Sentiment bei Hillary Clinton insgesamt hoch, und in der heißen Phase des Wahlkampfes sogar besonders ausgeprägt. Bei Donald Trump hingegen überwiegt im Juli 2016 das negative Sentiment und im Februar 2017 liegen positive und negative Begriffe ungefähr gleich auf. Bei Trump fallen starke Schwankungen auf, die bei Clinton fehlen.

Wie sieht die Twitter–Aktivität beider Kandidaten im direkten Vergleich aus? Wir überspringen den zweiten Schritt aus der obigen Analyse und gehen gleich dazu über, nur noch den verrechneten relativen Anteil des positiven Sentiments für beide Poltiker zu plotten.

sentiment.trump.prop <- dfm_weight(meine.dfm.trump, scheme = "prop") %>% 
  convert("data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Sentiment") %>% 
  mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>% 
  filter(Datum >= "2015-04-01" & Datum <= "2016-11-01") %>% 
  mutate(Kandidat = "Trump")
sentiment.clinton.prop <- dfm_weight(meine.dfm.clinton, scheme = "prop") %>% 
  convert("data.frame") %>%
  gather(positive, negative, key = "Polarität", value = "Sentiment") %>% 
  mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>% 
  filter(Datum >= "2015-04-01" & Datum <= "2016-11-01") %>% 
  mutate(Kandidat = "Clinton")
sentiment.trumpclinton <- bind_rows(sentiment.trump.prop, sentiment.clinton.prop) %>% 
  filter(Polarität == "positive") %>% 
  select(Datum, Kandidat, Sentiment) %>% 
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
  mutate(Kandidat = as_factor(Kandidat))
ggplot(sentiment.trumpclinton, aes(Datum, Sentiment, colour = Kandidat, group = Kandidat)) + 
  geom_line(size = 1) + 
  geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") + 
  scale_colour_brewer(palette = "Set1") + 
  scale_x_date(date_breaks = "1 month", date_labels = "%b %Y") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ggtitle("Verrechnete Sentiment-Scores für beide Kandidaten") + 
  xlab("Monat")

Der Zeitraum ist hier etwas anders gewählt als zuvor (1. April 2015 bis 30. November 2016), weil die sehr geringe Anzahl von Tweets durch Hillary Clinton nach der Wahl den Vergleich erschwert und zu Verzerrungen führt. Die Normalisierung zeigt die Höhen und Tiefen relativ zum Sentimentverlauf im Gesamtzeitrum, und verrechnet dabei wie auch schon zuvor die positiven und negativen Begriffe. Im Juli 2016 twittert Trump stark negativ, was aber nicht heißt, dass der Anteil negativer Begriffe grundsätzlich viel höher lag, als der positiver Begriffe. Es lassen sich interessante Bezüge zum Kontext der US-Wahlen herstellen, d.h. unter anderem die Nominierung von Donald Trump und Hillary Clinton als Kandidaten der Republikaner bzw. Demokraten, sowie die Enthüllung gehackter Emails des DNC, die auch schon 2016 den Vorwurf eines gezielten Manipulationsversuchs durch Russland laut werden ließ. Zahlreiche Tweets von Trump kritisieren die Medien für diese in den Augen des Kandidaten falsche Unterstellung. Der Grund für Trumps dennoch zum Teil hohen Sentiment–Werte liegt indes in der Verwendung vieler positiver Adjektive und Superlative (‘great’, ‘best’), die in Sentiment–Lexika natürlich vorkommen.

Der Code für diese Analyse lässt sich sich insofern noch etwas vereinfachen, als dass eine Reihe von Handgriffen nur für die Erstellung des Plots, nicht aber für die Erstellung der gewichteten DFM und der Anwendung des Lexikons notwendig sind.

Vergleich unterschiedlicher Sentiment-Lexika

Welche Unterschiede gibt es zwischen verschiedenen Lexika? Da unterschiedliche Lexika auch unterschiedliche Begriffe enthalten, ist diese Frage durchaus bedeutsam. Um sie zu beantworten, berechnen wir anhand der Tweets von Donald Trump drei unterschiedliche DFMs, jeweils mit einem anderen Lexikon. Wir lesen zunächst einmal alle drei Lexika ein.

sentiment.lexikon.bingliu <- dictionary(list(positive = scan("lexika/bingliu-positive-words.txt", what = "char", sep = "\n", skip = 35, quiet = T), negative = scan("lexika/bingliu-negative-words.txt", what = "char", sep = "\n", skip = 35, quiet = T)))
sentiment.lexikon.nrc <- dictionary(list(positive = scan("lexika/nrc.pos.txt", what = "char", sep = "\n", quiet = T), negative = scan("lexika/nrc.neg.txt", what = "char", sep = "\n", quiet = T)))
afinn <- read.csv("lexika/AFINN-111.txt", header = F, sep = "\t", stringsAsFactors = F)
sentiment.lexikon.afinn <- dictionary(list(positive = afinn$V1[afinn$V2>0], negative = afinn$V1[afinn$V2<0]))

Während der Import für das Bing Liu-Lexikon und das NRC Emotions Lexicon sehr einfach abläuft, hat das AFINN-Dictionary ein spezielles Format, bei dem eine Zahl zwischen -5 und +5 die Polarität von sehr negativ bis sehr positiv beschreibt. Wir nutzen hier diesen besonderen Vorteil nicht aus, sondern behandeln alle Begriffe als ‘einfach’ negativ oder positv (<0 oder >0).

Wieder wird das Lexikon angewendet, nach Monat und Jahr gruppiert, und anschließend proportional gewichtet – jeweils für jedes der drei Lexika.

meine.dfm.trump.bingliu <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.bingliu), scheme = "prop")
meine.dfm.trump.nrc <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.nrc), scheme = "prop")
meine.dfm.trump.afinn <- dfm_weight(dfm(korpus.trump, groups = c("monat", "jahr"), dictionary = sentiment.lexikon.afinn), scheme = "prop")

Schließlich werden die drei DFMs in Data Frames umgewandelt und eine Variable hinzugefügt, welche das jeweilige Lexikon identifiziert.

sentiment.trump.bingliu <- convert(meine.dfm.trump.bingliu, "data.frame") %>% mutate(Lexikon = "Bing Liu")
sentiment.trump.nrc <- convert(meine.dfm.trump.nrc, "data.frame") %>% mutate(Lexikon = "NRC")
sentiment.trump.afinn <- convert(meine.dfm.trump.afinn, "data.frame") %>% mutate(Lexikon = "AFINN")

Zuletzt wird ein gemeinsamer Data Frame zusammengesetzt und etwas umgeformt. Das resultierenden Plot zeigt die verrechneten Sentiment-Scores von Donald Trump für alle drei Lexika.

sentiment.trump.kombi <- bind_rows(sentiment.trump.bingliu, sentiment.trump.nrc, sentiment.trump.afinn) %>% 
  gather(positive, negative, key = "Polarität", value = "Sentiment") %>% 
  filter(Polarität == "positive") %>% 
  mutate(Datum = as.Date(paste("01", doc_id, sep = "."), "%d.%m.%Y")) %>% 
  filter(Datum >= "2015-04-01" & Datum <= "2017-03-01") %>% 
  select(Datum, Lexikon, Sentiment) %>% 
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1)))
ggplot(sentiment.trump.kombi, aes(Datum, Sentiment, colour = Lexikon, group = Lexikon)) + 
  geom_line(size = 1) + 
  geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") + 
  scale_colour_brewer(palette = "Dark2") + 
  scale_x_date(date_breaks = "2 months", date_labels = "%b %Y") + 
  ggtitle("Verrechnete Sentiment-Scores für Donald Trump mit drei Lexika") + 
  xlab("Monat") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Wie wir sehen, stimmt die Tendenz der drei Lexika zwar klar überein, jedoch ergeben sich durchaus markanten Unterschiede. So sind AFINN und Bing Liu gegenüber NRC etwas positiver. Zum Teil unterscheidet sich auch die Intensität der Ausschläge in beide Polaritätsrichtungen. Ein Grund für die Variation ist die Länge der Wortlisten, da umfangreichere Listen eine besser Abdeckung der tatsächlich verwendeten Begriffe erreichen. Grundsätzlich unterscheiden sich die drei Lexika aber nicht signifikant und stimmen bspw. in ihrer Messung der Sentiment–Schwankung zwischen September und November 2016 klar überein.

Sentiment in zwei Subreddits mit dem Lexicoder Sentiment Dictionary

Nun wenden wir uns einem Beispiel aus einer anderen Social Media Plattform – dem Diskussionsforum Reddit – zu. Es handelt sich um Beiträge aus zwei unterschiedlichen Bereichen der Plattform (sog. Subreddits). Wir laden zunächst den Datensatz welcher wie auch die Twitter-Daten schon als Quanteda-Korpus vorliegt. Die Metadaten ähneln teilweise denen von Twitter (“post_date”), weisen aber auch Spezifika auf (die Variabel “structure” liefert Informationen zur Schachtelung der Diskussion).

load("daten/reddit/reddit.RData")
as.data.frame(reddit.stats)

Das Korpus enthält etwa 20.000 Kommentare, die in zwei unterschiedlichen Subreddits veröffentlicht wurden, den Foren ‘science’ und ‘syriancivilwar’. Wir berechnen Sentiment-Scores für diese Nachrichten mit Hilfe des Lexicoder Sentiment Dictionary (LSD2015). Im Gegensatz zum Vorgehen in den vorausgehenden Beispielen verwenden wir die logarithmische Durchschnittsgewichtung und kürzen das Ergebnis, um so einen Polaritätswert pro Kommentar und nicht pro Wort zu bestimmen (beachten Sie, dass es hierfür unterschiedliche Strategien gibt).

reddit.dfm <- dfm(reddit.corpus, dictionary = data_dictionary_LSD2015) %>% 
  dfm_remove(c("neg_positive", "neg_negative"))
reddit.sentiment <- dfm_weight(reddit.dfm, scheme = "logave") %>% 
  convert("data.frame") %>%
  mutate(positive = trunc(positive), negative = trunc(negative)) %>% 
  mutate(neutral = positive == negative) %>% 
  left_join(reddit.stats, by = c("doc_id" = "Text"))
sentiment <- ""
sentiment[reddit.sentiment$positive==1] <- "positive"
sentiment[reddit.sentiment$negative==1] <- "negative"
sentiment[reddit.sentiment$neutral==T] <- "neutral"
reddit.sentiment.share <- reddit.sentiment %>% 
  select(doc_id, structure, comm_date, subreddit, user, comment_score) %>% 
  data.frame(Sentiment = sentiment)
reddit.sentiment.share

Nun plotten wir den relativen Anteil der Polarität je Kommentar innerhalb der zwei Subreddits.

reddit.sentiment.share <- data.frame(prop.table(table(reddit.sentiment.share$Sentiment, reddit.sentiment.share$subreddit), 2))
colnames(reddit.sentiment.share) <- c("Sentiment", "Subreddit", "Share")
ggplot(reddit.sentiment.share, aes(Subreddit, Share, colour = Sentiment, fill = Sentiment)) + 
  geom_bar(stat = "identity", position = position_dodge()) +
  scale_colour_brewer(palette = "Set1") + 
  scale_fill_brewer(palette = "Pastel1") + 
  ggtitle("Sentiment-Verteilung in zwei Subreddits") + 
  xlab("") + ylab("Sentiment-Anteil (%)")

Die nachstehenden Beispiele (Zufallssample) zeigen Kommentare und deren jeweilige vorhergesagte Polarität auf Grundlage des Lexikons. Es lassen sich zahlreiche Beispiele für fehlerhaft klassifizierte Kommentare entdecken, etwa ironische oder unverständliche Beiträge, oder solche, die sich schlicht einer klaren Einordnung in das Raster positiv/negativ entziehen. Allerdings scheint die Tendenz insofern korrekt, als das etwa Kommentare mit Kraftausdrücken als negativ und solche mit Glückwünschen als positiv klassifiziert werden (auch hier gibt es falsch positive Treffer).

data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>% 
  filter(sentiment == "positive") %>% 
  sample_n(10) %>% 
  select(comment, sentiment, subreddit, title)
data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>% 
  filter(sentiment == "negative") %>% 
  sample_n(10) %>% 
  select(comment, sentiment, subreddit, title)
data.frame(reddit.sentiment, sentiment, comment = texts(reddit.corpus)) %>% 
  filter(sentiment == "neutral") %>% 
  sample_n(10) %>% 
  select(comment, sentiment, subreddit, title)

Sentiment in Schweizer Tageszeitungen in der Berichterstattung zur Finanzkrise

Wir wechseln nun die Perspektive und untersuchen deutschsprachige Texte. Dazu lesen wir das Schweizer Korpus zur Finanzkrise ein. Dieses emthält rund 21,000 Artikel, die zwischen 2007 und 2012 in einer von fünf Schweizer Tageszeigungen veröffentlich wurden und den Begriff ‘Finanzkrise’ in Text oder Titel enthalten. Neben Sprache und Textsorte gibt einen weiteren Unterschied zu den Twitter-Daten: Die Aggregationsebene ist hier nicht mehr ein Monat, wie zuvor, sondern schlicht ein Artikel. Das leuchtet ein, allerdings ist dieser Ansatz hier auch deshalb ergiebig, weil Zeitungsartikel nun einmal deutlich länger sind als Tweets.

Zusätzlich zum Finanzkrise-Korpus laden wir auch gleich das deutschsprachige SentiWS-Lexikon. Im Gegensatz zu dem Bing Liu-Lexikon handelt es sich hier um eine RData-Datei, nicht um eine Textdatei, was aber praktisch keinerlei Unterschied macht.

load("lexika/sentiWS.RData")
load("daten/cosmas/finanzkrise/finanzkrise.korpus.RData")
sentiment.lexikon.sentiws <- dictionary(list(positive = positive.woerter.senti, negative = negative.woerter.senti))
head(korpus.finanzkrise.stats, 100)

Sentiment-Anteile nach Quelle (hier: Zeitung)

Im nächste Schritt berechnen wir eine DFM, die nun nicht nach Monat und Jahr gruppiert ist, sondern nach dem Feld quelle, d.h. der jeweiligen Zeitung. Dieser Schritt dauert deshalb etwas länger als zuvor, weil wir es mit einem Korpus von 21,000 Dokumenten und rund 4 Mio. Tokens zu tun haben – deutlich größer, als das Sherlock Holmes-Korpus (126T Wörter) und Twitter Korpus (459T Wörter). Heraus kommt eine Tabelle, die sich sogar vollständig ausgeben lässt, ohne den head–Befehl zu verwenden.

meine.dfm.finanzkrise <- dfm(korpus.finanzkrise, groups = "quelle", dictionary = sentiment.lexikon.sentiws)
meine.dfm.finanzkrise.prop <- dfm_weight(meine.dfm.finanzkrise, scheme = "prop")
meine.dfm.finanzkrise.prop
Document-feature matrix of: 5 documents, 2 features (0.0% sparse) and 1 docvar.
                       features
docs                     positive  negative
  Basler Zeitung        0.3255584 0.6744416
  Berner Zeitung        0.3592919 0.6407081
  Der Bund              0.3015228 0.6984772
  Neue Luzerner Zeitung 0.3489669 0.6510331
  Neue Zürcher Zeitung  0.3327953 0.6672047

Auch diese sehr einfache Tabelle können wir plotten, auch wenn das in diesem konkreten Fall vielleicht nicht unbedingt notwendig ist.

sentiment.finanzkrise <- convert(meine.dfm.finanzkrise.prop, "data.frame") %>% 
  gather(positive, negative, key = "Polarität", value = "Sentiment")
ggplot(sentiment.finanzkrise, aes(doc_id, Sentiment, colour = Polarität, fill = Polarität)) + 
  geom_bar(stat="identity") + 
  scale_colour_brewer(palette = "Set1") + 
  scale_fill_brewer(palette = "Pastel1") + 
  ggtitle("Sentiment-Scores in Beiträgen zur Finanzkrise aus fünf Schweizer Tageszeitungen") + 
  xlab("") + ylab("Sentiment-Anteil (%)") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Wie wir sehen ist die Berichterstattung zur Finanzkrise überwiegende negativ, was angesichts des Themas natürlich kaum überrascht. Es lassen sich aber auch keinerlei nennenswerte Unterschiede zwischen den Zeitungen erkennen, sondern die Übereinstimmung ist ausgesprochen klar.

Verrechnete Sentiment-Anteile nach Quelle und Jahr

Wie stellt sich das Sentiment in den fünf Zeitungen im Vergleich über die Zeit dar? Wir berechnen noch einmal eine DFM unter Verwendung des SentiWS–Lexikons, gruppieren aber dieses Mal sowohl nach Quelle als auch nach Jahr.

meine.dfm.finanzkrise <- dfm(korpus.finanzkrise, groups = c("quelle", "jahr"), dictionary = sentiment.lexikon.sentiws)
meine.dfm.finanzkrise.prop <- dfm_weight(meine.dfm.finanzkrise, scheme = "prop")
head(meine.dfm.finanzkrise.prop)
Document-feature matrix of: 6 documents, 2 features (0.0% sparse) and 2 docvars.
                            features
docs                          positive  negative
  Basler Zeitung.2007        0.3601036 0.6398964
  Berner Zeitung.2007        0.2903226 0.7096774
  Der Bund.2007              0.3322476 0.6677524
  Neue Luzerner Zeitung.2007 0.2552083 0.7447917
  Neue Zürcher Zeitung.2007  0.2971549 0.7028451
  Basler Zeitung.2008        0.3115323 0.6884677

Die resultierende DFM formen wir etwas um und plotten dann wieder eine Grafik, welche einerseits die zeitliche Entwicklung darstellt, und andererseits, wie auch schon im vorherigen Beispiel, die Unterschiede stark hervorhebt – einerseits zwischen den fünf Zeitungen und andererseits innerhalb des fünfjährigen Messzeitraums. So kann man erkennen, das ‘Der Bund’ durchweg etwas negativer ist, als es die anderen Zeitungen sind, dass die Berner Zeitung in 2010 positiver ist als die andere vier Blätter, und dass die NZZ tendenziell über die Zeit etwas positiver wird, aber im Vergleich doch negativ bleibt. Die Berner Zeitung schwankt im Vergleich relativ stark.

sentiment.finanzkrise.zeit <- convert(meine.dfm.finanzkrise.prop, "data.frame") 
finanzkrise.quellen <- data.frame(str_split(sentiment.finanzkrise.zeit$doc_id, "\\.", simplify = T))
colnames(finanzkrise.quellen) <- c("Zeitung", "Jahr")
sentiment.finanzkrise.zeit <- bind_cols(sentiment.finanzkrise.zeit, finanzkrise.quellen) %>%
  rename(Sentiment = positive) %>%
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>%
  select(Sentiment, Zeitung, Jahr)
ggplot(sentiment.finanzkrise.zeit , aes(Jahr, Sentiment)) + 
  geom_bar(stat = "identity", aes(fill = Zeitung), position = "dodge") + 
  scale_fill_brewer(palette = "Accent") + 
  ggtitle("Verrechnete Sentiment-Scores in Beiträgen zur Finanzkrise") 

Sentiment in Debatten des Deutschen Bundestags nach Partei, Sitzung und Sprecher

Wir schließen mit einem weitere deutschsprachigen Beispiel, dem Debattenkorpus des Deutschen Bundestags in der 18. Legislaturperiode. Wieder ist das Korpus dazu bereits als RData-Datei abgelegt und wir überspringen and dieser Stelle den Hintergrund dazu, wie genau das Korpus zusammengestellt wurde. Der Umfang ist mit 206.000 Wortmeldungen in 243 Sitzungen und rund 15 Mio. Tokens noch einmal erheblich größer, als der vorausgegangener Korpora.

load("daten/bundestag/bundestag.korpus.RData")
load("lexika/Rauh_SentDictionaryGerman.RData")
sentiment.lexikon.rauh <- dictionary(list(positive = str_trim(sent.dictionary$feature[sent.dictionary$sentiment>0]), negative = str_trim(sent.dictionary$feature[sent.dictionary$sentiment<0])))

Nachdem Korpus und Lexikon-Rohdaten geladen wurden, erstellen wir aus der umfangreichen Sentiment-Wortliste von Christian Rauh ein quanteda-Lexikon (dabei muss ähnlich wie beim AFINN-Diktionary noch etwas umgeformt werden). Anschließend berechnen wir dann eine DFM, welche Sentiment-Wörter nach Parteien auszählt.

meine.dfm.bundestag.partei <- dfm(korpus.bundestag, groups = "party", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.partei
Document-feature matrix of: 5 documents, 2 features (0.0% sparse) and 3 docvars.
            features
docs         positive negative
  CDU          301212   184566
  CSU          103447    62955
  DIE GRÜNEN   151134   132210
  DIE LINKE    119610   115444
  SPD          275064   173376

Auch hier plotten wir wieder das Resultat. Die Normalisierung, welche wir im vorausgegangenen Beispiel durchgeführt haben, lassen wir an dieser Stelle weg, um auch einen Eindruck von den Redenanteilen der Parteien zu erhalten.

sentiment.bundestag.partei <- convert(meine.dfm.bundestag.partei, "data.frame") %>% 
  gather(positive, negative, key = "Polarität", value = "Sentiment")
ggplot(sentiment.bundestag.partei, aes(doc_id, Sentiment, colour = Polarität, fill = Polarität)) + 
  geom_bar(stat="identity") + scale_colour_brewer(palette = "Set1") + 
  scale_fill_brewer(palette = "Pastel1") + 
  scale_y_continuous(labels = comma) + 
  ggtitle("Sentiment-Scores im Deutschen Bundestag nach Partei") + 
  xlab("") + ylab("Wörter") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

An diesem Ergebnis überrascht vielleicht etwas, dass das Sentiment der Regierungsparteien nicht merklich positiver ausfällt, als das der Opposition. Zwar hat Linke anteilig das negativste Sentiment und die CSU das positivste, aber die Unterschiede sind nicht sehr groß.

Nun wiederholen wir die Erstellung der DFM, gruppieren aber diesmal nicht nach Partei, sondern nach Sitzung. Das Ziel dieses Ansatzes besteht darin, Sitzungen zu identifizieren, in denen besonders negative Themen (was hier mehrere Bedeutungen haben kann) zu finden.

meine.dfm.bundestag.sitzung <- dfm(korpus.bundestag, groups = "sitzung", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.sitzung.prop <- dfm_weight(meine.dfm.bundestag.sitzung, scheme = "prop")
head(meine.dfm.bundestag.sitzung.prop)
Document-feature matrix of: 6 documents, 2 features (0.0% sparse) and 5 docvars.
    features
docs  positive  negative
   1 0.6353383 0.3646617
   2 0.5944617 0.4055383
   3 0.5908111 0.4091889
   4 0.7329193 0.2670807
   5 0.6277672 0.3722328
   6 0.5840116 0.4159884

Nachdem wir das anteilige Sentiment für alle 243 Sitzungen der Legislaturperiode 2013-2017 bestimmt haben, skalieren wir die Daten wieder und plotten das Resultat in einer (sehr dichten) Zeitreihe. Für das Plot filtern wir dabei die Daten so, dass nur das Jahr 2015 (Sitzungen 1-60) dargestellt wird.

sentiment.bundestag.sitzung <- convert(meine.dfm.bundestag.sitzung.prop, "data.frame") %>% 
  rename(Sitzung = doc_id, Sentiment = positive) %>%
  select(Sitzung, Sentiment) %>%
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>% 
  mutate(Sitzung = as_factor(Sitzung)) %>% 
  slice(1:60)
ggplot(sentiment.bundestag.sitzung, aes(Sitzung, Sentiment, group = 1)) + 
  geom_line() + 
  geom_point() + 
  geom_hline(yintercept = 0, linetype = "dashed", color = "lightgray") + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5, size = 8)) + 
  ggtitle("Verrechnete Sentiment-Scores in Sitzungen der Deutschen Bundestags")

Ähnlich wie bei Trump und Clinton lässt sich ein klarer Bezug zwischen Sentiment–Scores und historischen Ereignissen herstellen, wenn man den Blick auf solche Sitzungen mit besonders hohen/niedrigen Sentiment-Scores lenkt. Folgend wählen wir solche Sitzungen aus und reichern die Informationen zur Sitzung mit den enthaltenen Tagesordnungspunkten (TOPs) an. Das Sentiment ist allerdings in der nachstehenden Tabelle für die gesamte Sitzung, nicht nur für den jeweiligen TOP kalkuliert.

load("daten/bundestag/tagesordnungspunkte.RData")
tagesordnungspunkte <- select(tagesordnungspunkte, sitzung, held_on, name, category)
bundestag.possitzung <- head(arrange(sentiment.bundestag.sitzung, desc(sentiment.bundestag.sitzung$Sentiment)), 3)
bundestag.possitzung$Sitzung <- as.numeric(bundestag.possitzung$Sitzung)
bundestag.negsitzung <- head(arrange(sentiment.bundestag.sitzung, sentiment.bundestag.sitzung$Sentiment), 3)
bundestag.negsitzung$Sitzung <- as.numeric(bundestag.negsitzung$Sitzung)
# Sitzungen mit positivem Sentiment
left_join(bundestag.possitzung, tagesordnungspunkte, by = c("Sitzung" = "sitzung"))
# Sitzungen mit negativem Sentiment
left_join(bundestag.negsitzung, tagesordnungspunkte, by = c("Sitzung" = "sitzung"))

Beim positiven Sentiment sticht die Sitzung vom 17. Dezember 2013 heraus, in der Angela Merkel vereidigt wurde – dem Anlass und den nahenden Weihnachtsferien entsprechend ist die Stimmung gut. Negativ fallen Sitzungen zum kontroversen aussenpolitischen Themen ebenso wie zum CETA-Abkommen oder zur Bekämpfung der Kinderarmut auf. Die beiden TOPs, welche die Liste anführen (‘Regierungserklärung Humanitäre Hilfe im Irak’ sowie ‘Stabilitätshilfe zugunsten Griechenlands’) liefern bei augenscheinlicher Bertrachtung ein gutes Maß der besonders kontroversen Themen zurück, bei denen die Regierung mit Kritik der Opposition oder aus den eignenen Reihen umgehen musste.

Hierbei berücksichtigt unsere Herangehensweise indes nicht, ob ein Thema negative Äußerungen nach sich zieht, weil es sich um einen negativen Sachverhalt (etwa eine Naturkatastrophe) handelt, der aber nicht automatisch kontrovers sein muss, oder ob Kritik an der Regierung geübt wird.

Etwas zu individuellen Unterschieden erfahren wir, wenn wir die Sprecher mit besonders positivem und negativem Sentiment nach Parteizugehörigkeit betrachten. Wieder berechnen wir hierzu eine gruppierte DFM.

meine.dfm.bundestag.sprecher <- dfm(korpus.bundestag, groups = "speaker_cleaned", dictionary = sentiment.lexikon.rauh)
meine.dfm.bundestag.sprecher.prop <- dfm_weight(meine.dfm.bundestag.sprecher, scheme = "prop")
head(meine.dfm.bundestag.sprecher.prop)
Document-feature matrix of: 6 documents, 2 features (16.7% sparse) and 19 docvars.
                           features
docs                         positive  negative
  (Alexander Dobrindt       0.5000000 0.5000000
  Abg. Dr. Johannes Fechner 0         0        
  Achim Post                0.6164818 0.3835182
  Agnieszka Brugger         0.5289470 0.4710530
  Albert Rupprecht          0.6961326 0.3038674
  Albert Stegemann          0.6164223 0.3835777

Nun ziehen wir noch die Parteizugehörigkeit aus den Metadaten hinzu, skalieren das Sentiment-Ergebnis, und sortieren schließelich die 637 Abgeordneten einmal absteigend und einmal aufsteigend (s.u.).

sentiment.bundestag.sprecher <- convert(meine.dfm.bundestag.sprecher, "data.frame") %>% 
  left_join(unique(select(korpus.bundestag.stats, speaker_cleaned, party)), by = c("doc_id" = "speaker_cleaned")) %>% 
  rename(Sprecher = doc_id, Partei = party) %>% 
  filter(positive != 0, negative != 0) %>% 
  gather(positive, negative, key = "Polarität", value = "Sentiment") %>% 
  filter(Polarität == "positive") %>% 
  mutate(Sentiment = rescale(Sentiment, to = c(-1,1))) %>% 
  select(Sentiment, Sprecher, Partei)
# Sprecher mit positivem Sentiment
arrange(sentiment.bundestag.sprecher, desc(Sentiment))
# Sprecher mit negativem Sentiment
arrange(sentiment.bundestag.sprecher, Sentiment)

Der damalige Bundestagspräsident Norbert Lammert führt die Positiv–Liste an, gefolgt von den Vizepräsidentinnen Claudia Roth, Ulla Schmidt, Edelgard Bulmahn und Petra Pau, dazwischen die Bundeskanzlerin Angela Merkel und der damalige Vizekanzler Sigmar Gabriel. Die hohen Werte für die Vertreter der wichtigsten parlamentarischen Ämter sind wenig überraschend. Bei den Negativwerten fällt auf, dass die Regierungsparteien CDU/CSU/SPD überraschend stark vertretend sind, auch wenn die Liste von Oppositionspolitikerinnen der Grünen bzw. Linken angeführt wird.

Eine wirklich adäquate Analyse müsste unter anderem hier noch Sprecher mit sehr geringen Redeanteilen ausschließen, da deren Sentiment-Score durch einige wenige Begriffe in die eine oder andere Richtung ausschlagen kann, auch wenn dies intuitiv keierlei Sinn ergibt. Und wie eingangs beschrieben muss man den Kontext einer Äußerung und die Zugehörigkeit zur Regierung bzw. Opposition heranziehen, was etwa durch die Untersuchung der TOPs grundsätzlich möglich wird.

Abschließend sollte festgehalten werden, dass Sentimentanalyse mittels Quanteda über aus Auszählen von Begriffen abläuft, allerdings auch leichte Variationen dieses Ansatzes existieren. so will man etwa für Tweets aber auch für einzelnen Sätzen in einem Debattenkorpus ein Sentiment festlegen, welches nicht allein aus der Addition von positiven/negativen Wörtern ergibt. Darauf wird später noch eingegangen, allerdings erreichen auch die bereits beschriebenen Verfahren insgesamt recht gute Ergebnisse, solange die Korpusgröße und die Genauigkeit des Lexikons ausreichend sind.

LS0tCnRpdGxlOiAiQXV0b21hdGlzaWVydGUgSW5oYWx0c2FuYWx5c2UgbWl0IFIiCmF1dGhvcjogIkNvcm5lbGl1cyBQdXNjaG1hbm4iCnN1YnRpdGxlOiBTZW50aW1lbnRhbmFseXNlCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjwhLS0tClRvZG9zCiogUmVkZGl0OiBkaXN0aW5jdCB3b3JkcyBmb3IgcG9zaXRpdmUvbmVnYXRpdmUvbmV1dHJhbAoqIHRhcHBseShyZWRkaXQuc2VudGltZW50LnNoYXJlJGNvbW1lbnRfc2NvcmUsIHJlZGRpdC5zZW50aW1lbnQuc2hhcmUkU2VudGltZW50LCBtZWFuKQoqIC4uLgotLT4KCldpZSBhdWNoIGVpbmlnZSBhbmRlcmUgaGllciBiZWhhbmRlbHRlIFZlcmZhaHJlbiBoYXQgZGllIFtTZW50aW1lbnRhbmFseXNlXShodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9TZW50aW1lbnRhbmFseXNlKSBpaHJlIFd1cnplbG4gaW4gZGVyIENvbXB1dGVybGluZ3Vpc3RpayB1bmQgSW5mb3JtYXRpaywgd2lyZCBhYmVyIHNlaXQgZWluaWdlbiBKYWhyZW4gYXVjaCB6dW5laG1lbmQgaW4gZGVuIFNvemlhbHdpc3NlbnNjaGFmdGVuIGFuZ2V3YW5kdCwgdW0gZ2FueiB1bnRlcnNjaGllZGxpY2hlIFRleHRlIGF1dG9tYXRpc2NoIHp1IGtsYXNzaWZpemllcmVuLCBldHdhIFBhcmxhbWVudHNkZWJhdHRlbiwgRnJlaXRleHRhbnR3b3J0ZW4gaW4gQmVmcmFndW5nZW4sIG9kZXIgU29jaWFsIE1lZGlhLURpc2t1cnNlLiBaaWVsIGRlciBTZW50aW1lbnRhbmFseXNlIGlzdCBkaWUgQmVzdGltbXVuZyBkZXIgUG9sYXJpdMOkdCBlaW5lcyBUZXh0ZXMsIHdvbWl0IGdlbWVpbnQgaXN0LCBvYiBkaWUgZGFyaW4genVtIEF1c2RydWNrIGdlYnJhY2h0ZW4gRW1vdGlvbmVuIGVoZXIgcG9zaXRpdiBvZGVyIG5lZ2F0aXYgc2luZC4gRGllcyBnZXNjaGllaHQgaMOkdWZpZyBkdXJjaCBXb3J0bGlzdGVuIHVuZCDDvGJlciBkYXMgQXVzesOkaGxlbiB2b24gQmVncmlmZmVuLCBkaWUgenV2b3IgZGVuIEthdGVnb3JpZW4gKnBvc2l0aXYqIG9kZXIgKm5lZ2F0aXYqIHp1Z2VvcmRuZXQgd3VyZGVuLiBJbiB2aWVsZW4gVmVyZmFocmVuIHdpcmQgZGFzIFJlc3VsdGF0IGFuYWNobGllw59lbmQgc2thbGllcnQsIG9kZXIgZXMgd2VyZGVuIGFuZGVyZSBTY2hyaXR0ZSBhbmdld2FuZHQsIHVtIGZlaGxlcmhhZnRlIEtsYXNzaWZpemllcnVuZ2VuIHp1IHZlcm1laWRlbi4gRGllc2UgdHJldGVuIHZvciBhbGxlbSBkYW5uIGF1Ziwgd2VubiBOZWdhdGlvbiBvZGVyIElyb25pZSB2ZXJ3ZW5kZXQgd2VyZGVuLCBhYmVyIGF1Y2gsIHdlbm4gZGVyIEdlZ2Vuc3RhbmQgZGVyIHBvc2l0aXZlbiBvZGVyIG5lZ2F0aXZlbiBBdXNkcsO8Y2tlIHdlY2hzZWx0IG9kZXIgdW5rbGFyIGlzdC4gVW5zcHLDvG5nbGljaCB3dXJkZSBkaWUgU2VudGltZW50YW5hbHlzZSBhdWYgUHJvZHVrdGJld2VydHVuZ2VuIGF1ZiBFLUNvbW1lcmVuY2UtUGxhdHRmb3JtZW4gd2llIEFtYXpvbi5jb20gZ2V0ZXN0ZXQsIHdvIGRpZXNlIFByb2JsZW1lIGVpbmUgcmVsYXRpdiBnZXJpbmdlIFJvbGxlIHNwaWVsZW4uIEJlaSBQcmVzc2V0ZXh0ZW4gb2RlciBEaXNrdXJzZW4gaW4gZGVuIHNvemlhbGVuIE1lZGllbiBoaW5nZWdlbiwgaXN0IG9mdCBzY2h3ZXJlciB6dSBiZXdlcnRlbiwgYXVmIHdhcyBzaWNoIGVpbmUgU2VudGltZW50YmV3ZXJ0dW5nIGJlemllaHQsIG9kZXIgd2VsY2hlcyBTZW50aW1lbm5pdmVhdSBldHdhIGFscyDigJhub3JtYWzigJkgYmV0cmFjaHRldCB3ZXJkZW4gc29sbHRlLiBTbyBrb21tZW4gYmVpc3BpZWxzd2Vpc2UgaW4gUHJlc3NldGV4dGVuIGdlbmVyZWxsIHdlbmlnIEVtb3Rpb25lbiB6dW0gQXVzZHJ1Y2ssIHVuZCBkaWUgbmVnYXRpdmVuIEJlZ3JpZmZlIMO8YmVyd2llZ2VuIGjDpHVmaWcsIG9obmUgZGFzcyBkaWVzIG5vdHdlbmRpZ2Vyd2Vpc2UgYXVmIGVpbmVuIHNjaGxlY2h0ZW4gWnVzdGFuZCBkZXIgV2VsdCB6dXLDvGNrenVmw7xocmVuIHfDpHJlLiBTY2hsaWXDn2xpY2ggc29sbHRlIG1hbiBzaWNoIHZvciBBdWdlbiBmw7xocmVuLCBkYXNzIGRpZSBTZW50aW1lbnRhbmFseXNlIGVpbiBoZXVyaXN0aXNjaGVzIFZlcmZhaHJlbiBpc3QsIGRhc3MgaW1tZXIgYXVjaCBmZWhsZXJoYWZ0ZSBFaW56ZWxrbGFzc2lmaWthdGlvbmVuIHByb2R1emllcnQsIHdhcyBhYmVyIGlkZWFsZXJ3ZWlzZSBuaWNodCB6dSBzdGFyayBpbnMgR2V3aWNodCBmw6RsbHQsIHdlbm4gbWFuIGV0d2FzIFZlcsOkbmRlcnVuZ2VuIGltIFNlbnRpbWVudHZlcmxhdWYgw7xiZXIgZGllIFplaXQgdW50ZXJzdWNodC4KCldhcyBkaWUgdGVjaG5pc2NoZSBVbXNldHp1bmcgYW5nZWh0LCBzbyBnZWjDtnJlbiBkaWVzZXIgQWJzY2huaXR0IHVuZCBkYXMgbsOkY2hzdGUgenUgc3BlemlhbGlzaWVydGVuIExleGlrYSBpbnNvZmVybiB6dXNhbW1lbiwgYWxzIGRhc3MgZXMgc2ljaCBiZWkgYmVpZGVuIEFuc8OkdHplbiB1bSBnYW56IMOkaG5saWNoZSBWZXJmYWhyZW4gaGFuZGVsdC4gSW4gYmVpZGVuIEbDpGxsZW4gd2lyZCBlaW4gTGV4aWtvbiAoJ3NlbnRpbWVudC90b3BpYyBkaWN0aW9uYXJ5JykgdmVyd2VuZGV0LCB1bSBlaW5lIFJlaWhlIHZvbiBFaW56ZWxiZWdyaWZmZW4gaW4gZWluZXIgS2F0ZWdvcmllIHp1c2FtbWVuenVmYXNzZW4uIAoKV2lyIHZlcndlbmRlbiBpbiBkaWVzZW0gS2FwaXRlbCBzZWNocyB1bnRlcnNjaGllZGxpY2hlIFNlbnRpbWVudGxleGlrYSwgZGF2b24gdmllciBpbiBlbmdsaXNjaGVyIFNwcmFjaGUgdW5kIHp3ZWkgZsO8ciBEZXV0c2NoOgoKKiBbQmluZyBMaXUgU2VudGltZW50IExleGljb25dKGh0dHBzOi8vd3d3LmNzLnVpYy5lZHUvfmxpdWIvRkJTL3NlbnRpbWVudC1hbmFseXNpcy5odG1sI2xleGljb24pCiogW05SQyBFbW90aW9uIExleGljb25dKGh0dHA6Ly9zYWlmbW9oYW1tYWQuY29tL1dlYlBhZ2VzL05SQy1FbW90aW9uLUxleGljb24uaHRtKQoqIFtBRklOTl0oaHR0cDovL3d3dzIuaW1tLmR0dS5kay9wdWJkYi92aWV3cy9wdWJsaWNhdGlvbl9kZXRhaWxzLnBocD9pZD02MDEwKQoqIFtMZXhpY29kZXIgU2VudGltZW50IERpY3Rpb25hcnldKGh0dHBzOi8vcXVhbnRlZGEuaW8vcmVmZXJlbmNlL2RhdGFfZGljdGlvbmFyeV9MU0QyMDE1Lmh0bWwpCiogW1NlbnRpV1NdKGh0dHA6Ly93b3J0c2NoYXR6LnVuaS1sZWlwemlnLmRlL2RlL2Rvd25sb2FkI3NlbnRpV1NEb3dubG9hZCkKKiBbQ2hyaXN0aWFuIFJhdWgncyBTZW50aW1lbnQgRGljdGlvbmFyeV0oaHR0cHM6Ly9kYXRhdmVyc2UuaGFydmFyZC5lZHUvZGF0YXNldC54aHRtbD9wZXJzaXN0ZW50SWQ9ZG9pOjEwLjc5MTAvRFZOL0JLQlhXRCkKCkRpZXNlIExleGlrYSBzaW5kIGxlZGlnbGljaCBMaXN0ZW4gdm9uIFfDtnJ0ZXJuLCB3ZWxjaGUgd2llIG9iZW4gYmVzY2hyaWViZW4gZGVuIEthdGVnb3JpZW4gKnBvc3Rpdiogb2RlciAqbmVnYXRpdiogenVnZW9yZG5ldCBzaW5kLiBadW0gVGVpbCBleGlzdGllcnQgYXVjaCBub2NoIGVpbmUgZHJpdHRlIEthdGVnb3JpZSAqbmV1dHJhbCosIGRlc3dlaXRlcmVuIGvDtm5uZW4gQmVncmlmZmUgYXVjaCBtZWhyZXJlbiBLYXRlZ29yaWVuIHp1Z2VvcmRuZXQgc2Vpbiwgb2RlciBuZWJlbiBlaW5lciBadW9yZG51bmcgZGVyIFBvbGFyaXTDpHQgYXVjaCBub2NoIGVpbmUgU2VudGltZW50c3TDpHJrZSB6dWdlc2NocmllYmVuIGJla29tbWVuLiBEaWUgaGllciB2b3JnZXN0ZWxsdGUgVGVjaG5payBpc3QgdmVyZ2xlaWNoc3dlaXNlIHByaW1pdGl2LCB3ZWlsIHNpZSBsZWRpZ2xpY2ggV8O2cnRlciBhdXN6w6RobHQsIGFsbGVyZGluZ3MgbGFzc2VuIHNpY2ggZGllIFZlcmZhaHJlbiBsZWljaHQgbm9jaCB2ZXJmZWluZXJuICh2Z2wuIGJzcHcuIFtkaWVzZW4gQmVpdHJhZyB2b24gQ2hyaXN0aWFuIFJhdWhdKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9kb2kvZnVsbC8xMC4xMDgwLzE5MzMxNjgxLjIwMTguMTQ4NTYwOCkgenVyIFZhbGlkaWVydW5nIHBvbGl0aXNjaGVyIFNlbnRpbWVudC1MZXhpa2EpLiBBdWNoIFZlcmZhaHJlbiBkaWUgZ2V3aWNodGVuIG9kZXIgYW5kZXJlIEtuaWZmZSBmw7xyIGRpZSBWZXJyaW5nZXJ1bmcgZGVyIEZlaGxlcnJhdGUgZWluc2V0emVuLCBmdW5rdGlvbmllcmVuIHNvIOKAlCBkaWUgU2VudGltZW50YW5hbHllIGlzdCBlZmZla3RpdiwgYWJlciBhdWNoIGFsbGVzIGFuZGVyZSBhbHMgSGV4ZW53ZXJrLgoKRGllc2UgTGV4aWthIHdlbmRlbiB3aXIgZm9sZ2VuZCBhdWYgZsO8bmYgRGF0ZW5zw6R0emUgYW46IGRhcyBiZXJlaXRzIGJla2FubnRlIFNoZXJsb2NrIEhvbG1lc+KAlEtvcnB1cywgZWluZW4gRGF0ZW5zYXR6IGF1cyBUd2VldHMgdm9uIERvbmFsZCBUcnVtcCB1bmQgSGlsbGFyeSBDbGludGVuLCBlaW5lbiBLb21tZW50YXItS29ycHVzIGF1cyBkZXIgRGlza3Vzc2lvbnNwbGF0dGZvcm0gUmVkZGl0LCBlaW4gS29ycHVzIFNjaHdlaXplciBUYWdlc3plaXR1Z2VuIG1pdCBBcnRpa2VsbiB6dXIgRmluYW56a3Jpc2UsIGRpZSB6d2lzY2hlbiAyMDA3IHVuZCAyMDEyIHZlcmZhc3N0IHd1cmRlbiwgdW5kIHNjaGxpZcOfbGljaCBub2NoIGVpbmVuIERlYmF0dGVua29ycHVzIGRlcyAxOC4gRGV1dHNjaGVuIEJ1bmRlc3RhZ3MgKDIwMTMgYmlzIDIwMTcpLiBBdWYgZGllIFp1c2FtbWVuc3RlbGx1bmcgZGVyIEtvcnBvcmEgZ2VoZW4gd2lyIHNww6R0ZXIgbm9jaCBlaW4uCgojIyMgSW5zdGFsbGF0aW9uIHVuZCBMYWRlbiBkZXIgYmVuw7Z0aWd0ZW4gUi1CaWJsaW90aGVrZW4sIExhZGVuIGRlcyBLb3JwdXMKClp1bsOkY2hzdCB3ZXJkZW4gd2llZGVyIGRpZSBub3R3ZW5kaWdlbiBCaWJsaW90aGVrZW4gZ2VsYWRlbi4gTmV1IGlzdCBkaWUgQmlibGlvdGhlayBbc2NhbGVzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9wYWNrYWdlPXNjYWxlcykgZGllIGJlaSBkZXIgTm9ybWFsaXNpZXJ1bmcgdm9uIFNlbnRpbWVudOKAlFNjb3JlcyB6dW0gRWluc2F0eiBrb21tdC4gRGFubiB3aXJkIGluIGVpbmVtIHp3ZWl0ZW4gU2Nocml0dCBkYXMgU2hlcmxvY2stS29ycHVzIGdlbGFkZW4sIHdlbGNoZXMgd2lyIGphIGJlcmVpdHMgenV2b3IgbmVic3QgTWV0YWRhdGVuIGltIFJEYXRhLUZvcm1hdCBnZXNwZWljaGVydCBoYWJlbi4KCmBgYHtyIEluc3RhbGxhdGlvbiB1bmQgTGFkZW4gZGVyIGJlbsO2dGlndGVuIFItQmlibGlvdGhla2VuLCBtZXNzYWdlID0gRkFMU0V9CmlmKCFyZXF1aXJlKCJxdWFudGVkYSIpKSB7aW5zdGFsbC5wYWNrYWdlcygicXVhbnRlZGEiKTsgbGlicmFyeSgicXVhbnRlZGEiKX0KaWYoIXJlcXVpcmUoInJlYWR0ZXh0IikpIHtpbnN0YWxsLnBhY2thZ2VzKCJyZWFkdGV4dCIpOyBsaWJyYXJ5KCJyZWFkdGV4dCIpfQppZighcmVxdWlyZSgidGlkeXZlcnNlIikpIHtpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKTsgbGlicmFyeSgidGlkeXZlcnNlIil9CmlmKCFyZXF1aXJlKCJzY2FsZXMiKSkge2luc3RhbGwucGFja2FnZXMoInNjYWxlcyIpOyBsaWJyYXJ5KCJzY2FsZXMiKX0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgpgYGB7ciBMYWRlbiBkZXMgU2hlcmxvY2sgSG9sbWVzLUtvcnB1c30KbG9hZCgiZGF0ZW4vc2hlcmxvY2svc2hlcmxvY2sua29ycHVzLlJEYXRhIikKYGBgCgojIyMgRXJzdGVsbHVuZyBlaW5lcyBMZXhpa29ucyBpbiBxdWFudGVkYQoKV2lyIGJlZ2lubmVuIHp1bsOkY2hzdCBtaXQgZWluZXIgU2VudGltZW50YW5hbHlzZSBkZXIgU2hlcmxvY2sgSG9sbWVzLUVyesOkaGx1bmdlbiwgdW0gYmVpIGVpbmVtIGJlcmVpdHMgYXVzIEthcGl0ZWwgMSB1bmQgMiB2ZXJ0cmF1dGVuIEtvcnB1cyB6dSBibGVpYmVuLiBJbiBlaW5lbSBlcnN0ZW4gU2Nocml0dCBlcnN0ZWxsZW4gd2lyIGVpbiBzZWhyIGVpbmZhY2hlcyBBZCBob2MtTGV4aWtvbiBhdXMgbnVyIHNlY2hzIEJlZ3JpZmZlbiwgdW0gZGllIFN0cnVrdHVyIGVpbmVzIExleGlrb25zIGluIHF1YW50ZWRhIHp1IGlsbHVzdHJpZXJlbi4gRGllcyBnZXNjaGllaHQgbWl0IGRlbSBxdWFudGVkYS1CZWZlaGwgW2RpY3Rpb25hcnldKGh0dHA6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS9kaWN0aW9uYXJ5Lmh0bWwpLiBEaWN0aW9uYXJ5KCkgYWt6ZXB0aWVydCBlaW5lIFJlaWhlIHZvbiBTdGFuZGFyZGZvcm1hdGVuIChkYXp1IHNww6R0ZXIgbm9jaCBtZWhyKSwgYWJlciBhdWNoIFZla3RvcmVuLCB3ZWxjaGUgZGllIEJlZ3JpZmZlIGVudGhhbHRlbiwgZGllIGVpbmUgYWJzdHJha3RlIEthdGVnb3JpZSBvcGVyYXRpb25hbGlzaWVyZW4uIEJlbGllYmlnIHZpZWxlIEthdGVnb3JpZW4ga8O2bm5lbiBzbyBkZWZpbmllcnQgdW5kIGRhbm4gbWl0IHRhdXNlbmRlbiB2b24gQmVncmlmZmVuICdiZWbDvGxsdCcgd2VyZGVuLiBBdWNoIEthdGVnb3JpZW4gbWl0IG1laHJlcmVuIGhpZXJhcmNoaXNjaGVuIEViZW5lbiBzaW5kIG3DtmdsaWNoIOKAlCBkYXp1IGltIG7DpGNoc3RlbiBLYXBpdGVsIG5vY2ggZXR3YXMgbWVoci4KCmBgYHtyIEVyc3RlbGx1bmcgZWluZXMgVGV4dGxleGlrb25zfQp0ZXN0LmxleGlrb24gPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXRpdmUuYmVncmlmZmUgPSBjKCJnbMO8Y2siLCAiZnJldWRlIiwgImxpY2h0IiksIG5lZ2F0aXZlLmJlZ3JpZmZlID0gYygidHJhdWVyIiwgInd1dCIsICJkdW5rZWxoZWl0IikpKQp0ZXN0LmxleGlrb24KYGBgCgojIyMgRXJzdGUgU2VudGltZW50LUFuYWx5c2UgbWl0IGRlbSBTaGVybG9jayBIb2xtZXMtS29ycHVzCgpNaXQgZGllc2VtIExleGlrb24ga8O2bm5lbiB3aXIgbWl0IHVuc2VyZW0gZW5nbGlzY2hzcHJhY2hpZ2VuIEtvcnB1cyB3ZW5pZyBrb25rcmV0ZXMgYW5mYW5nZW4sIGRhaGVyIHdlY2hzZWxuIHdpciBiZXNzZXIgenUgZWluZW0gZWNodGVuIFNlbnRpbWVudGxleGlrb24uIEluIGVpbmVtIHp3ZWl0ZW4gU2Nocml0dCBsZXNlbiB3aXIgbWl0IGRlbSBCZWZlaGwgW3NjYW5dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9iYXNlL3ZlcnNpb25zLzMuNS4xL3RvcGljcy9zY2FuKSBkYXMgW0JpbmcgTGl1IFNlbnRpbWVudCBMZXhpa29uXShodHRwczovL3d3dy5jcy51aWMuZWR1L35saXViL0ZCUy9zZW50aW1lbnQtYW5hbHlzaXMuaHRtbCNsZXhpY29uKSBpbiBSIGVpbi4gRGllc2VzIExleGlrb24gdW1mYXNzdCDDvGJlciA2LjcwMCBlbmdsaXNjaHNwcmFjaGlnZSBCZWdyaWZmZSBkaWUgaW4gendlaSBlaW5mYWNoZW5uIFRleHRkYXRlaWVuIGFiZ2VsZWd0IHNpbmQsIGRpZSBqZXdlaWxzIGVpbiBXb3J0IGplIFplaWxlIGVudGhhbHRlbi4gV2lyIMO8YmVyc3ByaW5nZW4gbWl0IGRlbSBBcmd1bWVudCAqc2tpcCogZGllIGVyc3RlbiAzNSBaZWlsZW4sIGRhIGRpZXNlIE1ldGFpbmZvcm1hdGlvbmVuIMO8YmVyIGRhcyBMZXhpa29uIGVudGhhbHRlbi4gRGFzIEFyZ3VtZW50ICpxdWlldCogdmVyaGluZGV0IGRpZSBBdXNnYWJlIGVpbmVyIFN0YXR1c21lbGR1bmcuIAoKYGBge3IgRWlubGVzZW4gZGVzIEJpbmcgTGl1LUxleGlrb25zfQpwb3NpdGl2ZS53b2VydGVyLmJsIDwtIHNjYW4oImxleGlrYS9iaW5nbGl1LXBvc2l0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKQpuZWdhdGl2ZS53b2VydGVyLmJsIDwtIHNjYW4oImxleGlrYS9iaW5nbGl1LW5lZ2F0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKQpgYGAKCk51biBlcnN0ZWxsZW4gd2lyIGRhcyBMZXhpa29uIG1pdGhpbGZlIGRlciBnZXJhZGUgZWluZ2VsZXNlbmVuIFRleHR2ZWt0b3Jlbi4gRGllcyBlcmZvbGd0IHdpZWRlciBtaXQgZGVyIEZ1bmt0aW9uIGRpY3Rpb25hcnkoKSwgZGllc21hbCBtaXQgZGVuIGdlcmFkZSBlaW5nZWxlc2VuZW4gVmVrdG9yZW4gYWxzIEFyZ3VtZW50LgoKYGBge3IgR2VuZXJpZXJ1bmcgZWluZXMgTGV4aWtvbi1PYmpla3RzIGR1cmNoIGRpZSBlaW5nZWxlc2VuZW4gVGV4dHZla3RvcmVufQpzZW50aW1lbnQubGV4aWtvbiA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBwb3NpdGl2ZS53b2VydGVyLmJsLCBuZWdhdGl2ZSA9IG5lZ2F0aXZlLndvZXJ0ZXIuYmwpKQpzdHIoc2VudGltZW50LmxleGlrb24pCmBgYAoKV2llIG1hbiBzaWVodCwgc2luZCBudW4gbWVocmVyZSB0YXVzZW5kIEJlZ3JpZmZlIGRlbiBiZWlkZW4gS2F0ZWdvcmllbiBkZXMgTGV4aWtvbnMgenVnZW9yZG5ldCB3b3JkZW4uIEpldHp0IGvDtm5uZW4gd2lyIGVpbmUgREZNIGJlcmVjaG5lbiwgd2VsY2hlIGRhcyBlcnN0ZWxsdGUgTGV4aWtvbiBhdWYgZGFzIEtvcnB1cyBhbndlbmRldC4KCmBgYHtyIEVyc3RlbGx1bmcgZWluZXIgREZNIHVudGVyIEFud2VuZHVuZyBkZXMgTGV4aWtvbnN9Cm1laW5lLmRmbS5zZW50aW1lbnQgPC0gZGZtKGtvcnB1cywgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uKQptZWluZS5kZm0uc2VudGltZW50CmBgYAoKV2FzIGlzdCBnZXNjaGVoZW4/ICpBbGxlKiB0YXRzw6RjaGxpY2ggdm9ya29tbWVuZGVuIE5lbm51bmdlbiBkZXIgcnVuZCA2LjcwMCBpbSBCaW5nIExpdS1MZXhpa29uIGVudGhhbHRlbmVuIEJlZ3JpZmZlIGluIGRlbiB6d8O2bGYgU2hlcmxvY2sgSG9sbWVz4oCUUm9tYW5lbiBzaW5kIGpld2VpbHMgZHVyY2ggZGllIGlobmVuIHp1Z2VvcmRuZXRlIEthdGVnb3JpZSBlcnNldHp0IHdvcmRlbi4gU8OkbXRsaWNoZSBCZWdyaWZmZSwgZGllIG5pY2h0IGltIExleGlrb24gdm9ya29tbWVuLCBmYWxsZW4gZGFiZWkgZWluZmFjaCB3ZWcuIERhZHVyY2ggYmxlaWJ0IGVpbmUgVGFiZWxsZSB6dXLDvGNrLCBkaWUgbnVyIG5vY2ggendlaSBTcGFsdGVuIGVudGjDpGx0IOKAlCBkaWUgU3VtbWUgYWxsZXIgKnBvc2l0aXZlbiogdW5kICpuZWdhdGl2ZW4qIEJlZ3JpZmZlIHBybyBSb21hbi4gV2lyIHdlcmRlbiBkYXJhdWYgc3DDpHRlciBub2NoIGltIERldGFpbCB6dSBzcHJlY2hlbiBrb21tZW4sIGFiZXIgdmllbGxlaWNodCBoYWJlbiBTaWUgc2Nob24gYmVtZXJrdCwgZGFzcyBtaXR0ZWxzICpkaWN0aW9uYXJ5KiBkaWUgU3BhbHRlbiBlaW5lciBERk0genVzYW1tZW5nZWZhc3N0IHdlcmRlbiAoYWxzbyBkaWUgV8O2cnRlciksIHfDpGhyZW5kIGRhcyBBcmd1bWVudCAqZ3JvdXAqIGRlciBGdW5rdGlvbiBkZm0oKSBkaWUgWmVpbGVuIHp1c2FtbWVuZmFzc3QgKGFsc28gZGllIFRleHRlKS4gRGllc2UgZGltZW5zaW9uYWxlIFJlZHV6aWVydW5nIGdlaMO2cnQgenUgZGVuIG7DvHR6bGljaHN0ZW4gRWlnZW5zY2hhZnRlbiB2b24gcXVhbnRlZGEuICAKCkRhcyBmb2xnZW5kZSBQbG90IHplaWd0IGRpZSBTZW50aW1lbnQtVmVydGVpbHVuZyBpbiBkZW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1FcnrDpGhsdW5nZW4uIAoKYGBge3IgU2VudGltZW50LVNjb3JlcyBpbSBTaGVybG9jayBIb2xtZXMtS29ycHVzIHBsb3R0ZW59CnNlbnRpbWVudCA8LSBjb252ZXJ0KG1laW5lLmRmbS5zZW50aW1lbnQsICJkYXRhLmZyYW1lIikgJT4lCiAgZ2F0aGVyKHBvc2l0aXZlLCBuZWdhdGl2ZSwga2V5ID0gIlBvbGFyaXTDpHQiLCB2YWx1ZSA9ICJXw7ZydGVyIikgJT4lIAogIG11dGF0ZShkb2NfaWQgPSBhc19mYWN0b3IoZG9jX2lkKSkgJT4lIAogIHJlbmFtZShSb21hbiA9IGRvY19pZCkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBXw7ZydGVyLCBjb2xvdXIgPSBQb2xhcml0w6R0LCBncm91cCA9IFBvbGFyaXTDpHQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlNlbnRpbWVudC1TY29yZXMgaW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1Sb21hbmVuIikKYGBgCgojIyMgR2V3aWNodHVuZyB2b24gU2VudGltZW50LVNjb3JlcwoKRXJpbm5lcnQgbWFuIHNpY2ggYW4gZGllIGltIHZvcmhlcmlnZW4gS2FwaXRlbCBhZHJlc3NpZXJ0ZW4gUHJvYmxlbWUgYWJzb2x1dGVyIFdvcnRmcmVxdWVuemVuLCBzbyBtw7ZjaHRlIG1hbiB2aWVsbGVpY2h0IGxpZWJlciByZWxhdGl2ZSBGcmVxdWVuemVuIGJlcmVjaG5lbi4gRGFzIGJlZGV1dGV0IGJlaW0gRWluc2F0eiB2b24gTGV4aWthIGluIGRlciBSZWdlbCBuaWNodCBudXIsIGRhc3MgbWFuIGRhcyBBdWZ0cmV0ZW4gZGVyIExleGlrb24tQmVncmlmZmUgcmVsYXRpdiB6dXIgR2VzYW10d29ydGZyZXF1ZW56IG1pc3N0LCBzb25kZXJuIGRlcmVuIEFudGVpbCByZWxhdGl2IHp1IGVpbmFuZGVyIChhbHNvIGRhcyBWZXJow6RsdG5pcyBwb3NpdGl2ZXIgdW5kIG5lZ2F0aXZlciBCZWdyaWZmZSkuIERpZXMgaGF0IGRlbiBWb3J0ZWlsLCBkYXNzIG1hbiBkaWUgZ3Jvw59lIFphaGwgYWxsZXIgQmVncmlmZmUsIGRpZSB3ZWRlciBwb3NpdGl2IG5vY2ggbmVnYXRpdiBzaW5kLCB1bmJlcsO8Y2tzaWNodGlndCBsYXNzZW4ga2Fubiwgd2FzIGR1cmNoYXVzIFNpbm4gZXJnaWJ0LCB3ZW5uIG1hbiBzaWNoIGViZW4gbnVyIGbDvHIgZGFzIFNlbnRpbWVudCBpbnRlcmVzc2llcnQuCgpEYXMgZm9sZ2VuZGVuIEJlaXNwaWVsIHZlcmRldXRsaWNodCBkaWVzZXMgVm9yZ2VoZW4uIAoKYGBge3IgREZNIG1pdCBTZW50aW1lbnQtU2NvcmVzIGdld2ljaHRlbn0KbWVpbmUuZGZtLnNlbnRpbWVudC5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLnNlbnRpbWVudCwgc2NoZW1lID0gInByb3AiKQptZWluZS5kZm0uc2VudGltZW50LnByb3AKYGBgCgpBdWNoIGRpZXNlIERGTSBsw6Rzc3Qgc2ljaCBuYXTDvHJsaWNoIGxlaWNodCBwbG90dGVuLiAKCmBgYHtyIFJlbGF0aXZlIFNlbnRpbWVudC1TY29yZXMgcGxvdHRlbn0Kc2VudGltZW50IDwtIGNvbnZlcnQobWVpbmUuZGZtLnNlbnRpbWVudC5wcm9wLCAiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIG11dGF0ZShkb2NfaWQgPSBhc19mYWN0b3IoZG9jX2lkKSkgJT4lIAogIHJlbmFtZShSb21hbiA9IGRvY19pZCkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBTZW50aW1lbnQsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0ID0gMSkpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBpbiB6d8O2bGYgU2hlcmxvY2sgSG9sbWVzLVJvbWFuZW4gKHJlbGF0aXYpIikKYGBgCgoKIyMjIFZlcnJlY2hudW5nIHVuZCBTa2FsaWVydW5nIHBvc2l0aXZlciB1bmQgbmVnYXRpdmVyIFNlbnRpbWVudC1BbnRlaWxlCgpEaWUgRGFyc3RlbGx1bmcgdm9uIFNlbnRpbWVudGFudGVpbGVuIGlubmVyaGFsYiBkZXIgenfDtmxmIEVyesOkaGx1bmdlbiBsw6Rzc3Qgc2ljaCBub2NoIHZlcmJlc3Nlcm4sIGluZGVtIHdpciBkYXJhdWYgdmVyemljaHRlbiwgYmVpZGUgUG9sYXJpdMOkdGVuIGRhcnN6dXN0ZWxsZW4uIERhIGJlaSBkaWVzZXIgQW53ZW5kdW5nZW4gZGllIG5lZ2F0aXZlIFBvbGFyaXTDpHQgc2NobGljaHQgZGllIEludmVydGllcnVuZyBkZXMgcG9zaXRpdmVuIFNlbnRpbWVudHMgZXJnaWJ0LCByZWljaHQgZGllcyBhdXMuIFp1ZGVtIHNrYWxpZXJlbiB3aXIgZGllIFdlcnRlIG1pdHRlbHMgW3Jlc2NhbGVdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9zY2FsZXMvdmVyc2lvbnMvMC40LjEvdG9waWNzL3Jlc2NhbGUpIG5ldSwgc28gZGFzcyBzaWUgendpc2NoZW4g4oCTMSB1bmQgKzEgbGllZ2VuLgoKYGBge3IgVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBwbG90dGVufQpzZW50aW1lbnQgPC0gY29udmVydChtZWluZS5kZm0uc2VudGltZW50LnByb3AsICJkYXRhLmZyYW1lIikgJT4lCiAgcmVuYW1lKFJvbWFuID0gZG9jX2lkLCBTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgc2VsZWN0KFJvbWFuLCBTZW50aW1lbnQpICU+JQogIG11dGF0ZShTZW50aW1lbnQgPSByZXNjYWxlKFNlbnRpbWVudCwgdG8gPSBjKC0xLDEpKSkgJT4lCiAgbXV0YXRlKFJvbWFuID0gYXNfZmFjdG9yKFJvbWFuKSkKZ2dwbG90KHNlbnRpbWVudCwgYWVzKFJvbWFuLCBTZW50aW1lbnQsIGdyb3VwID0gMSkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImxpZ2h0Z3JheSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgaW4genfDtmxmIFNoZXJsb2NrIEhvbG1lcy1Sb21hbmVuIikKYGBgCgpXaXIgaGFsdGVuIGZlc3Q6IERpZSBSb21hbmUgenUgQmVnaW5uIGRlcyBTaGVybG9jayBIb2xtZXMtWnlsdXMgc2luZCBldHdhcyBwb3NpdGl2ZXIsIHfDpGhyZW5kIGVzIGluIGRlciBNaXR0ZSBkw7xzdGVyZXIgenVnZWh0LiBadW0gU2NobHVzcyBoZWJ0IHNpY2ggZGllIFN0aW1tdW5nIGFiZXIgLSBqZWRlbmZhbGxzIGltIFZlcmdsZWljaCAtIHdpZWRlci4gV2VubiBtYW4gZGllc2UgRGFyc3RlbGx1bmcgbWl0IGRlbSBlcnN0ZW4gUGxvdCBrb250cmFzdGllcnQgd2lyZCBrbGFyLCB3YXJ1bSB2ZXJyZWNobmV0ZSBGcmVxdWVuemVuIG9mdG1hbHMgZWluZW4gZ3V0ZW4gQW5zYXR6IGRhcnN0ZWxsZW4uIEFuZGVyZXJzZWl0cyBkYXJmIG1hbiBhYmVyIGF1Y2ggbmljaHQgZGVyIEFubmFobWUgYXVmIGRlbiBMZWltIGdlaGVuLCBkYXMgU2VudGltZW50IGluICdUaGUgQWR2ZW50dXJlIG9mIHRoZSBTcGVja2xlZCBCYW5kJyBzZWkgYXVzc2NobGllw59saWNoIG5lZ2F0aXYsIGFsc28gJ3p1IDAlIHBvc2l0aXYnLCBkZW5uIGRpZXMgaXN0IGVpbiBBcnRlZmFrdCB1bnNlcmVyIHByb3BvcnRpb25hbGVuIFNrYWxpZXJ1bmcuIEVzIGlzdCBsZWRpZ2xpY2ggKmFudGVpbGlnIG5lZ2F0aXZlciogYWxzIGluIGRlbiBhbmRlcmVuIGVsZiBFcnrDpGhsdW5nZW4uIAoKIyMjIFNlbnRpbWVudGFuYWx5c2UgbWl0IFR3aXR0ZXItRGF0ZW4gdm9uIERvbmFsZCBUcnVtcCB1bmQgSGlsbGFyeSBDbGludG9uCgpXZW5kZW4gd2lyIHVucyBqZXR6dCBlaW5lbSBldHdhcyBha3R1ZWxsZXJlbiBCZWlzcGllbCB6dSwgbsOkbWxpY2ggZGVyIEFuYWx5c2UgZGVzIFNlbnRpbWVudHMgaW4gZGVuIFR3ZWV0cyB2b24gRG9uYWxkIFRydW1wIHVuZCBIaWxsYXJ5IENsaW50b24gdm9yLCB3w6RocmVuZCwgdW5kIG5hY2ggZGVtIFVTLVByw6RzaWRlbnRzY2hhZnRzd2FobGthbXBmIHZvbiAyMDE2LgoKWnVuw6RjaHN0IGxhZGVuIHdpZSBkaWUgVHJ1bXAtIHVuZCBDbGludG9uLVR3aXR0ZXItRGF0ZW5zw6R0emUgKGJlcmVpdHMgaW4gZWluZW0gS29ycHVzLU9iamVrdCB6dXNhbW1lbmdlZmFzc3QgdW5kIGFscyBSRGF0YS1GaWxlIGdlc3BlaWNoZXJ0KS4gRGllc2UgRGF0ZW4gd3VyZGVuIGF1cyB2ZXJzY2hpZWRlbmVuIE9ubGluZeKAk0FyY2hpdmVuIHVuZCBkdXJjaCBkaWUgVHdpdHRlciBBUEkgenVzYW1tZW5nZXN0ZWxsdC4gSWNoIGdlaGUgaGllciBuaWNodCBnZW5hdWVyIGF1ZiBkaWUgRXJzdGVsbHVuZyBkZXMgS29ycHVzIGVpbiwgZGllcyB3aXJkIGFiZXIgc3DDpHRlciBub2NoIGVybMOkdXRlcnQuCgpgYGB7ciBUd2l0dGVyLUtvcnB1cyBsYWRlbn0KbG9hZCgiZGF0ZW4vdHdpdHRlci90cnVtcGNsaW50b24ua29ycHVzLlJEYXRhIikKa29ycHVzLnN0YXRzLm1vbmF0IDwtIHVuZ3JvdXAoa29ycHVzLnN0YXRzLm1vbmF0KQprb3JwdXMuc3RhdHMubW9uYXQKYGBgCgpEaWUgVGFiZWxsZSB6ZWlndCBkaWUgYmVyZWl0cyBtb25hdHN3ZWlzZSBhZ2dyZWdpZXJ0ZW4gVHdlZXQtWmFobGVuIChvZGVyIGdlbmF1ZXIsIGRpZSBBbnphaGwgZGVyIFR5cGVzL1Rva2Vucy9Tw6R0emUpLiBIaWVyIGVpbmlnZSBrb25rcmV0ZSBCZWlzcGllbGUgZsO8ciBUd2VldHMgZGVyIGJlaWRlbiBLYW5kaWRhdGVuICh3aWVkZXIga2FubiBtYW4gbWl0IGRlbSBQZmVpbOKAk0ljb24gbmFjaCByZWNodHMvbGlua3Mgc2Nyb2xsZW4pOgoKYGBge3IgQmVpc3BpZWx0d2VldHMgYW5zZWhlbn0KdHJ1bXBjbGludG9uLnNhbXBsZSA8LSBjb3JwdXNfc2FtcGxlKGtvcnB1cywgc2l6ZSA9IDIwKQpiaW5kX2NvbHModGV4dCA9IHRleHRzKHRydW1wY2xpbnRvbi5zYW1wbGUpLCBkb2N2YXJzKHRydW1wY2xpbnRvbi5zYW1wbGUpKQpgYGAKClp1bsOkY2hzdCBwbG90dGVuIHdpciBkaWUgV8O2cnRlciAob2RlciBnZW5hdWVyIGRlciBUb2tlbnMpIHBybyBNb25hdCBmw7xyIEhpbGxhcnkgQ2xpbnRvbiB1bmQgRG9uYWxkIFRydW1wIGltIFplaXRyYXVtIHZvbiBBcHJpbCAyMDE1IGJpcyBBcHJpbCAyMDE3LCB1bSB1bnMgZWluZW4gRWluZHJ1Y2sgaWhyZXIgQWt0aXZpdMOkdCB6dSB2ZXJzY2hhZmZlbi4gRGllcyBlbnRzcHJpY2h0IHJlbGF0aXYgZ3V0IGRlciBBbnphaGwgZGVyIFR3ZWV0cywgZGEgZGllIFZhcmlhdGlvbiBiZWkgZGVyIEzDpG5nZSBuaWNodCBhbGx6dSBzdGFyayBhdXNmw6RsbHQuIAoKQWNodHVuZzogSGllciB6ZWlndCBkaWUgRmFyYmUgZGVuIEthbmRpZGF0ZW4gYW4sIG5pY2h0IGRhcyBTZW50aW1lbnQuCgpgYGB7ciBUd2l0dGVyLUFrdGl2aXTDpHQgw7xiZXIgZGllIFplaXQgcGxvdHRlbn0KZ2dwbG90KGtvcnB1cy5zdGF0cy5tb25hdCwgYWVzKGRhdGUucHJpbnQsIFRva2VucywgZ3JvdXAgPSBLYW5kaWRhdCwgY29sID0gS2FuZGlkYXQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjIgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWIgJVkiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgeGxhYigiTW9uYXQiKSArIAogIGdndGl0bGUoIlRydW1wIHZzLiBDbGludG9uOiBUb2tlbnMgcHJvIE1vbmF0ICgyMDE1LTIwMTcpIikKYGBgCgpFcyBsw6Rzc3Qgc2ljaCBiZW9iYWNodGVuLCBkYXNzIEhpbGxhcnkgQ2xpbnRvbiBpbiBkZW4gc2VjaHMgTW9uYXRlbiB2b3IgZGVyIFdhaGwgaW0gTm92ZW1iZXIgMjAxNiBkZXV0bGljaCBha3RpdmVyIHd1cmRlLCB3w6RocmVuZCBEb25hbGQgVHJ1bXAgaW0gZGlyZWt0ZW4gVmVyZ2xlaWNoIHp1IHNlaW5lciBiZXJlaXRzIHNlaXQgMjAwOSBhbmhhbHRlbmRlbiBQcsOkc2VueiBiZWkgVHdpdHRlciBlaGVyIGV0d2FzIHdlbmlnZXIgQmVpdHLDpGdlIHZlcmZhc3N0ZS4gCgpXaXIgZXJzdGVsbGVuIG51biB6dW7DpGNoc3QgZWluZSBERk0gZsO8ciBqZWRlbiBkZXIgS2FuZGlkYXRlbiB1bmQgd2VuZGVuIGVybmV1dCBkYXMgQmluZyBMaXUgU2VudGltZW50LUxleGlrb24gYW4uIFdpciBiZWdpbm5lbiBtaXQgRG9uYWxkIFRydW1wIHVuZCBmaWx0ZXJuIHp1bsOkY2hzdCBkYXMgS29ycHVzIG5hY2ggc2VpbmVuIFR3ZWV0cywgZGllIHdpciBhbnNjaGxpZcOfZW5kIG1pdCBIaWxmZSBkZXMgQXJndW1lbnRzICpncm91cHMqIG5hY2ggTW9uYXQgdW5kIEphaHIgYWdncmVnaWVyZW4gKHNvbnN0IGVyaMOkbHQgbWFuIGRhcyBTZW50aW1lbnQtRXJnZWJuaXMgZsO8ciAqamVkZW4gZWluemVsbmVuIFR3ZWV0Kiwgd2FzIGJlaSBydW5kIDIwLjAwMCBUd2VldHMgbmljaHQgdW5iZWRpbmd0IGludGVycHJldGllcmJhciBpc3QpLiBBbnNjaGxpZcOfZW5kIGVyc3RlbGxlbiB3aXIgbWl0dGVscyBbY29udmVydF0oaHR0cHM6Ly9kb2NzLnF1YW50ZWRhLmlvL3JlZmVyZW5jZS9jb252ZXJ0Lmh0bWwpIGVpbmVuIGRhdGEgZnJhbWUsIHdlbGNoZW4gd2lyIG5vY2ggZXR3YXMgYmVhcmJlaXRlbiwgdW0gaWhuIGJlc3NlciBwbG90dGVuIHp1IGvDtm5uZW4uIAoKYGBge3IgU2VudGltZW50LVNjb3JlcyBmw7xyIERvbmFsZCBUcnVtcCBwbG90dGVufQprb3JwdXMudHJ1bXAgPC0gY29ycHVzX3N1YnNldChrb3JwdXMsIEthbmRpZGF0ID09ICJUcnVtcCIpCm1laW5lLmRmbS50cnVtcCA8LSBkZm0oa29ycHVzLnRydW1wLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbikKc2VudGltZW50LnRydW1wIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLCAiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiV8O2cnRlciIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTctMDQtMDEiKQpnZ3Bsb3Qoc2VudGltZW50LnRydW1wLCBhZXMoRGF0dW0sIFfDtnJ0ZXIsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlYiAlWSIpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBmw7xyIERvbmFsZCBUcnVtcCIpICsgeGxhYigiTW9uYXQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKRGVuIGV4YWt0IGdsZWljaCBQcm96ZXNzIHdpZWRlcmhvbGVuIHdpciBudW4gZsO8ciBIaWxsYXJ5IENsaW50b24uIFdpciBmaWx0ZXJuIGlocmUgVHdlZXRzIGF1cyBkZW0gR2VzYW10a29ycHVzIGhlcmF1cywgR3J1cHBpZXJlbiBlaW5lIERGTSBuYWNoIE1vbmF0IHVuZCBKYWhyIHVudGVyIEFud2VuZHVuZyBkZXMgTGV4aWtvbnMsIHVuZCBwbG90dGVuIGRhbm4gZGFzIEVyZ2VibmlzLiAKCmBgYHtyIFNlbnRpbWVudC1TY29yZXMgZsO8ciBIaWxsYXJ5IENsaW50b24gcGxvdHRlbn0Ka29ycHVzLmNsaW50b24gPC0gY29ycHVzX3N1YnNldChrb3JwdXMsIEthbmRpZGF0ID09ICJDbGludG9uIikKbWVpbmUuZGZtLmNsaW50b24gPC0gZGZtKGtvcnB1cy5jbGludG9uLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbikKc2VudGltZW50LmNsaW50b24gPC0gY29udmVydChtZWluZS5kZm0uY2xpbnRvbiwgImRhdGEuZnJhbWUiKSAlPiUKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlfDtnJ0ZXIiKSAlPiUgCiAgbXV0YXRlKERhdHVtID0gYXMuRGF0ZShwYXN0ZSgiMDEiLCBkb2NfaWQsIHNlcCA9ICIuIiksICIlZC4lbS4lWSIpKSAlPiUgCiAgZmlsdGVyKERhdHVtID49ICIyMDE1LTA0LTAxIiAmIERhdHVtIDw9ICIyMDE3LTA0LTAxIikKZ2dwbG90KHNlbnRpbWVudC5jbGludG9uLCBhZXMoRGF0dW0sIFfDtnJ0ZXIsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGdyb3VwID0gUG9sYXJpdMOkdCkpICsgCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiBtb250aHMiLCBkYXRlX2xhYmVscyA9ICIlYiAlWSIpICsgCiAgZ2d0aXRsZSgiU2VudGltZW50LVNjb3JlcyBmw7xyIEhpbGxhcnkgQ2xpbnRvbiIpICsgeGxhYigiTW9uYXQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKSW0gZGlyZWt0ZW4gVmVyZ2xlaWNoIHR3aXR0ZXJuIGJlaWRlIEthbmRpZGF0ZW4gZWhlciBwb3NpdGl2IGFscyBuZWdhdGl2LCB3YXMgdmllbGxlaWNodCDDvGJlcnJhc2NodC4gQWxsZXJkaW5ncyBpc3QgZGVyIEFic3RhbmQgendpc2NoZW4gcG9zaXRpdmVtIHVuZCBuZWdhdGl2ZW0gU2VudGltZW50IGJlaSBIaWxsYXJ5IENsaW50b24gaW5zZ2VzYW10IGhvY2gsIHVuZCBpbiBkZXIgaGVpw59lbiBQaGFzZSBkZXMgV2FobGthbXBmZXMgc29nYXIgYmVzb25kZXJzIGF1c2dlcHLDpGd0LiBCZWkgRG9uYWxkIFRydW1wIGhpbmdlZ2VuIMO8YmVyd2llZ3QgaW0gSnVsaSAyMDE2IGRhcyBuZWdhdGl2ZSBTZW50aW1lbnQgdW5kIGltIEZlYnJ1YXIgMjAxNyBsaWVnZW4gcG9zaXRpdmUgdW5kIG5lZ2F0aXZlIEJlZ3JpZmZlIHVuZ2Vmw6RociBnbGVpY2ggYXVmLiBCZWkgVHJ1bXAgZmFsbGVuIHN0YXJrZSBTY2h3YW5rdW5nZW4gYXVmLCBkaWUgYmVpIENsaW50b24gZmVobGVuLgoKV2llIHNpZWh0IGRpZSBUd2l0dGVy4oCTQWt0aXZpdMOkdCBiZWlkZXIgS2FuZGlkYXRlbiBpbSBkaXJla3RlbiBWZXJnbGVpY2ggYXVzPyBXaXIgw7xiZXJzcHJpbmdlbiBkZW4gendlaXRlbiBTY2hyaXR0IGF1cyBkZXIgb2JpZ2VuIEFuYWx5c2UgdW5kIGdlaGVuIGdsZWljaCBkYXp1IMO8YmVyLCBudXIgbm9jaCBkZW4gdmVycmVjaG5ldGVuIHJlbGF0aXZlbiBBbnRlaWwgZGVzIHBvc2l0aXZlbiBTZW50aW1lbnRzIGbDvHIgYmVpZGUgUG9sdGlrZXIgenUgcGxvdHRlbi4gCgpgYGB7ciBWZXJyZWNobmV0ZSBTZW50aW1lbnQtU2NvcmVzIGbDvHIgYmVpZGUgS2FuZGlkYXRlbiBwbG90dGVufQpzZW50aW1lbnQudHJ1bXAucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS50cnVtcCwgc2NoZW1lID0gInByb3AiKSAlPiUgCiAgY29udmVydCgiZGF0YS5mcmFtZSIpICU+JQogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIG11dGF0ZShEYXR1bSA9IGFzLkRhdGUocGFzdGUoIjAxIiwgZG9jX2lkLCBzZXAgPSAiLiIpLCAiJWQuJW0uJVkiKSkgJT4lIAogIGZpbHRlcihEYXR1bSA+PSAiMjAxNS0wNC0wMSIgJiBEYXR1bSA8PSAiMjAxNi0xMS0wMSIpICU+JSAKICBtdXRhdGUoS2FuZGlkYXQgPSAiVHJ1bXAiKQpzZW50aW1lbnQuY2xpbnRvbi5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmNsaW50b24sIHNjaGVtZSA9ICJwcm9wIikgJT4lIAogIGNvbnZlcnQoImRhdGEuZnJhbWUiKSAlPiUKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlNlbnRpbWVudCIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTYtMTEtMDEiKSAlPiUgCiAgbXV0YXRlKEthbmRpZGF0ID0gIkNsaW50b24iKQpzZW50aW1lbnQudHJ1bXBjbGludG9uIDwtIGJpbmRfcm93cyhzZW50aW1lbnQudHJ1bXAucHJvcCwgc2VudGltZW50LmNsaW50b24ucHJvcCkgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBzZWxlY3QoRGF0dW0sIEthbmRpZGF0LCBTZW50aW1lbnQpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpICU+JQogIG11dGF0ZShLYW5kaWRhdCA9IGFzX2ZhY3RvcihLYW5kaWRhdCkpCmdncGxvdChzZW50aW1lbnQudHJ1bXBjbGludG9uLCBhZXMoRGF0dW0sIFNlbnRpbWVudCwgY29sb3VyID0gS2FuZGlkYXQsIGdyb3VwID0gS2FuZGlkYXQpKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJsaWdodGdyYXkiKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gIiViICVZIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgZsO8ciBiZWlkZSBLYW5kaWRhdGVuIikgKyAKICB4bGFiKCJNb25hdCIpCmBgYAoKRGVyIFplaXRyYXVtIGlzdCBoaWVyIGV0d2FzIGFuZGVycyBnZXfDpGhsdCBhbHMgenV2b3IgKDEuIEFwcmlsIDIwMTUgYmlzIDMwLiBOb3ZlbWJlciAyMDE2KSwgd2VpbCBkaWUgc2VociBnZXJpbmdlIEFuemFobCB2b24gVHdlZXRzIGR1cmNoIEhpbGxhcnkgQ2xpbnRvbiBuYWNoIGRlciBXYWhsIGRlbiBWZXJnbGVpY2ggZXJzY2h3ZXJ0IHVuZCB6dSBWZXJ6ZXJydW5nZW4gZsO8aHJ0LiBEaWUgTm9ybWFsaXNpZXJ1bmcgemVpZ3QgZGllIEjDtmhlbiB1bmQgVGllZmVuIHJlbGF0aXYgenVtIFNlbnRpbWVudHZlcmxhdWYgaW0gR2VzYW10emVpdHJ1bSwgdW5kIHZlcnJlY2huZXQgZGFiZWkgd2llIGF1Y2ggc2Nob24genV2b3IgZGllIHBvc2l0aXZlbiB1bmQgbmVnYXRpdmVuIEJlZ3JpZmZlLiBJbSBKdWxpIDIwMTYgdHdpdHRlcnQgVHJ1bXAgc3RhcmsgbmVnYXRpdiwgd2FzIGFiZXIgbmljaHQgaGVpw590LCBkYXNzIGRlciBBbnRlaWwgbmVnYXRpdmVyIEJlZ3JpZmZlIGdydW5kc8OkdHpsaWNoIHZpZWwgaMO2aGVyIGxhZywgYWxzIGRlciBwb3NpdGl2ZXIgQmVncmlmZmUuIEVzIGxhc3NlbiBzaWNoIGludGVyZXNzYW50ZSBCZXrDvGdlIHp1bSBLb250ZXh0IGRlciBVUy1XYWhsZW4gaGVyc3RlbGxlbiwgZC5oLiB1bnRlciBhbmRlcmVtIGRpZSBOb21pbmllcnVuZyB2b24gRG9uYWxkIFRydW1wIHVuZCBIaWxsYXJ5IENsaW50b24gYWxzIEthbmRpZGF0ZW4gZGVyIFJlcHVibGlrYW5lciBiencuIERlbW9rcmF0ZW4sIHNvd2llIGRpZSBFbnRow7xsbHVuZyBbZ2VoYWNrdGVyIEVtYWlscyBkZXMgRE5DXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS8yMDE2X0RlbW9jcmF0aWNfTmF0aW9uYWxfQ29tbWl0dGVlX2VtYWlsX2xlYWspLCBkaWUgYXVjaCBzY2hvbiAyMDE2IGRlbiBWb3J3dXJmIGVpbmVzIGdlemllbHRlbiBNYW5pcHVsYXRpb25zdmVyc3VjaHMgZHVyY2ggUnVzc2xhbmQgbGF1dCB3ZXJkZW4gbGllw58uIFphaGxyZWljaGUgVHdlZXRzIHZvbiBUcnVtcCBrcml0aXNpZXJlbiBkaWUgTWVkaWVuIGbDvHIgZGllc2UgaW4gZGVuIEF1Z2VuIGRlcyBLYW5kaWRhdGVuIGZhbHNjaGUgVW50ZXJzdGVsbHVuZy4gRGVyIEdydW5kIGbDvHIgVHJ1bXBzIGRlbm5vY2ggenVtIFRlaWwgaG9oZW4gU2VudGltZW504oCTV2VydGUgbGllZ3QgaW5kZXMgaW4gZGVyIFZlcndlbmR1bmcgdmllbGVyIHBvc2l0aXZlciBBZGpla3RpdmUgdW5kIFN1cGVybGF0aXZlICgnZ3JlYXQnLCAnYmVzdCcpLCBkaWUgaW4gU2VudGltZW504oCTTGV4aWthIG5hdMO8cmxpY2ggdm9ya29tbWVuLgoKRGVyIENvZGUgZsO8ciBkaWVzZSBBbmFseXNlIGzDpHNzdCBzaWNoIHNpY2ggaW5zb2Zlcm4gbm9jaCBldHdhcyB2ZXJlaW5mYWNoZW4sIGFscyBkYXNzIGVpbmUgUmVpaGUgdm9uIEhhbmRncmlmZmVuIG51ciBmw7xyIGRpZSBFcnN0ZWxsdW5nIGRlcyBQbG90cywgbmljaHQgYWJlciBmw7xyIGRpZSBFcnN0ZWxsdW5nIGRlciBnZXdpY2h0ZXRlbiBERk0gdW5kIGRlciBBbndlbmR1bmcgZGVzIExleGlrb25zIG5vdHdlbmRpZyBzaW5kLgoKIyMjIFZlcmdsZWljaCB1bnRlcnNjaGllZGxpY2hlciBTZW50aW1lbnQtTGV4aWthCgpXZWxjaGUgVW50ZXJzY2hpZWRlIGdpYnQgZXMgendpc2NoZW4gdmVyc2NoaWVkZW5lbiBMZXhpa2E/IERhIHVudGVyc2NoaWVkbGljaGUgTGV4aWthIGF1Y2ggdW50ZXJzY2hpZWRsaWNoZSBCZWdyaWZmZSBlbnRoYWx0ZW4sIGlzdCBkaWVzZSBGcmFnZSBkdXJjaGF1cyBiZWRldXRzYW0uIFVtIHNpZSB6dSBiZWFudHdvcnRlbiwgYmVyZWNobmVuIHdpciBhbmhhbmQgZGVyIFR3ZWV0cyB2b24gRG9uYWxkIFRydW1wIGRyZWkgdW50ZXJzY2hpZWRsaWNoZSBERk1zLCBqZXdlaWxzIG1pdCBlaW5lbSBhbmRlcmVuIExleGlrb24uIFdpciBsZXNlbiB6dW7DpGNoc3QgZWlubWFsIGFsbGUgZHJlaSBMZXhpa2EgZWluLiAKCmBgYHtyIFNlbnRpbWVudC1MZXhpa2EgbGFkZW4gdW5kIGFubGVnZW59CnNlbnRpbWVudC5sZXhpa29uLmJpbmdsaXUgPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXZlID0gc2NhbigibGV4aWthL2JpbmdsaXUtcG9zaXRpdmUtd29yZHMudHh0Iiwgd2hhdCA9ICJjaGFyIiwgc2VwID0gIlxuIiwgc2tpcCA9IDM1LCBxdWlldCA9IFQpLCBuZWdhdGl2ZSA9IHNjYW4oImxleGlrYS9iaW5nbGl1LW5lZ2F0aXZlLXdvcmRzLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHNraXAgPSAzNSwgcXVpZXQgPSBUKSkpCnNlbnRpbWVudC5sZXhpa29uLm5yYyA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBzY2FuKCJsZXhpa2EvbnJjLnBvcy50eHQiLCB3aGF0ID0gImNoYXIiLCBzZXAgPSAiXG4iLCBxdWlldCA9IFQpLCBuZWdhdGl2ZSA9IHNjYW4oImxleGlrYS9ucmMubmVnLnR4dCIsIHdoYXQgPSAiY2hhciIsIHNlcCA9ICJcbiIsIHF1aWV0ID0gVCkpKQphZmlubiA8LSByZWFkLmNzdigibGV4aWthL0FGSU5OLTExMS50eHQiLCBoZWFkZXIgPSBGLCBzZXAgPSAiXHQiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKc2VudGltZW50LmxleGlrb24uYWZpbm4gPC0gZGljdGlvbmFyeShsaXN0KHBvc2l0aXZlID0gYWZpbm4kVjFbYWZpbm4kVjI+MF0sIG5lZ2F0aXZlID0gYWZpbm4kVjFbYWZpbm4kVjI8MF0pKQpgYGAKClfDpGhyZW5kIGRlciBJbXBvcnQgZsO8ciBkYXMgQmluZyBMaXUtTGV4aWtvbiB1bmQgZGFzIE5SQyBFbW90aW9ucyBMZXhpY29uIHNlaHIgZWluZmFjaCBhYmzDpHVmdCwgaGF0IGRhcyBBRklOTi1EaWN0aW9uYXJ5IGVpbiBzcGV6aWVsbGVzIEZvcm1hdCwgYmVpIGRlbSBlaW5lIFphaGwgendpc2NoZW4gLTUgdW5kICs1IGRpZSBQb2xhcml0w6R0IHZvbiBzZWhyIG5lZ2F0aXYgYmlzIHNlaHIgcG9zaXRpdiBiZXNjaHJlaWJ0LiBXaXIgbnV0emVuIGhpZXIgZGllc2VuIGJlc29uZGVyZW4gVm9ydGVpbCBuaWNodCBhdXMsIHNvbmRlcm4gYmVoYW5kZWxuIGFsbGUgQmVncmlmZmUgYWxzICdlaW5mYWNoJyBuZWdhdGl2IG9kZXIgcG9zaXR2ICg8MCBvZGVyID4wKS4KCldpZWRlciB3aXJkIGRhcyBMZXhpa29uIGFuZ2V3ZW5kZXQsIG5hY2ggTW9uYXQgdW5kIEphaHIgZ3J1cHBpZXJ0LCB1bmQgYW5zY2hsaWXDn2VuZCBwcm9wb3J0aW9uYWwgZ2V3aWNodGV0IC0tIGpld2VpbHMgZsO8ciBqZWRlcyBkZXIgZHJlaSBMZXhpa2EuIAoKYGBge3IgU2VudGltZW50LURGTXMgZ2V3aWNodGVufQptZWluZS5kZm0udHJ1bXAuYmluZ2xpdSA8LSBkZm1fd2VpZ2h0KGRmbShrb3JwdXMudHJ1bXAsIGdyb3VwcyA9IGMoIm1vbmF0IiwgImphaHIiKSwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLmJpbmdsaXUpLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS50cnVtcC5ucmMgPC0gZGZtX3dlaWdodChkZm0oa29ycHVzLnRydW1wLCBncm91cHMgPSBjKCJtb25hdCIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbi5ucmMpLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS50cnVtcC5hZmlubiA8LSBkZm1fd2VpZ2h0KGRmbShrb3JwdXMudHJ1bXAsIGdyb3VwcyA9IGMoIm1vbmF0IiwgImphaHIiKSwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLmFmaW5uKSwgc2NoZW1lID0gInByb3AiKQpgYGAKClNjaGxpZcOfbGljaCB3ZXJkZW4gZGllIGRyZWkgREZNcyBpbiBEYXRhIEZyYW1lcyB1bWdld2FuZGVsdCB1bmQgZWluZSBWYXJpYWJsZSBoaW56dWdlZsO8Z3QsIHdlbGNoZSBkYXMgamV3ZWlsaWdlIExleGlrb24gaWRlbnRpZml6aWVydC4KCmBgYHtyIFNlbnRpbWVudC1ERk1zIHp1IERhdGEgRnJhbWVzIGtvbnZlcnRpZXJlbn0Kc2VudGltZW50LnRydW1wLmJpbmdsaXUgPC0gY29udmVydChtZWluZS5kZm0udHJ1bXAuYmluZ2xpdSwgImRhdGEuZnJhbWUiKSAlPiUgbXV0YXRlKExleGlrb24gPSAiQmluZyBMaXUiKQpzZW50aW1lbnQudHJ1bXAubnJjIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLm5yYywgImRhdGEuZnJhbWUiKSAlPiUgbXV0YXRlKExleGlrb24gPSAiTlJDIikKc2VudGltZW50LnRydW1wLmFmaW5uIDwtIGNvbnZlcnQobWVpbmUuZGZtLnRydW1wLmFmaW5uLCAiZGF0YS5mcmFtZSIpICU+JSBtdXRhdGUoTGV4aWtvbiA9ICJBRklOTiIpCmBgYAoKWnVsZXR6dCB3aXJkIGVpbiBnZW1laW5zYW1lciBEYXRhIEZyYW1lIHp1c2FtbWVuZ2VzZXR6dCB1bmQgZXR3YXMgdW1nZWZvcm10LiBEYXMgcmVzdWx0aWVyZW5kZW4gUGxvdCB6ZWlndCBkaWUgdmVycmVjaG5ldGVuIFNlbnRpbWVudC1TY29yZXMgdm9uIERvbmFsZCBUcnVtcCBmw7xyIGFsbGUgZHJlaSBMZXhpa2EuIAoKYGBge3IgVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBmw7xyIGRyZWkgTGV4aWthIHBsb3R0ZW59CnNlbnRpbWVudC50cnVtcC5rb21iaSA8LSBiaW5kX3Jvd3Moc2VudGltZW50LnRydW1wLmJpbmdsaXUsIHNlbnRpbWVudC50cnVtcC5ucmMsIHNlbnRpbWVudC50cnVtcC5hZmlubikgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBtdXRhdGUoRGF0dW0gPSBhcy5EYXRlKHBhc3RlKCIwMSIsIGRvY19pZCwgc2VwID0gIi4iKSwgIiVkLiVtLiVZIikpICU+JSAKICBmaWx0ZXIoRGF0dW0gPj0gIjIwMTUtMDQtMDEiICYgRGF0dW0gPD0gIjIwMTctMDMtMDEiKSAlPiUgCiAgc2VsZWN0KERhdHVtLCBMZXhpa29uLCBTZW50aW1lbnQpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpCmdncGxvdChzZW50aW1lbnQudHJ1bXAua29tYmksIGFlcyhEYXR1bSwgU2VudGltZW50LCBjb2xvdXIgPSBMZXhpa29uLCBncm91cCA9IExleGlrb24pKSArIAogIGdlb21fbGluZShzaXplID0gMSkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJsaWdodGdyYXkiKSArIAogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsgCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjIgbW9udGhzIiwgZGF0ZV9sYWJlbHMgPSAiJWIgJVkiKSArIAogIGdndGl0bGUoIlZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgZsO8ciBEb25hbGQgVHJ1bXAgbWl0IGRyZWkgTGV4aWthIikgKyAKICB4bGFiKCJNb25hdCIpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCgpXaWUgd2lyIHNlaGVuLCBzdGltbXQgZGllIFRlbmRlbnogZGVyIGRyZWkgTGV4aWthIHp3YXIga2xhciDDvGJlcmVpbiwgamVkb2NoIGVyZ2ViZW4gc2ljaCBkdXJjaGF1cyBtYXJrYW50ZW4gVW50ZXJzY2hpZWRlLiBTbyBzaW5kIEFGSU5OIHVuZCBCaW5nIExpdSBnZWdlbsO8YmVyIE5SQyBldHdhcyBwb3NpdGl2ZXIuIFp1bSBUZWlsIHVudGVyc2NoZWlkZXQgc2ljaCBhdWNoIGRpZSBJbnRlbnNpdMOkdCBkZXIgQXVzc2NobMOkZ2UgaW4gYmVpZGUgUG9sYXJpdMOkdHNyaWNodHVuZ2VuLiBFaW4gR3J1bmQgZsO8ciBkaWUgVmFyaWF0aW9uIGlzdCBkaWUgTMOkbmdlIGRlciBXb3J0bGlzdGVuLCBkYSB1bWZhbmdyZWljaGVyZSBMaXN0ZW4gZWluZSBiZXNzZXIgQWJkZWNrdW5nIGRlciB0YXRzw6RjaGxpY2ggdmVyd2VuZGV0ZW4gQmVncmlmZmUgZXJyZWljaGVuLiBHcnVuZHPDpHR6bGljaCB1bnRlcnNjaGVpZGVuIHNpY2ggZGllIGRyZWkgTGV4aWthIGFiZXIgbmljaHQgc2lnbmlmaWthbnQgdW5kIHN0aW1tZW4gYnNwdy4gaW4gaWhyZXIgTWVzc3VuZyBkZXIgU2VudGltZW504oCTU2Nod2Fua3VuZyB6d2lzY2hlbiBTZXB0ZW1iZXIgdW5kIE5vdmVtYmVyIDIwMTYga2xhciDDvGJlcmVpbi4KCiMjIyBTZW50aW1lbnQgaW4gendlaSBTdWJyZWRkaXRzIG1pdCBkZW0gTGV4aWNvZGVyIFNlbnRpbWVudCBEaWN0aW9uYXJ5CgpOdW4gd2VuZGVuIHdpciB1bnMgZWluZW0gQmVpc3BpZWwgYXVzIGVpbmVyIGFuZGVyZW4gU29jaWFsIE1lZGlhIFBsYXR0Zm9ybSAtLSBkZW0gRGlza3Vzc2lvbnNmb3J1bSBSZWRkaXQgLS0genUuIEVzIGhhbmRlbHQgc2ljaCB1bSBCZWl0csOkZ2UgYXVzIHp3ZWkgdW50ZXJzY2hpZWRsaWNoZW4gQmVyZWljaGVuIGRlciBQbGF0dGZvcm0gKHNvZy4gU3VicmVkZGl0cykuIFdpciBsYWRlbiB6dW7DpGNoc3QgZGVuIERhdGVuc2F0eiB3ZWxjaGVyIHdpZSBhdWNoIGRpZSBUd2l0dGVyLURhdGVuIHNjaG9uIGFscyBRdWFudGVkYS1Lb3JwdXMgdm9ybGllZ3QuIERpZSBNZXRhZGF0ZW4gw6RobmVsbiB0ZWlsd2Vpc2UgZGVuZW4gdm9uIFR3aXR0ZXIgKCJwb3N0X2RhdGUiKSwgd2Vpc2VuIGFiZXIgYXVjaCBTcGV6aWZpa2EgYXVmIChkaWUgVmFyaWFiZWwgInN0cnVjdHVyZSIgbGllZmVydCBJbmZvcm1hdGlvbmVuIHp1ciBTY2hhY2h0ZWx1bmcgZGVyIERpc2t1c3Npb24pLiAgCgpgYGB7ciBSZWRkaXQtS29ycHVzIGxhZGVufQpsb2FkKCJkYXRlbi9yZWRkaXQvcmVkZGl0LlJEYXRhIikKYXMuZGF0YS5mcmFtZShyZWRkaXQuc3RhdHMpCmBgYAoKRGFzIEtvcnB1cyBlbnRow6RsdCBldHdhIDIwLjAwMCBLb21tZW50YXJlLCBkaWUgaW4gendlaSB1bnRlcnNjaGllZGxpY2hlbiBTdWJyZWRkaXRzIHZlcsO2ZmZlbnRsaWNodCB3dXJkZW4sIGRlbiBGb3JlbiAnc2NpZW5jZScgdW5kICdzeXJpYW5jaXZpbHdhcicuIFdpciBiZXJlY2huZW4gU2VudGltZW50LVNjb3JlcyBmw7xyIGRpZXNlIE5hY2hyaWNodGVuIG1pdCBIaWxmZSBkZXMgTGV4aWNvZGVyIFNlbnRpbWVudCBEaWN0aW9uYXJ5IChMU0QyMDE1KS4gSW0gR2VnZW5zYXR6IHp1bSBWb3JnZWhlbiBpbiBkZW4gdm9yYXVzZ2VoZW5kZW4gQmVpc3BpZWxlbiB2ZXJ3ZW5kZW4gd2lyIGRpZSBsb2dhcml0aG1pc2NoZSBEdXJjaHNjaG5pdHRzZ2V3aWNodHVuZyB1bmQga8O8cnplbiBkYXMgRXJnZWJuaXMsIHVtIHNvIGVpbmVuIFBvbGFyaXTDpHRzd2VydCBwcm8gS29tbWVudGFyIHVuZCBuaWNodCBwcm8gV29ydCB6dSBiZXN0aW1tZW4gKGJlYWNodGVuIFNpZSwgZGFzcyBlcyBoaWVyZsO8ciB1bnRlcnNjaGllZGxpY2hlIFN0cmF0ZWdpZW4gZ2lidCkuCgpgYGB7ciBERk0gYmVyZWNoZW4gdW5kIFBvbGFyaXTDpHQgYW5oYW5kIGRlcyBMU0QtTGV4aWtvbnMgYmVzdGltbWVufQpyZWRkaXQuZGZtIDwtIGRmbShyZWRkaXQuY29ycHVzLCBkaWN0aW9uYXJ5ID0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpICU+JSAKICBkZm1fcmVtb3ZlKGMoIm5lZ19wb3NpdGl2ZSIsICJuZWdfbmVnYXRpdmUiKSkKcmVkZGl0LnNlbnRpbWVudCA8LSBkZm1fd2VpZ2h0KHJlZGRpdC5kZm0sIHNjaGVtZSA9ICJsb2dhdmUiKSAlPiUgCiAgY29udmVydCgiZGF0YS5mcmFtZSIpICU+JQogIG11dGF0ZShwb3NpdGl2ZSA9IHRydW5jKHBvc2l0aXZlKSwgbmVnYXRpdmUgPSB0cnVuYyhuZWdhdGl2ZSkpICU+JSAKICBtdXRhdGUobmV1dHJhbCA9IHBvc2l0aXZlID09IG5lZ2F0aXZlKSAlPiUgCiAgbGVmdF9qb2luKHJlZGRpdC5zdGF0cywgYnkgPSBjKCJkb2NfaWQiID0gIlRleHQiKSkKc2VudGltZW50IDwtICIiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JHBvc2l0aXZlPT0xXSA8LSAicG9zaXRpdmUiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JG5lZ2F0aXZlPT0xXSA8LSAibmVnYXRpdmUiCnNlbnRpbWVudFtyZWRkaXQuc2VudGltZW50JG5ldXRyYWw9PVRdIDwtICJuZXV0cmFsIgpyZWRkaXQuc2VudGltZW50LnNoYXJlIDwtIHJlZGRpdC5zZW50aW1lbnQgJT4lIAogIHNlbGVjdChkb2NfaWQsIHN0cnVjdHVyZSwgY29tbV9kYXRlLCBzdWJyZWRkaXQsIHVzZXIsIGNvbW1lbnRfc2NvcmUpICU+JSAKICBkYXRhLmZyYW1lKFNlbnRpbWVudCA9IHNlbnRpbWVudCkKcmVkZGl0LnNlbnRpbWVudC5zaGFyZQpgYGAKCk51biBwbG90dGVuIHdpciBkZW4gcmVsYXRpdmVuIEFudGVpbCBkZXIgUG9sYXJpdMOkdCBqZSBLb21tZW50YXIgaW5uZXJoYWxiIGRlciB6d2VpIFN1YnJlZGRpdHMuCgpgYGB7ciBTZW50aW1lbnQtQW50ZWlsZSBpbSBSZWRkaXQtS29ycHVzIGJlcmVjaG5lbiB1bmQgZGFzIEVyZ2VibmlzIHBsb3R0ZW59CnJlZGRpdC5zZW50aW1lbnQuc2hhcmUgPC0gZGF0YS5mcmFtZShwcm9wLnRhYmxlKHRhYmxlKHJlZGRpdC5zZW50aW1lbnQuc2hhcmUkU2VudGltZW50LCByZWRkaXQuc2VudGltZW50LnNoYXJlJHN1YnJlZGRpdCksIDIpKQpjb2xuYW1lcyhyZWRkaXQuc2VudGltZW50LnNoYXJlKSA8LSBjKCJTZW50aW1lbnQiLCAiU3VicmVkZGl0IiwgIlNoYXJlIikKZ2dwbG90KHJlZGRpdC5zZW50aW1lbnQuc2hhcmUsIGFlcyhTdWJyZWRkaXQsIFNoYXJlLCBjb2xvdXIgPSBTZW50aW1lbnQsIGZpbGwgPSBTZW50aW1lbnQpKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwxIikgKyAKICBnZ3RpdGxlKCJTZW50aW1lbnQtVmVydGVpbHVuZyBpbiB6d2VpIFN1YnJlZGRpdHMiKSArIAogIHhsYWIoIiIpICsgeWxhYigiU2VudGltZW50LUFudGVpbCAoJSkiKQpgYGAKCkRpZSBuYWNoc3RlaGVuZGVuIEJlaXNwaWVsZSAoWnVmYWxsc3NhbXBsZSkgemVpZ2VuIEtvbW1lbnRhcmUgdW5kIGRlcmVuIGpld2VpbGlnZSB2b3JoZXJnZXNhZ3RlIFBvbGFyaXTDpHQgYXVmIEdydW5kbGFnZSBkZXMgTGV4aWtvbnMuIEVzIGxhc3NlbiBzaWNoIHphaGxyZWljaGUgQmVpc3BpZWxlIGbDvHIgZmVobGVyaGFmdCBrbGFzc2lmaXppZXJ0ZSBLb21tZW50YXJlIGVudGRlY2tlbiwgZXR3YSBpcm9uaXNjaGUgb2RlciB1bnZlcnN0w6RuZGxpY2hlIEJlaXRyw6RnZSwgb2RlciBzb2xjaGUsIGRpZSBzaWNoIHNjaGxpY2h0IGVpbmVyIGtsYXJlbiBFaW5vcmRudW5nIGluIGRhcyBSYXN0ZXIgcG9zaXRpdi9uZWdhdGl2IGVudHppZWhlbi4gQWxsZXJkaW5ncyBzY2hlaW50IGRpZSBUZW5kZW56IGluc29mZXJuIGtvcnJla3QsIGFscyBkYXMgZXR3YSBLb21tZW50YXJlIG1pdCBLcmFmdGF1c2Ryw7xja2VuIGFscyBuZWdhdGl2IHVuZCBzb2xjaGUgbWl0IEdsw7xja3fDvG5zY2hlbiBhbHMgcG9zaXRpdiBrbGFzc2lmaXppZXJ0IHdlcmRlbiAoYXVjaCBoaWVyIGdpYnQgZXMgZmFsc2NoIHBvc2l0aXZlIFRyZWZmZXIpLiAgCgpgYGB7ciBCZWlzcGllbGUgdm9uIFJlZGRpdC1Lb21tZW50YXJlbiB1bnRlcnNjaGllZGxpY2hlciBQb2xhcml0w6R0fQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKSAlPiUgCiAgc2FtcGxlX24oMTApICU+JSAKICBzZWxlY3QoY29tbWVudCwgc2VudGltZW50LCBzdWJyZWRkaXQsIHRpdGxlKQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKSAlPiUgCiAgc2FtcGxlX24oMTApICU+JSAKICBzZWxlY3QoY29tbWVudCwgc2VudGltZW50LCBzdWJyZWRkaXQsIHRpdGxlKQpkYXRhLmZyYW1lKHJlZGRpdC5zZW50aW1lbnQsIHNlbnRpbWVudCwgY29tbWVudCA9IHRleHRzKHJlZGRpdC5jb3JwdXMpKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmV1dHJhbCIpICU+JSAKICBzYW1wbGVfbigxMCkgJT4lIAogIHNlbGVjdChjb21tZW50LCBzZW50aW1lbnQsIHN1YnJlZGRpdCwgdGl0bGUpCmBgYAoKCiMjIyBTZW50aW1lbnQgaW4gU2Nod2VpemVyIFRhZ2VzemVpdHVuZ2VuIGluIGRlciBCZXJpY2h0ZXJzdGF0dHVuZyB6dXIgRmluYW56a3Jpc2UKCldpciB3ZWNoc2VsbiBudW4gZGllIFBlcnNwZWt0aXZlIHVuZCB1bnRlcnN1Y2hlbiBkZXV0c2Noc3ByYWNoaWdlIFRleHRlLiBEYXp1IGxlc2VuIHdpciBkYXMgU2Nod2VpemVyIEtvcnB1cyB6dXIgRmluYW56a3Jpc2UgZWluLiBEaWVzZXMgZW10aMOkbHQgcnVuZCAyMSwwMDAgQXJ0aWtlbCwgZGllIHp3aXNjaGVuIDIwMDcgdW5kIDIwMTIgaW4gZWluZXIgdm9uIGbDvG5mIFNjaHdlaXplciBUYWdlc3plaWd1bmdlbiB2ZXLDtmZmZW50bGljaCB3dXJkZW4gdW5kIGRlbiBCZWdyaWZmICdGaW5hbnprcmlzZScgaW4gVGV4dCBvZGVyIFRpdGVsIGVudGhhbHRlbi4gTmViZW4gU3ByYWNoZSB1bmQgVGV4dHNvcnRlIGdpYnQgZWluZW4gd2VpdGVyZW4gVW50ZXJzY2hpZWQgenUgZGVuIFR3aXR0ZXItRGF0ZW46IERpZSBBZ2dyZWdhdGlvbnNlYmVuZSBpc3QgaGllciBuaWNodCBtZWhyIGVpbiBNb25hdCwgd2llIHp1dm9yLCBzb25kZXJuIHNjaGxpY2h0IGVpbiBBcnRpa2VsLiBEYXMgbGV1Y2h0ZXQgZWluLCBhbGxlcmRpbmdzIGlzdCBkaWVzZXIgQW5zYXR6IGhpZXIgYXVjaCBkZXNoYWxiIGVyZ2llYmlnLCB3ZWlsIFplaXR1bmdzYXJ0aWtlbCBudW4gZWlubWFsIGRldXRsaWNoIGzDpG5nZXIgc2luZCBhbHMgVHdlZXRzLgoKWnVzw6R0emxpY2ggenVtIEZpbmFuemtyaXNlLUtvcnB1cyBsYWRlbiB3aXIgYXVjaCBnbGVpY2ggZGFzIGRldXRzY2hzcHJhY2hpZ2UgU2VudGlXUy1MZXhpa29uLiBJbSBHZWdlbnNhdHogenUgZGVtIEJpbmcgTGl1LUxleGlrb24gaGFuZGVsdCBlcyBzaWNoIGhpZXIgdW0gZWluZSBSRGF0YS1EYXRlaSwgbmljaHQgdW0gZWluZSBUZXh0ZGF0ZWksIHdhcyBhYmVyIHByYWt0aXNjaCBrZWluZXJsZWkgVW50ZXJzY2hpZWQgbWFjaHQuCgpgYGB7ciBTZW50aVdTLUxleGlrb24gdW5kIEZpbmFuemtyaXNlLUtvcnB1cyBsYWRlbn0KbG9hZCgibGV4aWthL3NlbnRpV1MuUkRhdGEiKQpsb2FkKCJkYXRlbi9jb3NtYXMvZmluYW56a3Jpc2UvZmluYW56a3Jpc2Uua29ycHVzLlJEYXRhIikKc2VudGltZW50LmxleGlrb24uc2VudGl3cyA8LSBkaWN0aW9uYXJ5KGxpc3QocG9zaXRpdmUgPSBwb3NpdGl2ZS53b2VydGVyLnNlbnRpLCBuZWdhdGl2ZSA9IG5lZ2F0aXZlLndvZXJ0ZXIuc2VudGkpKQpoZWFkKGtvcnB1cy5maW5hbnprcmlzZS5zdGF0cywgMTAwKQpgYGAKCgojIyMjIFNlbnRpbWVudC1BbnRlaWxlIG5hY2ggUXVlbGxlIChoaWVyOiBaZWl0dW5nKQoKSW0gbsOkY2hzdGUgU2Nocml0dCBiZXJlY2huZW4gd2lyIGVpbmUgREZNLCBkaWUgbnVuIG5pY2h0IG5hY2ggTW9uYXQgdW5kIEphaHIgZ3J1cHBpZXJ0IGlzdCwgc29uZGVybiBuYWNoIGRlbSBGZWxkICpxdWVsbGUqLCBkLmguIGRlciBqZXdlaWxpZ2VuIFplaXR1bmcuIERpZXNlciBTY2hyaXR0IGRhdWVydCBkZXNoYWxiIGV0d2FzIGzDpG5nZXIgYWxzIHp1dm9yLCB3ZWlsIHdpciBlcyBtaXQgZWluZW0gS29ycHVzIHZvbiAyMSwwMDAgRG9rdW1lbnRlbiB1bmQgcnVuZCA0IE1pby4gVG9rZW5zIHp1IHR1biBoYWJlbiAtLSBkZXV0bGljaCBncsO2w59lciwgYWxzIGRhcyBTaGVybG9jayBIb2xtZXMtS29ycHVzICgxMjZUIFfDtnJ0ZXIpIHVuZCBUd2l0dGVyIEtvcnB1cyAoNDU5VCBXw7ZydGVyKS4gSGVyYXVzIGtvbW10IGVpbmUgVGFiZWxsZSwgZGllIHNpY2ggc29nYXIgdm9sbHN0w6RuZGlnIGF1c2dlYmVuIGzDpHNzdCwgb2huZSBkZW4gW2hlYWRdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy91dGlscy92ZXJzaW9ucy8zLjUuMS90b3BpY3MvaGVhZCnigJNCZWZlaGwgenUgdmVyd2VuZGVuLiAKCmBgYHtyIERGTSBmw7xyIGRhcyBGaW5hbnprcmlzZS1Lb3JwdXMgYmVyZWNobmVuIHVuZCBnZXdpY2h0ZW59Cm1laW5lLmRmbS5maW5hbnprcmlzZSA8LSBkZm0oa29ycHVzLmZpbmFuemtyaXNlLCBncm91cHMgPSAicXVlbGxlIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnNlbnRpd3MpCm1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmZpbmFuemtyaXNlLCBzY2hlbWUgPSAicHJvcCIpCm1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wCmBgYAoKQXVjaCBkaWVzZSBzZWhyIGVpbmZhY2hlIFRhYmVsbGUga8O2bm5lbiB3aXIgcGxvdHRlbiwgYXVjaCB3ZW5uIGRhcyBpbiBkaWVzZW0ga29ua3JldGVuIEZhbGwgdmllbGxlaWNodCBuaWNodCB1bmJlZGluZ3Qgbm90d2VuZGlnIGlzdC4gCgpgYGB7ciBUcmVlbWFwIHp1IFNlbnRpbWVudC1BbnRlaWxlbiBuYWNoIE1lZGl1bSBpbSBGaW5hbnprcmlzZS1Lb3JwdXN9CnNlbnRpbWVudC5maW5hbnprcmlzZSA8LSBjb252ZXJ0KG1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wLCAiZGF0YS5mcmFtZSIpICU+JSAKICBnYXRoZXIocG9zaXRpdmUsIG5lZ2F0aXZlLCBrZXkgPSAiUG9sYXJpdMOkdCIsIHZhbHVlID0gIlNlbnRpbWVudCIpCmdncGxvdChzZW50aW1lbnQuZmluYW56a3Jpc2UsIGFlcyhkb2NfaWQsIFNlbnRpbWVudCwgY29sb3VyID0gUG9sYXJpdMOkdCwgZmlsbCA9IFBvbGFyaXTDpHQpKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyAKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwxIikgKyAKICBnZ3RpdGxlKCJTZW50aW1lbnQtU2NvcmVzIGluIEJlaXRyw6RnZW4genVyIEZpbmFuemtyaXNlIGF1cyBmw7xuZiBTY2h3ZWl6ZXIgVGFnZXN6ZWl0dW5nZW4iKSArIAogIHhsYWIoIiIpICsgeWxhYigiU2VudGltZW50LUFudGVpbCAoJSkiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYAoKV2llIHdpciBzZWhlbiBpc3QgZGllIEJlcmljaHRlcnN0YXR0dW5nIHp1ciBGaW5hbnprcmlzZSDDvGJlcndpZWdlbmRlIG5lZ2F0aXYsIHdhcyBhbmdlc2ljaHRzIGRlcyBUaGVtYXMgbmF0w7xybGljaCBrYXVtIMO8YmVycmFzY2h0LiBFcyBsYXNzZW4gc2ljaCBhYmVyIGF1Y2gga2VpbmVybGVpIG5lbm5lbnN3ZXJ0ZSBVbnRlcnNjaGllZGUgendpc2NoZW4gZGVuIFplaXR1bmdlbiBlcmtlbm5lbiwgc29uZGVybiBkaWUgw5xiZXJlaW5zdGltbXVuZyBpc3QgYXVzZ2VzcHJvY2hlbiBrbGFyLgoKIyMjIFZlcnJlY2huZXRlIFNlbnRpbWVudC1BbnRlaWxlIG5hY2ggUXVlbGxlIHVuZCBKYWhyCgpXaWUgc3RlbGx0IHNpY2ggZGFzIFNlbnRpbWVudCBpbiBkZW4gZsO8bmYgWmVpdHVuZ2VuIGltIFZlcmdsZWljaCDDvGJlciBkaWUgWmVpdCBkYXI/IFdpciBiZXJlY2huZW4gbm9jaCBlaW5tYWwgZWluZSBERk0gdW50ZXIgVmVyd2VuZHVuZyBkZXMgU2VudGlXU+KAk0xleGlrb25zLCBncnVwcGllcmVuIGFiZXIgZGllc2VzIE1hbCBzb3dvaGwgbmFjaCBRdWVsbGUgYWxzIGF1Y2ggbmFjaCBKYWhyLiAKCmBgYHtyIERGTSBtaXQgU2VudGltZW50LUVudHdpY2tsdW5nIG5hY2ggTWVkaXVtIHVuZCBaZWl0IGJlcmVjaG5lbn0KbWVpbmUuZGZtLmZpbmFuemtyaXNlIDwtIGRmbShrb3JwdXMuZmluYW56a3Jpc2UsIGdyb3VwcyA9IGMoInF1ZWxsZSIsICJqYWhyIiksIGRpY3Rpb25hcnkgPSBzZW50aW1lbnQubGV4aWtvbi5zZW50aXdzKQptZWluZS5kZm0uZmluYW56a3Jpc2UucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS5maW5hbnprcmlzZSwgc2NoZW1lID0gInByb3AiKQpoZWFkKG1laW5lLmRmbS5maW5hbnprcmlzZS5wcm9wKQpgYGAKCkRpZSByZXN1bHRpZXJlbmRlIERGTSBmb3JtZW4gd2lyIGV0d2FzIHVtIHVuZCBwbG90dGVuIGRhbm4gd2llZGVyIGVpbmUgR3JhZmlrLCB3ZWxjaGUgZWluZXJzZWl0cyBkaWUgemVpdGxpY2hlIEVudHdpY2tsdW5nIGRhcnN0ZWxsdCwgdW5kIGFuZGVyZXJzZWl0cywgd2llIGF1Y2ggc2Nob24gaW0gdm9yaGVyaWdlbiBCZWlzcGllbCwgZGllIFVudGVyc2NoaWVkZSBzdGFyayBoZXJ2b3JoZWJ0IC0tIGVpbmVyc2VpdHMgendpc2NoZW4gZGVuIGbDvG5mIFplaXR1bmdlbiB1bmQgYW5kZXJlcnNlaXRzIGlubmVyaGFsYiBkZXMgZsO8bmZqw6RocmlnZW4gTWVzc3plaXRyYXVtcy4gU28ga2FubiBtYW4gZXJrZW5uZW4sIGRhcyAnRGVyIEJ1bmQnIGR1cmNod2VnIGV0d2FzIG5lZ2F0aXZlciBpc3QsIGFscyBlcyBkaWUgYW5kZXJlbiBaZWl0dW5nZW4gc2luZCwgZGFzcyBkaWUgQmVybmVyIFplaXR1bmcgaW4gMjAxMCBwb3NpdGl2ZXIgaXN0IGFscyBkaWUgYW5kZXJlIHZpZXIgQmzDpHR0ZXIsIHVuZCBkYXNzIGRpZSBOWlogdGVuZGVuemllbGwgw7xiZXIgZGllIFplaXQgZXR3YXMgcG9zaXRpdmVyIHdpcmQsIGFiZXIgaW0gVmVyZ2xlaWNoIGRvY2ggbmVnYXRpdiBibGVpYnQuIERpZSBCZXJuZXIgWmVpdHVuZyBzY2h3YW5rdCBpbSBWZXJnbGVpY2ggcmVsYXRpdiBzdGFyay4gCgpgYGB7ciBTZW50aW1lbnQtRW50d2lja2x1bmcgbmFjaCBNZWRpdW0gdW5kIFplaXQgcGxvdHRlbn0Kc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQgPC0gY29udmVydChtZWluZS5kZm0uZmluYW56a3Jpc2UucHJvcCwgImRhdGEuZnJhbWUiKSAKZmluYW56a3Jpc2UucXVlbGxlbiA8LSBkYXRhLmZyYW1lKHN0cl9zcGxpdChzZW50aW1lbnQuZmluYW56a3Jpc2UuemVpdCRkb2NfaWQsICJcXC4iLCBzaW1wbGlmeSA9IFQpKQpjb2xuYW1lcyhmaW5hbnprcmlzZS5xdWVsbGVuKSA8LSBjKCJaZWl0dW5nIiwgIkphaHIiKQpzZW50aW1lbnQuZmluYW56a3Jpc2UuemVpdCA8LSBiaW5kX2NvbHMoc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQsIGZpbmFuemtyaXNlLnF1ZWxsZW4pICU+JQogIHJlbmFtZShTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgbXV0YXRlKFNlbnRpbWVudCA9IHJlc2NhbGUoU2VudGltZW50LCB0byA9IGMoLTEsMSkpKSAlPiUKICBzZWxlY3QoU2VudGltZW50LCBaZWl0dW5nLCBKYWhyKQpnZ3Bsb3Qoc2VudGltZW50LmZpbmFuemtyaXNlLnplaXQgLCBhZXMoSmFociwgU2VudGltZW50KSkgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWVzKGZpbGwgPSBaZWl0dW5nKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQWNjZW50IikgKyAKICBnZ3RpdGxlKCJWZXJyZWNobmV0ZSBTZW50aW1lbnQtU2NvcmVzIGluIEJlaXRyw6RnZW4genVyIEZpbmFuemtyaXNlIikgCmBgYAoKCiMjIyBTZW50aW1lbnQgaW4gRGViYXR0ZW4gZGVzIERldXRzY2hlbiBCdW5kZXN0YWdzIG5hY2ggUGFydGVpLCBTaXR6dW5nIHVuZCBTcHJlY2hlcgoKV2lyIHNjaGxpZcOfZW4gbWl0IGVpbmVtIHdlaXRlcmUgZGV1dHNjaHNwcmFjaGlnZW4gQmVpc3BpZWwsIGRlbSBEZWJhdHRlbmtvcnB1cyBkZXMgRGV1dHNjaGVuIEJ1bmRlc3RhZ3MgaW4gZGVyIDE4LiBMZWdpc2xhdHVycGVyaW9kZS4gV2llZGVyIGlzdCBkYXMgS29ycHVzIGRhenUgYmVyZWl0cyBhbHMgUkRhdGEtRGF0ZWkgYWJnZWxlZ3QgdW5kIHdpciDDvGJlcnNwcmluZ2VuIGFuZCBkaWVzZXIgU3RlbGxlIGRlbiBIaW50ZXJncnVuZCBkYXp1LCB3aWUgZ2VuYXUgZGFzIEtvcnB1cyB6dXNhbW1lbmdlc3RlbGx0IHd1cmRlLiBEZXIgVW1mYW5nIGlzdCBtaXQgMjA2LjAwMCBXb3J0bWVsZHVuZ2VuIGluIDI0MyBTaXR6dW5nZW4gdW5kIHJ1bmQgMTUgTWlvLiBUb2tlbnMgbm9jaCBlaW5tYWwgZXJoZWJsaWNoIGdyw7bDn2VyLCBhbHMgZGVyIHZvcmF1c2dlZ2FuZ2VuZXIgS29ycG9yYS4gCgpgYGB7ciBCdW5kZXN0YWdzLUtvcnB1cyB1bmQgU2VudGltZW50LUxleGlrb24gbGFkZW59CmxvYWQoImRhdGVuL2J1bmRlc3RhZy9idW5kZXN0YWcua29ycHVzLlJEYXRhIikKbG9hZCgibGV4aWthL1JhdWhfU2VudERpY3Rpb25hcnlHZXJtYW4uUkRhdGEiKQpzZW50aW1lbnQubGV4aWtvbi5yYXVoIDwtIGRpY3Rpb25hcnkobGlzdChwb3NpdGl2ZSA9IHN0cl90cmltKHNlbnQuZGljdGlvbmFyeSRmZWF0dXJlW3NlbnQuZGljdGlvbmFyeSRzZW50aW1lbnQ+MF0pLCBuZWdhdGl2ZSA9IHN0cl90cmltKHNlbnQuZGljdGlvbmFyeSRmZWF0dXJlW3NlbnQuZGljdGlvbmFyeSRzZW50aW1lbnQ8MF0pKSkKYGBgCgpOYWNoZGVtIEtvcnB1cyB1bmQgTGV4aWtvbi1Sb2hkYXRlbiBnZWxhZGVuIHd1cmRlbiwgZXJzdGVsbGVuIHdpciBhdXMgZGVyIHVtZmFuZ3JlaWNoZW4gU2VudGltZW50LVdvcnRsaXN0ZSB2b24gQ2hyaXN0aWFuIFJhdWggZWluIHF1YW50ZWRhLUxleGlrb24gKGRhYmVpIG11c3Mgw6RobmxpY2ggd2llIGJlaW0gQUZJTk4tRGlrdGlvbmFyeSBub2NoIGV0d2FzIHVtZ2Vmb3JtdCB3ZXJkZW4pLiBBbnNjaGxpZcOfZW5kIGJlcmVjaG5lbiB3aXIgZGFubiBlaW5lIERGTSwgd2VsY2hlIFNlbnRpbWVudC1Xw7ZydGVyIG5hY2ggUGFydGVpZW4gYXVzesOkaGx0LiAgCgpgYGB7ciBERk0gbmFjaCBQYXJ0ZWkgZsO8ciBkYXMgQnVuZGVzdGFncy1Lb3JwdXMgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnBhcnRlaSA8LSBkZm0oa29ycHVzLmJ1bmRlc3RhZywgZ3JvdXBzID0gInBhcnR5IiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcucGFydGVpCmBgYAoKQXVjaCBoaWVyIHBsb3R0ZW4gd2lyIHdpZWRlciBkYXMgUmVzdWx0YXQuIERpZSBOb3JtYWxpc2llcnVuZywgd2VsY2hlIHdpciBpbSB2b3JhdXNnZWdhbmdlbmVuIEJlaXNwaWVsIGR1cmNoZ2Vmw7xocnQgaGFiZW4sIGxhc3NlbiB3aXIgYW4gZGllc2VyIFN0ZWxsZSB3ZWcsIHVtIGF1Y2ggZWluZW4gRWluZHJ1Y2sgdm9uIGRlbiBSZWRlbmFudGVpbGVuIGRlciBQYXJ0ZWllbiB6dSBlcmhhbHRlbi4gCgpgYGB7ciBERk0gaW4gZWluZW4gRGF0YSBGcmFtZSB1bXdhbmRlbG4gdW5kIGFic29sdXRlIFNlbnRpbWVudC1WZXJ0ZWlsdW5nIHBsb3R0ZW59CnNlbnRpbWVudC5idW5kZXN0YWcucGFydGVpIDwtIGNvbnZlcnQobWVpbmUuZGZtLmJ1bmRlc3RhZy5wYXJ0ZWksICJkYXRhLmZyYW1lIikgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikKZ2dwbG90KHNlbnRpbWVudC5idW5kZXN0YWcucGFydGVpLCBhZXMoZG9jX2lkLCBTZW50aW1lbnQsIGNvbG91ciA9IFBvbGFyaXTDpHQsIGZpbGwgPSBQb2xhcml0w6R0KSkgKyAKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsgc2NhbGVfY29sb3VyX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMSIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIGdndGl0bGUoIlNlbnRpbWVudC1TY29yZXMgaW0gRGV1dHNjaGVuIEJ1bmRlc3RhZyBuYWNoIFBhcnRlaSIpICsgCiAgeGxhYigiIikgKyB5bGFiKCJXw7ZydGVyIikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQpgYGAKCkFuIGRpZXNlbSBFcmdlYm5pcyDDvGJlcnJhc2NodCB2aWVsbGVpY2h0IGV0d2FzLCBkYXNzIGRhcyBTZW50aW1lbnQgZGVyIFJlZ2llcnVuZ3NwYXJ0ZWllbiBuaWNodCBtZXJrbGljaCBwb3NpdGl2ZXIgYXVzZsOkbGx0LCBhbHMgZGFzIGRlciBPcHBvc2l0aW9uLiBad2FyIGhhdCBMaW5rZSBhbnRlaWxpZyBkYXMgbmVnYXRpdnN0ZSBTZW50aW1lbnQgdW5kIGRpZSBDU1UgZGFzIHBvc2l0aXZzdGUsIGFiZXIgZGllIFVudGVyc2NoaWVkZSBzaW5kIG5pY2h0IHNlaHIgZ3Jvw58uIAoKTnVuIHdpZWRlcmhvbGVuIHdpciBkaWUgRXJzdGVsbHVuZyBkZXIgREZNLCBncnVwcGllcmVuIGFiZXIgZGllc21hbCBuaWNodCBuYWNoIFBhcnRlaSwgc29uZGVybiBuYWNoIFNpdHp1bmcuIERhcyBaaWVsIGRpZXNlcyBBbnNhdHplcyBiZXN0ZWh0IGRhcmluLCBTaXR6dW5nZW4genUgaWRlbnRpZml6aWVyZW4sIGluIGRlbmVuIGJlc29uZGVycyBuZWdhdGl2ZSBUaGVtZW4gKHdhcyBoaWVyIG1laHJlcmUgQmVkZXV0dW5nZW4gaGFiZW4ga2FubikgenUgZmluZGVuLiAKCmBgYHtyIERGTSBuYWNoIFNpdHp1bmcgZ3J1cHBpZXJlbiB1bmQgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnNpdHp1bmcgPC0gZGZtKGtvcnB1cy5idW5kZXN0YWcsIGdyb3VwcyA9ICJzaXR6dW5nIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcuc2l0enVuZy5wcm9wIDwtIGRmbV93ZWlnaHQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLCBzY2hlbWUgPSAicHJvcCIpCmhlYWQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLnByb3ApCmBgYAoKTmFjaGRlbSB3aXIgZGFzIGFudGVpbGlnZSBTZW50aW1lbnQgZsO8ciBhbGxlIDI0MyBTaXR6dW5nZW4gZGVyIExlZ2lzbGF0dXJwZXJpb2RlIDIwMTMtMjAxNyBiZXN0aW1tdCBoYWJlbiwgc2thbGllcmVuIHdpciBkaWUgRGF0ZW4gd2llZGVyIHVuZCBwbG90dGVuIGRhcyBSZXN1bHRhdCBpbiBlaW5lciAoc2VociBkaWNodGVuKSBaZWl0cmVpaGUuIEbDvHIgZGFzIFBsb3QgZmlsdGVybiB3aXIgZGFiZWkgZGllIERhdGVuIHNvLCBkYXNzIG51ciBkYXMgSmFociAyMDE1IChTaXR6dW5nZW4gMS02MCkgZGFyZ2VzdGVsbHQgd2lyZC4KCmBgYHtyIFZlcnJlY2huZXRlIFNlbnRpbWVudC1TY29yZXMgbmFjaCBTaXR6dW5nc3ZlcmxhdWYgcGxvdHRlbn0Kc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nIDwtIGNvbnZlcnQobWVpbmUuZGZtLmJ1bmRlc3RhZy5zaXR6dW5nLnByb3AsICJkYXRhLmZyYW1lIikgJT4lIAogIHJlbmFtZShTaXR6dW5nID0gZG9jX2lkLCBTZW50aW1lbnQgPSBwb3NpdGl2ZSkgJT4lCiAgc2VsZWN0KFNpdHp1bmcsIFNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKFNlbnRpbWVudCA9IHJlc2NhbGUoU2VudGltZW50LCB0byA9IGMoLTEsMSkpKSAlPiUgCiAgbXV0YXRlKFNpdHp1bmcgPSBhc19mYWN0b3IoU2l0enVuZykpICU+JSAKICBzbGljZSgxOjYwKQpnZ3Bsb3Qoc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nLCBhZXMoU2l0enVuZywgU2VudGltZW50LCBncm91cCA9IDEpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAibGlnaHRncmF5IikgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41LCBzaXplID0gOCkpICsgCiAgZ2d0aXRsZSgiVmVycmVjaG5ldGUgU2VudGltZW50LVNjb3JlcyBpbiBTaXR6dW5nZW4gZGVyIERldXRzY2hlbiBCdW5kZXN0YWdzIikKYGBgCgrDhGhubGljaCB3aWUgYmVpIFRydW1wIHVuZCBDbGludG9uIGzDpHNzdCBzaWNoIGVpbiBrbGFyZXIgQmV6dWcgendpc2NoZW4gU2VudGltZW504oCTU2NvcmVzIHVuZCBoaXN0b3Jpc2NoZW4gRXJlaWduaXNzZW4gaGVyc3RlbGxlbiwgd2VubiBtYW4gZGVuIEJsaWNrIGF1ZiBzb2xjaGUgU2l0enVuZ2VuIG1pdCBiZXNvbmRlcnMgaG9oZW4vbmllZHJpZ2VuIFNlbnRpbWVudC1TY29yZXMgbGVua3QuIEZvbGdlbmQgd8OkaGxlbiB3aXIgc29sY2hlIFNpdHp1bmdlbiBhdXMgdW5kIHJlaWNoZXJuIGRpZSBJbmZvcm1hdGlvbmVuIHp1ciBTaXR6dW5nIG1pdCBkZW4gZW50aGFsdGVuZW4gVGFnZXNvcmRudW5nc3B1bmt0ZW4gKFRPUHMpIGFuLiBEYXMgU2VudGltZW50IGlzdCBhbGxlcmRpbmdzIGluIGRlciBuYWNoc3RlaGVuZGVuIFRhYmVsbGUgZsO8ciBkaWUgZ2VzYW10ZSBTaXR6dW5nLCBuaWNodCBudXIgZsO8ciBkZW4gamV3ZWlsaWdlbiBUT1Aga2Fsa3VsaWVydC4gCgpgYGB7ciBQb2xhcml0w6R0IGRlciBTaXR6dW5nZW4gbWl0IGRlbiBUYWdlc29yZG51bmdzcHVua3RlbiBpbiBCZXppZWh1bmcgc2V0emVufQpsb2FkKCJkYXRlbi9idW5kZXN0YWcvdGFnZXNvcmRudW5nc3B1bmt0ZS5SRGF0YSIpCnRhZ2Vzb3JkbnVuZ3NwdW5rdGUgPC0gc2VsZWN0KHRhZ2Vzb3JkbnVuZ3NwdW5rdGUsIHNpdHp1bmcsIGhlbGRfb24sIG5hbWUsIGNhdGVnb3J5KQpidW5kZXN0YWcucG9zc2l0enVuZyA8LSBoZWFkKGFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zaXR6dW5nLCBkZXNjKHNlbnRpbWVudC5idW5kZXN0YWcuc2l0enVuZyRTZW50aW1lbnQpKSwgMykKYnVuZGVzdGFnLnBvc3NpdHp1bmckU2l0enVuZyA8LSBhcy5udW1lcmljKGJ1bmRlc3RhZy5wb3NzaXR6dW5nJFNpdHp1bmcpCmJ1bmRlc3RhZy5uZWdzaXR6dW5nIDwtIGhlYWQoYXJyYW5nZShzZW50aW1lbnQuYnVuZGVzdGFnLnNpdHp1bmcsIHNlbnRpbWVudC5idW5kZXN0YWcuc2l0enVuZyRTZW50aW1lbnQpLCAzKQpidW5kZXN0YWcubmVnc2l0enVuZyRTaXR6dW5nIDwtIGFzLm51bWVyaWMoYnVuZGVzdGFnLm5lZ3NpdHp1bmckU2l0enVuZykKIyBTaXR6dW5nZW4gbWl0IHBvc2l0aXZlbSBTZW50aW1lbnQKbGVmdF9qb2luKGJ1bmRlc3RhZy5wb3NzaXR6dW5nLCB0YWdlc29yZG51bmdzcHVua3RlLCBieSA9IGMoIlNpdHp1bmciID0gInNpdHp1bmciKSkKIyBTaXR6dW5nZW4gbWl0IG5lZ2F0aXZlbSBTZW50aW1lbnQKbGVmdF9qb2luKGJ1bmRlc3RhZy5uZWdzaXR6dW5nLCB0YWdlc29yZG51bmdzcHVua3RlLCBieSA9IGMoIlNpdHp1bmciID0gInNpdHp1bmciKSkKYGBgCgpCZWltIHBvc2l0aXZlbiBTZW50aW1lbnQgc3RpY2h0IGRpZSBTaXR6dW5nIHZvbSAxNy4gRGV6ZW1iZXIgMjAxMyBoZXJhdXMsIGluIGRlciBBbmdlbGEgTWVya2VsIHZlcmVpZGlndCB3dXJkZSAtLSBkZW0gQW5sYXNzIHVuZCBkZW4gbmFoZW5kZW4gV2VpaG5hY2h0c2ZlcmllbiBlbnRzcHJlY2hlbmQgaXN0IGRpZSBTdGltbXVuZyBndXQuIE5lZ2F0aXYgZmFsbGVuIFNpdHp1bmdlbiB6dW0ga29udHJvdmVyc2VuIGF1c3NlbnBvbGl0aXNjaGVuIFRoZW1lbiBlYmVuc28gd2llIHp1bSBDRVRBLUFia29tbWVuIG9kZXIgenVyIEJla8OkbXBmdW5nIGRlciBLaW5kZXJhcm11dCBhdWYuIERpZSBiZWlkZW4gVE9Qcywgd2VsY2hlIGRpZSBMaXN0ZSBhbmbDvGhyZW4gKCdSZWdpZXJ1bmdzZXJrbMOkcnVuZyBIdW1hbml0w6RyZSBIaWxmZSBpbSBJcmFrJyBzb3dpZSAnU3RhYmlsaXTDpHRzaGlsZmUgenVndW5zdGVuIEdyaWVjaGVubGFuZHMnKSBsaWVmZXJuIGJlaSBhdWdlbnNjaGVpbmxpY2hlciBCZXJ0cmFjaHR1bmcgZWluIGd1dGVzIE1hw58gZGVyIGJlc29uZGVycyBrb250cm92ZXJzZW4gVGhlbWVuIHp1csO8Y2ssIGJlaSBkZW5lbiBkaWUgUmVnaWVydW5nIG1pdCBLcml0aWsgZGVyIE9wcG9zaXRpb24gb2RlciBhdXMgZGVuIGVpZ25lbmVuIFJlaWhlbiB1bWdlaGVuIG11c3N0ZS4KCkhpZXJiZWkgYmVyw7xja3NpY2h0aWd0IHVuc2VyZSBIZXJhbmdlaGVuc3dlaXNlIGluZGVzIG5pY2h0LCBvYiBlaW4gVGhlbWEgbmVnYXRpdmUgw4R1w59lcnVuZ2VuIG5hY2ggc2ljaCB6aWVodCwgd2VpbCBlcyBzaWNoIHVtIGVpbmVuIG5lZ2F0aXZlbiBTYWNodmVyaGFsdCAoZXR3YSBlaW5lIE5hdHVya2F0YXN0cm9waGUpIGhhbmRlbHQsIGRlciBhYmVyIG5pY2h0IGF1dG9tYXRpc2NoIGtvbnRyb3ZlcnMgc2VpbiBtdXNzLCBvZGVyIG9iIEtyaXRpayBhbiBkZXIgUmVnaWVydW5nIGdlw7xidCB3aXJkLgoKRXR3YXMgenUgaW5kaXZpZHVlbGxlbiBVbnRlcnNjaGllZGVuIGVyZmFocmVuIHdpciwgd2VubiB3aXIgZGllIFNwcmVjaGVyIG1pdCBiZXNvbmRlcnMgcG9zaXRpdmVtIHVuZCBuZWdhdGl2ZW0gU2VudGltZW50IG5hY2ggUGFydGVpenVnZWjDtnJpZ2tlaXQgYmV0cmFjaHRlbi4gV2llZGVyIGJlcmVjaG5lbiB3aXIgaGllcnp1IGVpbmUgZ3J1cHBpZXJ0ZSBERk0uIAoKYGBge3IgREZNIG5hY2ggU3ByZWNoZXIgZ3J1cHBpZXJlbiB1bmQgYmVyZWNobmVufQptZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyIDwtIGRmbShrb3JwdXMuYnVuZGVzdGFnLCBncm91cHMgPSAic3BlYWtlcl9jbGVhbmVkIiwgZGljdGlvbmFyeSA9IHNlbnRpbWVudC5sZXhpa29uLnJhdWgpCm1laW5lLmRmbS5idW5kZXN0YWcuc3ByZWNoZXIucHJvcCA8LSBkZm1fd2VpZ2h0KG1laW5lLmRmbS5idW5kZXN0YWcuc3ByZWNoZXIsIHNjaGVtZSA9ICJwcm9wIikKaGVhZChtZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyLnByb3ApCmBgYAoKTnVuIHppZWhlbiB3aXIgbm9jaCBkaWUgUGFydGVpenVnZWjDtnJpZ2tlaXQgYXVzIGRlbiBNZXRhZGF0ZW4gaGluenUsIHNrYWxpZXJlbiBkYXMgU2VudGltZW50LUVyZ2VibmlzLCB1bmQgc29ydGllcmVuIHNjaGxpZcOfZWxpY2ggZGllIDYzNyBBYmdlb3JkbmV0ZW4gZWlubWFsIGFic3RlaWdlbmQgdW5kIGVpbm1hbCBhdWZzdGVpZ2VuZCAocy51LikuIAoKYGBge3IgUG9sYXJpdMOkdCBqZSBTcHJlY2huZXIgYmVzdGltbWVuIHVuZCBzb3J0aWVyZW59CnNlbnRpbWVudC5idW5kZXN0YWcuc3ByZWNoZXIgPC0gY29udmVydChtZWluZS5kZm0uYnVuZGVzdGFnLnNwcmVjaGVyLCAiZGF0YS5mcmFtZSIpICU+JSAKICBsZWZ0X2pvaW4odW5pcXVlKHNlbGVjdChrb3JwdXMuYnVuZGVzdGFnLnN0YXRzLCBzcGVha2VyX2NsZWFuZWQsIHBhcnR5KSksIGJ5ID0gYygiZG9jX2lkIiA9ICJzcGVha2VyX2NsZWFuZWQiKSkgJT4lIAogIHJlbmFtZShTcHJlY2hlciA9IGRvY19pZCwgUGFydGVpID0gcGFydHkpICU+JSAKICBmaWx0ZXIocG9zaXRpdmUgIT0gMCwgbmVnYXRpdmUgIT0gMCkgJT4lIAogIGdhdGhlcihwb3NpdGl2ZSwgbmVnYXRpdmUsIGtleSA9ICJQb2xhcml0w6R0IiwgdmFsdWUgPSAiU2VudGltZW50IikgJT4lIAogIGZpbHRlcihQb2xhcml0w6R0ID09ICJwb3NpdGl2ZSIpICU+JSAKICBtdXRhdGUoU2VudGltZW50ID0gcmVzY2FsZShTZW50aW1lbnQsIHRvID0gYygtMSwxKSkpICU+JSAKICBzZWxlY3QoU2VudGltZW50LCBTcHJlY2hlciwgUGFydGVpKQojIFNwcmVjaGVyIG1pdCBwb3NpdGl2ZW0gU2VudGltZW50CmFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zcHJlY2hlciwgZGVzYyhTZW50aW1lbnQpKQojIFNwcmVjaGVyIG1pdCBuZWdhdGl2ZW0gU2VudGltZW50CmFycmFuZ2Uoc2VudGltZW50LmJ1bmRlc3RhZy5zcHJlY2hlciwgU2VudGltZW50KQpgYGAKCkRlciBkYW1hbGlnZSBCdW5kZXN0YWdzcHLDpHNpZGVudCBOb3JiZXJ0IExhbW1lcnQgZsO8aHJ0IGRpZSBQb3NpdGl24oCTTGlzdGUgYW4sIGdlZm9sZ3Qgdm9uIGRlbiBWaXplcHLDpHNpZGVudGlubmVuIENsYXVkaWEgUm90aCwgVWxsYSBTY2htaWR0LCBFZGVsZ2FyZCBCdWxtYWhuIHVuZCBQZXRyYSBQYXUsIGRhendpc2NoZW4gZGllIEJ1bmRlc2thbnpsZXJpbiBBbmdlbGEgTWVya2VsIHVuZCBkZXIgZGFtYWxpZ2UgVml6ZWthbnpsZXIgU2lnbWFyIEdhYnJpZWwuIERpZSBob2hlbiBXZXJ0ZSBmw7xyIGRpZSBWZXJ0cmV0ZXIgZGVyIHdpY2h0aWdzdGVuIHBhcmxhbWVudGFyaXNjaGVuIMOEbXRlciBzaW5kIHdlbmlnIMO8YmVycmFzY2hlbmQuIEJlaSBkZW4gTmVnYXRpdndlcnRlbiBmw6RsbHQgYXVmLCBkYXNzIGRpZSBSZWdpZXJ1bmdzcGFydGVpZW4gQ0RVL0NTVS9TUEQgw7xiZXJyYXNjaGVuZCBzdGFyayB2ZXJ0cmV0ZW5kIHNpbmQsIGF1Y2ggd2VubiBkaWUgTGlzdGUgdm9uIE9wcG9zaXRpb25zcG9saXRpa2VyaW5uZW4gZGVyIEdyw7xuZW4gYnp3LiBMaW5rZW4gYW5nZWbDvGhydCB3aXJkLiAKCkVpbmUgd2lya2xpY2ggYWTDpHF1YXRlIEFuYWx5c2UgbcO8c3N0ZSB1bnRlciBhbmRlcmVtIGhpZXIgbm9jaCBTcHJlY2hlciBtaXQgc2VociBnZXJpbmdlbiBSZWRlYW50ZWlsZW4gYXVzc2NobGllw59lbiwgZGEgZGVyZW4gU2VudGltZW50LVNjb3JlIGR1cmNoIGVpbmlnZSB3ZW5pZ2UgQmVncmlmZmUgaW4gZGllIGVpbmUgb2RlciBhbmRlcmUgUmljaHR1bmcgYXVzc2NobGFnZW4ga2FubiwgYXVjaCB3ZW5uIGRpZXMgaW50dWl0aXYga2VpZXJsZWkgU2lubiBlcmdpYnQuIFVuZCB3aWUgZWluZ2FuZ3MgYmVzY2hyaWViZW4gbXVzcyBtYW4gZGVuIEtvbnRleHQgZWluZXIgw4R1w59lcnVuZyB1bmQgZGllIFp1Z2Vow7ZyaWdrZWl0IHp1ciBSZWdpZXJ1bmcgYnp3LiBPcHBvc2l0aW9uIGhlcmFuemllaGVuLCB3YXMgZXR3YSBkdXJjaCBkaWUgVW50ZXJzdWNodW5nIGRlciBUT1BzIGdydW5kc8OkdHpsaWNoIG3DtmdsaWNoIHdpcmQuIAoKQWJzY2hsaWXDn2VuZCBzb2xsdGUgZmVzdGdlaGFsdGVuIHdlcmRlbiwgZGFzcyBTZW50aW1lbnRhbmFseXNlIG1pdHRlbHMgUXVhbnRlZGEgw7xiZXIgYXVzIEF1c3rDpGhsZW4gdm9uIEJlZ3JpZmZlbiBhYmzDpHVmdCwgYWxsZXJkaW5ncyBhdWNoIGxlaWNodGUgVmFyaWF0aW9uZW4gZGllc2VzIEFuc2F0emVzIGV4aXN0aWVyZW4uIHNvIHdpbGwgbWFuIGV0d2EgZsO8ciBUd2VldHMgYWJlciBhdWNoIGbDvHIgZWluemVsbmVuIFPDpHR6ZW4gaW4gZWluZW0gRGViYXR0ZW5rb3JwdXMgZWluIFNlbnRpbWVudCBmZXN0bGVnZW4sIHdlbGNoZXMgbmljaHQgYWxsZWluIGF1cyBkZXIgQWRkaXRpb24gdm9uIHBvc2l0aXZlbi9uZWdhdGl2ZW4gV8O2cnRlcm4gZXJnaWJ0LiBEYXJhdWYgd2lyZCBzcMOkdGVyIG5vY2ggZWluZ2VnYW5nZW4sIGFsbGVyZGluZ3MgZXJyZWljaGVuIGF1Y2ggZGllIGJlcmVpdHMgYmVzY2hyaWViZW5lbiBWZXJmYWhyZW4gaW5zZ2VzYW10IHJlY2h0IGd1dGUgRXJnZWJuaXNzZSwgc29sYW5nZSBkaWUgS29ycHVzZ3LDtsOfZSB1bmQgZGllIEdlbmF1aWdrZWl0IGRlcyBMZXhpa29ucyBhdXNyZWljaGVuZCBzaW5kLiAKCgo=