Introduction
This example shows how to load and parse an XML document into an internal relational model of data frames. It traverses the DOM tree node-by-node and then save the data into data frames. The data frames are eventually written to a new database. The example uses only Base R and does not use tidyverse which has additional support for managing relational structures.
We start by loading the required libraries.
library(XML)
library(RSQLite)
library(DBI)
library(knitr)
This section sets up the file names for the XML file (download purchaseorders.xml)and the SQLite database.
xmlfn <- "purchaseorders.xml"
dbfn <- "podb.db"
Load XML into Document Object Model (DOM)
Start by parsing the XML file and loading it into an internal tree structure in memory. Note that the parsing is not validating as there is no DTD in the provided XML.
# Reading the XML file and parse into DOM
xmlDOM <- xmlParse(file = xmlfn)
# get the root node of the DOM tree
r <- xmlRoot(xmlDOM)
ERD for PurchaseOrder XML
Before parsing the XML further we need to define the structure of the relational model. The in-memory structure in the dataframes will match the database structures and tables.
ERD Diagram for PurchaseOrder XML (not fully normalized)
Note that the model is not fully normalized. For example, there should be a separate ZipCodes table with columns zip, city, state, country and the Address table should only contain zip.
Parse into a Data Frame
Load the data into vectors and then merge into a data frame. Use attributes as PKs. The parsing and processing strategy used here only works when the elements are in a deterministic order and the order or cardinality does not change. Since there is no DTD or XML Schema it is not assured that the elements always appear in the expected order. The order is ascertained through inspection and depends on the XML provided and since it may change in the future this parsing code could “break”.
Create Internal Data Frames
The dataframes mimic the relational structure of the database. This only works when the data fits into memory. Otherwise, if the data is too large then the data should be parsed and written to the tables in the databases as it is retrieved. If the XML is really large then a SAX parsing approach should be used.
# get number of children of root (number of purchase orders)
numPO <- xmlSize(r)
# create various data frames to hold data; initial values are just
# to define data type and length and will be replaced; pre-allocation
# is better for performance than dynamic allocation of memory
PO.df <- data.frame (POnum = vector (mode = "integer",
length = numPO),
orderDate = vector (mode = "character",
length = numPO),
billing = vector (mode = "integer",
length = numPO),
shipping = vector (mode = "integer",
length = numPO),
delNotes = vector (mode = "character",
length = numPO),
stringsAsFactors = F)
# we actually do not know the number of addresses so we cannot
# pre-allocate the memory
Address.df <- data.frame (aID = integer(),
name = character(),
street = character(),
city = character(),
state = character(),
zip = character(),
country = character(),
stringsAsFactors = F)
Item.df <- data.frame (PartNumber = character(),
ProductName = character(),
Quantity = integer(),
USPrice = numeric(),
Comment = character(),
ShipDate = character(),
POnum = integer(),
stringsAsFactors = F)
Support Functions
The functions below are support functions used later in the code.
parseAddress
Parses the address XML node in anAddressNode and returns it in a one row data frame.
The address XML is presumed to look like this. The Type attribute is ignored.
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
parseAddress <- function (anAddressNode)
{
# parse the address into its components
name <- xmlValue(anAddressNode[[1]])
street <- xmlValue(anAddressNode[[2]])
city <- xmlValue(anAddressNode[[3]])
state <- xmlValue(anAddressNode[[4]])
zip <- xmlValue(anAddressNode[[5]])
country <- xmlValue(anAddressNode[[6]])
newAddr.df <- data.frame(name, street, city, state,
zip, country,
stringsAsFactors = F)
return(newAddr.df)
}
Function: parseItems
Get a node of with - children underneath and then parse them into a dataframe and return the items. Note that
- has three children that are always assumed to be present in that order: , , and , but then may have two additional children: or . It appears from the XML file provided that both cannot occur – but this may not be true.
parseItems <- function (anItemsNode)
{
newItems.df <- data.frame(prodName = character(),
qty = integer(),
USPrice = numeric(),
comment = character(),
shipDate = character(),
stringsAsFactors = F)
n <- xmlSize(anItemsNode)
# extract each of the <Item> nodes under <Items>
for (m in 1:n)
{
anItem <- anItemsNode[[m]]
# extract first child nodes that are always present
prodName <- xmlValue(anItem[[1]])
qty <- xmlValue(anItem[[2]])
price <- xmlValue(anItem[[3]])
comment <- xpathSApply(anItem, "./Comment", xmlValue)
if (length(comment) == 0)
comment <- ""
shipdate <- xpathSApply(anItem, "./ShipDate", xmlValue)
if (length(shipdate) == 0)
shipdate <- ""
newItems.df[m,1] <- prodName
newItems.df[m,2] <- qty
newItems.df[m,3] <- price
newItems.df[m,4] <- comment
newItems.df[m,5] <- shipdate
}
return(newItems.df)
}
When you add a new row to a data frame, it grows dynamically to accommodate the new row. However, you must use the syntax newItems.df[m,3]
and not newItems.df$USPrice[m]
, although newItems.df[m,‘USPrice’]
also works fine.
Function: rowExists
Checks if it already exists in the passed data frame. Returns a key to the item if it exists, 0 otherwise. The columns in the row aRow are expected to be in the same order as the columns in the data frame aDF.
rowExists <- function (aRow, aDF)
{
# check if that address is already in the data frame
n <- nrow(aDF)
c <- ncol(aDF)
if (n == 0)
{
# data frame is empty, so can't exist
return(0)
}
for (a in 1:n)
{
# check if all columns match for a row; ignore the aID column
if (all(aDF[a,] == aRow[1,]))
{
# found a match; return it's ID
return(a)
}
}
# none matched
return(0)
}
Iterate over Purchase Orders
Process the XML by individually looking at each purchase order and from there save off the addresses and items – after ensuring that they are not duplicates. The code contains specific comments as to its working, choice of approach, and use of functions.
# Reading the XML file and parse into DOM
xmlDOM <- xmlParse(file = xmlfn)
# get the root node of the DOM tree
r <- xmlRoot(xmlDOM)
numPO <- xmlSize(r)
# iterate over the first-level child elements off the root:
# the <PurchaseOrder> elements
for (i in 1:numPO)
{
# get next purchase order node
aPO <- r[[i]]
# get the purchase number order date attributes
# <PurchaseOrder PurchaseOrderNumber="903" OrderDate="2020-10-20">
a <- xmlAttrs(aPO)
# we assume that the purchase order is a number but we really
# should first check if PONum starts with a character
poNum <- as.numeric(a[1])
# order date is left as a string/text but can (and should be)
# converted to a Date object
orderDate <- a[2]
# add them to the purchase order data frame
PO.df$orderDate[i] <- orderDate
PO.df$POnum[i] <- poNum
# <DeliveryNotes> is optional, so skip if not present
items <- aPO[[3]]
if (xmlName(items) == "DeliveryNotes")
{
# save the delivery notes
PO.df$delNotes[i] <- xmlValue(items)
# items is now the fourth child and not the third and
# delivery notes remains empty as it's not in the XML
items <- aPO[[4]]
}
# process both addresses -- duplicates are possible
# assumes that first address is always "Shipping" and
# second address is always "Billing" and that both are
# present in the XML as the first two child nodes of
# <PurchaseOrder>
# parse shipping address
shipping <- parseAddress(aPO[[1]])
# check if address already exists
pk.Addr <- rowExists(shipping, Address.df[,2:ncol(Address.df)])
if (pk.Addr == 0)
{
# does not exist, so add
pk.Addr <- nrow(Address.df) + 1
Address.df[pk.Addr,2:ncol(Address.df)] <- shipping[1,]
Address.df[pk.Addr,1] <- pk.Addr
}
# set FK in PO to the shipping address
PO.df$shipping[i] <- pk.Addr
# parse billing address
billing <- parseAddress(aPO[[2]])
# check if address already exists
pk.Addr <- rowExists(billing, Address.df[,2:ncol(Address.df)])
if (pk.Addr == 0)
{
# does not exist, so add
pk.Addr <- nrow(Address.df) + 1
Address.df[pk.Addr,2:ncol(Address.df)] <- billing[1,]
Address.df[pk.Addr,1] <- pk.Addr
}
# set FK in PO to the billing address
PO.df$billing[i] <- pk.Addr
# process the set of items into a separate data frame
poItems <- parseItems(items)
# always add them regardless of duplicates
for (n in 1:nrow(poItems))
{
# set PK using PartNumber attribute in <item>
pk.Item <- xmlAttrs(items[[n]])[1]
# append them to the data frame
j <- nrow(Item.df)+1
Item.df[j,2:(ncol(Item.df)-1)] <- poItems[n,]
Item.df[j,1] <- pk.Item
# set FK to PO -- last column
Item.df[j,ncol(Item.df)] <- poNum
}
}
Save Data Frame to SQL Database
Create Connection to Database
Write Data to Database
Write the data frames to new tables. The tables are automatically created from the structure of the data frames. However, a drawback when automatically creating tables is that there are specified referential integrity constraints. So, the addresses and po tables are automatically created while the items table for Items is explicitly created with foreign key constraints.
Create Tables
While all tables should be created using CREATE TABLE
, only the table for items is created that way for now; the others are created automatically by dbWriteTable
below.
Drop (delete) the table for the items. The other tables are overwritten automatically by dbWriteTable
below. In order to ensure that the foreign keys exist for the shipping and billing addresses, those must be inserted first. The purchase order table is created next as it references the addresses, while the items table is created last as it references purchase orders.
drop table if exists items;
The items table for purchase orders must have the columns in the same sequence with the same data types as in the Item.df data frame.
Item.df <- data.frame (PartNumber = character(), ProductName = character(), Quantity = integer(), USPrice = numeric(), Comment = character(), ShipDate = character(), POnum = integer(), stringsAsFactors = F)
create table items (
pnum text,
prodname text,
qty integer,
price number,
comment text,
shipdate text,
ponum integer,
primary key (pnum),
foreign key (ponum) references po(POnum)
);
Write Data
dbWriteTable(PODBcon, "addresses", Address.df, overwrite = T)
dbWriteTable(PODBcon, "items", Item.df, overwrite = T)
dbWriteTable(PODBcon, "po", PO.df, overwrite = T)
To ensure that the data was written properly, let’s retreive some of the rows.
select * from addresses limit 5;
select * from items limit 5;
Table 1: 5 records
872-AA |
Lawnmower |
1 |
148.95 |
Confirm this is electric |
|
99503 |
926-AA |
Baby Monitor |
2 |
39.98 |
|
1999-05-21 |
99503 |
456-NM |
Power Supply |
1 |
45.99 |
|
|
99505 |
898-AZ |
Computer Keyboard |
1 |
29.99 |
|
|
99504 |
898-AM |
Wireless Mouse |
1 |
14.99 |
|
|
99504 |
select * from po limit 5;
Table 2: 3 records
99503 |
1999-10-20 |
2 |
1 |
Please leave packages in shed by driveway. |
99505 |
1999-10-22 |
3 |
3 |
Please notify me before shipping. |
99504 |
1999-10-22 |
5 |
4 |
|
Tutorial
References
No references.
Errata
None collected yet. Let us know.
LS0tDQp0aXRsZTogIlBhcnNpbmcgYW4gWE1MIERvY3VtZW50IGFuZCBTYXZpbmcgdG8gU1FMaXRlIERhdGFiYXNlIGluIFIiDQpwYXJhbXM6DQogIGNhdGVnb3J5OiA2DQogIG51bWJlcjogMzI4DQogIHRpbWU6IDQ1DQogIGxldmVsOiBiZWdpbm5lcg0KICB0YWdzOiAicix4cGF0aCx4bWwsZG9tLHBhcnNpbmcsc3FsaXRlLHNxbCINCiAgZGVzY3JpcHRpb246ICJUaGlzIGV4YW1wbGUgc2hvd3MgaG93IHRvIGxvYWQgYW5kIHBhcnNlIGFuIFhNTCBkb2N1bWVudCBpbnRvIGFuIGludGVybmFsIHJlbGF0aW9uYWwgbW9kZWwgb2YgZGF0YSBmcmFtZXMuIEl0IHRyYXZlcnNlcyB0aGUgRE9NIHRyZWUgbm9kZS1ieS1ub2RlIGFuZCB0aGVuIHNhdmUgdGhlIGRhdGEgaW50byBkYXRhIGZyYW1lcy4gVGhlIGRhdGEgZnJhbWVzIGFyZSBldmVudHVhbGx5IHdyaXR0ZW4gdG8gYSBuZXcgZGF0YWJhc2UuIFRoZSBleGFtcGxlIHVzZXMgb25seSBCYXNlIFIgYW5kIGRvZXMgbm90IHVzZSB0aWR5dmVyc2Ugd2hpY2ggaGFzIGFkZGl0aW9uYWwgc3VwcG9ydCBmb3IgbWFuYWdpbmcgcmVsYXRpb25hbCBzdHJ1Y3R1cmVzLiINCmRhdGU6ICI8c21hbGw+YHIgU3lzLkRhdGUoKWA8L3NtYWxsPiINCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiINCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1Ig0KYWZmaWxpdGF0aW9uOiAiTm9ydGhlYXN0ZXJuIFVuaXZlcnNpdHkiDQpvdXRwdXQ6IA0KICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KLS0tDQoNCi0tLQ0KdGl0bGU6ICI8c21hbGw+YHIgcGFyYW1zJGNhdGVnb3J5YC5gciBwYXJhbXMkbnVtYmVyYDwvc21hbGw+PGJyLz48c3BhbiBzdHlsZT0nY29sb3I6ICMyRTQwNTM7IGZvbnQtc2l6ZTogMC45ZW0nPmByIHJtYXJrZG93bjo6bWV0YWRhdGEkdGl0bGVgPC9zcGFuPiINCi0tLQ0KDQpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0NCmBgYA0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBleGFtcGxlIHNob3dzIGhvdyB0byBsb2FkIGFuZCBwYXJzZSBhbiBYTUwgZG9jdW1lbnQgaW50byBhbiBpbnRlcm5hbCByZWxhdGlvbmFsIG1vZGVsIG9mIGRhdGEgZnJhbWVzLiBJdCB0cmF2ZXJzZXMgdGhlIERPTSB0cmVlIG5vZGUtYnktbm9kZSBhbmQgdGhlbiBzYXZlIHRoZSBkYXRhIGludG8gZGF0YSBmcmFtZXMuIFRoZSBkYXRhIGZyYW1lcyBhcmUgZXZlbnR1YWxseSB3cml0dGVuIHRvIGEgbmV3IGRhdGFiYXNlLiBUaGUgZXhhbXBsZSB1c2VzIG9ubHkgQmFzZSBSIGFuZCBkb2VzIG5vdCB1c2UgdGlkeXZlcnNlIHdoaWNoIGhhcyBhZGRpdGlvbmFsIHN1cHBvcnQgZm9yIG1hbmFnaW5nIHJlbGF0aW9uYWwgc3RydWN0dXJlcy4NCg0KV2Ugc3RhcnQgYnkgbG9hZGluZyB0aGUgcmVxdWlyZWQgbGlicmFyaWVzLg0KDQpgYGB7ciBsb2FkTGlicmFyaWVzfQ0KbGlicmFyeShYTUwpDQoNCmxpYnJhcnkoUlNRTGl0ZSkNCmxpYnJhcnkoREJJKQ0KbGlicmFyeShrbml0cikNCmBgYA0KDQpUaGlzIHNlY3Rpb24gc2V0cyB1cCB0aGUgZmlsZSBuYW1lcyBmb3IgdGhlIFhNTCBmaWxlIChkb3dubG9hZCBbcHVyY2hhc2VvcmRlcnMueG1sXShwdXJjaGFzZW9yZGVycy54bWwpKWFuZCB0aGUgU1FMaXRlIGRhdGFiYXNlLg0KDQpgYGB7ciBmaWxlbmFtZXN9DQp4bWxmbiA8LSAicHVyY2hhc2VvcmRlcnMueG1sIg0KZGJmbiA8LSAicG9kYi5kYiINCmBgYA0KDQojIyBMb2FkIFhNTCBpbnRvIERvY3VtZW50IE9iamVjdCBNb2RlbCAoRE9NKQ0KDQpTdGFydCBieSBwYXJzaW5nIHRoZSBYTUwgZmlsZSBhbmQgbG9hZGluZyBpdCBpbnRvIGFuIGludGVybmFsIHRyZWUgc3RydWN0dXJlIGluIG1lbW9yeS4gTm90ZSB0aGF0IHRoZSBwYXJzaW5nIGlzIG5vdCB2YWxpZGF0aW5nIGFzIHRoZXJlIGlzIG5vIERURCBpbiB0aGUgcHJvdmlkZWQgWE1MLg0KDQpgYGB7cn0NCiMgUmVhZGluZyB0aGUgWE1MIGZpbGUgYW5kIHBhcnNlIGludG8gRE9NDQp4bWxET00gPC0geG1sUGFyc2UoZmlsZSA9IHhtbGZuKQ0KDQojIGdldCB0aGUgcm9vdCBub2RlIG9mIHRoZSBET00gdHJlZQ0KciA8LSB4bWxSb290KHhtbERPTSkNCmBgYA0KDQojIyBFUkQgZm9yIFB1cmNoYXNlT3JkZXIgWE1MDQoNCkJlZm9yZSBwYXJzaW5nIHRoZSBYTUwgZnVydGhlciB3ZSBuZWVkIHRvIGRlZmluZSB0aGUgc3RydWN0dXJlIG9mIHRoZSByZWxhdGlvbmFsIG1vZGVsLiBUaGUgaW4tbWVtb3J5IHN0cnVjdHVyZSBpbiB0aGUgZGF0YWZyYW1lcyB3aWxsIG1hdGNoIHRoZSBkYXRhYmFzZSBzdHJ1Y3R1cmVzIGFuZCB0YWJsZXMuDQoNCltFUkQgRGlhZ3JhbSBmb3IgUHVyY2hhc2VPcmRlciBYTUwgKG5vdCBmdWxseSBub3JtYWxpemVkKV0oRVJENFB1cmNoYXNlT3JkZXJYTUwuanBnKQ0KDQpOb3RlIHRoYXQgdGhlIG1vZGVsIGlzIG5vdCBmdWxseSBub3JtYWxpemVkLiBGb3IgZXhhbXBsZSwgdGhlcmUgc2hvdWxkIGJlIGEgc2VwYXJhdGUgKipaaXBDb2RlcyoqIHRhYmxlIHdpdGggY29sdW1ucyAqemlwKiwgKmNpdHkqLCAqc3RhdGUqLCAqY291bnRyeSogYW5kIHRoZSAqKkFkZHJlc3MqKiB0YWJsZSBzaG91bGQgb25seSBjb250YWluICp6aXAqLg0KDQojIyBQYXJzZSBpbnRvIGEgRGF0YSBGcmFtZQ0KDQpMb2FkIHRoZSBkYXRhIGludG8gdmVjdG9ycyBhbmQgdGhlbiBtZXJnZSBpbnRvIGEgZGF0YSBmcmFtZS4gVXNlIGF0dHJpYnV0ZXMgYXMgUEtzLiBUaGUgcGFyc2luZyBhbmQgcHJvY2Vzc2luZyBzdHJhdGVneSB1c2VkIGhlcmUgb25seSB3b3JrcyB3aGVuIHRoZSBlbGVtZW50cyBhcmUgaW4gYSBkZXRlcm1pbmlzdGljIG9yZGVyIGFuZCB0aGUgb3JkZXIgb3IgY2FyZGluYWxpdHkgZG9lcyBub3QgY2hhbmdlLiBTaW5jZSB0aGVyZSBpcyBubyBEVEQgb3IgWE1MIFNjaGVtYSBpdCBpcyBub3QgYXNzdXJlZCB0aGF0IHRoZSBlbGVtZW50cyBhbHdheXMgYXBwZWFyIGluIHRoZSBleHBlY3RlZCBvcmRlci4gVGhlIG9yZGVyIGlzIGFzY2VydGFpbmVkIHRocm91Z2ggaW5zcGVjdGlvbiBhbmQgZGVwZW5kcyBvbiB0aGUgWE1MIHByb3ZpZGVkIGFuZCBzaW5jZSBpdCBtYXkgY2hhbmdlIGluIHRoZSBmdXR1cmUgdGhpcyBwYXJzaW5nIGNvZGUgY291bGQgImJyZWFrIi4NCg0KIyMjIENyZWF0ZSBJbnRlcm5hbCBEYXRhIEZyYW1lcw0KDQpUaGUgZGF0YWZyYW1lcyBtaW1pYyB0aGUgcmVsYXRpb25hbCBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGFiYXNlLiBUaGlzIG9ubHkgd29ya3Mgd2hlbiB0aGUgZGF0YSBmaXRzIGludG8gbWVtb3J5LiBPdGhlcndpc2UsIGlmIHRoZSBkYXRhIGlzIHRvbyBsYXJnZSB0aGVuIHRoZSBkYXRhIHNob3VsZCBiZSBwYXJzZWQgYW5kIHdyaXR0ZW4gdG8gdGhlIHRhYmxlcyBpbiB0aGUgZGF0YWJhc2VzIGFzIGl0IGlzIHJldHJpZXZlZC4gSWYgdGhlIFhNTCBpcyByZWFsbHkgbGFyZ2UgdGhlbiBhIFNBWCBwYXJzaW5nIGFwcHJvYWNoIHNob3VsZCBiZSB1c2VkLg0KDQpgYGB7ciBjcmVhdGVERnN9DQojIGdldCBudW1iZXIgb2YgY2hpbGRyZW4gb2Ygcm9vdCAobnVtYmVyIG9mIHB1cmNoYXNlIG9yZGVycykNCm51bVBPIDwtIHhtbFNpemUocikNCg0KIyBjcmVhdGUgdmFyaW91cyBkYXRhIGZyYW1lcyB0byBob2xkIGRhdGE7IGluaXRpYWwgdmFsdWVzIGFyZSBqdXN0DQojIHRvIGRlZmluZSBkYXRhIHR5cGUgYW5kIGxlbmd0aCBhbmQgd2lsbCBiZSByZXBsYWNlZDsgcHJlLWFsbG9jYXRpb24NCiMgaXMgYmV0dGVyIGZvciBwZXJmb3JtYW5jZSB0aGFuIGR5bmFtaWMgYWxsb2NhdGlvbiBvZiBtZW1vcnkNClBPLmRmIDwtIGRhdGEuZnJhbWUgKFBPbnVtID0gdmVjdG9yIChtb2RlID0gImludGVnZXIiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGggPSBudW1QTyksDQogICAgICAgICAgICAgICAgICAgICBvcmRlckRhdGUgPSB2ZWN0b3IgKG1vZGUgPSAiY2hhcmFjdGVyIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IG51bVBPKSwNCiAgICAgICAgICAgICAgICAgICAgIGJpbGxpbmcgPSB2ZWN0b3IgKG1vZGUgPSAiaW50ZWdlciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoID0gbnVtUE8pLA0KICAgICAgICAgICAgICAgICAgICAgc2hpcHBpbmcgPSB2ZWN0b3IgKG1vZGUgPSAiaW50ZWdlciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IG51bVBPKSwNCiAgICAgICAgICAgICAgICAgICAgIGRlbE5vdGVzID0gdmVjdG9yIChtb2RlID0gImNoYXJhY3RlciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IG51bVBPKSwNCiAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KDQojIHdlIGFjdHVhbGx5IGRvIG5vdCBrbm93IHRoZSBudW1iZXIgb2YgYWRkcmVzc2VzIHNvIHdlIGNhbm5vdA0KIyBwcmUtYWxsb2NhdGUgdGhlIG1lbW9yeQ0KQWRkcmVzcy5kZiA8LSBkYXRhLmZyYW1lIChhSUQgPSBpbnRlZ2VyKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyZWV0ID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNpdHkgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdGUgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgemlwID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50cnkgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQoNCkl0ZW0uZGYgPC0gZGF0YS5mcmFtZSAoUGFydE51bWJlciA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgICAgICBQcm9kdWN0TmFtZSA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgICAgICBRdWFudGl0eSA9IGludGVnZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgVVNQcmljZSA9IG51bWVyaWMoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgQ29tbWVudCA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgICAgICBTaGlwRGF0ZSA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgICAgICBQT251bSA9IGludGVnZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpgYGANCg0KIyMjIFN1cHBvcnQgRnVuY3Rpb25zDQoNClRoZSBmdW5jdGlvbnMgYmVsb3cgYXJlIHN1cHBvcnQgZnVuY3Rpb25zIHVzZWQgbGF0ZXIgaW4gdGhlIGNvZGUuDQoNCiMjIyMgcGFyc2VBZGRyZXNzDQoNClBhcnNlcyB0aGUgYWRkcmVzcyBYTUwgbm9kZSBpbiAqYW5BZGRyZXNzTm9kZSogYW5kIHJldHVybnMgaXQgaW4gYSBvbmUgcm93IGRhdGEgZnJhbWUuDQoNClRoZSBhZGRyZXNzIFhNTCBpcyBwcmVzdW1lZCB0byBsb29rIGxpa2UgdGhpcy4gVGhlICpUeXBlKiBhdHRyaWJ1dGUgaXMgaWdub3JlZC4NCg0KYGBge3htbH0NCjxBZGRyZXNzIFR5cGU9IlNoaXBwaW5nIj4NCiAgPE5hbWU+RWxsZW4gQWRhbXM8L05hbWU+DQogIDxTdHJlZXQ+MTIzIE1hcGxlIFN0cmVldDwvU3RyZWV0Pg0KICA8Q2l0eT5NaWxsIFZhbGxleTwvQ2l0eT4NCiAgPFN0YXRlPkNBPC9TdGF0ZT4NCiAgPFppcD4xMDk5OTwvWmlwPg0KICA8Q291bnRyeT5VU0E8L0NvdW50cnk+DQo8L0FkZHJlc3M+DQpgYGANCg0KYGBge3IgcGFyc2VBZGRyZXNzfQ0KcGFyc2VBZGRyZXNzIDwtIGZ1bmN0aW9uIChhbkFkZHJlc3NOb2RlKQ0Kew0KICAjIHBhcnNlIHRoZSBhZGRyZXNzIGludG8gaXRzIGNvbXBvbmVudHMNCiAgbmFtZSA8LSB4bWxWYWx1ZShhbkFkZHJlc3NOb2RlW1sxXV0pDQogIHN0cmVldCA8LSB4bWxWYWx1ZShhbkFkZHJlc3NOb2RlW1syXV0pDQogIGNpdHkgPC0geG1sVmFsdWUoYW5BZGRyZXNzTm9kZVtbM11dKQ0KICBzdGF0ZSA8LSB4bWxWYWx1ZShhbkFkZHJlc3NOb2RlW1s0XV0pDQogIHppcCA8LSB4bWxWYWx1ZShhbkFkZHJlc3NOb2RlW1s1XV0pDQogIGNvdW50cnkgPC0geG1sVmFsdWUoYW5BZGRyZXNzTm9kZVtbNl1dKQ0KICANCiAgbmV3QWRkci5kZiA8LSBkYXRhLmZyYW1lKG5hbWUsIHN0cmVldCwgY2l0eSwgc3RhdGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgemlwLCBjb3VudHJ5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KICANCiAgcmV0dXJuKG5ld0FkZHIuZGYpDQp9DQpgYGANCg0KIyMjIyBGdW5jdGlvbjogcGFyc2VJdGVtcw0KDQpHZXQgYSBub2RlIG9mIDxpdGVtcz4gd2l0aCA8aXRlbT4gY2hpbGRyZW4gdW5kZXJuZWF0aCBhbmQgdGhlbiBwYXJzZSB0aGVtIGludG8gYSBkYXRhZnJhbWUgYW5kIHJldHVybiB0aGUgaXRlbXMuIE5vdGUgdGhhdCA8SXRlbT4gaGFzIHRocmVlIGNoaWxkcmVuIHRoYXQgYXJlIGFsd2F5cyBhc3N1bWVkIHRvIGJlIHByZXNlbnQgaW4gdGhhdCBvcmRlcjogPFByb2R1Y3ROYW1lPiwgPFF1YW50aXR5PiwgYW5kIDxVU1ByaWNlPiwgYnV0IHRoZW4gbWF5IGhhdmUgdHdvIGFkZGl0aW9uYWwgY2hpbGRyZW46IDxDb21tZW50PiBvciA8U2hpcERhdGU+LiBJdCBhcHBlYXJzIGZyb20gdGhlIFhNTCBmaWxlIHByb3ZpZGVkIHRoYXQgYm90aCBjYW5ub3Qgb2NjdXIgLS0gYnV0IHRoaXMgbWF5IG5vdCBiZSB0cnVlLg0KDQpgYGB7ciBwYXJzZUl0ZW1zfQ0KcGFyc2VJdGVtcyA8LSBmdW5jdGlvbiAoYW5JdGVtc05vZGUpDQp7DQogIG5ld0l0ZW1zLmRmIDwtIGRhdGEuZnJhbWUocHJvZE5hbWUgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdHkgPSBpbnRlZ2VyKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgVVNQcmljZSA9IG51bWVyaWMoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hpcERhdGUgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikNCiAgbiA8LSB4bWxTaXplKGFuSXRlbXNOb2RlKQ0KICANCiAgIyBleHRyYWN0IGVhY2ggb2YgdGhlIDxJdGVtPiBub2RlcyB1bmRlciA8SXRlbXM+DQogIGZvciAobSBpbiAxOm4pDQogIHsNCiAgICBhbkl0ZW0gPC0gYW5JdGVtc05vZGVbW21dXQ0KICAgICMgZXh0cmFjdCBmaXJzdCBjaGlsZCBub2RlcyB0aGF0IGFyZSBhbHdheXMgcHJlc2VudCANCiAgICBwcm9kTmFtZSA8LSB4bWxWYWx1ZShhbkl0ZW1bWzFdXSkNCiAgICBxdHkgPC0geG1sVmFsdWUoYW5JdGVtW1syXV0pDQogICAgcHJpY2UgPC0geG1sVmFsdWUoYW5JdGVtW1szXV0pDQogICAgY29tbWVudCA8LSB4cGF0aFNBcHBseShhbkl0ZW0sICIuL0NvbW1lbnQiLCB4bWxWYWx1ZSkNCiAgICBpZiAobGVuZ3RoKGNvbW1lbnQpID09IDApDQogICAgICBjb21tZW50IDwtICIiDQogICAgc2hpcGRhdGUgPC0geHBhdGhTQXBwbHkoYW5JdGVtLCAiLi9TaGlwRGF0ZSIsIHhtbFZhbHVlKQ0KICAgIGlmIChsZW5ndGgoc2hpcGRhdGUpID09IDApDQogICAgICBzaGlwZGF0ZSA8LSAiIg0KDQogICAgbmV3SXRlbXMuZGZbbSwxXSA8LSBwcm9kTmFtZQ0KICAgIG5ld0l0ZW1zLmRmW20sMl0gPC0gcXR5DQogICAgbmV3SXRlbXMuZGZbbSwzXSA8LSBwcmljZQ0KICAgIG5ld0l0ZW1zLmRmW20sNF0gPC0gY29tbWVudA0KICAgIG5ld0l0ZW1zLmRmW20sNV0gPC0gc2hpcGRhdGUNCiAgfQ0KICANCiAgcmV0dXJuKG5ld0l0ZW1zLmRmKQ0KfQ0KYGBgDQoNCj4gV2hlbiB5b3UgYWRkIGEgbmV3IHJvdyB0byBhIGRhdGEgZnJhbWUsIGl0IGdyb3dzIGR5bmFtaWNhbGx5IHRvIGFjY29tbW9kYXRlIHRoZSBuZXcgcm93LiBIb3dldmVyLCB5b3UgbXVzdCB1c2UgdGhlIHN5bnRheCA8Y29kZT5uZXdJdGVtcy5kZlttLDNdPC9jb2RlPiBhbmQgbm90IDxjb2RlPm5ld0l0ZW1zLmRmXCRVU1ByaWNlW21dPC9jb2RlPiwgYWx0aG91Z2ggPGNvZGU+bmV3SXRlbXMuZGZbbSwnVVNQcmljZSddPC9jb2RlPiBhbHNvIHdvcmtzIGZpbmUuDQoNCiMjIyMgRnVuY3Rpb246IHJvd0V4aXN0cw0KDQpDaGVja3MgaWYgaXQgYWxyZWFkeSBleGlzdHMgaW4gdGhlIHBhc3NlZCBkYXRhIGZyYW1lLiBSZXR1cm5zIGEga2V5IHRvIHRoZSBpdGVtIGlmIGl0IGV4aXN0cywgMCBvdGhlcndpc2UuIFRoZSBjb2x1bW5zIGluIHRoZSByb3cgKmFSb3cqIGFyZSBleHBlY3RlZCB0byBiZSBpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgY29sdW1ucyBpbiB0aGUgZGF0YSBmcmFtZSAqYURGKi4NCg0KYGBge3Igcm93RXhpc3RzfQ0Kcm93RXhpc3RzIDwtIGZ1bmN0aW9uIChhUm93LCBhREYpDQp7DQogICMgY2hlY2sgaWYgdGhhdCBhZGRyZXNzIGlzIGFscmVhZHkgaW4gdGhlIGRhdGEgZnJhbWUNCiAgbiA8LSBucm93KGFERikNCiAgYyA8LSBuY29sKGFERikNCiAgDQogIGlmIChuID09IDApDQogIHsNCiAgICAjIGRhdGEgZnJhbWUgaXMgZW1wdHksIHNvIGNhbid0IGV4aXN0DQogICAgcmV0dXJuKDApDQogIH0NCiAgDQogIGZvciAoYSBpbiAxOm4pDQogIHsNCiAgICAjIGNoZWNrIGlmIGFsbCBjb2x1bW5zIG1hdGNoIGZvciBhIHJvdzsgaWdub3JlIHRoZSBhSUQgY29sdW1uDQogICAgaWYgKGFsbChhREZbYSxdID09IGFSb3dbMSxdKSkNCiAgICB7DQogICAgICAjIGZvdW5kIGEgbWF0Y2g7IHJldHVybiBpdCdzIElEDQogICAgICByZXR1cm4oYSkNCiAgICB9DQogIH0NCiAgDQogICMgbm9uZSBtYXRjaGVkDQogIHJldHVybigwKQ0KfQ0KYGBgDQoNCiMjIyBJdGVyYXRlIG92ZXIgUHVyY2hhc2UgT3JkZXJzDQoNClByb2Nlc3MgdGhlIFhNTCBieSBpbmRpdmlkdWFsbHkgbG9va2luZyBhdCBlYWNoIHB1cmNoYXNlIG9yZGVyIGFuZCBmcm9tIHRoZXJlIHNhdmUgb2ZmIHRoZSBhZGRyZXNzZXMgYW5kIGl0ZW1zIC0tIGFmdGVyIGVuc3VyaW5nIHRoYXQgdGhleSBhcmUgbm90IGR1cGxpY2F0ZXMuIFRoZSBjb2RlIGNvbnRhaW5zIHNwZWNpZmljIGNvbW1lbnRzIGFzIHRvIGl0cyB3b3JraW5nLCBjaG9pY2Ugb2YgYXBwcm9hY2gsIGFuZCB1c2Ugb2YgZnVuY3Rpb25zLg0KDQpgYGB7ciBwcm9jZXNzIFBPc30NCg0KIyBSZWFkaW5nIHRoZSBYTUwgZmlsZSBhbmQgcGFyc2UgaW50byBET00NCnhtbERPTSA8LSB4bWxQYXJzZShmaWxlID0geG1sZm4pDQoNCiMgZ2V0IHRoZSByb290IG5vZGUgb2YgdGhlIERPTSB0cmVlDQpyIDwtIHhtbFJvb3QoeG1sRE9NKQ0KDQpudW1QTyA8LSB4bWxTaXplKHIpDQoNCiMgaXRlcmF0ZSBvdmVyIHRoZSBmaXJzdC1sZXZlbCBjaGlsZCBlbGVtZW50cyBvZmYgdGhlIHJvb3Q6DQojIHRoZSA8UHVyY2hhc2VPcmRlcj4gZWxlbWVudHMNCg0KZm9yIChpIGluIDE6bnVtUE8pDQp7DQogICMgZ2V0IG5leHQgcHVyY2hhc2Ugb3JkZXIgbm9kZQ0KICBhUE8gPC0gcltbaV1dDQogIA0KICAjIGdldCB0aGUgcHVyY2hhc2UgbnVtYmVyIG9yZGVyIGRhdGUgYXR0cmlidXRlcw0KICAjIDxQdXJjaGFzZU9yZGVyIFB1cmNoYXNlT3JkZXJOdW1iZXI9IjkwMyIgT3JkZXJEYXRlPSIyMDIwLTEwLTIwIj4NCiAgYSA8LSB4bWxBdHRycyhhUE8pDQogIA0KICAjIHdlIGFzc3VtZSB0aGF0IHRoZSBwdXJjaGFzZSBvcmRlciBpcyBhIG51bWJlciBidXQgd2UgcmVhbGx5DQogICMgc2hvdWxkIGZpcnN0IGNoZWNrIGlmIFBPTnVtIHN0YXJ0cyB3aXRoIGEgY2hhcmFjdGVyDQogIHBvTnVtIDwtIGFzLm51bWVyaWMoYVsxXSkNCiAgDQogICMgb3JkZXIgZGF0ZSBpcyBsZWZ0IGFzIGEgc3RyaW5nL3RleHQgYnV0IGNhbiAoYW5kIHNob3VsZCBiZSkNCiAgIyBjb252ZXJ0ZWQgdG8gYSBEYXRlIG9iamVjdA0KICBvcmRlckRhdGUgPC0gYVsyXQ0KICANCiAgIyBhZGQgdGhlbSB0byB0aGUgcHVyY2hhc2Ugb3JkZXIgZGF0YSBmcmFtZQ0KICBQTy5kZiRvcmRlckRhdGVbaV0gPC0gb3JkZXJEYXRlDQogIFBPLmRmJFBPbnVtW2ldIDwtIHBvTnVtDQogIA0KICAjIDxEZWxpdmVyeU5vdGVzPiBpcyBvcHRpb25hbCwgc28gc2tpcCBpZiBub3QgcHJlc2VudA0KICBpdGVtcyA8LSBhUE9bWzNdXQ0KICANCiAgaWYgKHhtbE5hbWUoaXRlbXMpID09ICJEZWxpdmVyeU5vdGVzIikNCiAgew0KICAgICMgc2F2ZSB0aGUgZGVsaXZlcnkgbm90ZXMNCiAgICBQTy5kZiRkZWxOb3Rlc1tpXSA8LSB4bWxWYWx1ZShpdGVtcykNCiAgICANCiAgICAjIGl0ZW1zIGlzIG5vdyB0aGUgZm91cnRoIGNoaWxkIGFuZCBub3QgdGhlIHRoaXJkIGFuZA0KICAgICMgZGVsaXZlcnkgbm90ZXMgcmVtYWlucyBlbXB0eSBhcyBpdCdzIG5vdCBpbiB0aGUgWE1MDQogICAgaXRlbXMgPC0gYVBPW1s0XV0NCiAgfQ0KICANCiAgIyBwcm9jZXNzIGJvdGggYWRkcmVzc2VzIC0tIGR1cGxpY2F0ZXMgYXJlIHBvc3NpYmxlDQogICMgYXNzdW1lcyB0aGF0IGZpcnN0IGFkZHJlc3MgaXMgYWx3YXlzICJTaGlwcGluZyIgYW5kDQogICMgc2Vjb25kIGFkZHJlc3MgaXMgYWx3YXlzICJCaWxsaW5nIiBhbmQgdGhhdCBib3RoIGFyZQ0KICAjIHByZXNlbnQgaW4gdGhlIFhNTCBhcyB0aGUgZmlyc3QgdHdvIGNoaWxkIG5vZGVzIG9mDQogICMgPFB1cmNoYXNlT3JkZXI+DQogIA0KICAjIHBhcnNlIHNoaXBwaW5nIGFkZHJlc3MNCiAgc2hpcHBpbmcgPC0gcGFyc2VBZGRyZXNzKGFQT1tbMV1dKQ0KICAjIGNoZWNrIGlmIGFkZHJlc3MgYWxyZWFkeSBleGlzdHMNCiAgcGsuQWRkciA8LSByb3dFeGlzdHMoc2hpcHBpbmcsIEFkZHJlc3MuZGZbLDI6bmNvbChBZGRyZXNzLmRmKV0pDQogIA0KICBpZiAocGsuQWRkciA9PSAwKQ0KICB7DQogICAgIyBkb2VzIG5vdCBleGlzdCwgc28gYWRkDQogICAgcGsuQWRkciA8LSBucm93KEFkZHJlc3MuZGYpICsgMQ0KICAgIEFkZHJlc3MuZGZbcGsuQWRkciwyOm5jb2woQWRkcmVzcy5kZildIDwtIHNoaXBwaW5nWzEsXQ0KICAgIEFkZHJlc3MuZGZbcGsuQWRkciwxXSA8LSBway5BZGRyDQogIH0NCiAgDQogICMgc2V0IEZLIGluIFBPIHRvIHRoZSBzaGlwcGluZyBhZGRyZXNzDQogIFBPLmRmJHNoaXBwaW5nW2ldIDwtIHBrLkFkZHINCiAgDQogICMgcGFyc2UgYmlsbGluZyBhZGRyZXNzDQogIGJpbGxpbmcgPC0gcGFyc2VBZGRyZXNzKGFQT1tbMl1dKQ0KICAjIGNoZWNrIGlmIGFkZHJlc3MgYWxyZWFkeSBleGlzdHMNCiAgcGsuQWRkciA8LSByb3dFeGlzdHMoYmlsbGluZywgQWRkcmVzcy5kZlssMjpuY29sKEFkZHJlc3MuZGYpXSkNCiAgDQogIGlmIChway5BZGRyID09IDApDQogIHsNCiAgICAjIGRvZXMgbm90IGV4aXN0LCBzbyBhZGQNCiAgICBway5BZGRyIDwtIG5yb3coQWRkcmVzcy5kZikgKyAxDQogICAgQWRkcmVzcy5kZltway5BZGRyLDI6bmNvbChBZGRyZXNzLmRmKV0gPC0gYmlsbGluZ1sxLF0NCiAgICBBZGRyZXNzLmRmW3BrLkFkZHIsMV0gPC0gcGsuQWRkcg0KICB9DQogIA0KICAjIHNldCBGSyBpbiBQTyB0byB0aGUgYmlsbGluZyBhZGRyZXNzDQogIFBPLmRmJGJpbGxpbmdbaV0gPC0gcGsuQWRkcg0KICANCiAgIyBwcm9jZXNzIHRoZSBzZXQgb2YgaXRlbXMgaW50byBhIHNlcGFyYXRlIGRhdGEgZnJhbWUNCiAgcG9JdGVtcyA8LSBwYXJzZUl0ZW1zKGl0ZW1zKQ0KICANCiAgIyBhbHdheXMgYWRkIHRoZW0gcmVnYXJkbGVzcyBvZiBkdXBsaWNhdGVzDQogIA0KICBmb3IgKG4gaW4gMTpucm93KHBvSXRlbXMpKQ0KICB7DQogICAgIyBzZXQgUEsgdXNpbmcgUGFydE51bWJlciBhdHRyaWJ1dGUgaW4gPGl0ZW0+IA0KICAgIHBrLkl0ZW0gPC0geG1sQXR0cnMoaXRlbXNbW25dXSlbMV0NCiAgICANCiAgICAjIGFwcGVuZCB0aGVtIHRvIHRoZSBkYXRhIGZyYW1lDQogICAgaiA8LSBucm93KEl0ZW0uZGYpKzENCiAgICBJdGVtLmRmW2osMjoobmNvbChJdGVtLmRmKS0xKV0gPC0gcG9JdGVtc1tuLF0NCiAgICBJdGVtLmRmW2osMV0gPC0gcGsuSXRlbQ0KICAgIA0KICAgICMgc2V0IEZLIHRvIFBPIC0tIGxhc3QgY29sdW1uDQogICAgSXRlbS5kZltqLG5jb2woSXRlbS5kZildIDwtIHBvTnVtDQogIH0NCiAgDQp9DQoNCmBgYA0KDQojIyBTYXZlIERhdGEgRnJhbWUgdG8gU1FMIERhdGFiYXNlDQoNCiMjIyBDcmVhdGUgQ29ubmVjdGlvbiB0byBEYXRhYmFzZQ0KDQpgYGB7ciBzZXR1cCBkYXRhYmFzZSwgaW5jbHVkZT1GQUxTRX0NCg0KUE9EQmNvbiA8LSBkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksIGRiZm4pDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCg0KIyBzZXQgY29ubmVjdGlvbiBvYmplY3QgZm9yIHN1YnNlcXVlbnQgU1FMIGNodW5rcw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbm5lY3Rpb24gPSAiUE9EQmNvbiIpDQpgYGANCg0KIyMjIFdyaXRlIERhdGEgdG8gRGF0YWJhc2UNCg0KV3JpdGUgdGhlIGRhdGEgZnJhbWVzIHRvIG5ldyB0YWJsZXMuIFRoZSB0YWJsZXMgYXJlIGF1dG9tYXRpY2FsbHkgY3JlYXRlZCBmcm9tIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgZnJhbWVzLiBIb3dldmVyLCBhIGRyYXdiYWNrIHdoZW4gYXV0b21hdGljYWxseSBjcmVhdGluZyB0YWJsZXMgaXMgdGhhdCB0aGVyZSBhcmUgc3BlY2lmaWVkIHJlZmVyZW50aWFsIGludGVncml0eSBjb25zdHJhaW50cy4gU28sIHRoZSAqKmFkZHJlc3NlcyoqIGFuZCAqKnBvKiogdGFibGVzIGFyZSBhdXRvbWF0aWNhbGx5IGNyZWF0ZWQgd2hpbGUgdGhlICoqaXRlbXMqKiB0YWJsZSBmb3IgSXRlbXMgaXMgZXhwbGljaXRseSBjcmVhdGVkIHdpdGggZm9yZWlnbiBrZXkgY29uc3RyYWludHMuDQoNCiMjIyMgQ3JlYXRlIFRhYmxlcw0KDQpXaGlsZSBhbGwgdGFibGVzIHNob3VsZCBiZSBjcmVhdGVkIHVzaW5nIDxjb2RlPkNSRUFURSBUQUJMRTwvY29kZT4sIG9ubHkgdGhlIHRhYmxlIGZvciBpdGVtcyBpcyBjcmVhdGVkIHRoYXQgd2F5IGZvciBub3c7IHRoZSBvdGhlcnMgYXJlIGNyZWF0ZWQgYXV0b21hdGljYWxseSBieSA8Y29kZT5kYldyaXRlVGFibGU8L2NvZGU+IGJlbG93Lg0KDQpEcm9wIChkZWxldGUpIHRoZSB0YWJsZSBmb3IgdGhlIGl0ZW1zLiBUaGUgb3RoZXIgdGFibGVzIGFyZSBvdmVyd3JpdHRlbiBhdXRvbWF0aWNhbGx5IGJ5IDxjb2RlPmRiV3JpdGVUYWJsZTwvY29kZT4gYmVsb3cuIEluIG9yZGVyIHRvIGVuc3VyZSB0aGF0IHRoZSBmb3JlaWduIGtleXMgZXhpc3QgZm9yIHRoZSBzaGlwcGluZyBhbmQgYmlsbGluZyBhZGRyZXNzZXMsIHRob3NlIG11c3QgYmUgaW5zZXJ0ZWQgZmlyc3QuIFRoZSBwdXJjaGFzZSBvcmRlciB0YWJsZSBpcyBjcmVhdGVkIG5leHQgYXMgaXQgcmVmZXJlbmNlcyB0aGUgYWRkcmVzc2VzLCB3aGlsZSB0aGUgaXRlbXMgdGFibGUgaXMgY3JlYXRlZCBsYXN0IGFzIGl0IHJlZmVyZW5jZXMgcHVyY2hhc2Ugb3JkZXJzLg0KDQpgYGB7c3FsIGRyb3AgdGFibGVzLCBjb25uZWN0aW9uID0gIlBPREJjb24ifQ0KZHJvcCB0YWJsZSBpZiBleGlzdHMgaXRlbXM7DQpgYGANCg0KVGhlICoqaXRlbXMqKiB0YWJsZSBmb3IgcHVyY2hhc2Ugb3JkZXJzIG11c3QgaGF2ZSB0aGUgY29sdW1ucyBpbiB0aGUgc2FtZSBzZXF1ZW5jZSB3aXRoIHRoZSBzYW1lIGRhdGEgdHlwZXMgYXMgaW4gdGhlICpJdGVtLmRmKiBkYXRhIGZyYW1lLg0KDQpJdGVtLmRmIFw8LSBkYXRhLmZyYW1lIChQYXJ0TnVtYmVyID0gY2hhcmFjdGVyKCksIFByb2R1Y3ROYW1lID0gY2hhcmFjdGVyKCksIFF1YW50aXR5ID0gaW50ZWdlcigpLCBVU1ByaWNlID0gbnVtZXJpYygpLCBDb21tZW50ID0gY2hhcmFjdGVyKCksIFNoaXBEYXRlID0gY2hhcmFjdGVyKCksIFBPbnVtID0gaW50ZWdlcigpLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCg0KYGBge3NxbCBjcmVhdGUgaXRlbXMgdGFibGUsY29ubmVjdGlvbiA9ICJQT0RCY29uIn0NCmNyZWF0ZSB0YWJsZSBpdGVtcyAoDQogIHBudW0gdGV4dCwNCiAgcHJvZG5hbWUgdGV4dCwNCiAgcXR5IGludGVnZXIsDQogIHByaWNlIG51bWJlciwNCiAgY29tbWVudCB0ZXh0LA0KICBzaGlwZGF0ZSB0ZXh0LA0KICBwb251bSBpbnRlZ2VyLA0KICBwcmltYXJ5IGtleSAocG51bSksDQogIGZvcmVpZ24ga2V5IChwb251bSkgcmVmZXJlbmNlcyBwbyhQT251bSkNCik7DQpgYGANCg0KIyMjIyBXcml0ZSBEYXRhDQoNCmBgYHtyfQ0KZGJXcml0ZVRhYmxlKFBPREJjb24sICJhZGRyZXNzZXMiLCBBZGRyZXNzLmRmLCBvdmVyd3JpdGUgPSBUKQ0KZGJXcml0ZVRhYmxlKFBPREJjb24sICJpdGVtcyIsIEl0ZW0uZGYsIG92ZXJ3cml0ZSA9IFQpDQpkYldyaXRlVGFibGUoUE9EQmNvbiwgInBvIiwgUE8uZGYsIG92ZXJ3cml0ZSA9IFQpDQpgYGANCg0KVG8gZW5zdXJlIHRoYXQgdGhlIGRhdGEgd2FzIHdyaXR0ZW4gcHJvcGVybHksIGxldCdzIHJldHJlaXZlIHNvbWUgb2YgdGhlIHJvd3MuDQoNCmBgYHtzcWwgY29ubmVjdGlvbiA9ICJQT0RCY29uIiwgb3V0cHV0LnZhciA9ICJhdXRob3JERiJ9DQpzZWxlY3QgKiBmcm9tIGFkZHJlc3NlcyBsaW1pdCA1Ow0KYGBgDQoNCmBgYHtzcWwgY29ubmVjdGlvbiA9ICJQT0RCY29uIn0NCnNlbGVjdCAqIGZyb20gaXRlbXMgbGltaXQgNTsNCmBgYA0KDQpgYGB7c3FsIGNvbm5lY3Rpb24gPSAiUE9EQmNvbiJ9DQpzZWxlY3QgKiBmcm9tIHBvIGxpbWl0IDU7DQpgYGANCg0KIyMjIyBEaXNjb25uZWN0IERhdGFiYXNlDQoNCmBgYHtyIGRpc2Nvbm5lY3QgREJ9DQpkYkRpc2Nvbm5lY3QoUE9EQmNvbikNCmBgYA0KDQojIyBUdXRvcmlhbA0KDQpgYGB7PWh0bWx9DQo8aWZyYW1lIHNyYz0iIiB3aWR0aD0iNDgwIiBoZWlnaHQ9IjI3MCIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlIiBhbGxvd2Z1bGxzY3JlZW4gZGF0YS1leHRlcm5hbD0iMSI+PC9pZnJhbWU+DQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEZpbGVzICYgUmVzb3VyY2VzDQoNCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQ0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIA0KICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksDQogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpDQoNCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgDQogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpDQoNCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUg0Ka25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgUmVmZXJlbmNlcw0KDQpObyByZWZlcmVuY2VzLg0KDQojIyBFcnJhdGENCg0KTm9uZSBjb2xsZWN0ZWQgeWV0LiBMZXQgdXMga25vdy4NCg0KYGBgez1odG1sfQ0KPHNjcmlwdCBzcmM9Imh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS9zdGF0aWMvZmVlZGJhY2syLmpzIiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPg0KICBuZXcgSm90Zm9ybUZlZWRiYWNrKHsNCiAgICBmb3JtSWQ6ICIyMTIxODcwNzI3ODQxNTciLA0KICAgIGJ1dHRvblRleHQ6ICJGZWVkYmFjayIsDQogICAgYmFzZTogImh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8iLA0KICAgIGJhY2tncm91bmQ6ICIjRjU5MjAyIiwNCiAgICBmb250Q29sb3I6ICIjRkZGRkZGIiwNCiAgICBidXR0b25TaWRlOiAibGVmdCIsDQogICAgYnV0dG9uQWxpZ246ICJjZW50ZXIiLA0KICAgIHR5cGU6IGZhbHNlLA0KICAgIHdpZHRoOiA3MDAsDQogICAgaGVpZ2h0OiA1MDAsDQogICAgaXNDYXJkRm9ybTogZmFsc2UNCiAgfSk7DQo8L3NjcmlwdD4NCmBgYA0KYGBge3IgY29kZT14ZnVuOjpyZWFkX3V0ZjgocGFzdGUwKGhlcmU6OmhlcmUoKSwnL1IvX2RlcGxveUtuaXQuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQ0KYGBgDQo=