You can find a tutorial for the whole process here: http://www.sthda.com/english/wiki/text-mining-and-word-cloud-fundamentals-in-r-5-simple-steps-you-should-know
The PDF of the PTM framework is here: https://www.bps.org.uk/news-and-policy/introducing-power-threat-meaning-framework
Convert PDF to Text
Conversion by pdftotext (you’ll need poppler-utils to do this)
pdf.fname <- "'INF299 PTM Main web.pdf'"
txt.fname <- "PTMF.txt"
sys.cmd <- paste("pdftotext", pdf.fname, txt.fname)
system(sys.cmd)
Read the text file
txt <- readLines(txt.fname)
incomplete final line found on 'PTMF.txt'
docs <- Corpus(VectorSource(txt))
# some cleaning up : mainly removing extraneous formatting
toSpace <- content_transformer(function (x , pattern ) gsub(pattern, " ", x))
docs <- tm_map(docs, toSpace, "/")
docs <- tm_map(docs, toSpace, "@")
docs <- tm_map(docs, toSpace, "\\|")
docs <- tm_map(docs, toSpace, "●")
docs <- tm_map(docs, toSpace, "■■")
docs <- tm_map(docs, toSpace, "•")
docs <- tm_map(docs, toSpace, "‘")
docs <- tm_map(docs, toSpace, "’")
docs <- tm_map(docs, toSpace, "–")
docs <- tm_map(docs, toSpace, "\\t")
docs <- tm_map(docs, toSpace, "\\f")
# Convert the text to lower case
docs <- tm_map(docs, content_transformer(tolower))
# Remove numbers
docs <- tm_map(docs, removeNumbers)
# Remove english common stopwords
docs <- tm_map(docs, removeWords, stopwords("english"))
# Remove punctuations
docs <- tm_map(docs, removePunctuation)
# Eliminate extra white spaces
docs <- tm_map(docs, stripWhitespace)
Build Term-Document Matrix and Wordcloud
dtm <- TermDocumentMatrix(docs)
m <- as.matrix(dtm)
v <- sort(rowSums(m),decreasing=TRUE)
d <- data.frame(word = names(v),freq=v)
# clear some space
rm(docs)
set.seed(1234)
wordcloud(words = d$word, freq = d$freq, scale = c(2,0.5), min.freq = 1,
max.words=100, random.order=FALSE, rot.per=0.35,
colors=brewer.pal(8, "Set1"))

Some More Quantitative Plots
Examine the frequency ‘drop off’ from highest to lowest occuring words (this will help filter the results)
plot( d$freq, type = "l", xaxt="n", xlab = "Rank", ylab = "Frequency" )
axis(1, at = c(0,nrow(d)), labels = c("Highest", "Lowest"))

Visually, it seems at around a frequency of 40 there is a rapid fall in word occurrence, so we’ll strip out those words. Look at the highest frequency terms:
d[ which( d$freq > 40 ), ]
Now, removing “power”, “threat”, “meaning” and “framework” (as they after all, the title of the document and likely to be extensively used):
rmv.idx <- which( d$word %in% c("power","threat","meaning","framework") )
d2 <- d[ -rmv.idx, ]
# keep terms with freq > 40
d2 <- d2[ which( d$freq > 40 ), ]
And replot the wordcloud:
wordcloud(words = d2$word, freq = d2$freq, scale = c(2,0.5), min.freq = 1,
max.words=100, random.order=FALSE, rot.per=0.35,
colors=brewer.pal(8, "Set1"))

Word Associations
Revisiting again the most frequent words (here, the top 10):
head( d2, 10 )
Note the high frequency of the modal verbs “may” and “can”. We can try and see what other terms correlate highly with these:
assc <- findAssocs(dtm, c("may","can"), c(0.0,0.0))
par(mfrow=c(1,2))
barplot(assc[[1]][1:15], las = 2,
col ="lightblue", main ="May",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:15], las = 2,
col ="lightblue", main ="Can",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

“may” correlates most with “also”, “describe” and “compound”, whereas “can” with “seen” (as in “can be seen”) and “accommodated” and “counteracted” (as in “can be counteracted”).
It might be instructive to look at the top three words: “social”, “mental” and “health”
assc <- findAssocs(dtm, c("social","mental","health"), c(0.0, 0.0, 0.0))
par(mfrow=c(2,2))
barplot(assc[[1]][1:10], las = 2,
col ="lightblue", main ="Social",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:10], las = 2,
col ="lightblue", main ="Mental",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[3]][1:10], las = 2,
col ="lightblue", main ="Health",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

Now, associations with some keywords. Plot correlations of “psychiatric” and “psychiatry”
assc <- findAssocs(dtm, c("psychiatric","psychiatry"), c(0.0,0.0))
par(mfrow=c(1,2))
barplot(assc[[1]][1:10], las = 2,
col ="lightblue", main ="Psychiatric",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:10], las = 2,
col ="lightblue", main ="Psychiatry",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

Let’s examine for any contrasts with “psychology” and “psychological” (but here, we’ll have to filter “british”, “society”, “clinical”, “divisions” and “forum” to eliminate hits professional body names, “january” because footer contains both “psychological” and “january”, “ptmmain” and “wwwbpsorguk” because it’s a weblink frequently cited to the forthcoming “main” framework document)
assc.psychology <- findAssocs(dtm, c("psychology"), c(0.0) )[[1]]
assc.psychology <- assc.psychology[ -which( names(assc.psychology) %in%
c("british", "society", "clinical",
"division", "divisions", "forum", "january", "ptmmain", "wwwbpsorguk") ) ]
assc.psychological <- findAssocs(dtm, c("psychological"), c(0.0) )[[1]]
assc.psychological <- assc.psychological[ -which( names(assc.psychological) %in%
c("british", "society", "clinical",
"division", "divisions", "forum", "january", "ptmmain", "wwwbpsorguk") ) ]
par(mfrow=c(1,2))
barplot(assc.psychology[1:10], las = 2,
col ="lightblue", main ="Psychology",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc.psychological[1:10], las = 2,
col ="lightblue", main ="Psychological",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

Examine the correlating terms for “power”, “threat” and “meaning” (but filtering so the title of the document is not the highest correlation, so we remove “overview”, “framework” as well)
assc.power <- findAssocs(dtm, c("power"), c(0.0))[[1]]
#remove "threat" and "meaning"
assc.power <- assc.power[ -which( names(assc.power) %in% c("threat","meaning","overview","framework") ) ]
assc.threat <- findAssocs(dtm, c("threat"), c(0.0))[[1]]
#remove "power" and "meaning"
assc.threat <- assc.threat[ -which( names(assc.threat) %in% c("power","meaning","overview","framework") ) ]
assc.meaning <- findAssocs(dtm, c("meaning"), c(0.0))[[1]]
#remove "power" and "threat"
assc.meaning <- assc.meaning[ -which( names(assc.meaning) %in% c("power","threat","overview","framework") ) ]
par(mfrow=c(2,2))
barplot(assc.power[1:10], las = 2,
col ="lightblue", main ="Power",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc.threat[1:10], las = 2,
col ="lightblue", main ="Threat",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc.meaning[1:10], las = 2,
col ="lightblue", main ="Meaning",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

More Controversial Keywords
Perhaps more loaded, is to ask what correlates with “empirical” and “evidence” in the document:
assc <- findAssocs(dtm, c("empirical","evidence"), c(0.0))
par(mfrow=c(1,2))
barplot(assc[[1]][1:10], las = 2,
col ="lightblue", main ="Empirical",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:10], las = 2,
col ="lightblue", main ="Evidence",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

And for “medical” and “medicalisation”
assc <- findAssocs(dtm, c("medical","medicalisation"), c(0.0))
par(mfrow=c(1,2))
barplot(assc[[1]][1:10], las = 2,
col ="lightblue", main ="Medical",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:10], las = 2,
col ="lightblue", main ="Medicalisation",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

And finally “formulation” and “narrative”:
assc <- findAssocs(dtm, c("formulation","narrative"), c(0.0))
par(mfrow=c(1,2))
barplot(assc[[1]][1:10], las = 2,
col ="lightblue", main ="formulation",
ylab = "Correlations",cex = 0.8, cex.lab=1.0, cex.axis=1.0 )
barplot(assc[[2]][1:10], las = 2,
col ="lightblue", main ="narrative",
ylab = "Correlations", cex = 0.8, cex.lab=1.0, cex.axis=1.0 )

LS0tCnRpdGxlOiAiQSBRdWljayBRdWFudGl0YXRpdmUgQ29udGVudCBBbmFseXNpcyBvZiB0aGUgUFRNIERvY3VtZW50IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBlY2hvID0gRkFMU0V9CiMjIFdlIG5lZWQgdGhlIGZvbGxvd2luZyBwYWNrYWdlcwojIGluc3RhbGwucGFja2FnZXMoInRtIikgICMgZm9yIHRleHQgbWluaW5nCiMgaW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIikgIyB3b3JkLWNsb3VkIGdlbmVyYXRvciAKIyBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKSAjIGNvbG9yIHBhbGV0dGVzCiMgc291cmNlKCJodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikKIyBiaW9jTGl0ZSgiUmdyYXBodml6IikKCmxpYnJhcnkoInRtIikKbGlicmFyeSgid29yZGNsb3VkIikKbGlicmFyeSgiUkNvbG9yQnJld2VyIikKbGlicmFyeSgiUmdyYXBodml6IikKCmBgYAoKWW91IGNhbiBmaW5kIGEgdHV0b3JpYWwgZm9yIHRoZSB3aG9sZSBwcm9jZXNzIGhlcmU6IGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS90ZXh0LW1pbmluZy1hbmQtd29yZC1jbG91ZC1mdW5kYW1lbnRhbHMtaW4tci01LXNpbXBsZS1zdGVwcy15b3Utc2hvdWxkLWtub3cKClRoZSBQREYgb2YgdGhlIFBUTSBmcmFtZXdvcmsgaXMgaGVyZToKaHR0cHM6Ly93d3cuYnBzLm9yZy51ay9uZXdzLWFuZC1wb2xpY3kvaW50cm9kdWNpbmctcG93ZXItdGhyZWF0LW1lYW5pbmctZnJhbWV3b3JrCgojIyBDb252ZXJ0IFBERiB0byBUZXh0CkNvbnZlcnNpb24gYnkgcGRmdG90ZXh0ICh5b3UnbGwgbmVlZCBwb3BwbGVyLXV0aWxzIHRvIGRvIHRoaXMpCgpgYGB7cn0KcGRmLmZuYW1lIDwtICInSU5GMjk5IFBUTSBNYWluIHdlYi5wZGYnIgp0eHQuZm5hbWUgPC0gIlBUTUYudHh0IgpzeXMuY21kICAgPC0gcGFzdGUoInBkZnRvdGV4dCIsIHBkZi5mbmFtZSwgdHh0LmZuYW1lKQpzeXN0ZW0oc3lzLmNtZCkKYGBgCiMjIFJlYWQgdGhlIHRleHQgZmlsZQpgYGB7cn0KdHh0IDwtIHJlYWRMaW5lcyh0eHQuZm5hbWUpCmRvY3MgPC0gQ29ycHVzKFZlY3RvclNvdXJjZSh0eHQpKQpgYGAKCmBgYHtyfQojIHNvbWUgY2xlYW5pbmcgdXAgOiBtYWlubHkgcmVtb3ZpbmcgZXh0cmFuZW91cyBmb3JtYXR0aW5nCnRvU3BhY2UgPC0gY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbiAoeCAsIHBhdHRlcm4gKSBnc3ViKHBhdHRlcm4sICIgIiwgeCkpCmRvY3MgPC0gdG1fbWFwKGRvY3MsIHRvU3BhY2UsICIvIikKZG9jcyA8LSB0bV9tYXAoZG9jcywgdG9TcGFjZSwgIkAiKQpkb2NzIDwtIHRtX21hcChkb2NzLCB0b1NwYWNlLCAiXFx8IikKZG9jcyA8LSB0bV9tYXAoZG9jcywgdG9TcGFjZSwgIuKXjyIpCmRvY3MgPC0gdG1fbWFwKGRvY3MsIHRvU3BhY2UsICLilqDilqAiKQpkb2NzIDwtIHRtX21hcChkb2NzLCB0b1NwYWNlLCAi4oCiIikKZG9jcyA8LSB0bV9tYXAoZG9jcywgdG9TcGFjZSwgIuKAmCIpCmRvY3MgPC0gdG1fbWFwKGRvY3MsIHRvU3BhY2UsICLigJkiKQpkb2NzIDwtIHRtX21hcChkb2NzLCB0b1NwYWNlLCAi4oCTIikKZG9jcyA8LSB0bV9tYXAoZG9jcywgdG9TcGFjZSwgIlxcdCIpCgpkb2NzIDwtIHRtX21hcChkb2NzLCB0b1NwYWNlLCAiXFxmIikKCiMgQ29udmVydCB0aGUgdGV4dCB0byBsb3dlciBjYXNlCmRvY3MgPC0gdG1fbWFwKGRvY3MsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCiMgUmVtb3ZlIG51bWJlcnMKZG9jcyA8LSB0bV9tYXAoZG9jcywgcmVtb3ZlTnVtYmVycykKIyBSZW1vdmUgZW5nbGlzaCBjb21tb24gc3RvcHdvcmRzCmRvY3MgPC0gdG1fbWFwKGRvY3MsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkKIyBSZW1vdmUgcHVuY3R1YXRpb25zCmRvY3MgPC0gdG1fbWFwKGRvY3MsIHJlbW92ZVB1bmN0dWF0aW9uKQojIEVsaW1pbmF0ZSBleHRyYSB3aGl0ZSBzcGFjZXMKZG9jcyA8LSB0bV9tYXAoZG9jcywgc3RyaXBXaGl0ZXNwYWNlKQpgYGAKCiMjIEJ1aWxkIFRlcm0tRG9jdW1lbnQgTWF0cml4IGFuZCBXb3JkY2xvdWQKYGBge3J9CmR0bSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoZG9jcykKbSA8LSBhcy5tYXRyaXgoZHRtKQp2IDwtIHNvcnQocm93U3VtcyhtKSxkZWNyZWFzaW5nPVRSVUUpCmQgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXModiksZnJlcT12KQoKIyBjbGVhciBzb21lIHNwYWNlCnJtKGRvY3MpCgpzZXQuc2VlZCgxMjM0KQp3b3JkY2xvdWQod29yZHMgPSBkJHdvcmQsIGZyZXEgPSBkJGZyZXEsIHNjYWxlID0gYygyLDAuNSksIG1pbi5mcmVxID0gMSwKICAgICAgICAgIG1heC53b3Jkcz0xMDAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjM1LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJTZXQxIikpCmBgYAoKIyMgU29tZSBNb3JlIFF1YW50aXRhdGl2ZSBQbG90cwpFeGFtaW5lIHRoZSBmcmVxdWVuY3kgJ2Ryb3Agb2ZmJyBmcm9tIGhpZ2hlc3QgdG8gbG93ZXN0IG9jY3VyaW5nIHdvcmRzICh0aGlzIHdpbGwgaGVscCBmaWx0ZXIgdGhlIHJlc3VsdHMpCmBgYHtyfQpwbG90KCBkJGZyZXEsIHR5cGUgPSAibCIsIHhheHQ9Im4iLCB4bGFiID0gIlJhbmsiLCB5bGFiID0gIkZyZXF1ZW5jeSIgKQpheGlzKDEsIGF0ID0gYygwLG5yb3coZCkpLCBsYWJlbHMgPSBjKCJIaWdoZXN0IiwgIkxvd2VzdCIpKQpgYGAKVmlzdWFsbHksIGl0IHNlZW1zIGF0IGFyb3VuZCBhIGZyZXF1ZW5jeSBvZiA0MCB0aGVyZSBpcyBhIHJhcGlkIGZhbGwgaW4gd29yZCBvY2N1cnJlbmNlLCBzbyB3ZSdsbCBzdHJpcCBvdXQgdGhvc2Ugd29yZHMuICBMb29rIGF0IHRoZSBoaWdoZXN0IGZyZXF1ZW5jeSB0ZXJtczoKYGBge3J9CmRbIHdoaWNoKCBkJGZyZXEgPiA0MCApLCBdCmBgYApOb3csIHJlbW92aW5nICJwb3dlciIsICJ0aHJlYXQiLCAibWVhbmluZyIgYW5kICJmcmFtZXdvcmsiIChhcyB0aGV5IGFmdGVyIGFsbCwgdGhlIHRpdGxlIG9mIHRoZSBkb2N1bWVudCBhbmQgbGlrZWx5IHRvIGJlIGV4dGVuc2l2ZWx5IHVzZWQpOiAKCmBgYHtyfQpybXYuaWR4IDwtIHdoaWNoKCBkJHdvcmQgJWluJSBjKCJwb3dlciIsInRocmVhdCIsIm1lYW5pbmciLCJmcmFtZXdvcmsiKSApCmQyIDwtIGRbIC1ybXYuaWR4LCBdIAojIGtlZXAgdGVybXMgd2l0aCBmcmVxID4gNDAKZDIgPC0gZDJbIHdoaWNoKCBkJGZyZXEgPiA0MCApLCBdCmBgYApBbmQgcmVwbG90IHRoZSB3b3JkY2xvdWQ6CmBgYHtyfQp3b3JkY2xvdWQod29yZHMgPSBkMiR3b3JkLCBmcmVxID0gZDIkZnJlcSwgc2NhbGUgPSBjKDIsMC41KSwgbWluLmZyZXEgPSAxLAogICAgICAgICAgbWF4LndvcmRzPTEwMCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMzUsIAogICAgICAgICAgY29sb3JzPWJyZXdlci5wYWwoOCwgIlNldDEiKSkKYGBgCgojIyBXb3JkIEFzc29jaWF0aW9ucwpSZXZpc2l0aW5nIGFnYWluIHRoZSBtb3N0IGZyZXF1ZW50IHdvcmRzIChoZXJlLCB0aGUgdG9wIDEwKToKYGBge3J9CmhlYWQoIGQyLCAxMCApCmBgYApOb3RlIHRoZSBoaWdoIGZyZXF1ZW5jeSBvZiB0aGUgbW9kYWwgdmVyYnMgIm1heSIgYW5kICJjYW4iLiAgV2UgY2FuIHRyeSBhbmQgc2VlIHdoYXQgb3RoZXIgdGVybXMgY29ycmVsYXRlIGhpZ2hseSB3aXRoIHRoZXNlOgpgYGB7cn0KCmFzc2MgPC0gZmluZEFzc29jcyhkdG0sIGMoIm1heSIsImNhbiIpLCBjKDAuMCwwLjApKQpwYXIobWZyb3c9YygxLDIpKQpiYXJwbG90KGFzc2NbWzFdXVsxOjE1XSwgbGFzID0gMiwKICAgICAgICBjb2wgPSJsaWdodGJsdWUiLCBtYWluID0iTWF5IiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzY1tbMl1dWzE6MTVdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJDYW4iLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIiwgY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKYGBgCiJtYXkiIGNvcnJlbGF0ZXMgbW9zdCB3aXRoICJhbHNvIiwgImRlc2NyaWJlIiBhbmQgImNvbXBvdW5kIiwgd2hlcmVhcyAiY2FuIiB3aXRoICJzZWVuIiAoYXMgaW4gImNhbiBiZSBzZWVuIikgYW5kICJhY2NvbW1vZGF0ZWQiIGFuZCAiY291bnRlcmFjdGVkIiAoYXMgaW4gImNhbiBiZSBjb3VudGVyYWN0ZWQiKS4KCkl0IG1pZ2h0IGJlIGluc3RydWN0aXZlIHRvIGxvb2sgYXQgdGhlIHRvcCB0aHJlZSB3b3JkczogInNvY2lhbCIsICJtZW50YWwiIGFuZCAiaGVhbHRoIgpgYGB7cn0KCmFzc2MgPC0gZmluZEFzc29jcyhkdG0sIGMoInNvY2lhbCIsIm1lbnRhbCIsImhlYWx0aCIpLCBjKDAuMCwgMC4wLCAwLjApKQpwYXIobWZyb3c9YygyLDIpKQpiYXJwbG90KGFzc2NbWzFdXVsxOjEwXSwgbGFzID0gMiwKICAgICAgICBjb2wgPSJsaWdodGJsdWUiLCBtYWluID0iU29jaWFsIiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzY1tbMl1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJNZW50YWwiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIiwgY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzY1tbM11dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJIZWFsdGgiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIiwgY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmBgYAoKTm93LCBhc3NvY2lhdGlvbnMgd2l0aCBzb21lIGtleXdvcmRzLiAgUGxvdCBjb3JyZWxhdGlvbnMgb2YgInBzeWNoaWF0cmljIiBhbmQgInBzeWNoaWF0cnkiCmBgYHtyfQoKYXNzYyA8LSBmaW5kQXNzb2NzKGR0bSwgYygicHN5Y2hpYXRyaWMiLCJwc3ljaGlhdHJ5IiksIGMoMC4wLDAuMCkpCnBhcihtZnJvdz1jKDEsMikpCmJhcnBsb3QoYXNzY1tbMV1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJQc3ljaGlhdHJpYyIsCiAgICAgICAgeWxhYiA9ICJDb3JyZWxhdGlvbnMiLGNleCA9IDAuOCwgY2V4LmxhYj0xLjAsIGNleC5heGlzPTEuMCApCgpiYXJwbG90KGFzc2NbWzJdXVsxOjEwXSwgbGFzID0gMiwKICAgICAgICBjb2wgPSJsaWdodGJsdWUiLCBtYWluID0iUHN5Y2hpYXRyeSIsCiAgICAgICAgeWxhYiA9ICJDb3JyZWxhdGlvbnMiLCBjZXggPSAwLjgsIGNleC5sYWI9MS4wLCBjZXguYXhpcz0xLjAgKQpgYGAKCkxldCdzIGV4YW1pbmUgZm9yIGFueSBjb250cmFzdHMgd2l0aCAicHN5Y2hvbG9neSIgYW5kICJwc3ljaG9sb2dpY2FsIiAoYnV0IGhlcmUsIHdlJ2xsIGhhdmUgdG8gZmlsdGVyICJicml0aXNoIiwgInNvY2lldHkiLCAiY2xpbmljYWwiLCAiZGl2aXNpb25zIiBhbmQgImZvcnVtIiB0byBlbGltaW5hdGUgaGl0cyBwcm9mZXNzaW9uYWwgYm9keSBuYW1lcywgImphbnVhcnkiIGJlY2F1c2UgZm9vdGVyIGNvbnRhaW5zIGJvdGggInBzeWNob2xvZ2ljYWwiIGFuZCAiamFudWFyeSIsICJwdG1tYWluIiBhbmQgInd3d2Jwc29yZ3VrIiBiZWNhdXNlIGl0J3MgYSB3ZWJsaW5rIGZyZXF1ZW50bHkgY2l0ZWQgdG8gdGhlIGZvcnRoY29taW5nICJtYWluIiBmcmFtZXdvcmsgZG9jdW1lbnQpCmBgYHtyfQphc3NjLnBzeWNob2xvZ3kgPC0gZmluZEFzc29jcyhkdG0sIGMoInBzeWNob2xvZ3kiKSwgYygwLjApIClbWzFdXQphc3NjLnBzeWNob2xvZ3kgPC0gYXNzYy5wc3ljaG9sb2d5WyAtd2hpY2goIG5hbWVzKGFzc2MucHN5Y2hvbG9neSkgJWluJSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoImJyaXRpc2giLCAic29jaWV0eSIsICJjbGluaWNhbCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZGl2aXNpb24iLCAiZGl2aXNpb25zIiwgImZvcnVtIiwgImphbnVhcnkiLCAicHRtbWFpbiIsICJ3d3dicHNvcmd1ayIpICkgXQoKYXNzYy5wc3ljaG9sb2dpY2FsIDwtIGZpbmRBc3NvY3MoZHRtLCBjKCJwc3ljaG9sb2dpY2FsIiksIGMoMC4wKSApW1sxXV0KYXNzYy5wc3ljaG9sb2dpY2FsIDwtIGFzc2MucHN5Y2hvbG9naWNhbFsgLXdoaWNoKCBuYW1lcyhhc3NjLnBzeWNob2xvZ2ljYWwpICVpbiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJicml0aXNoIiwgInNvY2lldHkiLCAiY2xpbmljYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZGl2aXNpb24iLCAiZGl2aXNpb25zIiwgImZvcnVtIiwgImphbnVhcnkiLCAicHRtbWFpbiIsICJ3d3dicHNvcmd1ayIpICkgXQoKcGFyKG1mcm93PWMoMSwyKSkKYmFycGxvdChhc3NjLnBzeWNob2xvZ3lbMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9IlBzeWNob2xvZ3kiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIixjZXggPSAwLjgsIGNleC5sYWI9MS4wLCBjZXguYXhpcz0xLjAgKQoKYmFycGxvdChhc3NjLnBzeWNob2xvZ2ljYWxbMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9IlBzeWNob2xvZ2ljYWwiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIiwgY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKYGBgCgpFeGFtaW5lIHRoZSBjb3JyZWxhdGluZyB0ZXJtcyBmb3IgInBvd2VyIiwgInRocmVhdCIgYW5kICJtZWFuaW5nIiAoYnV0IGZpbHRlcmluZyBzbyB0aGUgdGl0bGUgb2YgdGhlIGRvY3VtZW50IGlzIG5vdCB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiwgc28gd2UgcmVtb3ZlICJvdmVydmlldyIsICJmcmFtZXdvcmsiIGFzIHdlbGwpCmBgYHtyfQphc3NjLnBvd2VyIDwtIGZpbmRBc3NvY3MoZHRtLCBjKCJwb3dlciIpLCBjKDAuMCkpW1sxXV0KI3JlbW92ZSAidGhyZWF0IiBhbmQgIm1lYW5pbmciCmFzc2MucG93ZXIgPC0gYXNzYy5wb3dlclsgLXdoaWNoKCBuYW1lcyhhc3NjLnBvd2VyKSAlaW4lIGMoInRocmVhdCIsIm1lYW5pbmciLCJvdmVydmlldyIsImZyYW1ld29yayIpICkgXQoKYXNzYy50aHJlYXQgPC0gZmluZEFzc29jcyhkdG0sIGMoInRocmVhdCIpLCBjKDAuMCkpW1sxXV0KI3JlbW92ZSAicG93ZXIiIGFuZCAibWVhbmluZyIKYXNzYy50aHJlYXQgPC0gYXNzYy50aHJlYXRbIC13aGljaCggbmFtZXMoYXNzYy50aHJlYXQpICVpbiUgYygicG93ZXIiLCJtZWFuaW5nIiwib3ZlcnZpZXciLCJmcmFtZXdvcmsiKSApIF0KCmFzc2MubWVhbmluZyA8LSBmaW5kQXNzb2NzKGR0bSwgYygibWVhbmluZyIpLCBjKDAuMCkpW1sxXV0KI3JlbW92ZSAicG93ZXIiIGFuZCAidGhyZWF0Igphc3NjLm1lYW5pbmcgPC0gYXNzYy5tZWFuaW5nWyAtd2hpY2goIG5hbWVzKGFzc2MubWVhbmluZykgJWluJSBjKCJwb3dlciIsInRocmVhdCIsIm92ZXJ2aWV3IiwiZnJhbWV3b3JrIikgKSBdCgpwYXIobWZyb3c9YygyLDIpKQpiYXJwbG90KGFzc2MucG93ZXJbMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9IlBvd2VyIiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzYy50aHJlYXRbMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9IlRocmVhdCIsCiAgICAgICAgeWxhYiA9ICJDb3JyZWxhdGlvbnMiLGNleCA9IDAuOCwgY2V4LmxhYj0xLjAsIGNleC5heGlzPTEuMCApCgpiYXJwbG90KGFzc2MubWVhbmluZ1sxOjEwXSwgbGFzID0gMiwKICAgICAgICBjb2wgPSJsaWdodGJsdWUiLCBtYWluID0iTWVhbmluZyIsCiAgICAgICAgeWxhYiA9ICJDb3JyZWxhdGlvbnMiLGNleCA9IDAuOCwgY2V4LmxhYj0xLjAsIGNleC5heGlzPTEuMCApCgoKYGBgCgojIyBNb3JlIENvbnRyb3ZlcnNpYWwgS2V5d29yZHMKUGVyaGFwcyBtb3JlIGxvYWRlZCwgaXMgdG8gYXNrIHdoYXQgY29ycmVsYXRlcyB3aXRoICJlbXBpcmljYWwiIGFuZCAiZXZpZGVuY2UiIGluIHRoZSBkb2N1bWVudDoKYGBge3J9CmFzc2MgPC0gZmluZEFzc29jcyhkdG0sIGMoImVtcGlyaWNhbCIsImV2aWRlbmNlIiksIGMoMC4wKSkKCnBhcihtZnJvdz1jKDEsMikpCmJhcnBsb3QoYXNzY1tbMV1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJFbXBpcmljYWwiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIixjZXggPSAwLjgsIGNleC5sYWI9MS4wLCBjZXguYXhpcz0xLjAgKQoKYmFycGxvdChhc3NjW1syXV1bMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9IkV2aWRlbmNlIiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsIGNleCA9IDAuOCwgY2V4LmxhYj0xLjAsIGNleC5heGlzPTEuMCApCmBgYApBbmQgZm9yICJtZWRpY2FsIiBhbmQgIm1lZGljYWxpc2F0aW9uIgpgYGB7cn0KYXNzYyA8LSBmaW5kQXNzb2NzKGR0bSwgYygibWVkaWNhbCIsIm1lZGljYWxpc2F0aW9uIiksIGMoMC4wKSkKCnBhcihtZnJvdz1jKDEsMikpCmJhcnBsb3QoYXNzY1tbMV1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJNZWRpY2FsIiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzY1tbMl1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJNZWRpY2FsaXNhdGlvbiIsCiAgICAgICAgeWxhYiA9ICJDb3JyZWxhdGlvbnMiLCBjZXggPSAwLjgsIGNleC5sYWI9MS4wLCBjZXguYXhpcz0xLjAgKQpgYGAKQW5kIGZpbmFsbHkgImZvcm11bGF0aW9uIiBhbmQgIm5hcnJhdGl2ZSI6CgpgYGB7cn0KYXNzYyA8LSBmaW5kQXNzb2NzKGR0bSwgYygiZm9ybXVsYXRpb24iLCJuYXJyYXRpdmUiKSwgYygwLjApKQoKcGFyKG1mcm93PWMoMSwyKSkKYmFycGxvdChhc3NjW1sxXV1bMToxMF0sIGxhcyA9IDIsCiAgICAgICAgY29sID0ibGlnaHRibHVlIiwgbWFpbiA9ImZvcm11bGF0aW9uIiwKICAgICAgICB5bGFiID0gIkNvcnJlbGF0aW9ucyIsY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKCmJhcnBsb3QoYXNzY1tbMl1dWzE6MTBdLCBsYXMgPSAyLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJuYXJyYXRpdmUiLAogICAgICAgIHlsYWIgPSAiQ29ycmVsYXRpb25zIiwgY2V4ID0gMC44LCBjZXgubGFiPTEuMCwgY2V4LmF4aXM9MS4wICkKYGBgCgoKCg==