Prerequisites

This lesson presumes that you understand the structure of an XML document. If you are not familiar with XML, consult this lesson first.

Introduction

XML is a common means to externalize structured data an is commonly used for data interchange between systems and organizations. There are many standard XML languages for expressing data in specific domains, such as finance, agriculture, publishing, among others. It is also often used as a standard for configuration files and for web content.

Being able to extract data from XML is an important skill that every data programmers must master. This lesson explains how to use the functions from the XML package to extract data from XML files (also often called XML documents) in R. Other programming languages have very similar mechanisms and the skills learned in this lesson can be transferred to other languages, such as JavaScript, Java, C++, Rust, and many others.

Most often the data extracted from XML is analyzed statistically or used for constructing machine learning models, as well as being added to relational databases. These use cases are outside the scope of this lesson.

Required Packages

To extract data from XML, requires minimally the following packages:

  • XML

In addition, these packages contain functions that are often quite useful:

  • stringr

Note the XML is not the only package available for working with XML.

Loading an XML File

The process of extracting data starts by “parsing” the XML file from either a local folder or from a URL. The code below illustrates that for one of the included XML files (BookCatalog.xml). Download the file and run the code in your own R Notebook or R Script.

The code fragment below illustrates how to load and parse an XML document from a local file as well as from a URL.

library(XML)

xmlFile <- "BookCatalog.xml"
xmlURL <- "http://artificium.us/lessons/06.r/l-6-114-parse-xml-r-primer/BookCatalog.xml"

dom1 <- xmlParse(xmlFile)
dom2 <- xmlParse(xmlURL)

If you get the error “Error: XML content does not seem to be XML: ’’” then it is most likely that the file name, path, or URL are not correct.

Document Object Model (DOM)

The function xmlParse() returns a reference to an internal tree of nodes that represents the document object model (DOM) for the XML. Processing of the nodes and extraction of data is done through that reference.

Alternatively, one could use the function xmlTreeParse() which will also return a DOM but one that is represented as an R data structure rather than a C data structure1. While xmlParse() is more efficient and uses less memory, xmlTreeParse() has the benefit of element access using the $ operator. In practice, xmlParse() is most commonly used, though.

The variable dom (or to whichever variable you assigned the return value of xmlParse()) points to an in-memory representation of the XML tree. All access to the XML elements is via that pointer.

Processing an XML through its DOM requires that the XML is loaded completely into memory. Naturally, this is only feasible when sufficient memory is available. For very large XML files, another approach is available for extraction: SAX. This is beyond the scope of this tutorial.

Validation

By default, xmlParse() does not validate the file against any DTD or XML Schema; it only checks whether the XML is well-formed. To ensure that the XML conforms to the rules of a DTD or XML Schema, the parameter validate=T must be specified. Of course, this parameter is only meaningful if there is a DTD or Schema; if there isn’t and validate=T is specified then an error will result.

The XML file pagevisits.xml contains a DTD and therefore parsing with validation is possible.

library(XML)

xmlFile <- "pagevisits.xml"

dom <- xmlParse(xmlFile, validate=T)

Other useful parameters for xmlParse() include:

  • trim – a Boolean indicating whether to strip leading and trailing whitespace from values
  • getDTD – a Boolean flag indicating whether the DTD (both internal and external) should be returned along with the nodes
  • isURL – a Boolean indicating whether the document path is a URL; this is not strictly required if the URL starts with a common protocol such as http://

If you need to process HTML documents (a type of XML document), then the function htmlParse() is preferable.

Extracting Elements

The data in an XML document is contained in elements which are marked by pairs of tags, e.g., <tag>value</tag>. There are several common ways to extract the values of elements (also sometimes called “nodes”): access via indexing of the DOM object and through XPath. Let’s take a look at both ways, starting with accessing elements of the tree through indexing.

To illustrate the techniques, we will use a simple example XML (SimpleXML.xml) with two top-level elements.

<catalog>
   <book id="bk101" edition="3">
      <author>
        <surname>Gambardella</surname>
        <given>Matthew</given>
      </author>
      <title>XML Developer's Guide</title>
      <outofprint />
      <price currency="R$">349</price>
   </book>
   <book id="bk102" edition="1">
      <author>
        <surname>Ralls</surname>
        <given>Kim</given></author>
      <title>Midnight Rain</title>
      <price currency="US$">5.95</price>
   </book>
</catalog>

Let’s start by loading the XML into a DOM object. As it has no associated DTD, we will not validate the file during parsing.

xmlDoc <- xmlParse("SimpleXML.xml", validate=F)

Indexed Access

The first step in accessing the elements (nodes) of the DOM tree is to get the root element (for the above XML that would <catalog>) using the function xmlRoot().

root <- xmlRoot(xmlDoc)

Accessing the i^th child node directly underneath the root can be done using the list access operator [[i]]as the representation within R is a list.

# access the first child node underneath the root
aNode <- root[[1]]

print(aNode)
## <book id="bk101" edition="3">
##   <author>
##     <surname>Gambardella</surname>
##     <given>Matthew</given>
##   </author>
##   <title>XML Developer's Guide</title>
##   <outofprint/>
##   <price currency="R$">349</price>
## </book>

This can be continued down the tree. For example to access the given name element of the author for the second book, you would use:

aNode <- root[[2]][[1]][[2]]
print(aNode)
## <given>Kim</given>

To get its value, you would need to use the function xmlValue().

v <- xmlValue(aNode)

print(v)
## [1] "Kim"

The image below illustrates the syntax to access child elements by position within the DOM tree.

Iterating through Nodes

One of the advantages of using this mode of access is that the tree can be processed in a loop. For example, to get all the last names of the authors of all books, we can loop through the nodes. The function xmlSize() returns the number of direct child nodes underneath a given node.

The code fragment below extracts all surnames for all authors and places them into a vector. Each root[[i]][[1]][[1]] returns the value to the first child of the first child within each of the nodes underneath <catalog>. So, if you visualize the tree of the XML, that is <book><author><surname>

# number of <book> nodes
n <- xmlSize(root)

# pre-allocated vector for the surnames
names <- c(length = n)

for (i in 1:n)
{
  names[i] <- xmlValue(root[[i]][[1]][[1]])
}

cat(names)
## Gambardella Ralls

Of course, rather than placing them into a vector, one can also store them in a column of a data frame, which is, after all, simply a vector.

Optional Elements

Dealing with optional elements can be a bit tricky when extracting nodes using indexed access. For example, in the sample XML, the element <outofprint /> is not present in all nodes, so extracting the value of the book prices means that the <price> element can be child node 3 or 4 depending whether the element <outofprint /> is present before the element <price>. One technique is to check the name of the 3^rd element to see if it is price or outofprint using the function xmlName().

The code fragment below also illustrates the conversion of text values to numbers. All values returned from xmlvalue() are of type “character”, so using them a numeric values requires explicit coercion using as.numeric(). Of course, if the value had non-digit characters, such as “$”, then some string extraction would first be needed.

n <- xmlSize(root)

for (i in 1:n)
{
  node <- xmlName(root[[i]][[3]])
  if (node == "outofprint")
    price.node <- 4
  else
    price.node <- 3
  
  price <- xmlValue(root[[i]][[price.node]])
  price.value <- as.numeric(price)
}

Storing XML Data in Data Frame

The code below extracts the titles and the prices and places them into a data frame. This is a common strategy when converting the data from XML to a tabular format for externalization in a CSV or when saving the data to a relational database.

# number of <book> nodes
n <- xmlSize(root)

# empty data frame
df <- data.frame(title = as.character(n),
                 price = as.numeric(n))

for (i in 1:n)
{
  title <- xmlValue(root[[i]][[2]])
  node <- xmlName(root[[i]][[3]])
  if (node == "outofprint")
    price.node <- 4
  else
    price.node <- 3
  
  price <- xmlValue(root[[i]][[price.node]])
  price.value <- as.numeric(price)
  
  # add to data frame
  df[i,"title"] <- title
  df[i,"price"] <- price.value
}

print(head(df,3))
##                   title  price
## 1 XML Developer's Guide 349.00
## 2         Midnight Rain   5.95

The XML package contains the function xmlToDataFrame() that can more conveniently extract XML data to a dataframe but only if the XML has two levels and the elements are in the same order. It would not work properly on the sample XML SimpleXML.xml. See Lesson 6.323 Load Simple XML into Dataframe in R using xmlToDataFrame().

Extracting Attributes

Data is not only in elements but can also be in attributes of elements. For example, in SimpleXML.xml, the edition and id are attributes of the <book> element, as shown below.

<catalog>
   <book id="bk101" edition="3">
      <author>
        <surname>Gambardella</surname>
        <given>Matthew</given>
      </author>
      ...

Attributes are extracted using the xmlAttrs() function to which an element is passed. The function returns a list of all attributes, so we need the double-bracket access operator [[]] to access the elements or use the function unlist() to convert the list to a vector.

n <- xmlSize(root)

for (i in 1:n)
{
  # get the i-th book
  aBook <- root[[i]]
  
  # get attributes of the i-th book
  book.attrs <- xmlAttrs(aBook)
  
  # second attribute in the list is the edition
  edition <- book.attrs[[2]]
  
  print(edition)
}
## [1] "3"
## [1] "1"

To extract a specific attribute, the function xmlGetAttr() is often more convenient. Like values, attributes are returned as character strings requiring coercion to the appropriate data type using coercion functions such as as.numeric().

n <- xmlSize(root)

for (i in 1:n)
{
  # get the i-th book
  aBook <- root[[i]]
  
  # get the value of the attribute "edition"
  edition <- xmlGetAttr(aBook, "edition")
  
  print(edition)
}
## [1] "3"
## [1] "1"

Pretty Tables with “kable”

The code below demonstrates the use of the “knitr::kable()” package. First, we are extracting the title and the author of each book into a data frame and we use the kableExtra package to “pretty print” the data frames.

library(XML)

xmlURL <- "http://artificium.us/lessons/06.r/l-6-114-parse-xml-r-primer/BookCatalog.xml"

xmlDOM <- xmlParse(xmlURL, validate = F)

r <- xmlRoot(xmlDOM)

# number of <book> nodes
n <- xmlSize(r)

df.books <- data.frame(
  title = character(n),
  author = character(n)
)

for (i in 1:n)
{
  ## access the ith book node
  aBook <- r[[i]]
  
  ## extract title (child 2) and author (child 1) from
  ## the book node
  theTitle <- xmlValue(aBook[[2]])
  theAuthor <- xmlValue(aBook[[1]])
  
  ## store values in the ith row of the data frame
  df.books[i,1] <- theTitle
  df.books$author[i] <- theAuthor
}

head(df.books)
##                   title               author
## 1 XML Developer's Guide Gambardella, Matthew
## 2         Midnight Rain           Ralls, Kim
## 3       Maeve Ascendant          Corets, Eva
## 4       Oberon's Legacy          Corets, Eva
## 5    The Sundered Grail          Corets, Eva
## 6           Lover Birds     Randall, Cynthia

Example I

Note that only the first six rows of the data frame are displayed.

library(kableExtra)

df.books[1:6,] %>%
  kbl() %>%
  kable_paper("hover", full_width = F)
title author
XML Developer’s Guide Gambardella, Matthew
Midnight Rain Ralls, Kim
Maeve Ascendant Corets, Eva
Oberon’s Legacy Corets, Eva
The Sundered Grail Corets, Eva
Lover Birds Randall, Cynthia

Example 2

This example uses a different format style and also prints the table over the entire width of the document.

df.books[1:6,] %>%
  kbl(caption = "Books by Title with Author") %>%
  kable_classic(full_width = T, html_font = "Cambria")
Table 1: Books by Title with Author
title author
XML Developer’s Guide Gambardella, Matthew
Midnight Rain Ralls, Kim
Maeve Ascendant Corets, Eva
Oberon’s Legacy Corets, Eva
The Sundered Grail Corets, Eva
Lover Birds Randall, Cynthia

Tutorial I

The content from the lesson to this point is narrated in the code walk below.

XPath

A simple and more elegant, albeit less flexible and perhaps less efficient, way is to use XPath expressions to access elements and attributes. We will redo the above extractions using XPath rather than indexed node access.

Let’s start first by demonstrating how to execute an XPath query on an XML document. After the XML is parsed using xmlParse(), the XPath expression is executed using xpathSApply(). The function returns a list (not a vector) of all nodes that match the XPath path expression.

library(XML)

xmlDoc <- xmlParse("SimpleXML.xml", validate=F)

# XPath expression to get titles of all book nodes
xpathExpr <- "//book/title"

# execute/apply the XPath query
rs <- xpathSApply(xmlDoc, xpathExpr)

# print the list of matching elements
print(rs)
## [[1]]
## <title>XML Developer's Guide</title> 
## 
## [[2]]
## <title>Midnight Rain</title>

To get the values of the elements, add “xmlValue” as a parameter as shown below and xpathSApply() returns a vector of element values.

library(XML)

xmlDoc <- xmlParse("SimpleXML.xml", validate=F)

# XPath expression to get titles of all book nodes
xpathExpr <- "//book/title"

# execute/apply the XPath query and extract values
rs <- xpathSApply(xmlDoc, xpathExpr, xmlValue)

print(rs)
## [1] "XML Developer's Guide" "Midnight Rain"

The example below extracts the title, price, and edition using XPath expressions and adds them to a data frame.Notice how we are no longer concerned about whether the optional <outofprint/> element is part of a <book> node or not. XPath simplifies access.

library(XML)

xmlDoc <- xmlParse("SimpleXML.xml", validate=F)

# number of <book> nodes
n <- xmlSize(root)

titles <- xpathSApply(xmlDoc, 
                      "//book/title", xmlValue)

prices <- xpathSApply(xmlDoc, 
                      "//book/price", xmlValue)

editions <- xpathSApply(xmlDoc, 
                      "//book/@edition")


# create new data frame
df <- data.frame(title = titles,
                 price = as.numeric(prices),
                 edition = as.numeric(editions))


print(head(df,3))
##                   title  price edition
## 1 XML Developer's Guide 349.00       3
## 2         Midnight Rain   5.95       1

Notice how we do not use xmlValue when retrieving attribute values.

Missing Elements

The approach to check whether an element is present is a bit more difficult with XPath than indexed access. The function xpathSApply() returns an empty list when the XPath expression has no matching elements. We can check whether a list is empty by finding its length (or size) using the function length(). If it is empty, then its length is 0.

xpath <- "//book/genre"

# XPath expression should not return a value
rs <- xpathSApply(xmlDoc, xpath, xmlValue)

if (length(rs) == 0)
{
  print("no genre")
}
## [1] "no genre"

XPath with Indexed Access

The aforementioned code presumes that every book has one title, one price, and an edition. If there are multiple titles or prices, or some do not exist, then a combination of node traversal and XPath would be necessary.

Let’s say we had a more complex XML that had multiple prices and we only wanted to extract the US prices (where the currency attributes is “US$”). Here’s what one of the nodes in the XML file SimpleXML-2.xml looks like:

<catalog>
   <book id="bk101" edition="3">
      <author>
        <surname>Gambardella</surname>
        <given>Matthew</given>
      </author>
      <title>XML Developer's Guide</title>
      <outofprint />
      <price currency="R$">349</price>
      <price currency="US$">29.95</price>
      <price currency="€">34.00</price>
   </book>
   ...
 </catalog>

There are multiple ways to solve this. One approach, of course, is to use XPath expressions that extract price values only when currency is “US$”. However, to demonstrate the mixing of indexed access and XPath, we will choose an approach the uses loops. This approach is generally slower (loops are slow, especially in R), but affords more flexibility.

library(XML)
xmlDoc <- xmlParse("SimpleXML-2.xml")

n <- xmlSize(root)

# get all <book> nodes from the XML
books <- xpathSApply(xmlDoc, "//book")

# iterate over the <book> nodes
for (i in 1:n)
{
  # get the i-th book node
  aBook <- books[[i]]
  
  # use XPath to extract the <price> child elements
  price <- xpathSApply(aBook, 
                       "price[./@currency='US$']",
                       xmlValue)
  
  print(price)
}
## [1] "29.95"
## [1] "5.95"

Summary of XML Functions

This section presents the most useful functions from the XML package. Naturally, the list is not exclusive and you should consult the documentation for the package for more information.

  • xmlValue
  • xmlSize
  • xmlRoot
  • xmlAttrs
  • xmlxpathSApply
  • xmlParse
  • xmlName
  • xmlChildren

Summary

The XML package provides numerous functions for extracting data from XML documents in R.


Files & Resources

All Files for Lesson 6.114

Errata

Let us know.


  1. The XML package is written in C, so the “internal” representation of the DOM is a C data structure.↩︎

LS0tDQp0aXRsZTogIlByaW1lciBvbiBQYXJzaW5nIFhNTCB3aXRoIFIiDQpwYXJhbXM6DQogIGNhdGVnb3J5OiA2DQogIG51bWJlcjogMTE0DQogIHRpbWU6IDQ1DQogIGxldmVsOiBiZWdpbm5lcg0KICB0YWdzOiAicix4bWwseHBhdGgscHJpbWVyIg0KICBkZXNjcmlwdGlvbjogIkEgcHJpbWVyIG9uIGxvYWRpbmcgWE1MIGRvY3VtZW50cyBpbnRvIFINCiAgICAgICAgICAgICAgICBhbmQgcHJvY2Vzc2luZyB0aGUgZWxlbWVudHMuIg0KZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+Ig0KYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+Ig0KZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiDQphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSINCm91dHB1dDogDQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KLS0tDQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+Ig0KLS0tDQoNCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQ0KYGBgDQoNCiMjIFByZXJlcXVpc2l0ZXMNCg0KVGhpcyBsZXNzb24gcHJlc3VtZXMgdGhhdCB5b3UgdW5kZXJzdGFuZCB0aGUgc3RydWN0dXJlIG9mIGFuIFhNTCBkb2N1bWVudC4gSWYgeW91IGFyZSBub3QgZmFtaWxpYXIgd2l0aCBYTUwsIGNvbnN1bHQgdGhpcyBsZXNzb24gZmlyc3QuDQoNCi0gICBbODAuMTAxIEJhc2ljcyBvZiBYTUwgYW5kIERURF0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy84MC54bWwvbC04MC0xMDEtYmFzaWMteG1sL2wtODAtMTAxLmh0bWwpDQoNCiMjIEludHJvZHVjdGlvbg0KDQpYTUwgaXMgYSBjb21tb24gbWVhbnMgdG8gZXh0ZXJuYWxpemUgc3RydWN0dXJlZCBkYXRhIGFuIGlzIGNvbW1vbmx5IHVzZWQgZm9yIGRhdGEgaW50ZXJjaGFuZ2UgYmV0d2VlbiBzeXN0ZW1zIGFuZCBvcmdhbml6YXRpb25zLiBUaGVyZSBhcmUgbWFueSBzdGFuZGFyZCBYTUwgbGFuZ3VhZ2VzIGZvciBleHByZXNzaW5nIGRhdGEgaW4gc3BlY2lmaWMgZG9tYWlucywgc3VjaCBhcyBmaW5hbmNlLCBhZ3JpY3VsdHVyZSwgcHVibGlzaGluZywgYW1vbmcgb3RoZXJzLiBJdCBpcyBhbHNvIG9mdGVuIHVzZWQgYXMgYSBzdGFuZGFyZCBmb3IgY29uZmlndXJhdGlvbiBmaWxlcyBhbmQgZm9yIHdlYiBjb250ZW50Lg0KDQpCZWluZyBhYmxlIHRvIGV4dHJhY3QgZGF0YSBmcm9tIFhNTCBpcyBhbiBpbXBvcnRhbnQgc2tpbGwgdGhhdCBldmVyeSBkYXRhIHByb2dyYW1tZXJzIG11c3QgbWFzdGVyLiBUaGlzIGxlc3NvbiBleHBsYWlucyBob3cgdG8gdXNlIHRoZSBmdW5jdGlvbnMgZnJvbSB0aGUgKipYTUwqKiBwYWNrYWdlIHRvIGV4dHJhY3QgZGF0YSBmcm9tIFhNTCBmaWxlcyAoYWxzbyBvZnRlbiBjYWxsZWQgWE1MIGRvY3VtZW50cykgaW4gUi4gT3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzIGhhdmUgdmVyeSBzaW1pbGFyIG1lY2hhbmlzbXMgYW5kIHRoZSBza2lsbHMgbGVhcm5lZCBpbiB0aGlzIGxlc3NvbiBjYW4gYmUgdHJhbnNmZXJyZWQgdG8gb3RoZXIgbGFuZ3VhZ2VzLCBzdWNoIGFzIEphdmFTY3JpcHQsIEphdmEsIEMrKywgUnVzdCwgYW5kIG1hbnkgb3RoZXJzLg0KDQpNb3N0IG9mdGVuIHRoZSBkYXRhIGV4dHJhY3RlZCBmcm9tIFhNTCBpcyBhbmFseXplZCBzdGF0aXN0aWNhbGx5IG9yIHVzZWQgZm9yIGNvbnN0cnVjdGluZyBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgYXMgd2VsbCBhcyBiZWluZyBhZGRlZCB0byByZWxhdGlvbmFsIGRhdGFiYXNlcy4gVGhlc2UgdXNlIGNhc2VzIGFyZSBvdXRzaWRlIHRoZSBzY29wZSBvZiB0aGlzIGxlc3Nvbi4NCg0KIyMgUmVxdWlyZWQgUGFja2FnZXMNCg0KVG8gZXh0cmFjdCBkYXRhIGZyb20gWE1MLCByZXF1aXJlcyBtaW5pbWFsbHkgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoNCg0KLSAgICoqWE1MKioNCg0KSW4gYWRkaXRpb24sIHRoZXNlIHBhY2thZ2VzIGNvbnRhaW4gZnVuY3Rpb25zIHRoYXQgYXJlIG9mdGVuIHF1aXRlIHVzZWZ1bDoNCg0KLSAgICoqc3RyaW5ncioqDQoNCk5vdGUgdGhlICoqWE1MKiogaXMgbm90IHRoZSBvbmx5IHBhY2thZ2UgYXZhaWxhYmxlIGZvciB3b3JraW5nIHdpdGggWE1MLg0KDQojIyBMb2FkaW5nIGFuIFhNTCBGaWxlDQoNClRoZSBwcm9jZXNzIG9mIGV4dHJhY3RpbmcgZGF0YSBzdGFydHMgYnkgInBhcnNpbmciIHRoZSBYTUwgZmlsZSBmcm9tIGVpdGhlciBhIGxvY2FsIGZvbGRlciBvciBmcm9tIGEgVVJMLiBUaGUgY29kZSBiZWxvdyBpbGx1c3RyYXRlcyB0aGF0IGZvciBvbmUgb2YgdGhlIGluY2x1ZGVkIFhNTCBmaWxlcyAoW0Jvb2tDYXRhbG9nLnhtbF0oQm9va0NhdGFsb2cueG1sKSkuIERvd25sb2FkIHRoZSBmaWxlIGFuZCBydW4gdGhlIGNvZGUgaW4geW91ciBvd24gUiBOb3RlYm9vayBvciBSIFNjcmlwdC4NCg0KVGhlIGNvZGUgZnJhZ21lbnQgYmVsb3cgaWxsdXN0cmF0ZXMgaG93IHRvIGxvYWQgYW5kIHBhcnNlIGFuIFhNTCBkb2N1bWVudCBmcm9tIGEgbG9jYWwgZmlsZSBhcyB3ZWxsIGFzIGZyb20gYSBVUkwuDQoNCmBgYHtyIGxvYWRYTUxMb2NhbGx5fQ0KbGlicmFyeShYTUwpDQoNCnhtbEZpbGUgPC0gIkJvb2tDYXRhbG9nLnhtbCINCnhtbFVSTCA8LSAiaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMTQtcGFyc2UteG1sLXItcHJpbWVyL0Jvb2tDYXRhbG9nLnhtbCINCg0KZG9tMSA8LSB4bWxQYXJzZSh4bWxGaWxlKQ0KZG9tMiA8LSB4bWxQYXJzZSh4bWxVUkwpDQpgYGANCg0KSWYgeW91IGdldCB0aGUgZXJyb3IgKiJFcnJvcjogWE1MIGNvbnRlbnQgZG9lcyBub3Qgc2VlbSB0byBiZSBYTUw6ICcnIiogdGhlbiBpdCBpcyBtb3N0IGxpa2VseSB0aGF0IHRoZSBmaWxlIG5hbWUsIHBhdGgsIG9yIFVSTCBhcmUgbm90IGNvcnJlY3QuDQoNCiMjIERvY3VtZW50IE9iamVjdCBNb2RlbCAoRE9NKQ0KDQpUaGUgZnVuY3Rpb24gPGNvZGU+eG1sUGFyc2UoKTwvY29kZT4gcmV0dXJucyBhIHJlZmVyZW5jZSB0byBhbiBpbnRlcm5hbCB0cmVlIG9mIG5vZGVzIHRoYXQgcmVwcmVzZW50cyB0aGUgZG9jdW1lbnQgb2JqZWN0IG1vZGVsIChET00pIGZvciB0aGUgWE1MLiBQcm9jZXNzaW5nIG9mIHRoZSBub2RlcyBhbmQgZXh0cmFjdGlvbiBvZiBkYXRhIGlzIGRvbmUgdGhyb3VnaCB0aGF0IHJlZmVyZW5jZS4NCg0KQWx0ZXJuYXRpdmVseSwgb25lIGNvdWxkIHVzZSB0aGUgZnVuY3Rpb24gPGNvZGU+eG1sVHJlZVBhcnNlKCk8L2NvZGU+IHdoaWNoIHdpbGwgYWxzbyByZXR1cm4gYSBET00gYnV0IG9uZSB0aGF0IGlzIHJlcHJlc2VudGVkIGFzIGFuIFIgZGF0YSBzdHJ1Y3R1cmUgcmF0aGVyIHRoYW4gYSBDIGRhdGEgc3RydWN0dXJlW14xXS4gV2hpbGUgPGNvZGU+eG1sUGFyc2UoKTwvY29kZT4gaXMgbW9yZSBlZmZpY2llbnQgYW5kIHVzZXMgbGVzcyBtZW1vcnksIDxjb2RlPnhtbFRyZWVQYXJzZSgpPC9jb2RlPiBoYXMgdGhlIGJlbmVmaXQgb2YgZWxlbWVudCBhY2Nlc3MgdXNpbmcgdGhlICpcJCogb3BlcmF0b3IuIEluIHByYWN0aWNlLCA8Y29kZT54bWxQYXJzZSgpPC9jb2RlPiBpcyBtb3N0IGNvbW1vbmx5IHVzZWQsIHRob3VnaC4NCg0KW14xXTogVGhlIFhNTCBwYWNrYWdlIGlzIHdyaXR0ZW4gaW4gQywgc28gdGhlICJpbnRlcm5hbCIgcmVwcmVzZW50YXRpb24gb2YgdGhlIERPTSBpcyBhIEMgZGF0YSBzdHJ1Y3R1cmUuDQoNClRoZSB2YXJpYWJsZSAqZG9tKiAob3IgdG8gd2hpY2hldmVyIHZhcmlhYmxlIHlvdSBhc3NpZ25lZCB0aGUgcmV0dXJuIHZhbHVlIG9mIDxjb2RlPnhtbFBhcnNlKCk8L2NvZGU+KSBwb2ludHMgdG8gYW4gaW4tbWVtb3J5IHJlcHJlc2VudGF0aW9uIG9mIHRoZSBYTUwgdHJlZS4gQWxsIGFjY2VzcyB0byB0aGUgWE1MIGVsZW1lbnRzIGlzIHZpYSB0aGF0IHBvaW50ZXIuDQoNClByb2Nlc3NpbmcgYW4gWE1MIHRocm91Z2ggaXRzIERPTSByZXF1aXJlcyB0aGF0IHRoZSBYTUwgaXMgbG9hZGVkIGNvbXBsZXRlbHkgaW50byBtZW1vcnkuIE5hdHVyYWxseSwgdGhpcyBpcyBvbmx5IGZlYXNpYmxlIHdoZW4gc3VmZmljaWVudCBtZW1vcnkgaXMgYXZhaWxhYmxlLiBGb3IgdmVyeSBsYXJnZSBYTUwgZmlsZXMsIGFub3RoZXIgYXBwcm9hY2ggaXMgYXZhaWxhYmxlIGZvciBleHRyYWN0aW9uOiBTQVguIFRoaXMgaXMgYmV5b25kIHRoZSBzY29wZSBvZiB0aGlzIHR1dG9yaWFsLg0KDQojIyMgVmFsaWRhdGlvbg0KDQpCeSBkZWZhdWx0LCA8Y29kZT54bWxQYXJzZSgpPC9jb2RlPiBkb2VzIG5vdCB2YWxpZGF0ZSB0aGUgZmlsZSBhZ2FpbnN0IGFueSBEVEQgb3IgWE1MIFNjaGVtYTsgaXQgb25seSBjaGVja3Mgd2hldGhlciB0aGUgWE1MIGlzIHdlbGwtZm9ybWVkLiBUbyBlbnN1cmUgdGhhdCB0aGUgWE1MIGNvbmZvcm1zIHRvIHRoZSBydWxlcyBvZiBhIERURCBvciBYTUwgU2NoZW1hLCB0aGUgcGFyYW1ldGVyICp2YWxpZGF0ZT1UKiBtdXN0IGJlIHNwZWNpZmllZC4gT2YgY291cnNlLCB0aGlzIHBhcmFtZXRlciBpcyBvbmx5IG1lYW5pbmdmdWwgaWYgdGhlcmUgaXMgYSBEVEQgb3IgU2NoZW1hOyBpZiB0aGVyZSBpc24ndCBhbmQgKnZhbGlkYXRlPVQqIGlzIHNwZWNpZmllZCB0aGVuIGFuIGVycm9yIHdpbGwgcmVzdWx0Lg0KDQpUaGUgWE1MIGZpbGUgW3BhZ2V2aXNpdHMueG1sXShwYWdldmlzaXRzLnhtbCkgY29udGFpbnMgYSBEVEQgYW5kIHRoZXJlZm9yZSBwYXJzaW5nIHdpdGggdmFsaWRhdGlvbiBpcyBwb3NzaWJsZS4NCg0KYGBge3IgbG9hZFhNTExvY2FsbHlXaXRoVmFsaWRhdGlvbn0NCmxpYnJhcnkoWE1MKQ0KDQp4bWxGaWxlIDwtICJwYWdldmlzaXRzLnhtbCINCg0KZG9tIDwtIHhtbFBhcnNlKHhtbEZpbGUsIHZhbGlkYXRlPVQpDQpgYGANCg0KT3RoZXIgdXNlZnVsIHBhcmFtZXRlcnMgZm9yIDxjb2RlPnhtbFBhcnNlKCk8L2NvZGU+IGluY2x1ZGU6DQoNCi0gICAqdHJpbSogLS0gYSBCb29sZWFuIGluZGljYXRpbmcgd2hldGhlciB0byBzdHJpcCBsZWFkaW5nIGFuZCB0cmFpbGluZyB3aGl0ZXNwYWNlIGZyb20gdmFsdWVzDQotICAgKmdldERURCogLS0gYSBCb29sZWFuIGZsYWcgaW5kaWNhdGluZyB3aGV0aGVyIHRoZSBEVEQgKGJvdGggaW50ZXJuYWwgYW5kIGV4dGVybmFsKSBzaG91bGQgYmUgcmV0dXJuZWQgYWxvbmcgd2l0aCB0aGUgbm9kZXMNCi0gICAqaXNVUkwqIC0tIGEgQm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGRvY3VtZW50IHBhdGggaXMgYSBVUkw7IHRoaXMgaXMgbm90IHN0cmljdGx5IHJlcXVpcmVkIGlmIHRoZSBVUkwgc3RhcnRzIHdpdGggYSBjb21tb24gcHJvdG9jb2wgc3VjaCBhcyA8aHR0cDovLz4NCg0KSWYgeW91IG5lZWQgdG8gcHJvY2VzcyBIVE1MIGRvY3VtZW50cyAoYSB0eXBlIG9mIFhNTCBkb2N1bWVudCksIHRoZW4gdGhlIGZ1bmN0aW9uIDxjb2RlPmh0bWxQYXJzZSgpPC9jb2RlPiBpcyBwcmVmZXJhYmxlLg0KDQojIyBFeHRyYWN0aW5nIEVsZW1lbnRzDQoNClRoZSBkYXRhIGluIGFuIFhNTCBkb2N1bWVudCBpcyBjb250YWluZWQgaW4gZWxlbWVudHMgd2hpY2ggYXJlIG1hcmtlZCBieSBwYWlycyBvZiB0YWdzLCAqZS5nLiosIGA8dGFnPnZhbHVlPC90YWc+YC4gVGhlcmUgYXJlIHNldmVyYWwgY29tbW9uIHdheXMgdG8gZXh0cmFjdCB0aGUgdmFsdWVzIG9mIGVsZW1lbnRzIChhbHNvIHNvbWV0aW1lcyBjYWxsZWQgIm5vZGVzIik6IGFjY2VzcyB2aWEgaW5kZXhpbmcgb2YgdGhlIERPTSBvYmplY3QgYW5kIHRocm91Z2ggWFBhdGguIExldCdzIHRha2UgYSBsb29rIGF0IGJvdGggd2F5cywgc3RhcnRpbmcgd2l0aCBhY2Nlc3NpbmcgZWxlbWVudHMgb2YgdGhlIHRyZWUgdGhyb3VnaCBpbmRleGluZy4NCg0KVG8gaWxsdXN0cmF0ZSB0aGUgdGVjaG5pcXVlcywgd2Ugd2lsbCB1c2UgYSBzaW1wbGUgZXhhbXBsZSBYTUwgKFtTaW1wbGVYTUwueG1sXShTaW1wbGVYTUwueG1sKSkgd2l0aCB0d28gdG9wLWxldmVsIGVsZW1lbnRzLg0KDQpgYGAgeG1sDQo8Y2F0YWxvZz4NCiAgIDxib29rIGlkPSJiazEwMSIgZWRpdGlvbj0iMyI+DQogICAgICA8YXV0aG9yPg0KICAgICAgICA8c3VybmFtZT5HYW1iYXJkZWxsYTwvc3VybmFtZT4NCiAgICAgICAgPGdpdmVuPk1hdHRoZXc8L2dpdmVuPg0KICAgICAgPC9hdXRob3I+DQogICAgICA8dGl0bGU+WE1MIERldmVsb3BlcidzIEd1aWRlPC90aXRsZT4NCiAgICAgIDxvdXRvZnByaW50IC8+DQogICAgICA8cHJpY2UgY3VycmVuY3k9IlIkIj4zNDk8L3ByaWNlPg0KICAgPC9ib29rPg0KICAgPGJvb2sgaWQ9ImJrMTAyIiBlZGl0aW9uPSIxIj4NCiAgICAgIDxhdXRob3I+DQogICAgICAgIDxzdXJuYW1lPlJhbGxzPC9zdXJuYW1lPg0KICAgICAgICA8Z2l2ZW4+S2ltPC9naXZlbj48L2F1dGhvcj4NCiAgICAgIDx0aXRsZT5NaWRuaWdodCBSYWluPC90aXRsZT4NCiAgICAgIDxwcmljZSBjdXJyZW5jeT0iVVMkIj41Ljk1PC9wcmljZT4NCiAgIDwvYm9vaz4NCjwvY2F0YWxvZz4NCmBgYA0KDQpMZXQncyBzdGFydCBieSBsb2FkaW5nIHRoZSBYTUwgaW50byBhIERPTSBvYmplY3QuIEFzIGl0IGhhcyBubyBhc3NvY2lhdGVkIERURCwgd2Ugd2lsbCBub3QgdmFsaWRhdGUgdGhlIGZpbGUgZHVyaW5nIHBhcnNpbmcuDQoNCmBgYHtyfQ0KeG1sRG9jIDwtIHhtbFBhcnNlKCJTaW1wbGVYTUwueG1sIiwgdmFsaWRhdGU9RikNCmBgYA0KDQojIyMgSW5kZXhlZCBBY2Nlc3MNCg0KVGhlIGZpcnN0IHN0ZXAgaW4gYWNjZXNzaW5nIHRoZSBlbGVtZW50cyAobm9kZXMpIG9mIHRoZSBET00gdHJlZSBpcyB0byBnZXQgdGhlIHJvb3QgZWxlbWVudCAoZm9yIHRoZSBhYm92ZSBYTUwgdGhhdCB3b3VsZCBgPGNhdGFsb2c+YCkgdXNpbmcgdGhlIGZ1bmN0aW9uIGB4bWxSb290KClgLg0KDQpgYGB7cn0NCnJvb3QgPC0geG1sUm9vdCh4bWxEb2MpDQpgYGANCg0KQWNjZXNzaW5nIHRoZSAqaVxedGgqIGNoaWxkIG5vZGUgZGlyZWN0bHkgdW5kZXJuZWF0aCB0aGUgcm9vdCBjYW4gYmUgZG9uZSB1c2luZyB0aGUgbGlzdCBhY2Nlc3Mgb3BlcmF0b3IgYFtbaV1dYGFzIHRoZSByZXByZXNlbnRhdGlvbiB3aXRoaW4gUiBpcyBhIGxpc3QuDQoNCmBgYHtyfQ0KIyBhY2Nlc3MgdGhlIGZpcnN0IGNoaWxkIG5vZGUgdW5kZXJuZWF0aCB0aGUgcm9vdA0KYU5vZGUgPC0gcm9vdFtbMV1dDQoNCnByaW50KGFOb2RlKQ0KYGBgDQoNClRoaXMgY2FuIGJlIGNvbnRpbnVlZCBkb3duIHRoZSB0cmVlLiBGb3IgZXhhbXBsZSB0byBhY2Nlc3MgdGhlIGdpdmVuIG5hbWUgZWxlbWVudCBvZiB0aGUgYXV0aG9yIGZvciB0aGUgc2Vjb25kIGJvb2ssIHlvdSB3b3VsZCB1c2U6DQoNCmBgYHtyfQ0KYU5vZGUgPC0gcm9vdFtbMl1dW1sxXV1bWzJdXQ0KcHJpbnQoYU5vZGUpDQpgYGANCg0KVG8gZ2V0IGl0cyB2YWx1ZSwgeW91IHdvdWxkIG5lZWQgdG8gdXNlIHRoZSBmdW5jdGlvbiBgeG1sVmFsdWUoKWAuDQoNCmBgYHtyfQ0KdiA8LSB4bWxWYWx1ZShhTm9kZSkNCg0KcHJpbnQodikNCmBgYA0KDQpUaGUgaW1hZ2UgYmVsb3cgaWxsdXN0cmF0ZXMgdGhlIHN5bnRheCB0byBhY2Nlc3MgY2hpbGQgZWxlbWVudHMgYnkgcG9zaXRpb24gd2l0aGluIHRoZSBET00gdHJlZS4NCg0KIVtdKGltYWdlcy90cmVlLXRyYXZlcnNhbC5wbmcpe3dpZHRoPSI2MCUifQ0KDQojIyMgSXRlcmF0aW5nIHRocm91Z2ggTm9kZXMNCg0KT25lIG9mIHRoZSBhZHZhbnRhZ2VzIG9mIHVzaW5nIHRoaXMgbW9kZSBvZiBhY2Nlc3MgaXMgdGhhdCB0aGUgdHJlZSBjYW4gYmUgcHJvY2Vzc2VkIGluIGEgbG9vcC4gRm9yIGV4YW1wbGUsIHRvIGdldCBhbGwgdGhlIGxhc3QgbmFtZXMgb2YgdGhlIGF1dGhvcnMgb2YgYWxsIGJvb2tzLCB3ZSBjYW4gbG9vcCB0aHJvdWdoIHRoZSBub2Rlcy4gVGhlIGZ1bmN0aW9uIGB4bWxTaXplKClgIHJldHVybnMgdGhlIG51bWJlciBvZiBkaXJlY3QgY2hpbGQgbm9kZXMgdW5kZXJuZWF0aCBhIGdpdmVuIG5vZGUuDQoNClRoZSBjb2RlIGZyYWdtZW50IGJlbG93IGV4dHJhY3RzIGFsbCBzdXJuYW1lcyBmb3IgYWxsIGF1dGhvcnMgYW5kIHBsYWNlcyB0aGVtIGludG8gYSB2ZWN0b3IuIEVhY2ggYHJvb3RbW2ldXVtbMV1dW1sxXV1gIHJldHVybnMgdGhlIHZhbHVlIHRvIHRoZSBmaXJzdCBjaGlsZCBvZiB0aGUgZmlyc3QgY2hpbGQgd2l0aGluIGVhY2ggb2YgdGhlIG5vZGVzIHVuZGVybmVhdGggYDxjYXRhbG9nPmAuIFNvLCBpZiB5b3UgdmlzdWFsaXplIHRoZSB0cmVlIG9mIHRoZSBYTUwsIHRoYXQgaXMgYDxib29rPjxhdXRob3I+PHN1cm5hbWU+YA0KDQpgYGB7cn0NCiMgbnVtYmVyIG9mIDxib29rPiBub2Rlcw0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCiMgcHJlLWFsbG9jYXRlZCB2ZWN0b3IgZm9yIHRoZSBzdXJuYW1lcw0KbmFtZXMgPC0gYyhsZW5ndGggPSBuKQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICBuYW1lc1tpXSA8LSB4bWxWYWx1ZShyb290W1tpXV1bWzFdXVtbMV1dKQ0KfQ0KDQpjYXQobmFtZXMpDQpgYGANCg0KT2YgY291cnNlLCByYXRoZXIgdGhhbiBwbGFjaW5nIHRoZW0gaW50byBhIHZlY3Rvciwgb25lIGNhbiBhbHNvIHN0b3JlIHRoZW0gaW4gYSBjb2x1bW4gb2YgYSBkYXRhIGZyYW1lLCB3aGljaCBpcywgYWZ0ZXIgYWxsLCBzaW1wbHkgYSB2ZWN0b3IuDQoNCiMjIyMgT3B0aW9uYWwgRWxlbWVudHMNCg0KRGVhbGluZyB3aXRoIG9wdGlvbmFsIGVsZW1lbnRzIGNhbiBiZSBhIGJpdCB0cmlja3kgd2hlbiBleHRyYWN0aW5nIG5vZGVzIHVzaW5nIGluZGV4ZWQgYWNjZXNzLiBGb3IgZXhhbXBsZSwgaW4gdGhlIHNhbXBsZSBYTUwsIHRoZSBlbGVtZW50IGA8b3V0b2ZwcmludCAvPmAgaXMgbm90IHByZXNlbnQgaW4gYWxsIG5vZGVzLCBzbyBleHRyYWN0aW5nIHRoZSB2YWx1ZSBvZiB0aGUgYm9vayBwcmljZXMgbWVhbnMgdGhhdCB0aGUgYDxwcmljZT5gIGVsZW1lbnQgY2FuIGJlIGNoaWxkIG5vZGUgMyBvciA0IGRlcGVuZGluZyB3aGV0aGVyIHRoZSBlbGVtZW50IGA8b3V0b2ZwcmludCAvPmAgaXMgcHJlc2VudCBiZWZvcmUgdGhlIGVsZW1lbnQgYDxwcmljZT5gLiBPbmUgdGVjaG5pcXVlIGlzIHRvIGNoZWNrIHRoZSBuYW1lIG9mIHRoZSAzXF5yZCBlbGVtZW50IHRvIHNlZSBpZiBpdCBpcyAqcHJpY2UqIG9yICpvdXRvZnByaW50KiB1c2luZyB0aGUgZnVuY3Rpb24gYHhtbE5hbWUoKWAuDQoNClRoZSBjb2RlIGZyYWdtZW50IGJlbG93IGFsc28gaWxsdXN0cmF0ZXMgdGhlIGNvbnZlcnNpb24gb2YgdGV4dCB2YWx1ZXMgdG8gbnVtYmVycy4gQWxsIHZhbHVlcyByZXR1cm5lZCBmcm9tIGB4bWx2YWx1ZSgpYCBhcmUgb2YgdHlwZSAiY2hhcmFjdGVyIiwgc28gdXNpbmcgdGhlbSBhIG51bWVyaWMgdmFsdWVzIHJlcXVpcmVzIGV4cGxpY2l0IGNvZXJjaW9uIHVzaW5nIGBhcy5udW1lcmljKClgLiBPZiBjb3Vyc2UsIGlmIHRoZSB2YWx1ZSBoYWQgbm9uLWRpZ2l0IGNoYXJhY3RlcnMsIHN1Y2ggYXMgIlwkIiwgdGhlbiBzb21lIHN0cmluZyBleHRyYWN0aW9uIHdvdWxkIGZpcnN0IGJlIG5lZWRlZC4NCg0KYGBge3J9DQpuIDwtIHhtbFNpemUocm9vdCkNCg0KZm9yIChpIGluIDE6bikNCnsNCiAgbm9kZSA8LSB4bWxOYW1lKHJvb3RbW2ldXVtbM11dKQ0KICBpZiAobm9kZSA9PSAib3V0b2ZwcmludCIpDQogICAgcHJpY2Uubm9kZSA8LSA0DQogIGVsc2UNCiAgICBwcmljZS5ub2RlIDwtIDMNCiAgDQogIHByaWNlIDwtIHhtbFZhbHVlKHJvb3RbW2ldXVtbcHJpY2Uubm9kZV1dKQ0KICBwcmljZS52YWx1ZSA8LSBhcy5udW1lcmljKHByaWNlKQ0KfQ0KYGBgDQoNCiMjIyMgU3RvcmluZyBYTUwgRGF0YSBpbiBEYXRhIEZyYW1lDQoNClRoZSBjb2RlIGJlbG93IGV4dHJhY3RzIHRoZSB0aXRsZXMgYW5kIHRoZSBwcmljZXMgYW5kIHBsYWNlcyB0aGVtIGludG8gYSBkYXRhIGZyYW1lLiBUaGlzIGlzIGEgY29tbW9uIHN0cmF0ZWd5IHdoZW4gY29udmVydGluZyB0aGUgZGF0YSBmcm9tIFhNTCB0byBhIHRhYnVsYXIgZm9ybWF0IGZvciBleHRlcm5hbGl6YXRpb24gaW4gYSBDU1Ygb3Igd2hlbiBzYXZpbmcgdGhlIGRhdGEgdG8gYSByZWxhdGlvbmFsIGRhdGFiYXNlLg0KDQpgYGB7ciB4bWwyZGF0YWZyYW1lfQ0KIyBudW1iZXIgb2YgPGJvb2s+IG5vZGVzDQpuIDwtIHhtbFNpemUocm9vdCkNCg0KIyBlbXB0eSBkYXRhIGZyYW1lDQpkZiA8LSBkYXRhLmZyYW1lKHRpdGxlID0gYXMuY2hhcmFjdGVyKG4pLA0KICAgICAgICAgICAgICAgICBwcmljZSA9IGFzLm51bWVyaWMobikpDQoNCmZvciAoaSBpbiAxOm4pDQp7DQogIHRpdGxlIDwtIHhtbFZhbHVlKHJvb3RbW2ldXVtbMl1dKQ0KICBub2RlIDwtIHhtbE5hbWUocm9vdFtbaV1dW1szXV0pDQogIGlmIChub2RlID09ICJvdXRvZnByaW50IikNCiAgICBwcmljZS5ub2RlIDwtIDQNCiAgZWxzZQ0KICAgIHByaWNlLm5vZGUgPC0gMw0KICANCiAgcHJpY2UgPC0geG1sVmFsdWUocm9vdFtbaV1dW1twcmljZS5ub2RlXV0pDQogIHByaWNlLnZhbHVlIDwtIGFzLm51bWVyaWMocHJpY2UpDQogIA0KICAjIGFkZCB0byBkYXRhIGZyYW1lDQogIGRmW2ksInRpdGxlIl0gPC0gdGl0bGUNCiAgZGZbaSwicHJpY2UiXSA8LSBwcmljZS52YWx1ZQ0KfQ0KDQpwcmludChoZWFkKGRmLDMpKQ0KYGBgDQoNClRoZSBYTUwgcGFja2FnZSBjb250YWlucyB0aGUgZnVuY3Rpb24gYHhtbFRvRGF0YUZyYW1lKClgIHRoYXQgY2FuIG1vcmUgY29udmVuaWVudGx5IGV4dHJhY3QgWE1MIGRhdGEgdG8gYSBkYXRhZnJhbWUgYnV0IG9ubHkgaWYgdGhlIFhNTCBoYXMgdHdvIGxldmVscyBhbmQgdGhlIGVsZW1lbnRzIGFyZSBpbiB0aGUgc2FtZSBvcmRlci4gSXQgd291bGQgbm90IHdvcmsgcHJvcGVybHkgb24gdGhlIHNhbXBsZSBYTUwgKlNpbXBsZVhNTC54bWwqLiBTZWUgTGVzc29uIFs2LjMyMyBMb2FkIFNpbXBsZSBYTUwgaW50byBEYXRhZnJhbWUgaW4gUiB1c2luZyB4bWxUb0RhdGFGcmFtZSgpXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMyMy1sb2FkLXhtbC14bWxUb0RhdGFGcmFtZS9sLTYtMzIzLmh0bWwpLg0KDQojIyMjIEV4dHJhY3RpbmcgQXR0cmlidXRlcw0KDQpEYXRhIGlzIG5vdCBvbmx5IGluIGVsZW1lbnRzIGJ1dCBjYW4gYWxzbyBiZSBpbiBhdHRyaWJ1dGVzIG9mIGVsZW1lbnRzLiBGb3IgZXhhbXBsZSwgaW4gKlNpbXBsZVhNTC54bWwqLCB0aGUgKmVkaXRpb24qIGFuZCAqaWQqIGFyZSBhdHRyaWJ1dGVzIG9mIHRoZSBgPGJvb2s+YCBlbGVtZW50LCBhcyBzaG93biBiZWxvdy4NCg0KYGBgIHhtbA0KPGNhdGFsb2c+DQogICA8Ym9vayBpZD0iYmsxMDEiIGVkaXRpb249IjMiPg0KICAgICAgPGF1dGhvcj4NCiAgICAgICAgPHN1cm5hbWU+R2FtYmFyZGVsbGE8L3N1cm5hbWU+DQogICAgICAgIDxnaXZlbj5NYXR0aGV3PC9naXZlbj4NCiAgICAgIDwvYXV0aG9yPg0KICAgICAgLi4uDQpgYGANCg0KQXR0cmlidXRlcyBhcmUgZXh0cmFjdGVkIHVzaW5nIHRoZSBgeG1sQXR0cnMoKWAgZnVuY3Rpb24gdG8gd2hpY2ggYW4gZWxlbWVudCBpcyBwYXNzZWQuIFRoZSBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mICphbGwqIGF0dHJpYnV0ZXMsIHNvIHdlIG5lZWQgdGhlIGRvdWJsZS1icmFja2V0IGFjY2VzcyBvcGVyYXRvciBbW11dIHRvIGFjY2VzcyB0aGUgZWxlbWVudHMgb3IgdXNlIHRoZSBmdW5jdGlvbiBgdW5saXN0KClgIHRvIGNvbnZlcnQgdGhlIGxpc3QgdG8gYSB2ZWN0b3IuDQoNCmBgYHtyIGV4dHJhY3RBdHRyc30NCm4gPC0geG1sU2l6ZShyb290KQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rDQogIGFCb29rIDwtIHJvb3RbW2ldXQ0KICANCiAgIyBnZXQgYXR0cmlidXRlcyBvZiB0aGUgaS10aCBib29rDQogIGJvb2suYXR0cnMgPC0geG1sQXR0cnMoYUJvb2spDQogIA0KICAjIHNlY29uZCBhdHRyaWJ1dGUgaW4gdGhlIGxpc3QgaXMgdGhlIGVkaXRpb24NCiAgZWRpdGlvbiA8LSBib29rLmF0dHJzW1syXV0NCiAgDQogIHByaW50KGVkaXRpb24pDQp9DQpgYGANCg0KVG8gZXh0cmFjdCBhIHNwZWNpZmljIGF0dHJpYnV0ZSwgdGhlIGZ1bmN0aW9uIGB4bWxHZXRBdHRyKClgIGlzIG9mdGVuIG1vcmUgY29udmVuaWVudC4gTGlrZSB2YWx1ZXMsIGF0dHJpYnV0ZXMgYXJlIHJldHVybmVkIGFzIGNoYXJhY3RlciBzdHJpbmdzIHJlcXVpcmluZyBjb2VyY2lvbiB0byB0aGUgYXBwcm9wcmlhdGUgZGF0YSB0eXBlIHVzaW5nIGNvZXJjaW9uIGZ1bmN0aW9ucyBzdWNoIGFzIGBhcy5udW1lcmljKClgLg0KDQpgYGB7ciBleHRyYWN0U2luZ2xlQXR0cn0NCm4gPC0geG1sU2l6ZShyb290KQ0KDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rDQogIGFCb29rIDwtIHJvb3RbW2ldXQ0KICANCiAgIyBnZXQgdGhlIHZhbHVlIG9mIHRoZSBhdHRyaWJ1dGUgImVkaXRpb24iDQogIGVkaXRpb24gPC0geG1sR2V0QXR0cihhQm9vaywgImVkaXRpb24iKQ0KICANCiAgcHJpbnQoZWRpdGlvbikNCn0NCmBgYA0KDQojIyBQcmV0dHkgVGFibGVzIHdpdGggImthYmxlIg0KDQpUaGUgY29kZSBiZWxvdyBkZW1vbnN0cmF0ZXMgdGhlIHVzZSBvZiB0aGUgImtuaXRyOjprYWJsZSgpIiBwYWNrYWdlLiBGaXJzdCwgd2UgYXJlIGV4dHJhY3RpbmcgdGhlIHRpdGxlIGFuZCB0aGUgYXV0aG9yIG9mIGVhY2ggYm9vayBpbnRvIGEgZGF0YSBmcmFtZSBhbmQgd2UgdXNlIHRoZSAqKmthYmxlRXh0cmEqKiBwYWNrYWdlIHRvICJwcmV0dHkgcHJpbnQiIHRoZSBkYXRhIGZyYW1lcy4NCg0KYGBge3IgZWNobz1UfQ0KbGlicmFyeShYTUwpDQoNCnhtbFVSTCA8LSAiaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMTQtcGFyc2UteG1sLXItcHJpbWVyL0Jvb2tDYXRhbG9nLnhtbCINCg0KeG1sRE9NIDwtIHhtbFBhcnNlKHhtbFVSTCwgdmFsaWRhdGUgPSBGKQ0KDQpyIDwtIHhtbFJvb3QoeG1sRE9NKQ0KDQojIG51bWJlciBvZiA8Ym9vaz4gbm9kZXMNCm4gPC0geG1sU2l6ZShyKQ0KDQpkZi5ib29rcyA8LSBkYXRhLmZyYW1lKA0KICB0aXRsZSA9IGNoYXJhY3RlcihuKSwNCiAgYXV0aG9yID0gY2hhcmFjdGVyKG4pDQopDQoNCmZvciAoaSBpbiAxOm4pDQp7DQogICMjIGFjY2VzcyB0aGUgaXRoIGJvb2sgbm9kZQ0KICBhQm9vayA8LSByW1tpXV0NCiAgDQogICMjIGV4dHJhY3QgdGl0bGUgKGNoaWxkIDIpIGFuZCBhdXRob3IgKGNoaWxkIDEpIGZyb20NCiAgIyMgdGhlIGJvb2sgbm9kZQ0KICB0aGVUaXRsZSA8LSB4bWxWYWx1ZShhQm9va1tbMl1dKQ0KICB0aGVBdXRob3IgPC0geG1sVmFsdWUoYUJvb2tbWzFdXSkNCiAgDQogICMjIHN0b3JlIHZhbHVlcyBpbiB0aGUgaXRoIHJvdyBvZiB0aGUgZGF0YSBmcmFtZQ0KICBkZi5ib29rc1tpLDFdIDwtIHRoZVRpdGxlDQogIGRmLmJvb2tzJGF1dGhvcltpXSA8LSB0aGVBdXRob3INCn0NCg0KaGVhZChkZi5ib29rcykNCmBgYA0KDQojIyMgRXhhbXBsZSBJDQoNCk5vdGUgdGhhdCBvbmx5IHRoZSBmaXJzdCBzaXggcm93cyBvZiB0aGUgZGF0YSBmcmFtZSBhcmUgZGlzcGxheWVkLg0KDQpgYGB7ciBwcmludFdpdGhLYWJsZSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0KZGYuYm9va3NbMTo2LF0gJT4lDQogIGtibCgpICU+JQ0KICBrYWJsZV9wYXBlcigiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQojIyMgRXhhbXBsZSAyDQoNClRoaXMgZXhhbXBsZSB1c2VzIGEgZGlmZmVyZW50IGZvcm1hdCBzdHlsZSBhbmQgYWxzbyBwcmludHMgdGhlIHRhYmxlIG92ZXIgdGhlIGVudGlyZSB3aWR0aCBvZiB0aGUgZG9jdW1lbnQuDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpkZi5ib29rc1sxOjYsXSAlPiUNCiAga2JsKGNhcHRpb24gPSAiQm9va3MgYnkgVGl0bGUgd2l0aCBBdXRob3IiKSAlPiUNCiAga2FibGVfY2xhc3NpYyhmdWxsX3dpZHRoID0gVCwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KYGBgDQoNCiMjIFR1dG9yaWFsIEkNCg0KVGhlIGNvbnRlbnQgZnJvbSB0aGUgbGVzc29uIHRvIHRoaXMgcG9pbnQgaXMgbmFycmF0ZWQgaW4gdGhlIGNvZGUgd2FsayBiZWxvdy4NCg0KPGlmcmFtZSBzcmM9Imh0dHBzOi8vcGxheWVyLnZpbWVvLmNvbS92aWRlby85MTUyMzk3NjM/dGl0bGU9MCZhbXA7YnlsaW5lPTAmYW1wO3BvcnRyYWl0PTAmYW1wO2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNTYwIiBoZWlnaHQ9IjMxNSIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiB0aXRsZT0ibC02LTExNCBQYXJzaW5nIFhNTCBpbiBSIFVzaW5nIE5vZGUgVHJhdmVyc2FsIiBkYXRhLWV4dGVybmFsPSIxIj4NCg0KPC9pZnJhbWU+DQoNCiMjIFhQYXRoDQoNCkEgc2ltcGxlIGFuZCBtb3JlIGVsZWdhbnQsIGFsYmVpdCBsZXNzIGZsZXhpYmxlIGFuZCBwZXJoYXBzIGxlc3MgZWZmaWNpZW50LCB3YXkgaXMgdG8gdXNlIFhQYXRoIGV4cHJlc3Npb25zIHRvIGFjY2VzcyBlbGVtZW50cyBhbmQgYXR0cmlidXRlcy4gV2Ugd2lsbCByZWRvIHRoZSBhYm92ZSBleHRyYWN0aW9ucyB1c2luZyBYUGF0aCByYXRoZXIgdGhhbiBpbmRleGVkIG5vZGUgYWNjZXNzLg0KDQpMZXQncyBzdGFydCBmaXJzdCBieSBkZW1vbnN0cmF0aW5nIGhvdyB0byBleGVjdXRlIGFuIFhQYXRoIHF1ZXJ5IG9uIGFuIFhNTCBkb2N1bWVudC4gQWZ0ZXIgdGhlIFhNTCBpcyBwYXJzZWQgdXNpbmcgYHhtbFBhcnNlKClgLCB0aGUgWFBhdGggZXhwcmVzc2lvbiBpcyBleGVjdXRlZCB1c2luZyBgeHBhdGhTQXBwbHkoKWAuIFRoZSBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCAobm90IGEgdmVjdG9yKSBvZiBhbGwgbm9kZXMgdGhhdCBtYXRjaCB0aGUgWFBhdGggcGF0aCBleHByZXNzaW9uLg0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQp4bWxEb2MgPC0geG1sUGFyc2UoIlNpbXBsZVhNTC54bWwiLCB2YWxpZGF0ZT1GKQ0KDQojIFhQYXRoIGV4cHJlc3Npb24gdG8gZ2V0IHRpdGxlcyBvZiBhbGwgYm9vayBub2Rlcw0KeHBhdGhFeHByIDwtICIvL2Jvb2svdGl0bGUiDQoNCiMgZXhlY3V0ZS9hcHBseSB0aGUgWFBhdGggcXVlcnkNCnJzIDwtIHhwYXRoU0FwcGx5KHhtbERvYywgeHBhdGhFeHByKQ0KDQojIHByaW50IHRoZSBsaXN0IG9mIG1hdGNoaW5nIGVsZW1lbnRzDQpwcmludChycykNCmBgYA0KDQpUbyBnZXQgdGhlIHZhbHVlcyBvZiB0aGUgZWxlbWVudHMsIGFkZCAieG1sVmFsdWUiIGFzIGEgcGFyYW1ldGVyIGFzIHNob3duIGJlbG93IGFuZCBgeHBhdGhTQXBwbHkoKWAgcmV0dXJucyBhIHZlY3RvciBvZiBlbGVtZW50IHZhbHVlcy4NCg0KYGBge3J9DQpsaWJyYXJ5KFhNTCkNCg0KeG1sRG9jIDwtIHhtbFBhcnNlKCJTaW1wbGVYTUwueG1sIiwgdmFsaWRhdGU9RikNCg0KIyBYUGF0aCBleHByZXNzaW9uIHRvIGdldCB0aXRsZXMgb2YgYWxsIGJvb2sgbm9kZXMNCnhwYXRoRXhwciA8LSAiLy9ib29rL3RpdGxlIg0KDQojIGV4ZWN1dGUvYXBwbHkgdGhlIFhQYXRoIHF1ZXJ5IGFuZCBleHRyYWN0IHZhbHVlcw0KcnMgPC0geHBhdGhTQXBwbHkoeG1sRG9jLCB4cGF0aEV4cHIsIHhtbFZhbHVlKQ0KDQpwcmludChycykNCmBgYA0KDQpUaGUgZXhhbXBsZSBiZWxvdyBleHRyYWN0cyB0aGUgdGl0bGUsIHByaWNlLCBhbmQgZWRpdGlvbiB1c2luZyBYUGF0aCBleHByZXNzaW9ucyBhbmQgYWRkcyB0aGVtIHRvIGEgZGF0YSBmcmFtZS5Ob3RpY2UgaG93IHdlIGFyZSBubyBsb25nZXIgY29uY2VybmVkIGFib3V0IHdoZXRoZXIgdGhlIG9wdGlvbmFsIGA8b3V0b2ZwcmludC8+YCBlbGVtZW50IGlzIHBhcnQgb2YgYSBgPGJvb2s+YCBub2RlIG9yIG5vdC4gWFBhdGggc2ltcGxpZmllcyBhY2Nlc3MuDQoNCmBgYHtyfQ0KbGlicmFyeShYTUwpDQoNCnhtbERvYyA8LSB4bWxQYXJzZSgiU2ltcGxlWE1MLnhtbCIsIHZhbGlkYXRlPUYpDQoNCiMgbnVtYmVyIG9mIDxib29rPiBub2Rlcw0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCnRpdGxlcyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIA0KICAgICAgICAgICAgICAgICAgICAgICIvL2Jvb2svdGl0bGUiLCB4bWxWYWx1ZSkNCg0KcHJpY2VzIDwtIHhwYXRoU0FwcGx5KHhtbERvYywgDQogICAgICAgICAgICAgICAgICAgICAgIi8vYm9vay9wcmljZSIsIHhtbFZhbHVlKQ0KDQplZGl0aW9ucyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIA0KICAgICAgICAgICAgICAgICAgICAgICIvL2Jvb2svQGVkaXRpb24iKQ0KDQoNCiMgY3JlYXRlIG5ldyBkYXRhIGZyYW1lDQpkZiA8LSBkYXRhLmZyYW1lKHRpdGxlID0gdGl0bGVzLA0KICAgICAgICAgICAgICAgICBwcmljZSA9IGFzLm51bWVyaWMocHJpY2VzKSwNCiAgICAgICAgICAgICAgICAgZWRpdGlvbiA9IGFzLm51bWVyaWMoZWRpdGlvbnMpKQ0KDQoNCnByaW50KGhlYWQoZGYsMykpDQpgYGANCg0KTm90aWNlIGhvdyB3ZSBkbyBub3QgdXNlIGB4bWxWYWx1ZWAgd2hlbiByZXRyaWV2aW5nIGF0dHJpYnV0ZSB2YWx1ZXMuDQoNCiMjIyBNaXNzaW5nIEVsZW1lbnRzDQoNClRoZSBhcHByb2FjaCB0byBjaGVjayB3aGV0aGVyIGFuIGVsZW1lbnQgaXMgcHJlc2VudCBpcyBhIGJpdCBtb3JlIGRpZmZpY3VsdCB3aXRoIFhQYXRoIHRoYW4gaW5kZXhlZCBhY2Nlc3MuIFRoZSBmdW5jdGlvbiBgeHBhdGhTQXBwbHkoKWAgcmV0dXJucyBhbiBlbXB0eSBsaXN0IHdoZW4gdGhlIFhQYXRoIGV4cHJlc3Npb24gaGFzIG5vIG1hdGNoaW5nIGVsZW1lbnRzLiBXZSBjYW4gY2hlY2sgd2hldGhlciBhIGxpc3QgaXMgZW1wdHkgYnkgZmluZGluZyBpdHMgbGVuZ3RoIChvciBzaXplKSB1c2luZyB0aGUgZnVuY3Rpb24gYGxlbmd0aCgpYC4gSWYgaXQgaXMgZW1wdHksIHRoZW4gaXRzIGxlbmd0aCBpcyAwLg0KDQpgYGB7cn0NCnhwYXRoIDwtICIvL2Jvb2svZ2VucmUiDQoNCiMgWFBhdGggZXhwcmVzc2lvbiBzaG91bGQgbm90IHJldHVybiBhIHZhbHVlDQpycyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsIHhwYXRoLCB4bWxWYWx1ZSkNCg0KaWYgKGxlbmd0aChycykgPT0gMCkNCnsNCiAgcHJpbnQoIm5vIGdlbnJlIikNCn0NCmBgYA0KDQojIyMgWFBhdGggd2l0aCBJbmRleGVkIEFjY2Vzcw0KDQpUaGUgYWZvcmVtZW50aW9uZWQgY29kZSBwcmVzdW1lcyB0aGF0IGV2ZXJ5IGJvb2sgaGFzIG9uZSB0aXRsZSwgb25lIHByaWNlLCBhbmQgYW4gZWRpdGlvbi4gSWYgdGhlcmUgYXJlIG11bHRpcGxlIHRpdGxlcyBvciBwcmljZXMsIG9yIHNvbWUgZG8gbm90IGV4aXN0LCB0aGVuIGEgY29tYmluYXRpb24gb2Ygbm9kZSB0cmF2ZXJzYWwgYW5kIFhQYXRoIHdvdWxkIGJlIG5lY2Vzc2FyeS4NCg0KTGV0J3Mgc2F5IHdlIGhhZCBhIG1vcmUgY29tcGxleCBYTUwgdGhhdCBoYWQgbXVsdGlwbGUgcHJpY2VzIGFuZCB3ZSBvbmx5IHdhbnRlZCB0byBleHRyYWN0IHRoZSBVUyBwcmljZXMgKHdoZXJlIHRoZSAqY3VycmVuY3kqIGF0dHJpYnV0ZXMgaXMgIlVTXCQiKS4gSGVyZSdzIHdoYXQgb25lIG9mIHRoZSBub2RlcyBpbiB0aGUgWE1MIGZpbGUgW1NpbXBsZVhNTC0yLnhtbF0oU2ltcGxlWE1MLTIueG1sKSBsb29rcyBsaWtlOg0KDQpgYGAgeG1sDQo8Y2F0YWxvZz4NCiAgIDxib29rIGlkPSJiazEwMSIgZWRpdGlvbj0iMyI+DQogICAgICA8YXV0aG9yPg0KICAgICAgICA8c3VybmFtZT5HYW1iYXJkZWxsYTwvc3VybmFtZT4NCiAgICAgICAgPGdpdmVuPk1hdHRoZXc8L2dpdmVuPg0KICAgICAgPC9hdXRob3I+DQogICAgICA8dGl0bGU+WE1MIERldmVsb3BlcidzIEd1aWRlPC90aXRsZT4NCiAgICAgIDxvdXRvZnByaW50IC8+DQogICAgICA8cHJpY2UgY3VycmVuY3k9IlIkIj4zNDk8L3ByaWNlPg0KICAgICAgPHByaWNlIGN1cnJlbmN5PSJVUyQiPjI5Ljk1PC9wcmljZT4NCiAgICAgIDxwcmljZSBjdXJyZW5jeT0i4oKsIj4zNC4wMDwvcHJpY2U+DQogICA8L2Jvb2s+DQogICAuLi4NCiA8L2NhdGFsb2c+DQpgYGANCg0KVGhlcmUgYXJlIG11bHRpcGxlIHdheXMgdG8gc29sdmUgdGhpcy4gT25lIGFwcHJvYWNoLCBvZiBjb3Vyc2UsIGlzIHRvIHVzZSBYUGF0aCBleHByZXNzaW9ucyB0aGF0IGV4dHJhY3QgcHJpY2UgdmFsdWVzIG9ubHkgd2hlbiBjdXJyZW5jeSBpcyAiVVNcJCIuIEhvd2V2ZXIsIHRvIGRlbW9uc3RyYXRlIHRoZSBtaXhpbmcgb2YgaW5kZXhlZCBhY2Nlc3MgYW5kIFhQYXRoLCB3ZSB3aWxsIGNob29zZSBhbiBhcHByb2FjaCB0aGUgdXNlcyBsb29wcy4gVGhpcyBhcHByb2FjaCBpcyBnZW5lcmFsbHkgc2xvd2VyIChsb29wcyBhcmUgc2xvdywgZXNwZWNpYWxseSBpbiBSKSwgYnV0IGFmZm9yZHMgbW9yZSBmbGV4aWJpbGl0eS4NCg0KYGBge3J9DQpsaWJyYXJ5KFhNTCkNCnhtbERvYyA8LSB4bWxQYXJzZSgiU2ltcGxlWE1MLTIueG1sIikNCg0KbiA8LSB4bWxTaXplKHJvb3QpDQoNCiMgZ2V0IGFsbCA8Ym9vaz4gbm9kZXMgZnJvbSB0aGUgWE1MDQpib29rcyA8LSB4cGF0aFNBcHBseSh4bWxEb2MsICIvL2Jvb2siKQ0KDQojIGl0ZXJhdGUgb3ZlciB0aGUgPGJvb2s+IG5vZGVzDQpmb3IgKGkgaW4gMTpuKQ0Kew0KICAjIGdldCB0aGUgaS10aCBib29rIG5vZGUNCiAgYUJvb2sgPC0gYm9va3NbW2ldXQ0KICANCiAgIyB1c2UgWFBhdGggdG8gZXh0cmFjdCB0aGUgPHByaWNlPiBjaGlsZCBlbGVtZW50cw0KICBwcmljZSA8LSB4cGF0aFNBcHBseShhQm9vaywgDQogICAgICAgICAgICAgICAgICAgICAgICJwcmljZVsuL0BjdXJyZW5jeT0nVVMkJ10iLA0KICAgICAgICAgICAgICAgICAgICAgICB4bWxWYWx1ZSkNCiAgDQogIHByaW50KHByaWNlKQ0KfQ0KYGBgDQoNCiMjIFN1bW1hcnkgb2YgWE1MIEZ1bmN0aW9ucw0KDQpUaGlzIHNlY3Rpb24gcHJlc2VudHMgdGhlIG1vc3QgdXNlZnVsIGZ1bmN0aW9ucyBmcm9tIHRoZSAqKlhNTCoqIHBhY2thZ2UuIE5hdHVyYWxseSwgdGhlIGxpc3QgaXMgbm90IGV4Y2x1c2l2ZSBhbmQgeW91IHNob3VsZCBjb25zdWx0IHRoZSBbZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL1hNTC92ZXJzaW9ucy8zLjk5LTAuMTApIGZvciB0aGUgcGFja2FnZSBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KLSAgIGB4bWxWYWx1ZWANCi0gICBgeG1sU2l6ZWANCi0gICBgeG1sUm9vdGANCi0gICBgeG1sQXR0cnNgDQotICAgYHhtbHhwYXRoU0FwcGx5YA0KLSAgIGB4bWxQYXJzZWANCi0gICBgeG1sTmFtZWANCi0gICBgeG1sQ2hpbGRyZW5gDQoNCiMjIFN1bW1hcnkNCg0KVGhlICoqWE1MKiogcGFja2FnZSBwcm92aWRlcyBudW1lcm91cyBmdW5jdGlvbnMgZm9yIGV4dHJhY3RpbmcgZGF0YSBmcm9tIFhNTCBkb2N1bWVudHMgaW4gUi4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEZpbGVzICYgUmVzb3VyY2VzDQoNCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQ0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIA0KICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksDQogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpDQoNCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgDQogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpDQoNCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUg0Ka25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgU2VlIEFsc28NCg0KLSAgIFs2LjMwMyBEYXRhIFJldHJpZXZhbCBmcm9tIFhNTCB2aWEgWFBhdGggaW4gUl0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0zMDMteHBhdGgtaW4tci9sLTYtMzAzLmh0bWwpDQotICAgWzYuMzA1IFByb2Nlc3MgWE1MIERPTSB2aWEgWFBhdGggYW5kIE5vZGUgVHJhdmVyc2FsXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMwNS1wcm9jLXhtbC1kb20teHBhdGgtaW4tci9sLTYtMzA1Lmh0bWwpDQotICAgWzYuMzIzIExvYWQgU2ltcGxlIFhNTCBpbnRvIERhdGFmcmFtZSBpbiBSIHVzaW5nIHhtbFRvRGF0YUZyYW1lKCldKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzIzLWxvYWQteG1sLXhtbFRvRGF0YUZyYW1lL2wtNi0zMjMuaHRtbCkNCi0gICBbNi4zMjQgVHJhdmVyc2UgYW5kIFBhcnNlIFhNTCBET00gaW4gUl0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0zMjQtcGFyc2UteG1sLWRvbS9sLTYtMzI0Lmh0bWwpDQotICAgWzYuMzI4IFBhcnNpbmcgYW4gWE1MIERvY3VtZW50IGFuZCBTYXZpbmcgdG8gU1FMaXRlIERhdGFiYXNlIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzI4LXhtbC10by1yZWxkYi1zcWxpdGUvbC02LTMyOC5odG1sKQ0KDQojIyBSZWZlcmVuY2VzDQoNCltYTUwgUGFja2FnZSAzLjk5LTAuMTAuIFIgRG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL1hNTC92ZXJzaW9ucy8zLjk5LTAuMTApDQoNCiMjIEVycmF0YQ0KDQpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uDQo=