Introduction
This lessons demonstrates how to load simple row/column (2D) XML files directly into a data frame using xmlToDataFrame()
. Once the data is loaded it can be further manipulated, analyzed, or stored in a database or other storage structure.
xmlToDataFrame()
is a convenient way to load simple XML files into R for analysis. Such XML files are often the result of data exports from application, databases, systems, or third-parties.
Load 2L XML to Dataframe
Here is an example of a 2-dimensional XML that has a single level of child nodes in the same order for all nodes under the root.
<?xml version= "1.0" ?>
<document >
<row >
<Girth >8.3</Girth >
<Height >70</Height >
<Volume >10.3</Volume >
</row >
<row >
<Girth >8.6</Girth >
<Height >65</Height >
<Volume >10.3</Volume >
</row >
...
Let’s load this simple XML with a row column structure into a data frame using xmlToDataFrame()
. The structure must be a root with child nodes that each have a single level child with a single value in the same order – essentially a row/column arrangement expressed in XML.
library (XML)
fn <- "treedata2L.xml"
# load from local file
df <- xmlToDataFrame (fn)
head (df, 3 )
## Girth Height Volume
## 1 8.3 70 10.3
## 2 8.6 65 10.3
## 3 8.8 63 10.2
By default, all columns are converted to character (text) variables. To keep them as text, use the parameter stringsAsFactors = F , for any other data type, you must use explicit coercion,
library (XML)
fn <- "treedata2L.xml"
# load from local file
df <- xmlToDataFrame (fn, stringsAsFactors = F)
df$ Girth <- as.numeric (df$ Girth)
df$ Height <- as.numeric (df$ Height)
df$ Volume <- as.numeric (df$ Volume)
head (df, 3 )
## Girth Height Volume
## 1 8.3 70 10.3
## 2 8.6 65 10.3
## 3 8.8 63 10.2
Load 3L XML to Dataframe
Attempting to load a multi-level XML into a dataframe will not work properly as the example below illustrates.
<?xml version= "1.0" ?>
<document >
<row >
<Girth >8.3</Girth >
<dim >
<Height >70</Height >
<Width >65</Width >
</dim >
<Volume >10.3</Volume >
</row >
...
library (XML)
fn <- "treedata3L.xml"
df <- xmlToDataFrame (fn)
head (df)
## Girth dim Volume
## 1 8.3 7065 10.3
## 2 8.6 6555 10.3
## 3 8.8 7369 10.2
## 4 10.5 7266 16.4
Notice how any XML elements beyond the second level are concatenated into a single string.
Load XML from URL
Rather than loading a local XML file, xmlToDataFrame()
can also load an XML file from a URL. There are several ways to accomplish this; this is one way. Note that getURL()
only supports http and not https .
library (XML)
library (RCurl)
# URL to XML file; must be http://
xml.url <- "http://artificium.us/lessons/06.r/l-6-323-load-xml-xmlToDataFrame/plant_catalog.xml"
xData <- getURL (xml.url)
df <- xmlToDataFrame (xData)
head (df, 2 )
Again, all data is converted to text and must be explicitly converted to an appropriate data type.
df <- xmlToDataFrame (xData, stringsAsFactors = FALSE )
head (df)
df$ BOTANICAL <- as.factor (df$ BOTANICAL)
df$ PRICE <- as.numeric (substring (df$ PRICE, 2 ))
head (df)
Node Traversal
A complex XML must be parsed and loaded into a data frame through a combination of XPath expressions and node traversal of the DOM (Document Object Model). Below, we will illustrate how to traverse a multi-level XML using node traversal. It is not meant to be an exhaustive and complete treatment.
Recall that each node has this structure:
<document >
<row >
<Girth >8.3</Girth >
<dim >
<Height >70</Height >
<Width >65</Width >
</dim >
<Volume >10.3</Volume >
</row >
Let’s look at the code…
library (XML)
# load the XML into memory as a tree
xml.dom <- xmlParse ("treedata3L.xml" )
# get the root node <document>
root <- xmlRoot (xml.dom)
# get the number of <row> nodes under the root
n <- xmlSize (root)
# set up a data frame to store the data; initially contains zero rows
# but will expand dynamically as rows are added
df <- data.frame (girth = numeric (),
height = integer (),
width = integer (),
volume = numeric ()
)
# iterate over the <row> nodes
for (i in 1 : n) {
# get node i from the list of child nodes
aRow <- root[[i]]
# get value of <Girth> child
girth <- xmlValue (aRow[[1 ]])
# get value of <Height> and <Width> which are children of <dim>
height <- xmlValue (aRow[[2 ]][[1 ]])
width <- xmlValue (aRow[[2 ]][[2 ]])
# get the value of <Volume>
volume <- xmlValue (aRow[[3 ]])
# add the values to a new row in the data frame
# coerce text to appropriate data type
df[i,'girth' ] <- as.numeric (girth)
df[i,'height' ] <- as.integer (height)
df[i,'width' ] <- as.integer (width)
df[i,'volume' ] <- as.numeric (volume)
}
# print part of data frame to ensure it contains data from XML
head (df,2 )
## girth height width volume
## 1 8.3 70 65 10.3
## 2 8.6 65 55 10.3
Note that we set up an initial data frame with zero rows and as we add a new row at the end, the data frame’s size grows dynamically. Of course, we could have pre-allocated the data frame with the correct number of rows as each row corresponds to one child node.
The data is read from the XML as text (character) and must be explicitly coerced.
The root node is a list structure of child nodes and list elements are accessed with the [[]] operator. So, aRow[[2]][[1]] returns the first child of the second child of the node in aRow .
The function xmlValue()
returns the value of a node, e.g., it returns foo for foo.
CAREFUL
The statement df[i,‘girth’] <- girth
adds a new row but the “equivalent” statement df$girth[i] <- girth
results in an error.
Summary
xmlToDataFrame()
is a convenient way to load simple XML files into R for analysis. Such XML files are often the result of data exports from application, databases, systems, or third-parties.
More complex XML files must be processed with a combination of node traversal and XPath. See Lesson 6.303 Data Retrieval from XML via XPath in R .
Tutorial
The video tutorial below is a narration with examples of the concepts in this lesson.
VIDEO
References
No references.
Errata
None collected yet. Let us know.
LS0tDQp0aXRsZTogIkxvYWQgU2ltcGxlIFhNTCBpbnRvIERhdGFmcmFtZSBpbiBSIHVzaW5nIHhtbFRvRGF0YUZyYW1lKCkiDQpwYXJhbXM6DQogIGNhdGVnb3J5OiA2DQogIG51bWJlcjogMzIzDQogIHRpbWU6IDQ1DQogIGxldmVsOiBiZWdpbm5lcg0KICB0YWdzOiAicix4bWwseG1sVG9EYXRhRnJhbWUsZGF0YWZyYW1lIg0KICBkZXNjcmlwdGlvbjogIkV4cGxhaW5zIGhvdyB0byBsb2FkIGEgc2ltcGxlIFhNTCBmaWxlIGludG8gYSBkYXRhZnJhbWUuIg0KZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+Ig0KYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+Ig0KZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiDQphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSINCm91dHB1dDogDQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KLS0tDQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+Ig0KLS0tDQoNCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQ0KYGBgDQoNCiMjIEludHJvZHVjdGlvbg0KDQpUaGlzIGxlc3NvbnMgZGVtb25zdHJhdGVzIGhvdyB0byBsb2FkIHNpbXBsZSByb3cvY29sdW1uICgyRCkgWE1MIGZpbGVzIGRpcmVjdGx5IGludG8gYSBkYXRhIGZyYW1lIHVzaW5nIDxjb2RlPnhtbFRvRGF0YUZyYW1lKCk8L2NvZGU+LiBPbmNlIHRoZSBkYXRhIGlzIGxvYWRlZCBpdCBjYW4gYmUgZnVydGhlciBtYW5pcHVsYXRlZCwgYW5hbHl6ZWQsIG9yIHN0b3JlZCBpbiBhIGRhdGFiYXNlIG9yIG90aGVyIHN0b3JhZ2Ugc3RydWN0dXJlLg0KDQo8Y29kZT54bWxUb0RhdGFGcmFtZSgpPC9jb2RlPiBpcyBhIGNvbnZlbmllbnQgd2F5IHRvIGxvYWQgc2ltcGxlIFhNTCBmaWxlcyBpbnRvIFIgZm9yIGFuYWx5c2lzLiBTdWNoIFhNTCBmaWxlcyBhcmUgb2Z0ZW4gdGhlIHJlc3VsdCBvZiBkYXRhIGV4cG9ydHMgZnJvbSBhcHBsaWNhdGlvbiwgZGF0YWJhc2VzLCBzeXN0ZW1zLCBvciB0aGlyZC1wYXJ0aWVzLg0KDQojIyBMb2FkIDJMIFhNTCB0byBEYXRhZnJhbWUNCg0KSGVyZSBpcyBhbiBleGFtcGxlIG9mIGEgMi1kaW1lbnNpb25hbCBYTUwgdGhhdCBoYXMgYSBzaW5nbGUgbGV2ZWwgb2YgY2hpbGQgbm9kZXMgaW4gdGhlIHNhbWUgb3JkZXIgZm9yIGFsbCBub2RlcyB1bmRlciB0aGUgcm9vdC4NCg0KYGBgIHhtbA0KPD94bWwgdmVyc2lvbj0iMS4wIj8+DQoNCjxkb2N1bWVudD4NCiAgPHJvdz4NCiAgICA8R2lydGg+OC4zPC9HaXJ0aD4NCiAgICA8SGVpZ2h0PjcwPC9IZWlnaHQ+DQogICAgPFZvbHVtZT4xMC4zPC9Wb2x1bWU+DQogIDwvcm93Pg0KICA8cm93Pg0KICAgIDxHaXJ0aD44LjY8L0dpcnRoPg0KICAgIDxIZWlnaHQ+NjU8L0hlaWdodD4NCiAgICA8Vm9sdW1lPjEwLjM8L1ZvbHVtZT4NCiAgPC9yb3c+DQogIA0KICAuLi4NCmBgYA0KDQpMZXQncyBsb2FkIHRoaXMgc2ltcGxlIFhNTCB3aXRoIGEgcm93IGNvbHVtbiBzdHJ1Y3R1cmUgaW50byBhIGRhdGEgZnJhbWUgdXNpbmcgPGNvZGU+eG1sVG9EYXRhRnJhbWUoKTwvY29kZT4uIFRoZSBzdHJ1Y3R1cmUgbXVzdCBiZSBhIHJvb3Qgd2l0aCBjaGlsZCBub2RlcyB0aGF0IGVhY2ggaGF2ZSBhIHNpbmdsZSBsZXZlbCBjaGlsZCB3aXRoIGEgc2luZ2xlIHZhbHVlIGluIHRoZSBzYW1lIG9yZGVyIC0tIGVzc2VudGlhbGx5IGEgcm93L2NvbHVtbiBhcnJhbmdlbWVudCBleHByZXNzZWQgaW4gWE1MLg0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQpmbiA8LSAidHJlZWRhdGEyTC54bWwiDQoNCiMgbG9hZCBmcm9tIGxvY2FsIGZpbGUNCmRmIDwtIHhtbFRvRGF0YUZyYW1lKGZuKQ0KDQpoZWFkKGRmLCAzKQ0KYGBgDQoNCkJ5IGRlZmF1bHQsIGFsbCBjb2x1bW5zIGFyZSBjb252ZXJ0ZWQgdG8gY2hhcmFjdGVyICh0ZXh0KSB2YXJpYWJsZXMuIFRvIGtlZXAgdGhlbSBhcyB0ZXh0LCB1c2UgdGhlIHBhcmFtZXRlciAqc3RyaW5nc0FzRmFjdG9ycyA9IEYqLCBmb3IgYW55IG90aGVyIGRhdGEgdHlwZSwgeW91IG11c3QgdXNlIGV4cGxpY2l0IGNvZXJjaW9uLA0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQpmbiA8LSAidHJlZWRhdGEyTC54bWwiDQoNCiMgbG9hZCBmcm9tIGxvY2FsIGZpbGUNCmRmIDwtIHhtbFRvRGF0YUZyYW1lKGZuLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCg0KZGYkR2lydGggPC0gYXMubnVtZXJpYyhkZiRHaXJ0aCkNCmRmJEhlaWdodCA8LSBhcy5udW1lcmljKGRmJEhlaWdodCkNCmRmJFZvbHVtZSA8LSBhcy5udW1lcmljKGRmJFZvbHVtZSkNCg0KaGVhZChkZiwgMykNCmBgYA0KDQojIyBMb2FkIDNMIFhNTCB0byBEYXRhZnJhbWUNCg0KQXR0ZW1wdGluZyB0byBsb2FkIGEgbXVsdGktbGV2ZWwgWE1MIGludG8gYSBkYXRhZnJhbWUgd2lsbCBub3Qgd29yayBwcm9wZXJseSBhcyB0aGUgZXhhbXBsZSBiZWxvdyBpbGx1c3RyYXRlcy4NCg0KYGBgIHhtbA0KPD94bWwgdmVyc2lvbj0iMS4wIj8+DQoNCjxkb2N1bWVudD4NCiAgPHJvdz4NCiAgICA8R2lydGg+OC4zPC9HaXJ0aD4NCiAgICA8ZGltPg0KICAgICAgPEhlaWdodD43MDwvSGVpZ2h0Pg0KICAgICAgPFdpZHRoPjY1PC9XaWR0aD4NCiAgICA8L2RpbT4NCiAgICA8Vm9sdW1lPjEwLjM8L1ZvbHVtZT4NCiAgPC9yb3c+DQogIA0KICAuLi4NCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQpmbiA8LSAidHJlZWRhdGEzTC54bWwiDQoNCmRmIDwtIHhtbFRvRGF0YUZyYW1lKGZuKQ0KaGVhZChkZikNCmBgYA0KDQpOb3RpY2UgaG93IGFueSBYTUwgZWxlbWVudHMgYmV5b25kIHRoZSBzZWNvbmQgbGV2ZWwgYXJlIGNvbmNhdGVuYXRlZCBpbnRvIGEgc2luZ2xlIHN0cmluZy4NCg0KIyMgTG9hZCBYTUwgZnJvbSBVUkwNCg0KUmF0aGVyIHRoYW4gbG9hZGluZyBhIGxvY2FsIFhNTCBmaWxlLCA8Y29kZT54bWxUb0RhdGFGcmFtZSgpPC9jb2RlPiBjYW4gYWxzbyBsb2FkIGFuIFhNTCBmaWxlIGZyb20gYSBVUkwuIFRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gYWNjb21wbGlzaCB0aGlzOyB0aGlzIGlzIG9uZSB3YXkuIE5vdGUgdGhhdCA8Y29kZT5nZXRVUkwoKTwvY29kZT4gb25seSBzdXBwb3J0cyAqaHR0cCogYW5kIG5vdCAqaHR0cHMqLg0KDQpgYGB7ciBldmFsPUZ9DQpsaWJyYXJ5KFhNTCkNCmxpYnJhcnkoUkN1cmwpDQoNCiMgVVJMIHRvIFhNTCBmaWxlOyBtdXN0IGJlIGh0dHA6Ly8NCnhtbC51cmwgPC0gImh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzIzLWxvYWQteG1sLXhtbFRvRGF0YUZyYW1lL3BsYW50X2NhdGFsb2cueG1sIg0KDQp4RGF0YSA8LSBnZXRVUkwoeG1sLnVybCkNCg0KZGYgPC0geG1sVG9EYXRhRnJhbWUoeERhdGEpDQpoZWFkKGRmLCAyKQ0KYGBgDQoNCkFnYWluLCBhbGwgZGF0YSBpcyBjb252ZXJ0ZWQgdG8gdGV4dCBhbmQgbXVzdCBiZSBleHBsaWNpdGx5IGNvbnZlcnRlZCB0byBhbiBhcHByb3ByaWF0ZSBkYXRhIHR5cGUuDQoNCmBgYHtyIGV2YWw9Rn0NCmRmIDwtIHhtbFRvRGF0YUZyYW1lKHhEYXRhLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpoZWFkKGRmKQ0KDQpkZiRCT1RBTklDQUwgPC0gYXMuZmFjdG9yKGRmJEJPVEFOSUNBTCkNCmRmJFBSSUNFIDwtIGFzLm51bWVyaWMoc3Vic3RyaW5nKGRmJFBSSUNFLCAyKSkNCmhlYWQoZGYpDQpgYGANCg0KIyMgTm9kZSBUcmF2ZXJzYWwNCg0KQSBjb21wbGV4IFhNTCBtdXN0IGJlIHBhcnNlZCBhbmQgbG9hZGVkIGludG8gYSBkYXRhIGZyYW1lIHRocm91Z2ggYSBjb21iaW5hdGlvbiBvZiBYUGF0aCBleHByZXNzaW9ucyBhbmQgbm9kZSB0cmF2ZXJzYWwgb2YgdGhlIERPTSAoRG9jdW1lbnQgT2JqZWN0IE1vZGVsKS4gQmVsb3csIHdlIHdpbGwgaWxsdXN0cmF0ZSBob3cgdG8gdHJhdmVyc2UgYSBtdWx0aS1sZXZlbCBYTUwgdXNpbmcgbm9kZSB0cmF2ZXJzYWwuIEl0IGlzIG5vdCBtZWFudCB0byBiZSBhbiBleGhhdXN0aXZlIGFuZCBjb21wbGV0ZSB0cmVhdG1lbnQuDQoNClJlY2FsbCB0aGF0IGVhY2ggbm9kZSBoYXMgdGhpcyBzdHJ1Y3R1cmU6DQoNCmBgYCB4bWwNCjxkb2N1bWVudD4NCiAgPHJvdz4NCiAgICA8R2lydGg+OC4zPC9HaXJ0aD4NCiAgICA8ZGltPg0KICAgICAgPEhlaWdodD43MDwvSGVpZ2h0Pg0KICAgICAgPFdpZHRoPjY1PC9XaWR0aD4NCiAgICA8L2RpbT4NCiAgICA8Vm9sdW1lPjEwLjM8L1ZvbHVtZT4NCiAgPC9yb3c+DQpgYGANCg0KTGV0J3MgbG9vayBhdCB0aGUgY29kZS4uLg0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KDQojIGxvYWQgdGhlIFhNTCBpbnRvIG1lbW9yeSBhcyBhIHRyZWUNCnhtbC5kb20gPC0geG1sUGFyc2UoInRyZWVkYXRhM0wueG1sIikNCg0KIyBnZXQgdGhlIHJvb3Qgbm9kZSAgPGRvY3VtZW50Pg0Kcm9vdCA8LSB4bWxSb290KHhtbC5kb20pDQoNCiMgZ2V0IHRoZSBudW1iZXIgb2YgPHJvdz4gbm9kZXMgdW5kZXIgdGhlIHJvb3QNCm4gPC0geG1sU2l6ZShyb290KQ0KDQojIHNldCB1cCBhIGRhdGEgZnJhbWUgdG8gc3RvcmUgdGhlIGRhdGE7IGluaXRpYWxseSBjb250YWlucyB6ZXJvIHJvd3MNCiMgYnV0IHdpbGwgZXhwYW5kIGR5bmFtaWNhbGx5IGFzIHJvd3MgYXJlIGFkZGVkDQpkZiA8LSBkYXRhLmZyYW1lKGdpcnRoID0gbnVtZXJpYygpLA0KICAgICAgICAgICAgICAgICBoZWlnaHQgPSBpbnRlZ2VyKCksDQogICAgICAgICAgICAgICAgIHdpZHRoID0gaW50ZWdlcigpLA0KICAgICAgICAgICAgICAgICB2b2x1bWUgPSBudW1lcmljKCkNCiAgICAgICAgICAgICAgICAgKQ0KDQojIGl0ZXJhdGUgb3ZlciB0aGUgPHJvdz4gbm9kZXMNCmZvciAoaSBpbiAxOm4pIHsNCiAgIyBnZXQgbm9kZSBpIGZyb20gdGhlIGxpc3Qgb2YgY2hpbGQgbm9kZXMNCiAgYVJvdyA8LSByb290W1tpXV0NCiAgDQogICMgZ2V0IHZhbHVlIG9mIDxHaXJ0aD4gY2hpbGQNCiAgZ2lydGggPC0geG1sVmFsdWUoYVJvd1tbMV1dKQ0KICANCiAgIyBnZXQgdmFsdWUgb2YgPEhlaWdodD4gYW5kIDxXaWR0aD4gd2hpY2ggYXJlIGNoaWxkcmVuIG9mIDxkaW0+DQogIGhlaWdodCA8LSB4bWxWYWx1ZShhUm93W1syXV1bWzFdXSkNCiAgd2lkdGggPC0geG1sVmFsdWUoYVJvd1tbMl1dW1syXV0pDQogIA0KICAjIGdldCB0aGUgdmFsdWUgb2YgPFZvbHVtZT4NCiAgdm9sdW1lIDwtIHhtbFZhbHVlKGFSb3dbWzNdXSkNCiAgDQogICMgYWRkIHRoZSB2YWx1ZXMgdG8gYSBuZXcgcm93IGluIHRoZSBkYXRhIGZyYW1lDQogICMgY29lcmNlIHRleHQgdG8gYXBwcm9wcmlhdGUgZGF0YSB0eXBlDQogIGRmW2ksJ2dpcnRoJ10gPC0gYXMubnVtZXJpYyhnaXJ0aCkNCiAgZGZbaSwnaGVpZ2h0J10gPC0gYXMuaW50ZWdlcihoZWlnaHQpDQogIGRmW2ksJ3dpZHRoJ10gPC0gYXMuaW50ZWdlcih3aWR0aCkNCiAgZGZbaSwndm9sdW1lJ10gPC0gYXMubnVtZXJpYyh2b2x1bWUpDQp9DQoNCiMgcHJpbnQgcGFydCBvZiBkYXRhIGZyYW1lIHRvIGVuc3VyZSBpdCBjb250YWlucyBkYXRhIGZyb20gWE1MDQpoZWFkKGRmLDIpDQpgYGANCg0KTm90ZSB0aGF0IHdlIHNldCB1cCBhbiBpbml0aWFsIGRhdGEgZnJhbWUgd2l0aCB6ZXJvIHJvd3MgYW5kIGFzIHdlIGFkZCBhIG5ldyByb3cgYXQgdGhlIGVuZCwgdGhlIGRhdGEgZnJhbWUncyBzaXplIGdyb3dzIGR5bmFtaWNhbGx5LiBPZiBjb3Vyc2UsIHdlIGNvdWxkIGhhdmUgcHJlLWFsbG9jYXRlZCB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBjb3JyZWN0IG51bWJlciBvZiByb3dzIGFzIGVhY2ggcm93IGNvcnJlc3BvbmRzIHRvIG9uZSBjaGlsZCBub2RlLg0KDQpUaGUgZGF0YSBpcyByZWFkIGZyb20gdGhlIFhNTCBhcyB0ZXh0IChjaGFyYWN0ZXIpIGFuZCBtdXN0IGJlIGV4cGxpY2l0bHkgY29lcmNlZC4NCg0KVGhlIHJvb3Qgbm9kZSBpcyBhIGxpc3Qgc3RydWN0dXJlIG9mIGNoaWxkIG5vZGVzIGFuZCBsaXN0IGVsZW1lbnRzIGFyZSBhY2Nlc3NlZCB3aXRoIHRoZSBbW11dIG9wZXJhdG9yLiBTbywgKmFSb3dbWzJdXVtbMV1dKiByZXR1cm5zIHRoZSBmaXJzdCBjaGlsZCBvZiB0aGUgc2Vjb25kIGNoaWxkIG9mIHRoZSBub2RlIGluICphUm93Ki4NCg0KVGhlIGZ1bmN0aW9uIDxjb2RlPnhtbFZhbHVlKCk8L2NvZGU+IHJldHVybnMgdGhlIHZhbHVlIG9mIGEgbm9kZSwgZS5nLiwgaXQgcmV0dXJucyAqZm9vKiBmb3IgPHRhZz5mb288dGFnPi4NCg0KOjo6IHsuaW5mb2JveCAuY2F1dGlvbkJveCBkYXRhLWxhdGV4PSJ7Y2F1dGlvbn0ifQ0KKipDQVJFRlVMKioNCg0KVGhlIHN0YXRlbWVudCA8Y29kZT5kZltpLCdnaXJ0aCddIFw8LSBnaXJ0aDwvY29kZT4gYWRkcyBhIG5ldyByb3cgYnV0IHRoZSAiZXF1aXZhbGVudCIgc3RhdGVtZW50IDxjb2RlPmRmXCRnaXJ0aFtpXSBcPC0gZ2lydGg8L2NvZGU+IHJlc3VsdHMgaW4gYW4gZXJyb3IuDQo6OjoNCg0KIyMgU3VtbWFyeQ0KDQo8Y29kZT54bWxUb0RhdGFGcmFtZSgpPC9jb2RlPiBpcyBhIGNvbnZlbmllbnQgd2F5IHRvIGxvYWQgc2ltcGxlIFhNTCBmaWxlcyBpbnRvIFIgZm9yIGFuYWx5c2lzLiBTdWNoIFhNTCBmaWxlcyBhcmUgb2Z0ZW4gdGhlIHJlc3VsdCBvZiBkYXRhIGV4cG9ydHMgZnJvbSBhcHBsaWNhdGlvbiwgZGF0YWJhc2VzLCBzeXN0ZW1zLCBvciB0aGlyZC1wYXJ0aWVzLg0KDQpNb3JlIGNvbXBsZXggWE1MIGZpbGVzIG11c3QgYmUgcHJvY2Vzc2VkIHdpdGggYSBjb21iaW5hdGlvbiBvZiBub2RlIHRyYXZlcnNhbCBhbmQgWFBhdGguIFNlZSBMZXNzb24gWzYuMzAzIERhdGEgUmV0cmlldmFsIGZyb20gWE1MIHZpYSBYUGF0aCBpbiBSXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMwMy14cGF0aC1pbi1yL2wtNi0zMDMuaHRtbCkuDQoNCiMjIFR1dG9yaWFsDQoNClRoZSB2aWRlbyB0dXRvcmlhbCBiZWxvdyBpcyBhIG5hcnJhdGlvbiB3aXRoIGV4YW1wbGVzIG9mIHRoZSBjb25jZXB0cyBpbiB0aGlzIGxlc3Nvbi4NCg0KYGBgez1odG1sfQ0KPGlmcmFtZSBzcmM9Imh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2VtYmVkLy1XbmMxOExra253IiB3aWR0aD0iNDgwIiBoZWlnaHQ9IjI3MCIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4gZGF0YS1leHRlcm5hbD0iMSI+PC9pZnJhbWU+DQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEZpbGVzICYgUmVzb3VyY2VzDQoNCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQ0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIA0KICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksDQogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpDQoNCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgDQogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpDQoNCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUg0Ka25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgUmVmZXJlbmNlcw0KDQpObyByZWZlcmVuY2VzLg0KDQojIyBFcnJhdGENCg0KTm9uZSBjb2xsZWN0ZWQgeWV0LiBMZXQgdXMga25vdy4NCg0KYGBgez1odG1sfQ0KPHN0eWxlPg0KLmluZm9ib3ggew0KICBwYWRkaW5nOiAxZW0gMWVtIDFlbSAxZW07DQogIG1hcmdpbi1ib3R0b206IDEwcHg7DQogIGJvcmRlcjogMnB4IHNvbGlkIG9yYW5nZTsNCiAgYm9yZGVyLXJhZGl1czogMTBweDsNCiAgYmFja2dyb3VuZDogI2Y1ZjVmNSA1cHggY2VudGVyLzNlbSBuby1yZXBlYXQ7DQp9DQoNCjwvc3R5bGU+DQpgYGANCmBgYHs9aHRtbH0NCjxzY3JpcHQgc3JjPSJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vc3RhdGljL2ZlZWRiYWNrMi5qcyIgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCiAgbmV3IEpvdGZvcm1GZWVkYmFjayh7DQogICAgZm9ybUlkOiAiMjEyMTg3MDcyNzg0MTU3IiwNCiAgICBidXR0b25UZXh0OiAiRmVlZGJhY2siLA0KICAgIGJhc2U6ICJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vIiwNCiAgICBiYWNrZ3JvdW5kOiAiI0Y1OTIwMiIsDQogICAgZm9udENvbG9yOiAiI0ZGRkZGRiIsDQogICAgYnV0dG9uU2lkZTogImxlZnQiLA0KICAgIGJ1dHRvbkFsaWduOiAiY2VudGVyIiwNCiAgICB0eXBlOiBmYWxzZSwNCiAgICB3aWR0aDogNzAwLA0KICAgIGhlaWdodDogNTAwLA0KICAgIGlzQ2FyZEZvcm06IGZhbHNlDQogIH0pOw0KPC9zY3JpcHQ+DQpgYGANCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19kZXBsb3lLbml0LlInKSksIGluY2x1ZGUgPSBGQUxTRX0NCmBgYA0K