About

Stanford CoreNLP is an integrated framework. Its goal is to make it very easy to apply a bunch of linguistic analysis tools to a piece of text. A CoreNLP tool pipeline can be run on a piece of plain text with just two lines of code. It is designed to be highly flexible and extensible.

Capabilities

Stanford CoreNLP provides a set of natural language analysis tools. It can give the base forms of words, their parts of speech, whether they are names of companies, people, etc., normalize dates, times, and numeric quantities, and mark up the structure of sentences in terms of phrases and word dependencies, indicate which noun phrases refer to the same entities, indicate sentiment, extract open-class relations between mentions, etc.

Why to use CoreNLP

Download

http://stanfordnlp.github.io/CoreNLP/download.html


R + CoreNLP 👽

First you need to install the Stanford CoreNLP application in your machine or connect to a server running the application. In our case the Analytics Lab has the CoreNLP application running in our server.

Setup

We are going to need the following R packeges.

if(!require(xml2)) install.packages("xml2")
if(!require(XML)) install.packages("XML")
if(!require(httr)) install.packages("httr")
Loading required package: httr

In order to connect and parse the output from CoreNLP we need a couple of functions. ⚠️ These functions are part of the setup, you should not change anything unless you are familiar witht the CoreNLP syntax.


Function to connect to the local/remote CoreNLP server.

connNLP <- function(host="localhost", port="30200",
                    tokenize.whitespace="true", annotators="",
                    outputFormat="xml") {
                      conn <- paste("http://",host,":",port,"/?",sep="")
                      annotators <- gsub(",","%2C",annotators)
                      properties <- paste("properties=%7B",
                                          "%22tokenize.whitespace%22%3A",
                                          "%22",tokenize.whitespace,"%22%2C",
                                          "%22annotators%22%3A",
                                          "%22",annotators,"%22",
                                          "%2C%22outputFormat%22%3A",
                                          "%22",outputFormat,"%22%7D",
                                          sep="")
                      conn <- paste(conn,properties,sep="")
                      request <- list("conn"=conn,"out"=outputFormat)
                      return(request)
            }

Function to make requests to the CoreNLP server to analyse a text document.

getNLP <- function(text,conn){
              text <- gsub("'","",text)
              if(tolower(conn['out'])=="xml"){
                require("XML")
                require("httr")
                result <- POST(conn['conn'][[1]],body = text,encode = "multipart")
                doc <- xmlParse(content(result,"parsed","application/xml",encoding = "UTF-8"))
                }
              if(tolower(conn['out'])=="json"){
                result <- POST(conn['conn'][[1]],body = text,encode = "json")
                doc <- content(result, "parsed","application/json",encoding = "UTF-8")
              }
              if(tolower(conn['out'])=="text"){
                result <- POST(conn['conn'][[1]],body = text,encode = "multipart")
                doc <- content(result, "parsed","application/text",encoding = "UTF-8")
              }
              return(doc)
        }

Function to extract the result of the CoreNLP sentiment annotator for the all text document.

getSent <- function(docNLP){
  scr <- as.integer(xpathSApply(docNLP, "//sentences/sentence/@sentimentValue"))
  sent <- toString(xpathSApply(docNLP, "//sentences/sentence/@sentiment"))
  result <- list(sentiment = c(sent),score=c(scr))
  return(result)
}

Function to extract the result of the CoreNLP sentiment annotator for each word in the text document.

getWords <- function(docNLP){
  word <- xpathSApply(docNLP, "//token/word",xmlValue)
  word_sent <- xpathSApply(docNLP, "//token/sentiment",xmlValue)
  df <- data.frame(word,word_sent)
  return(df)
}

Functions to pull information from an RSS feed.

getRSS <- function(url=""){
  doc <- xmlParse(url)
  title <- xpathSApply(doc, "//item/title", xmlValue)
  desc <- xpathSApply(doc, "//item/description", xmlValue)
  date <- xpathSApply(doc, "//item/pubDate", xmlValue)
  date <- strptime(date,format ="%a, %d %b %Y %H:%M:%S",tz="GMT")
  rss <- list(title=title,description = desc,date = date)
  return(rss)
}

CoreNLP in Action 🚀

Now that we hava the functions to connect, parse and analyze the output of the CoreNLP application. Using the connection function we can connect to the CoreNLP server and set the parameters that we are going to use in the natural language analysis.

conn <- connNLP( host = "10.38.30.10",  port = "30200",
                 tokenize.whitespace = "true", annotators = "sentiment",
                 outputFormat = "xml" )

In order to perform natural language analysis we need to feed a text document to the application. To do that we are going to get some news from an RSS feed using the getRSS function and the link to the RSS page.


Lets analyze the first headline in the US News RSS feed.

  1. We get the news from the RSS feed.
  2. Get the first headline.
  3. Feed the text to the CoreNLP application.
text
[1] "Immigrants 'Missing Ingredient' to Economic Growth, Says Philadelphia Fed President Patrick Harker"

Now lets process the document that contains the result of the natural language analysis, using the getSent function.

text_sentiment
$sentiment
[1] "Negative"

$score
[1] 1

We can also analyze the sentiment of each word in the headline, using the getWords function.

We can do the same for the news description.

description
[1] "A senior official at the Fed indicated Thursday that immigrants could help jump-start the U.S. economy."
desc_sentiment
$sentiment
[1] "Negative"

$score
[1] 1

Now lets get the sentiment of each word for the description.


Sentiment analysis of news headlines 🌎

news_nlp = list()

for(i in 1:length(news$title)){
  doc <- getNLP(news$title[i],conn)
  sent <- getSent(doc)
  df <- data.frame("title"=news$title[i],
                          "description"=news$desc[i],
                          "sentiment"=sent['sentiment'],
                          "score"=sent['score'])
  news_nlp[[i]] <- df
  Sys.sleep(1)
}

news_sentiment <- do.call(“rbind”,news_nlp) #write.csv(news_sentiment,file = “news.csv”) ```

{r}unts <- table(news_sentiment$sentiment) barplot(counts, main="Sentiment Distribution")

LS0tCnRpdGxlOiAiU3RhbmZvcmQgQ29yZU5MUCAtIE5hdHVyYWwgTGFuZ3VhZ2UgQW5hbHlzaXMiCmF1dGhvcjogIkpvc2UgTHVpcyBSb2RyaWd1ZXoiCmRhdGU6ICJPY3RvYmVyIDE0LCAyMDE2IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKc3VidGl0bGU6IENNRSBHcm91cCBGb3VuZGF0aW9uIEJ1c2luZXNzIEFuYWx5dGljcyBMYWIKLS0tCgotLS0tLS0tLS0tLS0tLS0KCiMjIyBBYm91dAoKU3RhbmZvcmQgQ29yZU5MUCBpcyBhbiBpbnRlZ3JhdGVkIGZyYW1ld29yay4gSXRzIGdvYWwgaXMgdG8gbWFrZSBpdCB2ZXJ5IGVhc3kgdG8gYXBwbHkgYSBidW5jaCBvZiBsaW5ndWlzdGljIGFuYWx5c2lzIHRvb2xzIHRvIGEgcGllY2Ugb2YgdGV4dC4gQSBDb3JlTkxQIHRvb2wgcGlwZWxpbmUgY2FuIGJlIHJ1biBvbiBhIHBpZWNlIG9mIHBsYWluIHRleHQgd2l0aCBqdXN0IHR3byBsaW5lcyBvZiBjb2RlLiBJdCBpcyBkZXNpZ25lZCB0byBiZSBoaWdobHkgZmxleGlibGUgYW5kIGV4dGVuc2libGUuIAoKIyMjIENhcGFiaWxpdGllcwoKU3RhbmZvcmQgQ29yZU5MUCBwcm92aWRlcyBhIHNldCBvZiBuYXR1cmFsIGxhbmd1YWdlIGFuYWx5c2lzIHRvb2xzLiBJdCBjYW4gZ2l2ZSB0aGUgYmFzZSBmb3JtcyBvZiB3b3JkcywgdGhlaXIgcGFydHMgb2Ygc3BlZWNoLCB3aGV0aGVyIHRoZXkgYXJlIG5hbWVzIG9mIGNvbXBhbmllcywgcGVvcGxlLCBldGMuLCBub3JtYWxpemUgZGF0ZXMsIHRpbWVzLCBhbmQgbnVtZXJpYyBxdWFudGl0aWVzLCBhbmQgbWFyayB1cCB0aGUgc3RydWN0dXJlIG9mIHNlbnRlbmNlcyBpbiB0ZXJtcyBvZiBwaHJhc2VzIGFuZCB3b3JkIGRlcGVuZGVuY2llcywgaW5kaWNhdGUgd2hpY2ggbm91biBwaHJhc2VzIHJlZmVyIHRvIHRoZSBzYW1lIGVudGl0aWVzLCBpbmRpY2F0ZSBzZW50aW1lbnQsIGV4dHJhY3Qgb3Blbi1jbGFzcyByZWxhdGlvbnMgYmV0d2VlbiBtZW50aW9ucywgZXRjLgoKIyMjIFdoeSB0byB1c2UgQ29yZU5MUAoKKiBBbiBpbnRlZ3JhdGVkIHRvb2xraXQgd2l0aCBhIGdvb2QgcmFuZ2Ugb2YgZ3JhbW1hdGljYWwgYW5hbHlzaXMgdG9vbHMKKiBGYXN0LCByZWxpYWJsZSBhbmFseXNpcyBvZiBhcmJpdHJhcnkgdGV4dHMKKiBUaGUgb3ZlcmFsbCBoaWdoZXN0IHF1YWxpdHkgdGV4dCBhbmFseXRpY3MKKiBTdXBwb3J0IGZvciBhIG51bWJlciBvZiBtYWpvciAoaHVtYW4pIGxhbmd1YWdlcwoqIEludGVyZmFjZXMgYXZhaWxhYmxlIGZvciB2YXJpb3VzIG1ham9yIG1vZGVybiBwcm9ncmFtbWluZyBsYW5ndWFnZXMKKiBBYmlsaXR5IHRvIHJ1biBhcyBhIHNpbXBsZSB3ZWIgc2VydmljZQoKCiMjIyBEb3dubG9hZAoKPGh0dHA6Ly9zdGFuZm9yZG5scC5naXRodWIuaW8vQ29yZU5MUC9kb3dubG9hZC5odG1sPgoKLS0tLS0tLS0tLS0tLS0tCgojIFIgKyBDb3JlTkxQIPCfkb0KCkZpcnN0IHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlIFN0YW5mb3JkIENvcmVOTFAgYXBwbGljYXRpb24gaW4geW91ciBtYWNoaW5lIG9yIGNvbm5lY3QgdG8gYSBzZXJ2ZXIgcnVubmluZyB0aGUgYXBwbGljYXRpb24uIEluIG91ciBjYXNlIHRoZSBBbmFseXRpY3MgTGFiIGhhcyB0aGUgQ29yZU5MUCBhcHBsaWNhdGlvbiBydW5uaW5nIGluIG91ciBzZXJ2ZXIuCgojIyMgU2V0dXAKV2UgYXJlIGdvaW5nIHRvIG5lZWQgdGhlIGZvbGxvd2luZyBSIHBhY2tlZ2VzLgoKYGBge3J9CmlmKCFyZXF1aXJlKHhtbDIpKSBpbnN0YWxsLnBhY2thZ2VzKCJ4bWwyIikKaWYoIXJlcXVpcmUoWE1MKSkgaW5zdGFsbC5wYWNrYWdlcygiWE1MIikKaWYoIXJlcXVpcmUoaHR0cikpIGluc3RhbGwucGFja2FnZXMoImh0dHIiKQpgYGAKCkluIG9yZGVyIHRvIGNvbm5lY3QgYW5kIHBhcnNlIHRoZSBvdXRwdXQgZnJvbSBDb3JlTkxQIHdlIG5lZWQgYSBjb3VwbGUgb2YgZnVuY3Rpb25zLiDimqDvuI8gVGhlc2UgZnVuY3Rpb25zIGFyZSBwYXJ0IG9mIHRoZSBzZXR1cCwgeW91IHNob3VsZCBub3QgY2hhbmdlIGFueXRoaW5nIHVubGVzcyB5b3UgYXJlIGZhbWlsaWFyIHdpdGh0IHRoZSBDb3JlTkxQIHN5bnRheC4KCgotLS0tLS0tLS0tLS0tLS0KCiMjIyMgRnVuY3Rpb24gdG8gY29ubmVjdCB0byB0aGUgbG9jYWwvcmVtb3RlIENvcmVOTFAgc2VydmVyLgoKYGBge3J9CmNvbm5OTFAgPC0gZnVuY3Rpb24oaG9zdD0ibG9jYWxob3N0IiwgcG9ydD0iOTAwMCIsCiAgICAgICAgICAgICAgICAgICAgdG9rZW5pemUud2hpdGVzcGFjZT0idHJ1ZSIsIGFubm90YXRvcnM9IiIsCiAgICAgICAgICAgICAgICAgICAgb3V0cHV0Rm9ybWF0PSJ4bWwiKSB7CiAgICAgICAgICAgICAgICAgICAgICBjb25uIDwtIHBhc3RlKCJodHRwOi8vIixob3N0LCI6Iixwb3J0LCIvPyIsc2VwPSIiKQogICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdG9ycyA8LSBnc3ViKCIsIiwiJTJDIixhbm5vdGF0b3JzKQogICAgICAgICAgICAgICAgICAgICAgcHJvcGVydGllcyA8LSBwYXN0ZSgicHJvcGVydGllcz0lN0IiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJTIydG9rZW5pemUud2hpdGVzcGFjZSUyMiUzQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIlMjIiLHRva2VuaXplLndoaXRlc3BhY2UsIiUyMiUyQyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIlMjJhbm5vdGF0b3JzJTIyJTNBIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiUyMiIsYW5ub3RhdG9ycywiJTIyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiUyQyUyMm91dHB1dEZvcm1hdCUyMiUzQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIlMjIiLG91dHB1dEZvcm1hdCwiJTIyJTdEIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIiKQogICAgICAgICAgICAgICAgICAgICAgY29ubiA8LSBwYXN0ZShjb25uLHByb3BlcnRpZXMsc2VwPSIiKQogICAgICAgICAgICAgICAgICAgICAgcmVxdWVzdCA8LSBsaXN0KCJjb25uIj1jb25uLCJvdXQiPW91dHB1dEZvcm1hdCkKICAgICAgICAgICAgICAgICAgICAgIHJldHVybihyZXF1ZXN0KQogICAgICAgICAgICB9CmBgYAoKLS0tLS0tLS0tLS0tLS0tCgojIyMjIEZ1bmN0aW9uIHRvIG1ha2UgcmVxdWVzdHMgdG8gdGhlIENvcmVOTFAgc2VydmVyIHRvIGFuYWx5c2UgYSB0ZXh0IGRvY3VtZW50LgoKYGBge3J9CmdldE5MUCA8LSBmdW5jdGlvbih0ZXh0LGNvbm4pewogICAgICAgICAgICAgIHRleHQgPC0gZ3N1YigiJyIsIiIsdGV4dCkKICAgICAgICAgICAgICBpZih0b2xvd2VyKGNvbm5bJ291dCddKT09InhtbCIpewogICAgICAgICAgICAgICAgcmVxdWlyZSgiWE1MIikKICAgICAgICAgICAgICAgIHJlcXVpcmUoImh0dHIiKQogICAgICAgICAgICAgICAgcmVzdWx0IDwtIFBPU1QoY29ublsnY29ubiddW1sxXV0sYm9keSA9IHRleHQsZW5jb2RlID0gIm11bHRpcGFydCIpCiAgICAgICAgICAgICAgICBkb2MgPC0geG1sUGFyc2UoY29udGVudChyZXN1bHQsInBhcnNlZCIsImFwcGxpY2F0aW9uL3htbCIsZW5jb2RpbmcgPSAiVVRGLTgiKSkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBpZih0b2xvd2VyKGNvbm5bJ291dCddKT09Impzb24iKXsKICAgICAgICAgICAgICAgIHJlc3VsdCA8LSBQT1NUKGNvbm5bJ2Nvbm4nXVtbMV1dLGJvZHkgPSB0ZXh0LGVuY29kZSA9ICJqc29uIikKICAgICAgICAgICAgICAgIGRvYyA8LSBjb250ZW50KHJlc3VsdCwgInBhcnNlZCIsImFwcGxpY2F0aW9uL2pzb24iLGVuY29kaW5nID0gIlVURi04IikKICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgaWYodG9sb3dlcihjb25uWydvdXQnXSk9PSJ0ZXh0Iil7CiAgICAgICAgICAgICAgICByZXN1bHQgPC0gUE9TVChjb25uWydjb25uJ11bWzFdXSxib2R5ID0gdGV4dCxlbmNvZGUgPSAibXVsdGlwYXJ0IikKICAgICAgICAgICAgICAgIGRvYyA8LSBjb250ZW50KHJlc3VsdCwgInBhcnNlZCIsImFwcGxpY2F0aW9uL3RleHQiLGVuY29kaW5nID0gIlVURi04IikKICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgcmV0dXJuKGRvYykKICAgICAgICB9CmBgYAoKLS0tLS0tLS0tLS0tLS0tCgojIyMjIEZ1bmN0aW9uIHRvIGV4dHJhY3QgdGhlIHJlc3VsdCBvZiB0aGUgQ29yZU5MUCBzZW50aW1lbnQgYW5ub3RhdG9yIGZvciB0aGUgYWxsIHRleHQgZG9jdW1lbnQuCgpgYGB7cn0KZ2V0U2VudCA8LSBmdW5jdGlvbihkb2NOTFApewogIHNjciA8LSBhcy5pbnRlZ2VyKHhwYXRoU0FwcGx5KGRvY05MUCwgIi8vc2VudGVuY2VzL3NlbnRlbmNlL0BzZW50aW1lbnRWYWx1ZSIpKQogIHNlbnQgPC0gdG9TdHJpbmcoeHBhdGhTQXBwbHkoZG9jTkxQLCAiLy9zZW50ZW5jZXMvc2VudGVuY2UvQHNlbnRpbWVudCIpKQogIHJlc3VsdCA8LSBsaXN0KHNlbnRpbWVudCA9IGMoc2VudCksc2NvcmU9YyhzY3IpKQogIHJldHVybihyZXN1bHQpCn0KYGBgCgotLS0tLS0tLS0tLS0tLS0KCiMjIyMgRnVuY3Rpb24gdG8gZXh0cmFjdCB0aGUgcmVzdWx0IG9mIHRoZSBDb3JlTkxQIHNlbnRpbWVudCBhbm5vdGF0b3IgZm9yIGVhY2ggd29yZCBpbiB0aGUgdGV4dCBkb2N1bWVudC4KCmBgYHtyfQpnZXRXb3JkcyA8LSBmdW5jdGlvbihkb2NOTFApewogIHdvcmQgPC0geHBhdGhTQXBwbHkoZG9jTkxQLCAiLy90b2tlbi93b3JkIix4bWxWYWx1ZSkKICB3b3JkX3NlbnQgPC0geHBhdGhTQXBwbHkoZG9jTkxQLCAiLy90b2tlbi9zZW50aW1lbnQiLHhtbFZhbHVlKQogIGRmIDwtIGRhdGEuZnJhbWUod29yZCx3b3JkX3NlbnQpCiAgcmV0dXJuKGRmKQp9CmBgYAoKLS0tLS0tLS0tLS0tLS0tCgojIyMjIEZ1bmN0aW9ucyB0byBwdWxsIGluZm9ybWF0aW9uIGZyb20gYW4gUlNTIGZlZWQuCgpgYGB7cn0KZ2V0UlNTIDwtIGZ1bmN0aW9uKHVybD0iIil7CiAgZG9jIDwtIHhtbFBhcnNlKHVybCkKICB0aXRsZSA8LSB4cGF0aFNBcHBseShkb2MsICIvL2l0ZW0vdGl0bGUiLCB4bWxWYWx1ZSkKICBkZXNjIDwtIHhwYXRoU0FwcGx5KGRvYywgIi8vaXRlbS9kZXNjcmlwdGlvbiIsIHhtbFZhbHVlKQogIGRhdGUgPC0geHBhdGhTQXBwbHkoZG9jLCAiLy9pdGVtL3B1YkRhdGUiLCB4bWxWYWx1ZSkKICBkYXRlIDwtIHN0cnB0aW1lKGRhdGUsZm9ybWF0ID0iJWEsICVkICViICVZICVIOiVNOiVTIix0ej0iR01UIikKICByc3MgPC0gbGlzdCh0aXRsZT10aXRsZSxkZXNjcmlwdGlvbiA9IGRlc2MsZGF0ZSA9IGRhdGUpCiAgcmV0dXJuKHJzcykKfQpgYGAKCi0tLS0tLS0tLS0tLS0tLQoKIyBDb3JlTkxQIGluIEFjdGlvbiDwn5qACgpOb3cgdGhhdCB3ZSBoYXZhIHRoZSBmdW5jdGlvbnMgdG8gY29ubmVjdCwgcGFyc2UgYW5kIGFuYWx5emUgdGhlIG91dHB1dCBvZiB0aGUgQ29yZU5MUCBhcHBsaWNhdGlvbi4gVXNpbmcgdGhlIGNvbm5lY3Rpb24gZnVuY3Rpb24gd2UgY2FuIGNvbm5lY3QgdG8gdGhlIENvcmVOTFAgc2VydmVyIGFuZCBzZXQgdGhlIHBhcmFtZXRlcnMgdGhhdCB3ZSBhcmUgZ29pbmcgdG8gdXNlIGluIHRoZSBuYXR1cmFsIGxhbmd1YWdlIGFuYWx5c2lzLgoKYGBge3J9CmNvbm4gPC0gY29ubk5MUCggaG9zdCA9ICJsb2NhbGhvc3QiLCAgcG9ydCA9ICI5MDAwIiwKICAgICAgICAgICAgICAgICB0b2tlbml6ZS53aGl0ZXNwYWNlID0gInRydWUiLCBhbm5vdGF0b3JzID0gInNlbnRpbWVudCIsCiAgICAgICAgICAgICAgICAgb3V0cHV0Rm9ybWF0ID0gInhtbCIgKQpgYGAKCkluIG9yZGVyIHRvIHBlcmZvcm0gbmF0dXJhbCBsYW5ndWFnZSBhbmFseXNpcyB3ZSBuZWVkIHRvIGZlZWQgYSB0ZXh0IGRvY3VtZW50IHRvIHRoZSBhcHBsaWNhdGlvbi4gVG8gZG8gdGhhdCB3ZSBhcmUgZ29pbmcgdG8gZ2V0IHNvbWUgbmV3cyBmcm9tIGFuIFJTUyBmZWVkIHVzaW5nIHRoZSBgZ2V0UlNTYCBmdW5jdGlvbiBhbmQgdGhlIGxpbmsgdG8gdGhlIFJTUyBwYWdlLgoKLS0tLS0tLS0tLS0tLS0tCgojIyMjIExldHMgYW5hbHl6ZSB0aGUgZmlyc3QgaGVhZGxpbmUgaW4gdGhlIFVTIE5ld3MgUlNTIGZlZWQuCgoxLiBXZSBnZXQgdGhlIG5ld3MgZnJvbSB0aGUgUlNTIGZlZWQuCjIuIEdldCB0aGUgZmlyc3QgaGVhZGxpbmUuCjMuIEZlZWQgdGhlIHRleHQgdG8gdGhlIENvcmVOTFAgYXBwbGljYXRpb24uCgpgYGB7cn0KbmV3cyA8LSBnZXRSU1ModXJsPSJodHRwOi8vd3d3LnVzbmV3cy5jb20vcnNzL25ld3MiKQp0ZXh0IDwtIG5ld3MkdGl0bGVbMV0KZGVzY3JpcHRpb24gPSBuZXdzJGRlc2NbMV0KZG9jIDwtIGdldE5MUCh0ZXh0LGNvbm4pCnRleHQKYGBgCgotLS0tLS0tLS0tLS0tLS0KCiMjIyMgTm93IGxldHMgcHJvY2VzcyB0aGUgZG9jdW1lbnQgdGhhdCBjb250YWlucyB0aGUgcmVzdWx0IG9mIHRoZSBuYXR1cmFsIGxhbmd1YWdlIGFuYWx5c2lzLCB1c2luZyB0aGUgYGdldFNlbnRgIGZ1bmN0aW9uLgoKYGBge3J9CnRleHRfc2VudGltZW50IDwtIGdldFNlbnQoZG9jKQp0ZXh0X3NlbnRpbWVudApgYGAKCiMjIyMgV2UgY2FuIGFsc28gYW5hbHl6ZSB0aGUgc2VudGltZW50IG9mIGVhY2ggd29yZCBpbiB0aGUgaGVhZGxpbmUsIHVzaW5nIHRoZSBgZ2V0V29yZHNgIGZ1bmN0aW9uLgoKYGBge3J9CndvcmRzX3NlbnRpbWVudCA8LSBnZXRXb3Jkcyhkb2MpCndvcmRzX3NlbnRpbWVudApgYGAKCiMjIyMgV2UgY2FuIGRvIHRoZSBzYW1lIGZvciB0aGUgbmV3cyBkZXNjcmlwdGlvbi4KYGBge3J9CmRlc2NyaXB0aW9uCmBgYApgYGB7cn0KZG9jIDwtIGdldE5MUChkZXNjcmlwdGlvbixjb25uKQpkZXNjX3NlbnRpbWVudCA8LSBnZXRTZW50KGRvYykKZGVzY19zZW50aW1lbnQKYGBgCgojIyMjIE5vdyBsZXRzIGdldCB0aGUgc2VudGltZW50IG9mIGVhY2ggd29yZCBmb3IgdGhlIGRlc2NyaXB0aW9uLgoKYGBge3J9CndvcmRzX3NlbnRpbWVudCA8LSBnZXRXb3Jkcyhkb2MpCndvcmRzX3NlbnRpbWVudApgYGAKCi0tLS0tLS0tLS0tLS0tLQoKIyBTZW50aW1lbnQgYW5hbHlzaXMgb2YgbmV3cyBoZWFkbGluZXMg8J+MjgoKYGBge3IsZXZhbD1GQUxTRX0KbmV3c19ubHAgPSBsaXN0KCkKCmZvcihpIGluIDE6bGVuZ3RoKG5ld3MkdGl0bGUpKXsKICBkb2MgPC0gZ2V0TkxQKG5ld3MkdGl0bGVbaV0sY29ubikKICBzZW50IDwtIGdldFNlbnQoZG9jKQogIGRmIDwtIGRhdGEuZnJhbWUoInRpdGxlIj1uZXdzJHRpdGxlW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI9bmV3cyRkZXNjW2ldLAogICAgICAgICAgICAgICAgICAgICAgICAgICJzZW50aW1lbnQiPXNlbnRbJ3NlbnRpbWVudCddLAogICAgICAgICAgICAgICAgICAgICAgICAgICJzY29yZSI9c2VudFsnc2NvcmUnXSkKICBuZXdzX25scFtbaV1dIDwtIGRmCiAgU3lzLnNsZWVwKDEpCn0KYGBgCm5ld3Nfc2VudGltZW50IDwtIGRvLmNhbGwoInJiaW5kIixuZXdzX25scCkKI3dyaXRlLmNzdihuZXdzX3NlbnRpbWVudCxmaWxlID0gIm5ld3MuY3N2IikKYGBgCgpgYGB7cn11bnRzIDwtIHRhYmxlKG5ld3Nfc2VudGltZW50JHNlbnRpbWVudCkKYmFycGxvdChjb3VudHMsIG1haW49IlNlbnRpbWVudCBEaXN0cmlidXRpb24iKQpgYGAKCgo=