Introduction

In this lesson we will demonstrate how to use a combination of node-by-node traversal and XPath expressions to extract data from an XML document and store the data in a data frame for further processing, analysis, or storage in a database.

Prerequisites

This lesson presumes that the learner has an understanding of XML, the structure of an XML DOM, and knows how to formulate XPath expressions.

See also:

Packages

To use any of the XML parsing or any of the XPath function you will need an XML package. The XML package is one of several packages and the one we are using in this tutorial. Note that the XML package only supports XPath Version 1.0 and not the newer 2.0 and 3.1 versions.

library(XML)

Loading an XML Document

Let’s start by loading an XML document. There are several functions for loading them which generally all work the same way, although some create different in-memory structures representing the XML tree and thus some are more and some are less efficient. XML documents (or files) can be loaded from the local file system or from a URL.

Load XML from File

xmlFile <- "CDCatalog2.xml"

xmlObj <- xmlParse(xmlFile)
xmlObjTree <- xmlTreeParse(xmlFile)

The error Error: XML content does not seem to be XML: ’’ is often caused by a file that cannot be found and is often due to a misspelled file or path name.

Load XML via URL

xmlURL <-"http://d396qusza40orc.cloudfront.net/getdata%2Fdata%2Frestaurants.xml"
xmlObjTree <- xmlTreeParse(xmlURL)

Note that the R parsing functions do not support https so be sure that any URL starts with http:// rather than https://. If you get the error “XML content does not seem to be XML” then that is often the cause.

xmlParse vs xmlTreeParse

xmlParse is a version of xmlTreeParse where the argument useInternalNodes is set to TRUE. If you want to get an R object use xmlTreeParse. While this is generally not very efficient for large document and often unnecessary if you want to extract only parts of the XML document, it has the benefit that you can traverse the XML tree using named traversal, e.g., root\(child1\)child$…

Using xmlParse is generally more efficient as it returns a pointer to a C structure. To access this structure requires XPath, although xmlTreeParse supports XPath as well.

Applying an XPath Expression

There are several ways to apply an XPath expression to a parsed XML object, the most common of which to use the function xmlPathSApply.

The xmlPathSApply function applies the function passed as a parameter to all matching elements of an XPath expression rather than returning the elements. In the code chunk below, each matching element has the xmlValue function applied to it and thus the value of the matching elements are extracted. Recall that the value of an element is everything that is between the opening and closing tags. For example, the value of {xml} <tag>some value</tag> is someValue. Note that the returned object is a vector of characters (like an array of strings in other programming languages) and thus can be accessed as such.

xmlObj <- xmlParse(xmlFile)

xpathEx <- "//cd/title"
artists <- xpathSApply(xmlObj, xpathEx, xmlValue)

head(artists, 3)
## [1] "Empire Burlesque" "Hide your heart"  "Greatest Hits"
# access the second element
print(paste("The second artist is: ",artists[2]))
## [1] "The second artist is:  Hide your heart"

Retrieving XML Attributes

There are two ways to retrieve an element’s attributes. One, use an XPath expression with xpathSApply (but without applying the xmlValue function). Two, use the xmlAttrs function from a specific node – which requires traversing the tree.

The use of an XPath expression is generally preferable and more maintainable.

xmlObj <- xmlParse(xmlFile)

# Approach 1: use an XPath expression to get the attribute country

xpathEx <- "//cd/company/@country"
countries <- xpathSApply(xmlObj, xpathEx)

head(countries, 3)
## country country country 
##   "USA"    "UK"   "USA"

Using Values in R

All of the values retrieved from XML are text and must be converted to strings, often after parsing the text.

xpathEx <- "//cd/price"
prices <- xpathSApply(xmlObj, xpathEx, xmlValue)

# the values in the vector "prices" are character strings
# mean(prices) results in an error

prices.n <- as.numeric(prices)
avg <- mean(prices.n)

print(paste0("The average price is $", round(avg,2)))
## [1] "The average price is $9.12"

Summary

Tutorial


Files & Resources

All Files for Lesson 6.305

References

No references.

Errata

None collected yet. Let us know.

LS0tDQp0aXRsZTogIlByb2Nlc3MgWE1MIERPTSB2aWEgWFBhdGggYW5kIE5vZGUgVHJhdmVyc2FsIg0KcGFyYW1zOg0KICBjYXRlZ29yeTogNg0KICBudW1iZXI6IDMwNQ0KICB0aW1lOiA0NQ0KICBsZXZlbDogYmVnaW5uZXINCiAgdGFnczogInIseHBhdGgseG1sIg0KICBkZXNjcmlwdGlvbjogIkV4cGxhaW5zIGhvdyB0byByZXRyaWV2ZSBkYXRhIGZyb20gYW4gWE1MIGludG8gYSBkYXRhIGZyYW1lDQogICAgICAgICAgICAgICAgdXNpbmcgYSBjb21iaW5hdGlvbiBvZiBub2RlIHRyYXZlcnNhbCBhbmQNCiAgICAgICAgICAgICAgICBYUGF0aCBleHByZXNzaW9ucy4iDQpkYXRlOiAiPHNtYWxsPmByIFN5cy5EYXRlKClgPC9zbWFsbD4iDQphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iDQplbWFpbDogIm0uc2NoZWRsYmF1ZXJAbmV1LmVkdSINCmFmZmlsaXRhdGlvbjogIk5vcnRoZWFzdGVybiBVbml2ZXJzaXR5Ig0Kb3V0cHV0OiANCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIGhpZ2hsaWdodDogdGFuZ28NCi0tLQ0KDQotLS0NCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iDQotLS0NCg0KYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2luc2VydDJEQi5SJykpLCBpbmNsdWRlID0gRkFMU0V9DQpgYGANCg0KIyMgSW50cm9kdWN0aW9uDQoNCkluIHRoaXMgbGVzc29uIHdlIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvIHVzZSBhIGNvbWJpbmF0aW9uIG9mIG5vZGUtYnktbm9kZSB0cmF2ZXJzYWwgYW5kIFhQYXRoIGV4cHJlc3Npb25zIHRvIGV4dHJhY3QgZGF0YSBmcm9tIGFuIFhNTCBkb2N1bWVudCBhbmQgc3RvcmUgdGhlIGRhdGEgaW4gYSBkYXRhIGZyYW1lIGZvciBmdXJ0aGVyIHByb2Nlc3NpbmcsIGFuYWx5c2lzLCBvciBzdG9yYWdlIGluIGEgZGF0YWJhc2UuDQoNCiMjIFByZXJlcXVpc2l0ZXMNCg0KVGhpcyBsZXNzb24gcHJlc3VtZXMgdGhhdCB0aGUgbGVhcm5lciBoYXMgYW4gdW5kZXJzdGFuZGluZyBvZiBYTUwsIHRoZSBzdHJ1Y3R1cmUgb2YgYW4gWE1MIERPTSwgYW5kIGtub3dzIGhvdyB0byBmb3JtdWxhdGUgWFBhdGggZXhwcmVzc2lvbnMuDQoNClNlZSBhbHNvOg0KDQotICAgW1hNTCBMZXNzb24gSGVyZV0oKQ0KLSAgIFs2LjMwMyBEYXRhIFJldHJpZXZhbCBmcm9tIFhNTCB2aWEgWFBhdGggaW4gUl0oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0zMDMteHBhdGgtaW4tci9sLTYtMzAzLmh0bWwpDQotICAgWzYuMzIzIExvYWQgU2ltcGxlIFhNTCBpbnRvIERhdGFmcmFtZSBpbiBSIHVzaW5nIHhtbFRvRGF0YUZyYW1lKCldKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzIzLWxvYWQteG1sLXhtbFRvRGF0YUZyYW1lL2wtNi0zMjMuaHRtbCkNCg0KIyMgUGFja2FnZXMNCg0KVG8gdXNlIGFueSBvZiB0aGUgWE1MIHBhcnNpbmcgb3IgYW55IG9mIHRoZSBYUGF0aCBmdW5jdGlvbiB5b3Ugd2lsbCBuZWVkIGFuIFhNTCBwYWNrYWdlLiBUaGUgKipYTUwqKiBwYWNrYWdlIGlzIG9uZSBvZiBzZXZlcmFsIHBhY2thZ2VzIGFuZCB0aGUgb25lIHdlIGFyZSB1c2luZyBpbiB0aGlzIHR1dG9yaWFsLiBOb3RlIHRoYXQgdGhlICoqWE1MKiogcGFja2FnZSBvbmx5IHN1cHBvcnRzIFhQYXRoIFZlcnNpb24gMS4wIGFuZCBub3QgdGhlIG5ld2VyIDIuMCBhbmQgMy4xIHZlcnNpb25zLg0KDQpgYGB7cn0NCmxpYnJhcnkoWE1MKQ0KYGBgDQoNCiMjIExvYWRpbmcgYW4gWE1MIERvY3VtZW50DQoNCkxldCdzIHN0YXJ0IGJ5IGxvYWRpbmcgYW4gWE1MIGRvY3VtZW50LiBUaGVyZSBhcmUgc2V2ZXJhbCBmdW5jdGlvbnMgZm9yIGxvYWRpbmcgdGhlbSB3aGljaCBnZW5lcmFsbHkgYWxsIHdvcmsgdGhlIHNhbWUgd2F5LCBhbHRob3VnaCBzb21lIGNyZWF0ZSBkaWZmZXJlbnQgaW4tbWVtb3J5IHN0cnVjdHVyZXMgcmVwcmVzZW50aW5nIHRoZSBYTUwgdHJlZSBhbmQgdGh1cyBzb21lIGFyZSBtb3JlIGFuZCBzb21lIGFyZSBsZXNzIGVmZmljaWVudC4gWE1MIGRvY3VtZW50cyAob3IgZmlsZXMpIGNhbiBiZSBsb2FkZWQgZnJvbSB0aGUgbG9jYWwgZmlsZSBzeXN0ZW0gb3IgZnJvbSBhIFVSTC4NCg0KIyMjIExvYWQgWE1MIGZyb20gRmlsZQ0KDQpgYGB7cn0NCnhtbEZpbGUgPC0gIkNEQ2F0YWxvZzIueG1sIg0KDQp4bWxPYmogPC0geG1sUGFyc2UoeG1sRmlsZSkNCnhtbE9ialRyZWUgPC0geG1sVHJlZVBhcnNlKHhtbEZpbGUpDQpgYGANCg0KPiBUaGUgZXJyb3IgKipFcnJvcjogWE1MIGNvbnRlbnQgZG9lcyBub3Qgc2VlbSB0byBiZSBYTUw6ICcnKiogaXMgb2Z0ZW4gY2F1c2VkIGJ5IGEgZmlsZSB0aGF0IGNhbm5vdCBiZSBmb3VuZCBhbmQgaXMgb2Z0ZW4gZHVlIHRvIGEgbWlzc3BlbGxlZCBmaWxlIG9yIHBhdGggbmFtZS4NCg0KIyMjIExvYWQgWE1MIHZpYSBVUkwNCg0KYGBge3J9DQp4bWxVUkwgPC0iaHR0cDovL2QzOTZxdXN6YTQwb3JjLmNsb3VkZnJvbnQubmV0L2dldGRhdGElMkZkYXRhJTJGcmVzdGF1cmFudHMueG1sIg0KeG1sT2JqVHJlZSA8LSB4bWxUcmVlUGFyc2UoeG1sVVJMKQ0KYGBgDQoNCj4gTm90ZSB0aGF0IHRoZSBSIHBhcnNpbmcgZnVuY3Rpb25zIGRvIG5vdCBzdXBwb3J0ICpodHRwcyogc28gYmUgc3VyZSB0aGF0IGFueSBVUkwgc3RhcnRzIHdpdGggWypodHRwOi8vKl0oaHR0cDovLyl7LnVyaX0gcmF0aGVyIHRoYW4gWypodHRwczovLypdKGh0dHBzOi8vKXsudXJpfS4gSWYgeW91IGdldCB0aGUgZXJyb3IgIlhNTCBjb250ZW50IGRvZXMgbm90IHNlZW0gdG8gYmUgWE1MIiB0aGVuIHRoYXQgaXMgb2Z0ZW4gdGhlIGNhdXNlLg0KDQojIyMgeG1sUGFyc2UgKnZzKiB4bWxUcmVlUGFyc2UNCg0KPGNvZGU+eG1sUGFyc2U8L2NvZGU+IGlzIGEgdmVyc2lvbiBvZiA8Y29kZT54bWxUcmVlUGFyc2U8L2NvZGU+IHdoZXJlIHRoZSBhcmd1bWVudCAqdXNlSW50ZXJuYWxOb2RlcyogaXMgc2V0IHRvICpUUlVFKi4gSWYgeW91IHdhbnQgdG8gZ2V0IGFuIFIgb2JqZWN0IHVzZSA8Y29kZT54bWxUcmVlUGFyc2U8L2NvZGU+LiBXaGlsZSB0aGlzIGlzIGdlbmVyYWxseSBub3QgdmVyeSBlZmZpY2llbnQgZm9yIGxhcmdlIGRvY3VtZW50IGFuZCBvZnRlbiB1bm5lY2Vzc2FyeSBpZiB5b3Ugd2FudCB0byBleHRyYWN0IG9ubHkgcGFydHMgb2YgdGhlIFhNTCBkb2N1bWVudCwgaXQgaGFzIHRoZSBiZW5lZml0IHRoYXQgeW91IGNhbiB0cmF2ZXJzZSB0aGUgWE1MIHRyZWUgdXNpbmcgbmFtZWQgdHJhdmVyc2FsLCAqZS5nLiosIHJvb3QkY2hpbGQxJGNoaWxkXCQuLi4NCg0KVXNpbmcgPGNvZGU+eG1sUGFyc2U8L2NvZGU+IGlzIGdlbmVyYWxseSBtb3JlIGVmZmljaWVudCBhcyBpdCByZXR1cm5zIGEgcG9pbnRlciB0byBhIEMgc3RydWN0dXJlLiBUbyBhY2Nlc3MgdGhpcyBzdHJ1Y3R1cmUgcmVxdWlyZXMgWFBhdGgsIGFsdGhvdWdoIDxjb2RlPnhtbFRyZWVQYXJzZTwvY29kZT4gc3VwcG9ydHMgWFBhdGggYXMgd2VsbC4NCg0KIyMgQXBwbHlpbmcgYW4gWFBhdGggRXhwcmVzc2lvbg0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIGFwcGx5IGFuIFhQYXRoIGV4cHJlc3Npb24gdG8gYSBwYXJzZWQgWE1MIG9iamVjdCwgdGhlIG1vc3QgY29tbW9uIG9mIHdoaWNoIHRvIHVzZSB0aGUgZnVuY3Rpb24gPGNvZGU+eG1sUGF0aFNBcHBseTwvY29kZT4uDQoNClRoZSA8Y29kZT54bWxQYXRoU0FwcGx5PC9jb2RlPiBmdW5jdGlvbiBhcHBsaWVzIHRoZSBmdW5jdGlvbiBwYXNzZWQgYXMgYSBwYXJhbWV0ZXIgdG8gYWxsIG1hdGNoaW5nIGVsZW1lbnRzIG9mIGFuIFhQYXRoIGV4cHJlc3Npb24gcmF0aGVyIHRoYW4gcmV0dXJuaW5nIHRoZSBlbGVtZW50cy4gSW4gdGhlIGNvZGUgY2h1bmsgYmVsb3csIGVhY2ggbWF0Y2hpbmcgZWxlbWVudCBoYXMgdGhlIDxjb2RlPnhtbFZhbHVlPC9jb2RlPiBmdW5jdGlvbiBhcHBsaWVkIHRvIGl0IGFuZCB0aHVzIHRoZSB2YWx1ZSBvZiB0aGUgbWF0Y2hpbmcgZWxlbWVudHMgYXJlIGV4dHJhY3RlZC4gUmVjYWxsIHRoYXQgdGhlIHZhbHVlIG9mIGFuIGVsZW1lbnQgaXMgZXZlcnl0aGluZyB0aGF0IGlzIGJldHdlZW4gdGhlIG9wZW5pbmcgYW5kIGNsb3NpbmcgdGFncy4gRm9yIGV4YW1wbGUsIHRoZSB2YWx1ZSBvZiBge3htbH0gPHRhZz5zb21lIHZhbHVlPC90YWc+YCBpcyAqc29tZVZhbHVlKi4gTm90ZSB0aGF0IHRoZSByZXR1cm5lZCBvYmplY3QgaXMgYSB2ZWN0b3Igb2YgY2hhcmFjdGVycyAobGlrZSBhbiBhcnJheSBvZiBzdHJpbmdzIGluIG90aGVyIHByb2dyYW1taW5nIGxhbmd1YWdlcykgYW5kIHRodXMgY2FuIGJlIGFjY2Vzc2VkIGFzIHN1Y2guDQoNCmBgYHtyfQ0KeG1sT2JqIDwtIHhtbFBhcnNlKHhtbEZpbGUpDQoNCnhwYXRoRXggPC0gIi8vY2QvdGl0bGUiDQphcnRpc3RzIDwtIHhwYXRoU0FwcGx5KHhtbE9iaiwgeHBhdGhFeCwgeG1sVmFsdWUpDQoNCmhlYWQoYXJ0aXN0cywgMykNCg0KIyBhY2Nlc3MgdGhlIHNlY29uZCBlbGVtZW50DQpwcmludChwYXN0ZSgiVGhlIHNlY29uZCBhcnRpc3QgaXM6ICIsYXJ0aXN0c1syXSkpDQpgYGANCg0KIyMjIFJldHJpZXZpbmcgWE1MIEF0dHJpYnV0ZXMNCg0KVGhlcmUgYXJlIHR3byB3YXlzIHRvIHJldHJpZXZlIGFuIGVsZW1lbnQncyBhdHRyaWJ1dGVzLiBPbmUsIHVzZSBhbiBYUGF0aCBleHByZXNzaW9uIHdpdGggPGNvZGU+eHBhdGhTQXBwbHk8L2NvZGU+IChidXQgd2l0aG91dCBhcHBseWluZyB0aGUgPGNvZGU+eG1sVmFsdWU8L2NvZGU+IGZ1bmN0aW9uKS4gVHdvLCB1c2UgdGhlIDxjb2RlPnhtbEF0dHJzPC9jb2RlPiBmdW5jdGlvbiBmcm9tIGEgc3BlY2lmaWMgbm9kZSAtLSB3aGljaCByZXF1aXJlcyB0cmF2ZXJzaW5nIHRoZSB0cmVlLg0KDQpUaGUgdXNlIG9mIGFuIFhQYXRoIGV4cHJlc3Npb24gaXMgZ2VuZXJhbGx5IHByZWZlcmFibGUgYW5kIG1vcmUgbWFpbnRhaW5hYmxlLg0KDQpgYGB7cn0NCnhtbE9iaiA8LSB4bWxQYXJzZSh4bWxGaWxlKQ0KDQojIEFwcHJvYWNoIDE6IHVzZSBhbiBYUGF0aCBleHByZXNzaW9uIHRvIGdldCB0aGUgYXR0cmlidXRlIGNvdW50cnkNCg0KeHBhdGhFeCA8LSAiLy9jZC9jb21wYW55L0Bjb3VudHJ5Ig0KY291bnRyaWVzIDwtIHhwYXRoU0FwcGx5KHhtbE9iaiwgeHBhdGhFeCkNCg0KaGVhZChjb3VudHJpZXMsIDMpDQoNCmBgYA0KDQojIyBVc2luZyBWYWx1ZXMgaW4gUg0KDQpBbGwgb2YgdGhlIHZhbHVlcyByZXRyaWV2ZWQgZnJvbSBYTUwgYXJlIHRleHQgYW5kIG11c3QgYmUgY29udmVydGVkIHRvIHN0cmluZ3MsIG9mdGVuIGFmdGVyIHBhcnNpbmcgdGhlIHRleHQuDQoNCmBgYHtyfQ0KeHBhdGhFeCA8LSAiLy9jZC9wcmljZSINCnByaWNlcyA8LSB4cGF0aFNBcHBseSh4bWxPYmosIHhwYXRoRXgsIHhtbFZhbHVlKQ0KDQojIHRoZSB2YWx1ZXMgaW4gdGhlIHZlY3RvciAicHJpY2VzIiBhcmUgY2hhcmFjdGVyIHN0cmluZ3MNCiMgbWVhbihwcmljZXMpIHJlc3VsdHMgaW4gYW4gZXJyb3INCg0KcHJpY2VzLm4gPC0gYXMubnVtZXJpYyhwcmljZXMpDQphdmcgPC0gbWVhbihwcmljZXMubikNCg0KcHJpbnQocGFzdGUwKCJUaGUgYXZlcmFnZSBwcmljZSBpcyAkIiwgcm91bmQoYXZnLDIpKSkNCg0KYGBgDQoNCiMjIFN1bW1hcnkNCg0KIyMgTmV4dCBTdGVwcw0KDQotICAgWzYuMzI0IFRyYXZlcnNlIGFuZCBQYXJzZSBYTUwgRE9NIGluIFJdKGh0dHA6Ly9hcnRpZmljaXVtLnVzL2xlc3NvbnMvMDYuci9sLTYtMzI0LXBhcnNlLXhtbC1kb20vbC02LTMyNC5odG1sKQ0KLSAgIFs2LjMyOCBQYXJzaW5nIGFuIFhNTCBEb2N1bWVudCBhbmQgU2F2aW5nIHRvIFNRTGl0ZSBEYXRhYmFzZSBpbiBSXShodHRwOi8vYXJ0aWZpY2l1bS51cy9sZXNzb25zLzA2LnIvbC02LTMyOC14bWwtdG8tcmVsZGItc3FsaXRlL2wtNi0zMjguaHRtbCkNCg0KIyMgVHV0b3JpYWwNCg0KYGBgez1odG1sfQ0KPGlmcmFtZSBzcmM9IiIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIyNzAiIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYXV0b3BsYXk7IGZ1bGxzY3JlZW47IHBpY3R1cmUtaW4tcGljdHVyZSIgYWxsb3dmdWxsc2NyZWVuIGRhdGEtZXh0ZXJuYWw9IjEiPjwvaWZyYW1lPg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyBGaWxlcyAmIFJlc291cmNlcw0KDQpgYGB7ciB6aXBGaWxlcywgZWNobz1GQUxTRX0NCnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCANCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LA0KICAgICAgICAgICAgICAgICBwYXJhbXMkbnVtYmVyKQ0KDQp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIA0KICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LCIuIixwYXJhbXMkbnVtYmVyKQ0KDQojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlINCmtuaXRyOjpyYXdfaHRtbChkb3dubG9hZEZpbGVzTGluaygiLiIsIHppcE5hbWUsIHRleHRBTGluaykpDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIFJlZmVyZW5jZXMNCg0KTm8gcmVmZXJlbmNlcy4NCg0KIyMgRXJyYXRhDQoNCk5vbmUgY29sbGVjdGVkIHlldC4gTGV0IHVzIGtub3cuDQoNCmBgYHs9aHRtbH0NCjxzY3JpcHQgc3JjPSJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vc3RhdGljL2ZlZWRiYWNrMi5qcyIgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCiAgbmV3IEpvdGZvcm1GZWVkYmFjayh7DQogICAgZm9ybUlkOiAiMjEyMTg3MDcyNzg0MTU3IiwNCiAgICBidXR0b25UZXh0OiAiRmVlZGJhY2siLA0KICAgIGJhc2U6ICJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vIiwNCiAgICBiYWNrZ3JvdW5kOiAiI0Y1OTIwMiIsDQogICAgZm9udENvbG9yOiAiI0ZGRkZGRiIsDQogICAgYnV0dG9uU2lkZTogImxlZnQiLA0KICAgIGJ1dHRvbkFsaWduOiAiY2VudGVyIiwNCiAgICB0eXBlOiBmYWxzZSwNCiAgICB3aWR0aDogNzAwLA0KICAgIGhlaWdodDogNTAwLA0KICAgIGlzQ2FyZEZvcm06IGZhbHNlDQogIH0pOw0KPC9zY3JpcHQ+DQpgYGANCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19kZXBsb3lLbml0LlInKSksIGluY2x1ZGUgPSBGQUxTRX0NCmBgYA0K