In der täglichen Arbeit begegnen uns Daten nicht immer in Tabellenform. Wenn wir an die vorhergehenden Projekte denken, so konnten wir die benötigten Daten auf der jeweiligen Webseite oder eines API, mittels eines Download-Buttons herunterladen. Doch auf vielen Webseiten steht ein solcher Download-Button nicht zur Verfügung. Die Informationen sind in der HTML der Webseite eingebettet und nur von dort verfügbar. Um solche Informationen zu analysieren müssen wir diese zuerst in ein für uns geeignetes Format bringen. Hier hilft uns das sogenannte web scraping weiter, welches in R durch rvest implementiert werden kann.

Der Terminus web scraping wird oft verwendet um den Download von Daten eine Webseite zu beschreiben. Dies ist möglich, da der Computercode, welcher in hyper text markup language (HTML) geschrieben wurde, vom Browser als Text empfangen und entsprechend gerendert wird. Um sich den HTML-Code einer Website anzeigen zu lassen können Sie auf der jeweiligen Website die rechte Maustaste drücken und dann auf Seitenquelltext anzeigen ( View Source ) gehen.

Wenn wir uns HTML-Code anschauen, dann scheint es eher mühsam zu sein die jeweiligen Informationen herunterzuladen und in Tabellenform zu bringen. Jedoch bietet R einige Pakete um den Prozess deutlich zu vereinfachen. Für einen ersten Einblick, wie HTML-Code aussehen könnte, zeigen wir ihnen hier einen Ausschnitt der Seite www.imdb.com mit den besten 250 Filmen. Im Laufe dieses Tutoriums lernen Sie, wie Sie diese Informationen der HTML automatisiert entnehmen.

      <table class="chart full-width" data-caller-name="chart-top250movie">
        <colgroup>
          <col class="chartTableColumnPoster"/>
          <col class="chartTableColumnTitle"/>
          <col class="chartTableColumnIMDbRating"/>
          <col class="chartTableColumnYourRating"/>
          <col class="chartTableColumnWatchlistRibbon"/>
        </colgroup>
        <thead>
        <tr>
          <th></th>
          <th>Rank &amp; Title</th>
          <th>IMDb Rating</th>
          <th>Your Rating</th>
          <th></th>
        </tr>
        </thead>
        <tbody class="lister-list">

  <tr>
    <td class="posterColumn">

    <span name="rk" data-value="1"></span>
    <span name="ir" data-value="9.216784978442458"></span>
    <span name="us" data-value="7.791552E11"></span>
    <span name="nv" data-value="1967223"></span>
    <span name="ur" data-value="-1.7832150215575417"></span>
<a href="/title/tt0111161/?pf_rd_m=A2FGELUUNOQJNL&pf_rd_p=e31d89dd-322d-4646-8962-327b42fe94b1&pf_rd_r=X25RJR4TAEAZG9P04HNC&pf_rd_s=center-1&pf_rd_t=15506&pf_rd_i=top&ref_=chttp_tt_1"
> <img src="https://m.media-amazon.com/images/M/MV5BMDFkYTc0MGEtZmNhMC00ZDIzLWFmNTEtODM1ZmRlYWMwMWFmXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_UY67_CR0,0,45,67_AL_.jpg" width="45" height="67"/>
</a>    </td>
    <td class="titleColumn">
      1.
      <a href="/title/tt0111161/?pf_rd_m=A2FGELUUNOQJNL&pf_rd_p=e31d89dd-322d-4646-8962-327b42fe94b1&pf_rd_r=X25RJR4TAEAZG9P04HNC&pf_rd_s=center-1&pf_rd_t=15506&pf_rd_i=top&ref_=chttp_tt_1"
title="Frank Darabont (dir.), Tim Robbins, Morgan Freeman" >Die Verurteilten</a>
        <span class="secondaryInfo">(1994)</span>
    </td>
    <td class="ratingColumn imdbRating">
            <strong title="9,2 based on 1.967.223 user ratings">9,2</strong>
    </td>
    <td class="ratingColumn">
    <div class="seen-widget seen-widget-tt0111161 pending" data-titleid="tt0111161">
        <div class="boundary">
            <div class="popover">
<span class="delete">&nbsp;</span><ol><li>1<li>2<li>3<li>4<li>5<li>6<li>7<li>8<li>9<li>10</ol>            </div>
        </div>
        <div class="inline">
            <div class="pending"></div>
            <div class="unseeable">NOT YET RELEASED</div>
            <div class="unseen"> </div>
            <div class="rating"></div>
            <div class="seen">Seen</div>
        </div>
    </div>
    </td>
    <td class="watchlistColumn">
        <div class="wlb_ribbon" data-tconst="tt0111161" data-recordmetrics="true"></div>
    </td>
  </tr>

Hier sehen Sie die Daten direkt! Der Ausschnitt zeigt, dass der Film “Die Verurteilten” mit 9,2 Punkten (basierend auf 1 967 223 Bewertungen) laut imdb.com der beste Film aller Zeiten ist. In der HTML finden Sie weiterhin die Hauptdarsteller und das Jahr der Veröffentlichung.

Mittels HTML Tags und CSS können Sie diese Informationen auch aus der HTML extrahieren. Hierzu im Laufe des Tutorials mehr. Wenn Sie sich überlegen zukünftig im Bereich der Datenanalyse zu arbeiten, so ist es immer vorteilhaft etwas über HTML und CSS zu wissen. Dies verbessert ihre web scraping Fähigkeiten und natürlich auch ihre Fähigkeit eigene Webseiten zu gestalten. Tutorials zu HTML und CSS finden sie beispielsweise auf code academy und WWW3 school

Das Paket rvest

Innerhalb von tidyverse gibt es das Paket rvest, mit welchem Sie web scraping in einer ihnen bekannte Syntax durchführen können. Zuerst importieren Sie die gewünschte Seite in R. Hierfür nutzen Sie den Befehl read_html:

library(tidyverse)
library(rvest)
url <- "https://www.imdb.com/chart/top"
imdb250 <- read_html(url)

Nun ist die komplette Website von imdb mit den 250 besten Filmen in imdb250 gespeichert. Die Klasse von imdb250 ist:

class(imdb250)
## [1] "xml_document" "xml_node"

Das rvest Paket ist sehr allgemein gehalten und kann XML Dokumente handhaben. Eine XML Datei ist in einer allgemeinen Markup Sprache geschrieben (daher ML) und kann zur Repräsentation jeglicher Daten dienen. HTML Dateien sind eine Sonderform von XML Dateien um Webseiten darzustellen.

Unser Ziel ist es nun, die Informationen aus der imdb Seite über Ranking, Rating, Anzahl der Stimmen etc. zu extrahieren. Wenn wir uns imdb250 ausgeben lassen, so sind wir diesem Ziel noch nicht näher gekommen, außer das die HTML nun als R Objekt verfügbar ist:

imdb250
## {html_document}
## <html xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
## [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
## [2] <body id="styleguide-v2" class="fixed">\n            <img height="1" widt ...

Denken wir zurück an unseren HTML Ausschnitt von oben. In diesem wird deutlich, dass unsere Informationen sich in einer HTML Tabelle befinden. Zu sehen ist dies an der Code-Zeile <table class="chart full-width" data-caller-name="chart-top250movie">. Die einzelnen Teile einer HTML, welche oft mit einem sprechenden Text zwischen < und > definiert sind, werden als Nodes bezeichnet. Mit dem Paket rvest können wir die Nodes einer HTML extrahieren: html_nodes liest hierbei alle Nodes mit der entsprechenden Definition aus und html_node nur die erste Node. Beispielsweise können wir die Tabelle mit den 250 besten Filmen mit folgendem Befehl in der Variable table speichern:

table <- imdb250 %>% 
  html_node("table")

table
## {html_node}
## <table class="chart full-width" data-caller-name="chart-top250movie">
## [1] <colgroup>\n<col class="chartTableColumnPoster">\n<col class="chartTableC ...
## [2] <thead><tr>\n<th></th>\n          <th>Rank &amp; Title</th>\n          <t ...
## [3] <tbody class="lister-list">\n<tr>\n<td class="posterColumn">\n\n    <span ...

Allerdings sieht diese Tabelle immer noch sehr stark nach HTML aus und weniger nach einem für uns zu bearbeitenden Data Frame. Mittels rvest können wir das jedoch schnell beheben und aus der HTML Tabelle einen Data Frame machen, hierzu nutzen wir die Funktion html_table:

table <- table %>% 
  html_table()

class(table)
## [1] "tbl_df"     "tbl"        "data.frame"
nrow(table)
## [1] 250

Nun sind wir unserem Wunschergebnis schon sehr viel näher:

head(table)
## # A tibble: 6 x 5
##   ``    `Rank & Title`          `IMDb Rating` `Your Rating`                ``   
##   <lgl> <chr>                           <dbl> <chr>                        <lgl>
## 1 NA    "1.\n      Die Verurte…           9.2 "12345678910\n        \n   … NA   
## 2 NA    "2.\n      Der Pate\n …           9.1 "12345678910\n        \n   … NA   
## 3 NA    "3.\n      Der Pate 2\…           9   "12345678910\n        \n   … NA   
## 4 NA    "4.\n      The Dark Kn…           9   "12345678910\n        \n   … NA   
## 5 NA    "5.\n      Die zwölf G…           8.9 "12345678910\n        \n   … NA   
## 6 NA    "6.\n      Schindlers …           8.9 "12345678910\n        \n   … NA

Hier müssten wir immer noch ein paar Dinge umgestalten, wie z.B. die newline ( “” ) entfernen, um zu einem wirklich zufriedenstellenden Ergebnis zu gelangen. Jedoch gibt es bessere und allgemeineren Vorgehensweisen um Informationen aus HTML Seiten zu extrahieren. Hierfür widmen wir uns im nächsten Abschnitt CSS-Selektoren.

CSS-Selectoren

Webseiten, welche ausschließlich mit grundständigem HTML programmiert sind, wirken recht unattraktiv. Um eine ansprechende Webseite zu gestalten greifen die meisten Webmaster auf CSS zurück. Durch CSS können Sie beispielsweise alle Seiten eines Unternehmens einheitlich gestalten, indem sie auf eine einheitliche CSS Datei zurückgreifen. Grundsätzlich können Sie mit CSS das Aussehen jedes Elements einer Webseite bestimmen. Hierunter fallen unter anderem der Titel, Überschriften, Listen, Tabellen und Links, wobei jedes Element seine eigene Schrift, Farbe, Größe etc. erhalten kann. Um die einzelnen Anpassungen zu machen nutzt CSS so genannte Selektoren. Beispielsweise wäre table ein solcher Selektor, welchen wir zuvor verwendet haben.

Wenn wir nun Daten aus einer Webseite herunterladen und wir kennen eben diese Selektoren, so können wir die Informationen direkt mit html_nodes herunterladen. Leider ist es nicht immer einfach herauszufinden, welcher Selektor für welches Element maßgeblich ist. Glücklicherweise gibt es SelectorGadget welches Sie als Lesezeichen zu ihrem Browser hinzufügen können. Bitte schauen Sie sich den Screencast auf www.selectorgadget.com an um einen Einblick in Selectorgadget zu erhalten. Weiterhin erhalten Sie hier eine Einführung in SelectorGadget

Mit SelectorGadget können Sie interaktiv jeden CSS Selektor bestimmen welchen Sie ansteuern möchten. Somit können Sie spezifische Inhalte einer Webseite genau ansteuern und herunterladen. Wenn Sie mit SelectorGadget auf ein Element einer Webseite klicken werden alle mit diesem CSS Selektor zusammenhängenden Objekte entsprechend gelb markiert.

Um die Möglichkeiten von CSS Selektoren etwas besser zu veranschaulichen wollen wir uns wieder imdb.com zuwenden. Im weiteren Verlauf des Tutorials wollen wir Daten zu allen Filme, welche 2018 auf den Markt gekommen sind, herunterladen und analysieren.

Hierzu suchen wir zuerst auf www.imdb.com nach den entsprechenden Filmen um die url herauszufinden Der Link in dem unteren Chunk enthält die url unserer Suche.

imdb2018 <- read_html("https://www.imdb.com/search/title?count=100&release_date=2018,2018")

Schauen wir uns diesen Link etwas genauer an:

  • count: Anzahl der Filme die auf einer Seite gezeigt werden, hier 100
  • release_date: Jahr in dem der Film gedreht wurde (wird als [von,bis] angegeben, d.h. im gesamten Jahr 2018)

Im Folgenden möchten wir uns nun folgende Informationen über den jeweiligen Film genauer anschauen: - Popularität - Titel - Rating - Dauer - Genre - Anzahl der Stimmen

#Sortierung nach Popularität
popul <- imdb2018 %>% 
  html_nodes(".text-primary") %>% 
  html_text() %>% 
  as.numeric()

#Titel
titel <- imdb2018 %>% 
  html_nodes(".lister-item-header a") %>% 
  html_text()

#Rating
rating <- imdb2018 %>% 
  html_nodes(".ratings-imdb-rating strong") %>% 
  html_text() %>% 
  as.numeric()

#Dauer
dauer <- imdb2018 %>% 
  html_nodes(".text-muted .runtime") %>% 
  html_text() %>% 
  str_replace(" min", "") %>% 
  as.numeric()

#Genre (immer das erste Genre als Klassifikation)
genre <- imdb2018 %>% 
  html_nodes(".genre") %>% 
  html_text() %>% 
  str_replace("\n", "") %>% 
  str_replace(" ", "") %>% 
  str_replace(",.*", "") %>% 
  str_trim() %>% 
  as.factor()

#Anzahl an Stimmen
stimmen <- imdb2018 %>% 
  html_nodes(".sort-num_votes-visible span:nth-child(2)") %>% 
  html_text() %>% 
  str_replace(",", "") %>% 
  as.numeric()

Teilweise sind die Selektoren recht komplex und wir bekommen auch nicht immer 100%-ig saubere Werte zurückgeliefert, sondern müssen diese nachträglich bearbeiten. Dies geschieht mit dem Paket stringr, welches Teil des tidyverse ist, insbesondere der Funktion str_replace um bspw. Ersetzungen vorzunehmen. Doch es wird auch deutlich, dass wir durch CSS-Selektoren direkt auf einzelnen Elemente zugreifen können und dadurch deutlich mehr Flexibilität als mit reinen HTML Befehlen haben.

Auf dieser Grundlage können wir ein Data Frame erstellen, welcher alle geforderten Informationen beinhaltet:

Filme2018 <- tibble(popul,titel, rating, dauer, genre, stimmen)
Filme2018
## # A tibble: 100 x 6
##    popul titel               rating dauer genre  stimmen
##    <dbl> <chr>                <dbl> <dbl> <fct>    <dbl>
##  1     1 The Kominsky Method    8.2    30 Comedy   27138
##  2     2 A Quiet Place          7.5    90 Drama   426138
##  3     3 9-1-1: Notruf L.A.     7.7    43 Action   24006
##  4     4 The Rookie             8      43 Comedy   23231
##  5     5 Yellowstone            8.6    60 Drama    36799
##  6     6 New Amsterdam          8.1    43 Drama    25455
##  7     7 Innocent               7.4    45 Crime     3084
##  8     8 Cobra Kai              8.6    30 Action  118685
##  9     9 Mr Inbetween           8.4    30 Crime     6636
## 10    10 FBI                    6.8    60 Action   11378
## # … with 90 more rows

Dadurch, dass alle Seitenelemente dem gleichen Schema folgen, könnten wir für die obere Abfrage auch eine Funktion schreiben.

Diese Funktion können wir anschließend auf die 2. Seite anwenden (und auf alle weiteren):

get_imdb2018 <- function(url){
  imdb <- read_html(url)
  popul <- imdb %>% html_nodes(".text-primary") %>% html_text() %>% as.numeric()
  titel <- imdb %>% html_nodes(".lister-item-header a") %>% html_text()
  rating <- imdb %>% html_nodes(".ratings-imdb-rating strong") %>% html_text() %>% as.numeric()
  dauer <- imdb %>% html_nodes(".text-muted .runtime") %>% html_text() %>% str_replace(" min", "") %>% as.numeric()
  genre <- imdb %>% html_nodes(".genre") %>% html_text() %>% str_replace("\n", "") %>% str_replace(" ", "") %>% 
    str_replace(",.*", "") %>% as.factor()
  stimmen <- imdb %>% html_nodes(".sort-num_votes-visible span:nth-child(2)") %>% html_text() %>% str_replace(",", "") %>% 
    as.numeric()
  tibble(popul,titel, rating, dauer, genre, stimmen)
}
Filme2018_2 <- get_imdb2018("https://www.imdb.com/search/title?count=100&release_date=2018,2018&start=101")

Erkennen Sie, was sich an der url geändert hat?

Richtig! In der url steht nun ein “start=101”, was heißt, dass die 100 Filme, die auf dieser Seite gezeigt werden bei Film 101 im Ranking beginnen. Sie sollten in den urls immer auf derartige Details achten, wenn Sie mehrere Seiten scrapen möchten!

Die Informationen zu den Top 100 Filmen in 2018, welche wir uns heruntergeladen haben, können wir nun auch analysieren. Beispielsweise wäre interessant zu wissen, welche Filme in 2017 die meisten Stimmen mit dem höchsten Rating erhalten haben. Dies getrennt nach Genre und Dauer des Films:

options(scipen = 999)

#which genre has the highest votes
ggplot(Filme2018,aes(x=dauer,y=rating)) + 
  geom_point(aes(size=stimmen,col=genre)) +
  theme_minimal() +
  labs(title = "Welches Genre hat das höchste ImdB Rating?",
       x = "Dauer (in Minuten)",
       y = "Rating")

Zur Übung könnten Sie sich auch noch die nächsten 100 Filme genauer anschauen (diese haben Sie in dem Tutorial nämlich auch eingelesen).

Wenn Sie sich vertieft mit web scraping beschäftigen wollen so können Sie sich insbesondere noch die Funktionen html_form, set_values, und submit_form anschauen. Jedoch gehen diese Funktionen für eine Einführung in das web scraping etwas zu weit.

Seien Sie nett im Internet

Angenommen Sie möchten nun nicht nur zwei Seiten von IMDb auswerten, sondern alle Filme, die aus dem Jahr 2018 bei IMDb hinterlegt sind (insgesamt 380 682 Filme). Insbesondere wenn Sie größere Datenmengen aus dem Internet analysieren möchten sollten Sie sich darüber im Klaren sein, dass jede ihrer Anfragen auf dem Server des Betreibers landet und dort zu Traffic führt.

Daraus ergeben sich für Sie einige Verhaltensregeln beim web scraping:

  1. Sie sollten die robots.txt Datei respektieren. Wenn in dieser steht, dass Sie einzelne Elemente der Webseite nicht herunterladen dürfen, dann sollten Sie das auch nicht tun.
    • Die robots.txt Datei finden Sie indem Sie an die jeweilige url “robots.txt” anhängen.
    • Beispielsweise finden Sie die “robots.txt” Datei von IMDb hier: “https://www.imdb.com/robots.txt
  2. Seien Sie nett, Sie sollten die Webseite, die Sie scrapen wollen nicht überlasten
    • D.h. wenn Sie mehrere Webseiten herunterladen, dann sollten Sie zwischen den einzelnen Aufrufen immer einige Sekunden Pause einlegen
    • Dies können Sie in R durch “Sys.sleep(x)” erreichen, wobei “x” die Anzahl an Sekunden darstellt, die das System pausiert
    • Sie sollten i.d.R. zwischen zwei Seitenaufrufen rund 5 Sekunden Pause lassen, hier ein Beispiel für eine zufällige Pause von 5-10 Sekunden:
#Durch diese Funktion ruht das System zufällig zwischen 5-10 Sekunden
Sys.sleep(sample(5:10),1)
  1. Laden Sie ihre Daten immer herunter und speichern diese lokal ab um für eine spätere Analyse nicht noch einmal die Webseite aufrufen zu müssen!
    • Diese Regel ist nicht nur im Sinne des Systemadmins der Webseite, sondern auch in ihrem Interesse um ihre Analyse reproduzierbar zu gestalten
    • Um eine Datei herunterzuladen können Sie z.B. “download.file()” verwenden:
#Durch diese Funktion laden Sie die Zielseite lokal auf ihren PC
download.file("https://www.imdb.com/search/title?count=100&release_date=2018,2018", "IMDB_2018_Movies.html")

einlesen <- read_html("IMDB_2018_Movies.html")
LS0tCnRpdGxlOiAiRWluZsO8aHJ1bmcgaW4gV2ViLVNjcmFwaW5nIG1pdCBSIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogY29zbW8KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKSW4gZGVyIHTDpGdsaWNoZW4gQXJiZWl0IGJlZ2VnbmVuIHVucyBEYXRlbiBuaWNodCBpbW1lciBpbiBUYWJlbGxlbmZvcm0uIFdlbm4gd2lyIGFuIGRpZSB2b3JoZXJnZWhlbmRlbiBQcm9qZWt0ZSBkZW5rZW4sIHNvIGtvbm50ZW4gd2lyIGRpZSBiZW7DtnRpZ3RlbiBEYXRlbiBhdWYgZGVyIGpld2VpbGlnZW4gV2Vic2VpdGUgb2RlciBlaW5lcyBBUEksIG1pdHRlbHMgZWluZXMgRG93bmxvYWQtQnV0dG9ucyBoZXJ1bnRlcmxhZGVuLiBEb2NoIGF1ZiB2aWVsZW4gV2Vic2VpdGVuIHN0ZWh0IGVpbiBzb2xjaGVyIERvd25sb2FkLUJ1dHRvbiBuaWNodCB6dXIgVmVyZsO8Z3VuZy4gRGllIEluZm9ybWF0aW9uZW4gc2luZCBpbiBkZXIgSFRNTCBkZXIgV2Vic2VpdGUgZWluZ2ViZXR0ZXQgdW5kIG51ciB2b24gZG9ydCB2ZXJmw7xnYmFyLiBVbSBzb2xjaGUgSW5mb3JtYXRpb25lbiB6dSBhbmFseXNpZXJlbiBtw7xzc2VuIHdpciBkaWVzZSB6dWVyc3QgaW4gZWluIGbDvHIgdW5zIGdlZWlnbmV0ZXMgRm9ybWF0IGJyaW5nZW4uIEhpZXIgaGlsZnQgdW5zIGRhcyBzb2dlbmFubnRlIF93ZWIgc2NyYXBpbmdfIHdlaXRlciwgd2VsY2hlcyBpbiBSIGR1cmNoIGBydmVzdGAgaW1wbGVtZW50aWVydCB3ZXJkZW4ga2Fubi4KCkRlciBUZXJtaW51cyBfd2ViIHNjcmFwaW5nXyB3aXJkIG9mdCB2ZXJ3ZW5kZXQgdW0gZGVuIERvd25sb2FkIHZvbiBEYXRlbiBlaW5lIFdlYnNlaXRlIHp1IGJlc2NocmVpYmVuLiBEaWVzIGlzdCBtw7ZnbGljaCwgZGEgZGVyIENvbXB1dGVyY29kZSwgd2VsY2hlciBpbiBoeXBlciB0ZXh0IG1hcmt1cCBsYW5ndWFnZSAoSFRNTCkgZ2VzY2hyaWViZW4gd3VyZGUsIHZvbSBCcm93c2VyIGFscyAqKlRleHQqKiBlbXBmYW5nZW4gdW5kIGVudHNwcmVjaGVuZCBnZXJlbmRlcnQgd2lyZC4gVW0gc2ljaCBkZW4gSFRNTC1Db2RlIGVpbmVyIFdlYnNpdGUgYW56ZWlnZW4genUgbGFzc2VuIGvDtm5uZW4gU2llIGF1ZiBkZXIgamV3ZWlsaWdlbiBXZWJzaXRlIGRpZSByZWNodGUgTWF1c3Rhc3RlIGRyw7xja2VuIHVuZCBkYW5uIGF1ZiBfU2VpdGVucXVlbGx0ZXh0IGFuemVpZ2VuXyAoIF9WaWV3IFNvdXJjZV8gKSBnZWhlbi4KCldlbm4gd2lyIHVucyBIVE1MLUNvZGUgYW5zY2hhdWVuLCBkYW5uIHNjaGVpbnQgZXMgZWhlciBtw7xoc2FtIHp1IHNlaW4gZGllIGpld2VpbGlnZW4gSW5mb3JtYXRpb25lbiBoZXJ1bnRlcnp1bGFkZW4gdW5kIGluIFRhYmVsbGVuZm9ybSB6dSBicmluZ2VuLiBKZWRvY2ggYmlldGV0IFIgZWluaWdlIFBha2V0ZSB1bSBkZW4gUHJvemVzcyBkZXV0bGljaCB6dSB2ZXJlaW5mYWNoZW4uIEbDvHIgZWluZW4gZXJzdGVuIEVpbmJsaWNrLCB3aWUgSFRNTC1Db2RlIGF1c3NlaGVuIGvDtm5udGUsIHplaWdlbiB3aXIgaWhuZW4gaGllciBlaW5lbiBBdXNzY2huaXR0IGRlciBTZWl0ZSB3d3cuaW1kYi5jb20gbWl0IGRlbiBiZXN0ZW4gMjUwIEZpbG1lbi4gSW0gTGF1ZmUgZGllc2VzIFR1dG9yaXVtcyBsZXJuZW4gU2llLCB3aWUgU2llIGRpZXNlIEluZm9ybWF0aW9uZW4gZGVyIEhUTUwgYXV0b21hdGlzaWVydCBlbnRuZWhtZW4uCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQogICAgICA8dGFibGUgY2xhc3M9ImNoYXJ0IGZ1bGwtd2lkdGgiIGRhdGEtY2FsbGVyLW5hbWU9ImNoYXJ0LXRvcDI1MG1vdmllIj4KICAgICAgICA8Y29sZ3JvdXA+CiAgICAgICAgICA8Y29sIGNsYXNzPSJjaGFydFRhYmxlQ29sdW1uUG9zdGVyIi8+CiAgICAgICAgICA8Y29sIGNsYXNzPSJjaGFydFRhYmxlQ29sdW1uVGl0bGUiLz4KICAgICAgICAgIDxjb2wgY2xhc3M9ImNoYXJ0VGFibGVDb2x1bW5JTURiUmF0aW5nIi8+CiAgICAgICAgICA8Y29sIGNsYXNzPSJjaGFydFRhYmxlQ29sdW1uWW91clJhdGluZyIvPgogICAgICAgICAgPGNvbCBjbGFzcz0iY2hhcnRUYWJsZUNvbHVtbldhdGNobGlzdFJpYmJvbiIvPgogICAgICAgIDwvY29sZ3JvdXA+CiAgICAgICAgPHRoZWFkPgogICAgICAgIDx0cj4KICAgICAgICAgIDx0aD48L3RoPgogICAgICAgICAgPHRoPlJhbmsgJmFtcDsgVGl0bGU8L3RoPgogICAgICAgICAgPHRoPklNRGIgUmF0aW5nPC90aD4KICAgICAgICAgIDx0aD5Zb3VyIFJhdGluZzwvdGg+CiAgICAgICAgICA8dGg+PC90aD4KICAgICAgICA8L3RyPgogICAgICAgIDwvdGhlYWQ+CiAgICAgICAgPHRib2R5IGNsYXNzPSJsaXN0ZXItbGlzdCI+CgogIDx0cj4KICAgIDx0ZCBjbGFzcz0icG9zdGVyQ29sdW1uIj4KCiAgICA8c3BhbiBuYW1lPSJyayIgZGF0YS12YWx1ZT0iMSI+PC9zcGFuPgogICAgPHNwYW4gbmFtZT0iaXIiIGRhdGEtdmFsdWU9IjkuMjE2Nzg0OTc4NDQyNDU4Ij48L3NwYW4+CiAgICA8c3BhbiBuYW1lPSJ1cyIgZGF0YS12YWx1ZT0iNy43OTE1NTJFMTEiPjwvc3Bhbj4KICAgIDxzcGFuIG5hbWU9Im52IiBkYXRhLXZhbHVlPSIxOTY3MjIzIj48L3NwYW4+CiAgICA8c3BhbiBuYW1lPSJ1ciIgZGF0YS12YWx1ZT0iLTEuNzgzMjE1MDIxNTU3NTQxNyI+PC9zcGFuPgo8YSBocmVmPSIvdGl0bGUvdHQwMTExMTYxLz9wZl9yZF9tPUEyRkdFTFVVTk9RSk5MJnBmX3JkX3A9ZTMxZDg5ZGQtMzIyZC00NjQ2LTg5NjItMzI3YjQyZmU5NGIxJnBmX3JkX3I9WDI1UkpSNFRBRUFaRzlQMDRITkMmcGZfcmRfcz1jZW50ZXItMSZwZl9yZF90PTE1NTA2JnBmX3JkX2k9dG9wJnJlZl89Y2h0dHBfdHRfMSIKPiA8aW1nIHNyYz0iaHR0cHM6Ly9tLm1lZGlhLWFtYXpvbi5jb20vaW1hZ2VzL00vTVY1Qk1ERmtZVGMwTUdFdFptTmhNQzAwWkRJekxXRm1OVEV0T0RNMVptUmxZV013TVdGbVhrRXlYa0ZxY0dkZVFYVnlNVE14T0RrMk9UVUAuX1YxX1VZNjdfQ1IwLDAsNDUsNjdfQUxfLmpwZyIgd2lkdGg9IjQ1IiBoZWlnaHQ9IjY3Ii8+CjwvYT4gICAgPC90ZD4KICAgIDx0ZCBjbGFzcz0idGl0bGVDb2x1bW4iPgogICAgICAxLgogICAgICA8YSBocmVmPSIvdGl0bGUvdHQwMTExMTYxLz9wZl9yZF9tPUEyRkdFTFVVTk9RSk5MJnBmX3JkX3A9ZTMxZDg5ZGQtMzIyZC00NjQ2LTg5NjItMzI3YjQyZmU5NGIxJnBmX3JkX3I9WDI1UkpSNFRBRUFaRzlQMDRITkMmcGZfcmRfcz1jZW50ZXItMSZwZl9yZF90PTE1NTA2JnBmX3JkX2k9dG9wJnJlZl89Y2h0dHBfdHRfMSIKdGl0bGU9IkZyYW5rIERhcmFib250IChkaXIuKSwgVGltIFJvYmJpbnMsIE1vcmdhbiBGcmVlbWFuIiA+RGllIFZlcnVydGVpbHRlbjwvYT4KICAgICAgICA8c3BhbiBjbGFzcz0ic2Vjb25kYXJ5SW5mbyI+KDE5OTQpPC9zcGFuPgogICAgPC90ZD4KICAgIDx0ZCBjbGFzcz0icmF0aW5nQ29sdW1uIGltZGJSYXRpbmciPgogICAgICAgICAgICA8c3Ryb25nIHRpdGxlPSI5LDIgYmFzZWQgb24gMS45NjcuMjIzIHVzZXIgcmF0aW5ncyI+OSwyPC9zdHJvbmc+CiAgICA8L3RkPgogICAgPHRkIGNsYXNzPSJyYXRpbmdDb2x1bW4iPgogICAgPGRpdiBjbGFzcz0ic2Vlbi13aWRnZXQgc2Vlbi13aWRnZXQtdHQwMTExMTYxIHBlbmRpbmciIGRhdGEtdGl0bGVpZD0idHQwMTExMTYxIj4KICAgICAgICA8ZGl2IGNsYXNzPSJib3VuZGFyeSI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InBvcG92ZXIiPgo8c3BhbiBjbGFzcz0iZGVsZXRlIj4mbmJzcDs8L3NwYW4+PG9sPjxsaT4xPGxpPjI8bGk+MzxsaT40PGxpPjU8bGk+NjxsaT43PGxpPjg8bGk+OTxsaT4xMDwvb2w+ICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iaW5saW5lIj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icGVuZGluZyI+PC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9InVuc2VlYWJsZSI+Tk9UIFlFVCBSRUxFQVNFRDwvZGl2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJ1bnNlZW4iPiA8L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0icmF0aW5nIj48L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0ic2VlbiI+U2VlbjwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8L3RkPgogICAgPHRkIGNsYXNzPSJ3YXRjaGxpc3RDb2x1bW4iPgogICAgICAgIDxkaXYgY2xhc3M9IndsYl9yaWJib24iIGRhdGEtdGNvbnN0PSJ0dDAxMTExNjEiIGRhdGEtcmVjb3JkbWV0cmljcz0idHJ1ZSI+PC9kaXY+CiAgICA8L3RkPgogIDwvdHI+CgpgYGAKCkhpZXIgc2VoZW4gU2llIGRpZSBEYXRlbiBkaXJla3QhIERlciBBdXNzY2huaXR0IHplaWd0LCBkYXNzIGRlciBGaWxtICJEaWUgVmVydXJ0ZWlsdGVuIiBtaXQgOSwyIFB1bmt0ZW4gKGJhc2llcmVuZCBhdWYgMSA5NjcgMjIzIEJld2VydHVuZ2VuKSBsYXV0IGltZGIuY29tIGRlciBiZXN0ZSBGaWxtIGFsbGVyIFplaXRlbiBpc3QuIEluIGRlciBIVE1MIGZpbmRlbiBTaWUgd2VpdGVyaGluIGRpZSBIYXVwdGRhcnN0ZWxsZXIgdW5kIGRhcyBKYWhyIGRlciBWZXLDtmZmZW50bGljaHVuZy4gCgpNaXR0ZWxzIEhUTUwgVGFncyB1bmQgQ1NTIGvDtm5uZW4gU2llIGRpZXNlIEluZm9ybWF0aW9uZW4gYXVjaCBhdXMgZGVyIEhUTUwgZXh0cmFoaWVyZW4uIEhpZXJ6dSBpbSBMYXVmZSBkZXMgVHV0b3JpYWxzIG1laHIuIApXZW5uIFNpZSBzaWNoIMO8YmVybGVnZW4genVrw7xuZnRpZyBpbSBCZXJlaWNoIGRlciBEYXRlbmFuYWx5c2UgenUgYXJiZWl0ZW4sIHNvIGlzdCBlcyBpbW1lciB2b3J0ZWlsaGFmdCBldHdhcyDDvGJlciBIVE1MIHVuZCBDU1MgenUgd2lzc2VuLiBEaWVzIHZlcmJlc3NlcnQgaWhyZSBfd2ViIHNjcmFwaW5nXyBGw6RoaWdrZWl0ZW4gdW5kIG5hdMO8cmxpY2ggYXVjaCBpaHJlIEbDpGhpZ2tlaXQgZWlnZW5lIFdlYnNlaXRlbiB6dSBnZXN0YWx0ZW4uIFR1dG9yaWFscyB6dSBIVE1MIHVuZCBDU1MgZmluZGVuIHNpZSBiZWlzcGllbHN3ZWlzZSBhdWYgW2NvZGUgYWNhZGVteV0oaHR0cHM6Ly93d3cuY29kZWNhZGVteS5jb20vbGVhcm4vbGVhcm4taHRtbCkgdW5kIFtXV1czIHNjaG9vbF0oaHR0cHM6Ly93d3cudzNzY2hvb2xzLmNvbS8pCgojIyMgRGFzIFBha2V0IGBydmVzdGAKCklubmVyaGFsYiB2b24gYHRpZHl2ZXJzZWAgZ2lidCBlcyBkYXMgUGFrZXQgYHJ2ZXN0YCwgbWl0IHdlbGNoZW0gU2llIF93ZWIgc2NyYXBpbmdfIGluIGVpbmVyIGlobmVuIGJla2FubnRlIFN5bnRheCBkdXJjaGbDvGhyZW4ga8O2bm5lbi4gWnVlcnN0IGltcG9ydGllcmVuIFNpZSBkaWUgZ2V3w7xuc2NodGUgU2VpdGUgaW4gUi4gSGllcmbDvHIgbnV0emVuIFNpZSBkZW4gQmVmZWhsIF9yZWFkXF9odG1sXzoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShydmVzdCkKdXJsIDwtICJodHRwczovL3d3dy5pbWRiLmNvbS9jaGFydC90b3AiCmltZGIyNTAgPC0gcmVhZF9odG1sKHVybCkKYGBgCgpOdW4gaXN0IGRpZSBrb21wbGV0dGUgV2Vic2l0ZSB2b24gaW1kYiBtaXQgZGVuIDI1MCBiZXN0ZW4gRmlsbWVuIGluIGBpbWRiMjUwYCBnZXNwZWljaGVydC4gRGllIEtsYXNzZSB2b24gYGltZGIyNTBgIGlzdDoKCmBgYHtyfQpjbGFzcyhpbWRiMjUwKQpgYGAKCkRhcyBgcnZlc3RgIFBha2V0IGlzdCBzZWhyIGFsbGdlbWVpbiBnZWhhbHRlbiB1bmQga2FubiBYTUwgRG9rdW1lbnRlIGhhbmRoYWJlbi4gRWluZSBYTUwgRGF0ZWkgaXN0IGluIGVpbmVyIGFsbGdlbWVpbmVuIE1hcmt1cCBTcHJhY2hlIGdlc2NocmllYmVuIChkYWhlciBNTCkgdW5kIGthbm4genVyIFJlcHLDpHNlbnRhdGlvbiBqZWdsaWNoZXIgRGF0ZW4gZGllbmVuLiBIVE1MIERhdGVpZW4gc2luZCBlaW5lIFNvbmRlcmZvcm0gdm9uIFhNTCBEYXRlaWVuIHVtIFdlYnNlaXRlbiBkYXJ6dXN0ZWxsZW4uIAoKVW5zZXIgWmllbCBpc3QgZXMgbnVuLCBkaWUgSW5mb3JtYXRpb25lbiBhdXMgZGVyIGltZGIgU2VpdGUgw7xiZXIgUmFua2luZywgUmF0aW5nLCBBbnphaGwgZGVyIFN0aW1tZW4gZXRjLiB6dSBleHRyYWhpZXJlbi4gV2VubiB3aXIgdW5zIGBpbWRiMjUwYCBhdXNnZWJlbiBsYXNzZW4sIHNvIHNpbmQgd2lyIGRpZXNlbSBaaWVsIG5vY2ggbmljaHQgbsOkaGVyIGdla29tbWVuLCBhdcOfZXIgZGFzIGRpZSBIVE1MIG51biBhbHMgUiBPYmpla3QgdmVyZsO8Z2JhciBpc3Q6CgpgYGB7cn0KaW1kYjI1MApgYGAKCkRlbmtlbiB3aXIgenVyw7xjayBhbiB1bnNlcmVuIEhUTUwgQXVzc2Nobml0dCB2b24gb2Jlbi4gSW4gZGllc2VtIHdpcmQgZGV1dGxpY2gsIGRhc3MgdW5zZXJlIEluZm9ybWF0aW9uZW4gc2ljaCBpbiBlaW5lciBIVE1MIFRhYmVsbGUgYmVmaW5kZW4uIFp1IHNlaGVuIGlzdCBkaWVzIGFuIGRlciBDb2RlLVplaWxlIGA8dGFibGUgY2xhc3M9ImNoYXJ0IGZ1bGwtd2lkdGgiIGRhdGEtY2FsbGVyLW5hbWU9ImNoYXJ0LXRvcDI1MG1vdmllIj5gLiBEaWUgZWluemVsbmVuIFRlaWxlIGVpbmVyIEhUTUwsIHdlbGNoZSBvZnQgbWl0IGVpbmVtIHNwcmVjaGVuZGVuIFRleHQgendpc2NoZW4gYDxgIHVuZCBgPmAgZGVmaW5pZXJ0IHNpbmQsIHdlcmRlbiBhbHMgX05vZGVzXyBiZXplaWNobmV0LiBNaXQgZGVtIFBha2V0IGBydmVzdGAga8O2bm5lbiB3aXIgZGllIE5vZGVzIGVpbmVyIEhUTUwgZXh0cmFoaWVyZW46IGBodG1sX25vZGVzYCBsaWVzdCBoaWVyYmVpIGFsbGUgTm9kZXMgbWl0IGRlciBlbnRzcHJlY2hlbmRlbiBEZWZpbml0aW9uIGF1cyB1bmQgYGh0bWxfbm9kZWAgbnVyIGRpZSBlcnN0ZSBOb2RlLgpCZWlzcGllbHN3ZWlzZSBrw7ZubmVuIHdpciBkaWUgVGFiZWxsZSBtaXQgZGVuIDI1MCBiZXN0ZW4gRmlsbWVuIG1pdCBmb2xnZW5kZW0gQmVmZWhsIGluIGRlciBWYXJpYWJsZSBgdGFibGVgIHNwZWljaGVybjoKIApgYGB7cn0gCnRhYmxlIDwtIGltZGIyNTAgJT4lIAogIGh0bWxfbm9kZSgidGFibGUiKQoKdGFibGUKYGBgCgpBbGxlcmRpbmdzIHNpZWh0IGRpZXNlIFRhYmVsbGUgaW1tZXIgbm9jaCBzZWhyIHN0YXJrIG5hY2ggSFRNTCBhdXMgdW5kIHdlbmlnZXIgbmFjaCBlaW5lbSBmw7xyIHVucyB6dSBiZWFyYmVpdGVuZGVuIERhdGEgRnJhbWUuIE1pdHRlbHMgYHJ2ZXN0YCBrw7ZubmVuIHdpciBkYXMgamVkb2NoIHNjaG5lbGwgYmVoZWJlbiB1bmQgYXVzIGRlciBIVE1MIFRhYmVsbGUgZWluZW4gRGF0YSBGcmFtZSBtYWNoZW4sIGhpZXJ6dSBudXR6ZW4gd2lyIGRpZSBGdW5rdGlvbiBgaHRtbF90YWJsZWA6CgpgYGB7cn0KdGFibGUgPC0gdGFibGUgJT4lIAogIGh0bWxfdGFibGUoKQoKY2xhc3ModGFibGUpCm5yb3codGFibGUpCmBgYAoKTnVuIHNpbmQgd2lyIHVuc2VyZW0gV3Vuc2NoZXJnZWJuaXMgc2Nob24gc2VociB2aWVsIG7DpGhlcjoKCmBgYHtyfQpoZWFkKHRhYmxlKQpgYGAKCkhpZXIgbcO8c3N0ZW4gd2lyIGltbWVyIG5vY2ggZWluIHBhYXIgRGluZ2UgdW1nZXN0YWx0ZW4sIHdpZSB6LkIuIGRpZSBfbmV3bGluZV8gKCAiXG4iICkgZW50ZmVybmVuLCB1bSB6dSBlaW5lbSB3aXJrbGljaCB6dWZyaWVkZW5zdGVsbGVuZGVuIEVyZ2VibmlzIHp1IGdlbGFuZ2VuLiBKZWRvY2ggZ2lidCBlcyBiZXNzZXJlIHVuZCBhbGxnZW1laW5lcmVuIFZvcmdlaGVuc3dlaXNlbiB1bSBJbmZvcm1hdGlvbmVuIGF1cyBIVE1MIFNlaXRlbiB6dSBleHRyYWhpZXJlbi4gSGllcmbDvHIgd2lkbWVuIHdpciB1bnMgaW0gbsOkY2hzdGVuIEFic2Nobml0dCBDU1MtU2VsZWt0b3Jlbi4KCiMjIyBDU1MtU2VsZWN0b3JlbgoKV2Vic2VpdGVuLCB3ZWxjaGUgYXVzc2NobGllw59saWNoIG1pdCBncnVuZHN0w6RuZGlnZW0gSFRNTCBwcm9ncmFtbWllcnQgc2luZCwgd2lya2VuIHJlY2h0IHVuYXR0cmFrdGl2LiBVbSBlaW5lIGFuc3ByZWNoZW5kZSBXZWJzZWl0ZSB6dSBnZXN0YWx0ZW4gZ3JlaWZlbiBkaWUgbWVpc3RlbiBXZWJtYXN0ZXIgYXVmIENTUyB6dXLDvGNrLiBEdXJjaCBDU1Mga8O2bm5lbiBTaWUgYmVpc3BpZWxzd2Vpc2UgYWxsZSBTZWl0ZW4gZWluZXMgVW50ZXJuZWhtZW5zIGVpbmhlaXRsaWNoIGdlc3RhbHRlbiwgaW5kZW0gc2llIGF1ZiBlaW5lIGVpbmhlaXRsaWNoZSBDU1MgRGF0ZWkgenVyw7xja2dyZWlmZW4uIEdydW5kc8OkdHpsaWNoIGvDtm5uZW4gU2llIG1pdCBDU1MgZGFzIEF1c3NlaGVuIGplZGVzIEVsZW1lbnRzIGVpbmVyIFdlYnNlaXRlIGJlc3RpbW1lbi4gSGllcnVudGVyIGZhbGxlbiB1bnRlciBhbmRlcmVtIGRlciBUaXRlbCwgw5xiZXJzY2hyaWZ0ZW4sIExpc3RlbiwgVGFiZWxsZW4gdW5kIExpbmtzLCB3b2JlaSBqZWRlcyBFbGVtZW50IHNlaW5lIGVpZ2VuZSBTY2hyaWZ0LCBGYXJiZSwgR3LDtsOfZSBldGMuIGVyaGFsdGVuIGthbm4uIFVtIGRpZSBlaW56ZWxuZW4gQW5wYXNzdW5nZW4genUgbWFjaGVuIG51dHp0IENTUyBzbyBnZW5hbm50ZSBTZWxla3RvcmVuLiBCZWlzcGllbHN3ZWlzZSB3w6RyZSBgdGFibGVgIGVpbiBzb2xjaGVyIFNlbGVrdG9yLCB3ZWxjaGVuIHdpciB6dXZvciB2ZXJ3ZW5kZXQgaGFiZW4uCgpXZW5uIHdpciBudW4gRGF0ZW4gYXVzIGVpbmVyIFdlYnNlaXRlIGhlcnVudGVybGFkZW4gdW5kIHdpciBrZW5uZW4gZWJlbiBkaWVzZSBTZWxla3RvcmVuLCBzbyBrw7ZubmVuIHdpciBkaWUgSW5mb3JtYXRpb25lbiBkaXJla3QgbWl0IGBodG1sX25vZGVzYCBoZXJ1bnRlcmxhZGVuLiBMZWlkZXIgaXN0IGVzIG5pY2h0IGltbWVyIGVpbmZhY2ggaGVyYXVzenVmaW5kZW4sIHdlbGNoZXIgU2VsZWt0b3IgZsO8ciB3ZWxjaGVzIEVsZW1lbnQgbWHDn2dlYmxpY2ggaXN0LiBHbMO8Y2tsaWNoZXJ3ZWlzZSBnaWJ0IGVzIFtTZWxlY3RvckdhZGdldF0oaHR0cDovL3NlbGVjdG9yZ2FkZ2V0LmNvbS8pIHdlbGNoZXMgU2llIGFscyBMZXNlemVpY2hlbiB6dSBpaHJlbSBCcm93c2VyIGhpbnp1ZsO8Z2VuIGvDtm5uZW4uIEJpdHRlIHNjaGF1ZW4gU2llIHNpY2ggZGVuIFNjcmVlbmNhc3QgYXVmIHd3dy5zZWxlY3RvcmdhZGdldC5jb20gYW4gdW0gZWluZW4gRWluYmxpY2sgaW4gU2VsZWN0b3JnYWRnZXQgenUgZXJoYWx0ZW4uIFdlaXRlcmhpbiBlcmhhbHRlbiBTaWUgW2hpZXIgZWluZSBFaW5mw7xocnVuZyBpbiBTZWxlY3RvckdhZGdldF0oaHR0cHM6Ly9ydmVzdC50aWR5dmVyc2Uub3JnL2FydGljbGVzL3NlbGVjdG9yZ2FkZ2V0Lmh0bWwpCgpNaXQgU2VsZWN0b3JHYWRnZXQga8O2bm5lbiBTaWUgaW50ZXJha3RpdiBqZWRlbiBDU1MgU2VsZWt0b3IgYmVzdGltbWVuIHdlbGNoZW4gU2llIGFuc3RldWVybiBtw7ZjaHRlbi4gU29taXQga8O2bm5lbiBTaWUgc3BlemlmaXNjaGUgSW5oYWx0ZSBlaW5lciBXZWJzZWl0ZSBnZW5hdSBhbnN0ZXVlcm4gdW5kIGhlcnVudGVybGFkZW4uIFdlbm4gU2llIG1pdCBTZWxlY3RvckdhZGdldCBhdWYgZWluIEVsZW1lbnQgZWluZXIgV2Vic2VpdGUga2xpY2tlbiB3ZXJkZW4gYWxsZSBtaXQgZGllc2VtIENTUyBTZWxla3RvciB6dXNhbW1lbmjDpG5nZW5kZW4gT2JqZWt0ZSBlbnRzcHJlY2hlbmQgZ2VsYiBtYXJraWVydC4gCgpVbSBkaWUgTcO2Z2xpY2hrZWl0ZW4gdm9uIENTUyBTZWxla3RvcmVuIGV0d2FzIGJlc3NlciB6dSB2ZXJhbnNjaGF1bGljaGVuIHdvbGxlbiB3aXIgdW5zIHdpZWRlciBpbWRiLmNvbSB6dXdlbmRlbi4gSW0gd2VpdGVyZW4gVmVybGF1ZiBkZXMgVHV0b3JpYWxzIHdvbGxlbiB3aXIgRGF0ZW4genUgYWxsZW4gRmlsbWUsIHdlbGNoZSAyMDE4IGF1ZiBkZW4gTWFya3QgZ2Vrb21tZW4gc2luZCwgaGVydW50ZXJsYWRlbiB1bmQgYW5hbHlzaWVyZW4uCgpIaWVyenUgc3VjaGVuIHdpciB6dWVyc3QgYXVmIHd3dy5pbWRiLmNvbSBuYWNoIGRlbiBlbnRzcHJlY2hlbmRlbiBGaWxtZW4gdW0gZGllIHVybCBoZXJhdXN6dWZpbmRlbgpEZXIgTGluayBpbiBkZW0gdW50ZXJlbiBDaHVuayBlbnRow6RsdCBkaWUgdXJsIHVuc2VyZXIgU3VjaGUuIAoKYGBge3J9CmltZGIyMDE4IDwtIHJlYWRfaHRtbCgiaHR0cHM6Ly93d3cuaW1kYi5jb20vc2VhcmNoL3RpdGxlP2NvdW50PTEwMCZyZWxlYXNlX2RhdGU9MjAxOCwyMDE4IikKYGBgCgpTY2hhdWVuIHdpciB1bnMgZGllc2VuIExpbmsgZXR3YXMgZ2VuYXVlciBhbjoKCi0gY291bnQ6IEFuemFobCBkZXIgRmlsbWUgZGllIGF1ZiBlaW5lciBTZWl0ZSBnZXplaWd0IHdlcmRlbiwgaGllciAxMDAKLSByZWxlYXNlX2RhdGU6IEphaHIgaW4gZGVtIGRlciBGaWxtIGdlZHJlaHQgd3VyZGUgKHdpcmQgYWxzIFt2b24sYmlzXSBhbmdlZ2ViZW4sIGQuaC4gaW0gZ2VzYW10ZW4gSmFociAyMDE4KQoKSW0gRm9sZ2VuZGVuIG3DtmNodGVuIHdpciB1bnMgbnVuIGZvbGdlbmRlIEluZm9ybWF0aW9uZW4gw7xiZXIgZGVuIGpld2VpbGlnZW4gRmlsbSBnZW5hdWVyIGFuc2NoYXVlbjogCiAgICAtIFBvcHVsYXJpdMOkdAogICAgLSBUaXRlbAogICAgLSBSYXRpbmcKICAgIC0gRGF1ZXIKICAgIC0gR2VucmUKICAgIC0gQW56YWhsIGRlciBTdGltbWVuCgpgYGB7cn0KI1NvcnRpZXJ1bmcgbmFjaCBQb3B1bGFyaXTDpHQKcG9wdWwgPC0gaW1kYjIwMTggJT4lIAogIGh0bWxfbm9kZXMoIi50ZXh0LXByaW1hcnkiKSAlPiUgCiAgaHRtbF90ZXh0KCkgJT4lIAogIGFzLm51bWVyaWMoKQoKI1RpdGVsCnRpdGVsIDwtIGltZGIyMDE4ICU+JSAKICBodG1sX25vZGVzKCIubGlzdGVyLWl0ZW0taGVhZGVyIGEiKSAlPiUgCiAgaHRtbF90ZXh0KCkKCiNSYXRpbmcKcmF0aW5nIDwtIGltZGIyMDE4ICU+JSAKICBodG1sX25vZGVzKCIucmF0aW5ncy1pbWRiLXJhdGluZyBzdHJvbmciKSAlPiUgCiAgaHRtbF90ZXh0KCkgJT4lIAogIGFzLm51bWVyaWMoKQoKI0RhdWVyCmRhdWVyIDwtIGltZGIyMDE4ICU+JSAKICBodG1sX25vZGVzKCIudGV4dC1tdXRlZCAucnVudGltZSIpICU+JSAKICBodG1sX3RleHQoKSAlPiUgCiAgc3RyX3JlcGxhY2UoIiBtaW4iLCAiIikgJT4lIAogIGFzLm51bWVyaWMoKQoKI0dlbnJlIChpbW1lciBkYXMgZXJzdGUgR2VucmUgYWxzIEtsYXNzaWZpa2F0aW9uKQpnZW5yZSA8LSBpbWRiMjAxOCAlPiUgCiAgaHRtbF9ub2RlcygiLmdlbnJlIikgJT4lIAogIGh0bWxfdGV4dCgpICU+JSAKICBzdHJfcmVwbGFjZSgiXG4iLCAiIikgJT4lIAogIHN0cl9yZXBsYWNlKCIgIiwgIiIpICU+JSAKICBzdHJfcmVwbGFjZSgiLC4qIiwgIiIpICU+JSAKICBzdHJfdHJpbSgpICU+JSAKICBhcy5mYWN0b3IoKQoKI0FuemFobCBhbiBTdGltbWVuCnN0aW1tZW4gPC0gaW1kYjIwMTggJT4lIAogIGh0bWxfbm9kZXMoIi5zb3J0LW51bV92b3Rlcy12aXNpYmxlIHNwYW46bnRoLWNoaWxkKDIpIikgJT4lIAogIGh0bWxfdGV4dCgpICU+JSAKICBzdHJfcmVwbGFjZSgiLCIsICIiKSAlPiUgCiAgYXMubnVtZXJpYygpCmBgYAoKVGVpbHdlaXNlIHNpbmQgZGllIFNlbGVrdG9yZW4gcmVjaHQga29tcGxleCB1bmQgd2lyIGJla29tbWVuIGF1Y2ggbmljaHQgaW1tZXIgMTAwJS1pZyBzYXViZXJlIFdlcnRlIHp1csO8Y2tnZWxpZWZlcnQsIHNvbmRlcm4gbcO8c3NlbiBkaWVzZSBuYWNodHLDpGdsaWNoIGJlYXJiZWl0ZW4uIERpZXMgZ2VzY2hpZWh0IG1pdCBkZW0gUGFrZXQgYHN0cmluZ3JgLCB3ZWxjaGVzIFRlaWwgZGVzIGB0aWR5dmVyc2VgIGlzdCwgaW5zYmVzb25kZXJlIGRlciBGdW5rdGlvbiBfc3RyXF9yZXBsYWNlXyB1bSBic3B3LiBFcnNldHp1bmdlbiB2b3J6dW5laG1lbi4gRG9jaCBlcyB3aXJkIGF1Y2ggZGV1dGxpY2gsIGRhc3Mgd2lyIGR1cmNoIENTUy1TZWxla3RvcmVuIGRpcmVrdCBhdWYgZWluemVsbmVuIEVsZW1lbnRlIHp1Z3JlaWZlbiBrw7ZubmVuIHVuZCBkYWR1cmNoIGRldXRsaWNoIG1laHIgRmxleGliaWxpdMOkdCBhbHMgbWl0IHJlaW5lbiBIVE1MIEJlZmVobGVuIGhhYmVuLgoKQXVmIGRpZXNlciBHcnVuZGxhZ2Uga8O2bm5lbiB3aXIgZWluIERhdGEgRnJhbWUgZXJzdGVsbGVuLCB3ZWxjaGVyIGFsbGUgZ2Vmb3JkZXJ0ZW4gSW5mb3JtYXRpb25lbiBiZWluaGFsdGV0OgoKYGBge3J9CkZpbG1lMjAxOCA8LSB0aWJibGUocG9wdWwsdGl0ZWwsIHJhdGluZywgZGF1ZXIsIGdlbnJlLCBzdGltbWVuKQpGaWxtZTIwMTgKYGBgCgpEYWR1cmNoLCBkYXNzIGFsbGUgU2VpdGVuZWxlbWVudGUgZGVtIGdsZWljaGVuIFNjaGVtYSBmb2xnZW4sIGvDtm5udGVuIHdpciBmw7xyIGRpZSBvYmVyZSBBYmZyYWdlIGF1Y2ggZWluZSBGdW5rdGlvbiBzY2hyZWliZW4uIAoKRGllc2UgRnVua3Rpb24ga8O2bm5lbiB3aXIgYW5zY2hsaWXDn2VuZCBhdWYgZGllIDIuIFNlaXRlIGFud2VuZGVuICh1bmQgYXVmIGFsbGUgd2VpdGVyZW4pOgoKYGBge3J9CmdldF9pbWRiMjAxOCA8LSBmdW5jdGlvbih1cmwpewogIGltZGIgPC0gcmVhZF9odG1sKHVybCkKICBwb3B1bCA8LSBpbWRiICU+JSBodG1sX25vZGVzKCIudGV4dC1wcmltYXJ5IikgJT4lIGh0bWxfdGV4dCgpICU+JSBhcy5udW1lcmljKCkKICB0aXRlbCA8LSBpbWRiICU+JSBodG1sX25vZGVzKCIubGlzdGVyLWl0ZW0taGVhZGVyIGEiKSAlPiUgaHRtbF90ZXh0KCkKICByYXRpbmcgPC0gaW1kYiAlPiUgaHRtbF9ub2RlcygiLnJhdGluZ3MtaW1kYi1yYXRpbmcgc3Ryb25nIikgJT4lIGh0bWxfdGV4dCgpICU+JSBhcy5udW1lcmljKCkKICBkYXVlciA8LSBpbWRiICU+JSBodG1sX25vZGVzKCIudGV4dC1tdXRlZCAucnVudGltZSIpICU+JSBodG1sX3RleHQoKSAlPiUgc3RyX3JlcGxhY2UoIiBtaW4iLCAiIikgJT4lIGFzLm51bWVyaWMoKQogIGdlbnJlIDwtIGltZGIgJT4lIGh0bWxfbm9kZXMoIi5nZW5yZSIpICU+JSBodG1sX3RleHQoKSAlPiUgc3RyX3JlcGxhY2UoIlxuIiwgIiIpICU+JSBzdHJfcmVwbGFjZSgiICIsICIiKSAlPiUgCiAgICBzdHJfcmVwbGFjZSgiLC4qIiwgIiIpICU+JSBhcy5mYWN0b3IoKQogIHN0aW1tZW4gPC0gaW1kYiAlPiUgaHRtbF9ub2RlcygiLnNvcnQtbnVtX3ZvdGVzLXZpc2libGUgc3BhbjpudGgtY2hpbGQoMikiKSAlPiUgaHRtbF90ZXh0KCkgJT4lIHN0cl9yZXBsYWNlKCIsIiwgIiIpICU+JSAKICAgIGFzLm51bWVyaWMoKQogIHRpYmJsZShwb3B1bCx0aXRlbCwgcmF0aW5nLCBkYXVlciwgZ2VucmUsIHN0aW1tZW4pCn0KYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KRmlsbWUyMDE4XzIgPC0gZ2V0X2ltZGIyMDE4KCJodHRwczovL3d3dy5pbWRiLmNvbS9zZWFyY2gvdGl0bGU/Y291bnQ9MTAwJnJlbGVhc2VfZGF0ZT0yMDE4LDIwMTgmc3RhcnQ9MTAxIikKYGBgCgpFcmtlbm5lbiBTaWUsIHdhcyBzaWNoIGFuIGRlciB1cmwgZ2XDpG5kZXJ0IGhhdD8KClJpY2h0aWchIEluIGRlciB1cmwgc3RlaHQgbnVuIGVpbiAic3RhcnQ9MTAxIiwgd2FzIGhlacOfdCwgZGFzcyBkaWUgMTAwIEZpbG1lLCBkaWUgYXVmIGRpZXNlciBTZWl0ZSBnZXplaWd0IHdlcmRlbiBiZWkgRmlsbSAxMDEgaW0gUmFua2luZyBiZWdpbm5lbi4gU2llIHNvbGx0ZW4gaW4gZGVuIHVybHMgaW1tZXIgYXVmIGRlcmFydGlnZSBEZXRhaWxzIGFjaHRlbiwgd2VubiBTaWUgbWVocmVyZSBTZWl0ZW4gc2NyYXBlbiBtw7ZjaHRlbiEKCkRpZSBJbmZvcm1hdGlvbmVuIHp1IGRlbiBUb3AgMTAwIEZpbG1lbiBpbiAyMDE4LCB3ZWxjaGUgd2lyIHVucyBoZXJ1bnRlcmdlbGFkZW4gaGFiZW4sIGvDtm5uZW4gd2lyIG51biBhdWNoIGFuYWx5c2llcmVuLiBCZWlzcGllbHN3ZWlzZSB3w6RyZSBpbnRlcmVzc2FudCB6dSB3aXNzZW4sIHdlbGNoZSBGaWxtZSBpbiAyMDE3IGRpZSBtZWlzdGVuIFN0aW1tZW4gbWl0IGRlbSBow7ZjaHN0ZW4gUmF0aW5nIGVyaGFsdGVuIGhhYmVuLiBEaWVzIGdldHJlbm50IG5hY2ggR2VucmUgdW5kIERhdWVyIGRlcyBGaWxtczoKCmBgYHtyfQpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiN3aGljaCBnZW5yZSBoYXMgdGhlIGhpZ2hlc3Qgdm90ZXMKZ2dwbG90KEZpbG1lMjAxOCxhZXMoeD1kYXVlcix5PXJhdGluZykpICsgCiAgZ2VvbV9wb2ludChhZXMoc2l6ZT1zdGltbWVuLGNvbD1nZW5yZSkpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiV2VsY2hlcyBHZW5yZSBoYXQgZGFzIGjDtmNoc3RlIEltZEIgUmF0aW5nPyIsCiAgICAgICB4ID0gIkRhdWVyIChpbiBNaW51dGVuKSIsCiAgICAgICB5ID0gIlJhdGluZyIpCmBgYAoKWnVyIMOcYnVuZyBrw7ZubnRlbiBTaWUgc2ljaCBhdWNoIG5vY2ggZGllIG7DpGNoc3RlbiAxMDAgRmlsbWUgZ2VuYXVlciBhbnNjaGF1ZW4gKGRpZXNlIGhhYmVuIFNpZSBpbiBkZW0gVHV0b3JpYWwgbsOkbWxpY2ggYXVjaCBlaW5nZWxlc2VuKS4KCldlbm4gU2llIHNpY2ggdmVydGllZnQgbWl0IF93ZWIgc2NyYXBpbmdfIGJlc2Now6RmdGlnZW4gd29sbGVuIHNvIGvDtm5uZW4gU2llIHNpY2ggaW5zYmVzb25kZXJlIG5vY2ggZGllIEZ1bmt0aW9uZW4gYGh0bWxfZm9ybWAsIGBzZXRfdmFsdWVzYCwgdW5kIGBzdWJtaXRfZm9ybWAgYW5zY2hhdWVuLiBKZWRvY2ggZ2VoZW4gZGllc2UgRnVua3Rpb25lbiBmw7xyIGVpbmUgRWluZsO8aHJ1bmcgaW4gZGFzIF93ZWIgc2NyYXBpbmdfIGV0d2FzIHp1IHdlaXQuCgoKIyBTZWllbiBTaWUgbmV0dCBpbSBJbnRlcm5ldAoKQW5nZW5vbW1lbiBTaWUgbcO2Y2h0ZW4gbnVuIG5pY2h0IG51ciB6d2VpIFNlaXRlbiB2b24gSU1EYiBhdXN3ZXJ0ZW4sIHNvbmRlcm4gYWxsZSBGaWxtZSwgZGllIGF1cyBkZW0gSmFociAyMDE4IGJlaSBJTURiIGhpbnRlcmxlZ3Qgc2luZCAoaW5zZ2VzYW10IDM4MCA2ODIgRmlsbWUpLgpJbnNiZXNvbmRlcmUgd2VubiBTaWUgZ3LDtsOfZXJlIERhdGVubWVuZ2VuIGF1cyBkZW0gSW50ZXJuZXQgYW5hbHlzaWVyZW4gbcO2Y2h0ZW4gc29sbHRlbiBTaWUgc2ljaCBkYXLDvGJlciBpbSBLbGFyZW4gc2VpbiwgZGFzcyBqZWRlIGlocmVyIEFuZnJhZ2VuIGF1ZiBkZW0gU2VydmVyIGRlcyBCZXRyZWliZXJzIGxhbmRldCB1bmQgZG9ydCB6dSBUcmFmZmljIGbDvGhydC4KCkRhcmF1cyBlcmdlYmVuIHNpY2ggZsO8ciBTaWUgZWluaWdlIFZlcmhhbHRlbnNyZWdlbG4gYmVpbSBfd2ViIHNjcmFwaW5nXzoKCjEuIFNpZSBzb2xsdGVuIGRpZSByb2JvdHMudHh0IERhdGVpIHJlc3Bla3RpZXJlbi4gV2VubiBpbiBkaWVzZXIgc3RlaHQsIGRhc3MgU2llIGVpbnplbG5lIEVsZW1lbnRlIGRlciBXZWJzZWl0ZSBuaWNodCBoZXJ1bnRlcmxhZGVuIGTDvHJmZW4sIGRhbm4gc29sbHRlbiBTaWUgZGFzIGF1Y2ggbmljaHQgdHVuLgogICAgLSBEaWUgcm9ib3RzLnR4dCBEYXRlaSBmaW5kZW4gU2llIGluZGVtIFNpZSBhbiBkaWUgamV3ZWlsaWdlIHVybCAicm9ib3RzLnR4dCIgYW5ow6RuZ2VuLiAKICAgIC0gQmVpc3BpZWxzd2Vpc2UgZmluZGVuIFNpZSBkaWUgInJvYm90cy50eHQiIERhdGVpIHZvbiBJTURiIGhpZXI6ICJodHRwczovL3d3dy5pbWRiLmNvbS9yb2JvdHMudHh0IgoyLiBTZWllbiBTaWUgbmV0dCwgU2llIHNvbGx0ZW4gZGllIFdlYnNlaXRlLCBkaWUgU2llIHNjcmFwZW4gd29sbGVuIG5pY2h0IMO8YmVybGFzdGVuCiAgICAtIEQuaC4gd2VubiBTaWUgbWVocmVyZSBXZWJzZWl0ZW4gaGVydW50ZXJsYWRlbiwgZGFubiBzb2xsdGVuIFNpZSB6d2lzY2hlbiBkZW4gZWluemVsbmVuIEF1ZnJ1ZmVuIGltbWVyIGVpbmlnZSBTZWt1bmRlbiBQYXVzZSBlaW5sZWdlbgogICAgLSBEaWVzIGvDtm5uZW4gU2llIGluIFIgZHVyY2ggIlN5cy5zbGVlcCh4KSIgZXJyZWljaGVuLCB3b2JlaSAieCIgZGllIEFuemFobCBhbiBTZWt1bmRlbiBkYXJzdGVsbHQsIGRpZSBkYXMgU3lzdGVtIHBhdXNpZXJ0CiAgICAtIFNpZSBzb2xsdGVuIGkuZC5SLiB6d2lzY2hlbiB6d2VpIFNlaXRlbmF1ZnJ1ZmVuIHJ1bmQgNSBTZWt1bmRlbiBQYXVzZSBsYXNzZW4sIGhpZXIgZWluIEJlaXNwaWVsIGbDvHIgZWluZSB6dWbDpGxsaWdlIFBhdXNlIHZvbiA1LTEwIFNla3VuZGVuOgoKYGBge3IsIGV2YWw9RkFMU0V9CiNEdXJjaCBkaWVzZSBGdW5rdGlvbiBydWh0IGRhcyBTeXN0ZW0genVmw6RsbGlnIHp3aXNjaGVuIDUtMTAgU2VrdW5kZW4KU3lzLnNsZWVwKHNhbXBsZSg1OjEwKSwxKQpgYGAKCjMuIExhZGVuIFNpZSBpaHJlIERhdGVuIGltbWVyIGhlcnVudGVyIHVuZCBzcGVpY2hlcm4gZGllc2UgbG9rYWwgYWIgdW0gZsO8ciBlaW5lIHNww6R0ZXJlIEFuYWx5c2UgbmljaHQgbm9jaCBlaW5tYWwgZGllIFdlYnNlaXRlIGF1ZnJ1ZmVuIHp1IG3DvHNzZW4hCiAgICAtIERpZXNlIFJlZ2VsIGlzdCBuaWNodCBudXIgaW0gU2lubmUgZGVzIFN5c3RlbWFkbWlucyBkZXIgV2Vic2VpdGUsIHNvbmRlcm4gYXVjaCBpbiBpaHJlbSBJbnRlcmVzc2UgdW0gaWhyZSBBbmFseXNlIHJlcHJvZHV6aWVyYmFyIHp1IGdlc3RhbHRlbgogICAgLSBVbSBlaW5lIERhdGVpIGhlcnVudGVyenVsYWRlbiBrw7ZubmVuIFNpZSB6LkIuICJkb3dubG9hZC5maWxlKCkiIHZlcndlbmRlbjoKICAgIApgYGB7ciwgZXZhbD1GQUxTRX0KI0R1cmNoIGRpZXNlIEZ1bmt0aW9uIGxhZGVuIFNpZSBkaWUgWmllbHNlaXRlIGxva2FsIGF1ZiBpaHJlbiBQQwpkb3dubG9hZC5maWxlKCJodHRwczovL3d3dy5pbWRiLmNvbS9zZWFyY2gvdGl0bGU/Y291bnQ9MTAwJnJlbGVhc2VfZGF0ZT0yMDE4LDIwMTgiLCAiSU1EQl8yMDE4X01vdmllcy5odG1sIikKCmVpbmxlc2VuIDwtIHJlYWRfaHRtbCgiSU1EQl8yMDE4X01vdmllcy5odG1sIikKYGBgCgojIFdlaXRlcmUgVHV0b3JpYWxzCgotIFtFaW4gVHV0b3JpYWwgd2llIFNpZSBGdcOfYmFsbGRhdGVuIHZvbiAidHJhbnNmZXJtYXJrdC5kZSIgc2NyYXBlbl0oaHR0cHM6Ly9yeW8tbjcuZ2l0aHViLmlvLzIwMjAtMDUtMTQtd2Vic2NyYXBlLXNvY2Nlci1kYXRhLXdpdGgtUi8pCi0gW0RpZSBWaWduZXR0ZSB2b24gcnZlc3QsIGltbWVyIGVpbiBndXRlciBTdGFydHB1bmt0IGbDvHIgZGFzIFdlYnNjcmFwaW5nXShodHRwczovL3J2ZXN0LnRpZHl2ZXJzZS5vcmcvYXJ0aWNsZXMvaGFydmVzdGluZy10aGUtd2ViLmh0bWwpCgoKCgoK