1 | import "source-map-support/register";
|
2 | // TODO should I get rid of the lib above for production browser build?
|
3 | import { assign as assignM } from "lodash";
|
4 | import { assign, compact, curry, difference, flow, intersection, isArray, isObject, map, omit, partition, sortBy, startCase, toPairsIn } from "lodash/fp";
|
5 | import * as hl from "highland";
|
6 | import * as VError from "verror";
|
7 | import { CXMLXPath } from "../spinoffs/cxml-xpath";
|
8 | import * as GPML2013a from "../../xmlns/pathvisio.org/GPML/2013a";
|
9 | import * as GPMLDefaults from "../GPMLDefaults";
|
10 | import { Processor } from "../Processor";
|
11 | import { preprocessGPML as preprocessEdgeGPML, postprocessPVJSON as postprocessEdgePVJSON } from "../edge/edge";
|
12 | import { preprocessGPML as preprocessGroupGPML, postprocessPVJSON as postprocessGroupPVJSON } from "../group";
|
13 | import { arrayify, insertIfNotExists, isDefinedCXML, isPvjsonBurr, isPvjsonEdge, isPvjsonGroup, isPvjsonSingleFreeNode, isPvjsonNode, isPvjsonEdgeOrBurr, sortByMap, supportedNamespaces, unionLSV } from "../gpml-utilities";
|
14 | import * as VOCABULARY_NAME_TO_IRI from "../spinoffs/VOCABULARY_NAME_TO_IRI.json";
|
15 | import * as GPML2013aKeyMappings from "./KeyMappings.json";
|
16 | import * as GPML2013aKeyValueMappings from "./KeyValueConverters";
|
17 | import * as GPML2013aValueMappings from "./ValueMappings.json";
|
18 | import * as GPML2013aValueConverters from "./ValueConverters";
|
19 | // TODO get text alignment correctly mapped to Box Model CSS terms
|
20 | import * as iassign from "immutable-assign";
|
21 | iassign.setOption({
|
22 | // Deep freeze both input and output. Used in development to make sure they don't change.
|
23 | // TODO watch issue and re-enable when addressed: https://github.com/engineforce/ImassignM/issues/11
|
24 | //freeze: true,
|
25 | ignoreIfNoChange: true
|
26 | });
|
27 | export const RECURSION_LIMIT = 1000;
|
28 | function partitionStream(s, partitioner) {
|
29 | const yes = s.fork().filter(x => partitioner(x));
|
30 | const no = s.fork().filter(x => !partitioner(x));
|
31 | return [yes, no];
|
32 | }
|
33 | function extendDeep(targetOrTargetArray, source) {
|
34 | const target = isArray(targetOrTargetArray)
|
35 | ? targetOrTargetArray[0]
|
36 | : targetOrTargetArray;
|
37 | // TODO: We run into problems if we try to extend both
|
38 | // GraphicalLine and Interaction, because they share
|
39 | // EdgeGraphicsType. To avoid an infinite recursion of
|
40 | // extending, I'm using a short-term solution of just
|
41 | // marking whether a target has been extended, and
|
42 | // if so, skipping it.
|
43 | // Look into a better way of handling this.
|
44 | if (!target.hasOwnProperty("_extended")) {
|
45 | toPairsIn(target)
|
46 | .filter(([targetKey, targetValue]) => source.hasOwnProperty(targetKey) && isObject(source[targetKey]))
|
47 | .forEach(function ([targetKey, targetValue]) {
|
48 | extendDeep(targetValue, source[targetKey]);
|
49 | });
|
50 | assignM(target.constructor.prototype, source);
|
51 | target._extended = true;
|
52 | }
|
53 | }
|
54 | const stringifyKeyValue = curry(function (source, key) {
|
55 | return source.hasOwnProperty(key)
|
56 | ? startCase(key) + ": " + source[key]
|
57 | : null;
|
58 | });
|
59 | extendDeep(GPML2013a.document.Pathway.constructor.prototype, GPMLDefaults.Pathway);
|
60 | extendDeep(GPML2013a.DataNodeType.prototype, GPMLDefaults.DataNode);
|
61 | extendDeep(GPML2013a.GraphicalLineType.prototype, GPMLDefaults.GraphicalLine);
|
62 | extendDeep(GPML2013a.GroupType.prototype, GPMLDefaults.Group);
|
63 | extendDeep(GPML2013a.InteractionType.prototype, GPMLDefaults.Interaction);
|
64 | extendDeep(GPML2013a.LabelType.prototype, GPMLDefaults.Label);
|
65 | extendDeep(GPML2013a.ShapeType.prototype, GPMLDefaults.Shape);
|
66 | extendDeep(GPML2013a.StateType.prototype, GPMLDefaults.State);
|
67 | extendDeep(GPML2013a.EdgeGraphicsType.prototype.Anchor, GPMLDefaults.Anchor);
|
68 | // TODO specify types
|
69 | export function toPvjson(inputStreamWithMessedUpRDFIDs, pathwayIri) {
|
70 | // NOTE: GPML2013a incorrectly uses "rdf:id" instead of "rdf:ID".
|
71 | // We need to fix this error so that CXML can process the GPML.
|
72 | const inputStream = hl(inputStreamWithMessedUpRDFIDs)
|
73 | .splitBy(' rdf:id="')
|
74 | .intersperse(' rdf:ID="');
|
75 | const selectorToCXML = {
|
76 | // TODO why does TS require that we use the Pathway's "constructor.prototype"
|
77 | // instead of just the Pathway?
|
78 | // Why does Pathway.Graphics not need that?
|
79 | // Why do many of the other require using the prototype?
|
80 | //
|
81 | "/Pathway/@*": GPML2013a.document.Pathway.constructor.prototype,
|
82 | "/Pathway/Comment": GPML2013a.document.Pathway.Comment[0],
|
83 | "/Pathway/Graphics/@*": GPML2013a.document.Pathway.Graphics,
|
84 | "/Pathway/DataNode": GPML2013a.DataNodeType.prototype,
|
85 | "/Pathway/State": GPML2013a.StateType.prototype,
|
86 | "/Pathway/Interaction": GPML2013a.InteractionType.prototype,
|
87 | "/Pathway/GraphicalLine": GPML2013a.GraphicalLineType.prototype,
|
88 | "/Pathway/Label": GPML2013a.LabelType.prototype,
|
89 | "/Pathway/Shape": GPML2013a.ShapeType.prototype,
|
90 | "/Pathway/Group": GPML2013a.GroupType.prototype,
|
91 | "/Pathway/InfoBox": GPML2013a.InfoBoxType.prototype,
|
92 | "/Pathway/Legend": GPML2013a.LegendType.prototype,
|
93 | "/Pathway/Biopax/bp:PublicationXref": GPML2013a.document.Pathway.Biopax.PublicationXref[0],
|
94 | "/Pathway/Biopax/bp:openControlledVocabulary": GPML2013a.document.Pathway.Biopax.openControlledVocabulary[0]
|
95 | };
|
96 | const cxmlXPath = new CXMLXPath(inputStream, GPML2013a, {
|
97 | bp: "http://www.biopax.org/release/biopax-level3.owl#"
|
98 | //rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
99 | });
|
100 | const cxmlSources = cxmlXPath.parse(selectorToCXML);
|
101 | const processor = new Processor(GPML2013aKeyMappings, GPML2013aKeyValueMappings, GPML2013aValueMappings, GPML2013aValueConverters);
|
102 | const { fillInGPMLPropertiesFromParent, getPvjsonEntityLatestByGraphId, graphIdsByGraphRef, graphIdToZIndex, getGPMLElementByGraphId, preprocessGPMLElement, processGPMLAndPropertiesAndType, processProperties, processPropertiesAndType, setPvjsonEntity } = processor;
|
103 | if (pathwayIri) {
|
104 | processor.output = iassign(processor.output, function (o) {
|
105 | return o.pathway;
|
106 | }, function (pathway) {
|
107 | pathway.id = pathwayIri;
|
108 | return pathway;
|
109 | });
|
110 | }
|
111 | const sortByZIndex = sortByMap(graphIdToZIndex);
|
112 | const pathwayMetadataStream = hl([
|
113 | cxmlSources["/Pathway/@*"].doto(function (pathway) {
|
114 | if (supportedNamespaces.indexOf(pathway._namespace) === -1) {
|
115 | // TODO should we do anything further?
|
116 | throw new Error(`Unsupported namespace: ${pathway._namespace}`);
|
117 | }
|
118 | }),
|
119 | cxmlSources["/Pathway/Graphics/@*"]
|
120 | ])
|
121 | .merge()
|
122 | .map(processProperties)
|
123 | .reduce({}, function (acc, metadataChunk) {
|
124 | return assign(acc, metadataChunk);
|
125 | })
|
126 | .map(function (metadata) {
|
127 | processor.output = iassign(processor.output, function (o) {
|
128 | return o.pathway;
|
129 | }, function (pathway) {
|
130 | const mergedPathway = assign(pathway, metadata);
|
131 | // NOTE: GPML schema specifies that name is required
|
132 | const { name } = mergedPathway;
|
133 | const splitName = name.split(" (");
|
134 | if (!!splitName &&
|
135 | splitName.length === 2 &&
|
136 | !!name.match(/\(/g) &&
|
137 | name.match(/\(/g).length === 1 &&
|
138 | !!name.match(/\)/g) &&
|
139 | name.match(/\)/g).length === 1) {
|
140 | mergedPathway.standardName = splitName[0];
|
141 | mergedPathway.displayName = splitName[1].replace(")", "");
|
142 | }
|
143 | else {
|
144 | mergedPathway.standardName = name;
|
145 | mergedPathway.displayName = name;
|
146 | }
|
147 | const stringifyKeyValueForPathway = stringifyKeyValue(mergedPathway);
|
148 | mergedPathway.textContent = compact([
|
149 | stringifyKeyValueForPathway("name"),
|
150 | stringifyKeyValueForPathway("license"),
|
151 | stringifyKeyValueForPathway("lastModified"),
|
152 | stringifyKeyValueForPathway("organism")
|
153 | ]).join("\n");
|
154 | const context = [
|
155 | "https://cdn.rawgit.com/wikipathways/WpVocabularies/7a46a05/contexts/pvjs.jsonld"
|
156 | ];
|
157 | if (!!mergedPathway.id) {
|
158 | context.push({
|
159 | "@base": mergedPathway.id + "/"
|
160 | });
|
161 | }
|
162 | else {
|
163 | // If there's no pathway IRI specified, we at least give the user a URL
|
164 | // to search WikiPathways. This way, the user at least has a chance of
|
165 | // to search WikiPathways to possibly find the source for this data.
|
166 | // NOTE: GPML schema specifies that organism is optional
|
167 | const organismIriComponent = mergedPathway.hasOwnProperty("organism")
|
168 | ? `&species=${mergedPathway.organism}`
|
169 | : "";
|
170 | mergedPathway.isSimilarTo = encodeURI(`http://wikipathways.org/index.php/Special:SearchPathways?query=${name}${organismIriComponent}&doSearch=1`);
|
171 | }
|
172 | return assign({
|
173 | "@context": context
|
174 | }, mergedPathway);
|
175 | });
|
176 | return processor.output;
|
177 | })
|
178 | .errors(function (err) {
|
179 | throw new VError(err, ` when processing pathwayMetadataStream
|
180 | `);
|
181 | });
|
182 | const pathwayCommentStream = hl(cxmlSources["/Pathway/Comment"]).map(function (Comment) {
|
183 | processor.output = iassign(processor.output, function (o) {
|
184 | return o.pathway;
|
185 | }, function (pathway) {
|
186 | const comments = pathway.comments || [];
|
187 | comments.push(processProperties(Comment));
|
188 | pathway.comments = comments;
|
189 | return pathway;
|
190 | });
|
191 | return processor.output;
|
192 | });
|
193 | const dataNodeStream = cxmlSources["/Pathway/DataNode"]
|
194 | .map(processGPMLAndPropertiesAndType("DataNode"))
|
195 | .map(function (entity) {
|
196 | // TODO fix type def for unionLSV so I don't have to use "as"
|
197 | entity.type = unionLSV(entity.type, entity.wpType);
|
198 | return entity;
|
199 | });
|
200 | const stateStream = cxmlSources["/Pathway/State"]
|
201 | .map(preprocessGPMLElement)
|
202 | .flatMap(function (gpmlState) {
|
203 | return hl(getGPMLElementByGraphId(gpmlState.GraphRef)).map(function (gpmlDataNode) {
|
204 | return fillInGPMLPropertiesFromParent(gpmlDataNode, gpmlState);
|
205 | });
|
206 | })
|
207 | .map(processPropertiesAndType("State"));
|
208 | const shapeStream = cxmlSources["/Pathway/Shape"]
|
209 | .map(processGPMLAndPropertiesAndType("Shape"))
|
210 | .map(function (pvjsonEntity) {
|
211 | const { cellularComponent } = pvjsonEntity;
|
212 | // CellularComponent is not a BioPAX term, but "PhysicalEntity" is.
|
213 | if (!!cellularComponent) {
|
214 | pvjsonEntity.type = unionLSV(pvjsonEntity.type, "PhysicalEntity", "CellularComponent", cellularComponent);
|
215 | }
|
216 | return pvjsonEntity;
|
217 | });
|
218 | const labelStream = cxmlSources["/Pathway/Label"].map(processGPMLAndPropertiesAndType("Label"));
|
219 | const gpmlInteractionStream = cxmlSources["/Pathway/Interaction"].map(preprocessGPMLElement);
|
220 | const gpmlGraphicalLineStream = cxmlSources["/Pathway/GraphicalLine"].map(preprocessGPMLElement);
|
221 | const edgeStream = hl([
|
222 | gpmlInteractionStream
|
223 | .fork()
|
224 | .map(preprocessEdgeGPML)
|
225 | .map(processPropertiesAndType("Interaction")),
|
226 | gpmlGraphicalLineStream
|
227 | .fork()
|
228 | .map(preprocessEdgeGPML)
|
229 | .map(processPropertiesAndType("GraphicalLine"))
|
230 | ]).merge();
|
231 | const anchorStream = hl([
|
232 | gpmlInteractionStream.fork(),
|
233 | gpmlGraphicalLineStream.fork()
|
234 | ])
|
235 | .merge()
|
236 | .filter(function (gpmlEdge) {
|
237 | return (isDefinedCXML(gpmlEdge.Graphics) &&
|
238 | isDefinedCXML(gpmlEdge.Graphics.Anchor));
|
239 | })
|
240 | .flatMap(function (gpmlEdge) {
|
241 | const { GraphId, Graphics } = gpmlEdge;
|
242 | const fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(gpmlEdge);
|
243 | const gpmlAnchors = Graphics.Anchor;
|
244 | return hl(gpmlAnchors)
|
245 | .map(function (gpmlAnchor) {
|
246 | const anchorShape = gpmlAnchor.Shape;
|
247 | if (anchorShape === "None") {
|
248 | // NOTE: For Anchors with Shape="None", PathVisio-Java displays
|
249 | // the anchor as a 4x4 square when nothing is connected,
|
250 | // but does not display it when something is connected.
|
251 | // TODO: right now, PathVisio-Java writes out GPML such that the
|
252 | // Anchor only has a GraphId when an Edge connects to this Anchor,
|
253 | // but we may not be able to rely on this in the future.
|
254 | if (isDefinedCXML(gpmlAnchor.GraphId)) {
|
255 | assignM(gpmlAnchor.Graphics, {
|
256 | Height: 0,
|
257 | Width: 0
|
258 | });
|
259 | }
|
260 | else {
|
261 | gpmlAnchor.Shape = "Rectangle";
|
262 | assignM(gpmlAnchor.Graphics, {
|
263 | Height: 4,
|
264 | Width: 4
|
265 | });
|
266 | }
|
267 | }
|
268 | else if (anchorShape === "Circle") {
|
269 | assignM(gpmlAnchor.Graphics, {
|
270 | Height: 8,
|
271 | Width: 8
|
272 | });
|
273 | }
|
274 | else {
|
275 | throw new Error(`Anchor Shape "${anchorShape}" is not supported.`);
|
276 | }
|
277 | return gpmlAnchor;
|
278 | })
|
279 | .map(preprocessGPMLElement)
|
280 | .map(function (gpmlAnchor) {
|
281 | const filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor);
|
282 | filledInAnchor.GraphRef = GraphId;
|
283 | return filledInAnchor;
|
284 | })
|
285 | .map(processPropertiesAndType("Anchor"));
|
286 | });
|
287 | const groupStream = cxmlSources["/Pathway/Group"]
|
288 | .map(preprocessGroupGPML(processor))
|
289 | .filter((Group) => !!Group.Contains)
|
290 | .map(processGPMLAndPropertiesAndType("Group"));
|
291 | const EDGES = ["Interaction", "GraphicalLine"];
|
292 | const NODES = ["DataNode", "Shape", "Label", "State", "Group"];
|
293 | function postprocessAll(s) {
|
294 | /*
|
295 | // We are sorting the elements by the order in which we must do their
|
296 | // post-processing, e.g., if one edge is attached to another edge via
|
297 | // an anchor, we must post-process the edge with the anchor before we
|
298 | // post-process the other edge.
|
299 | const isProcessableTests = [
|
300 | curry(function(sortedIds: string[], pvjsonEntity: PvjsonEntity) {
|
301 | // In this test, we ensure the entity
|
302 | // 1) is not a group AND
|
303 | // 2) is not attached to a group and not to an edge
|
304 | // (ie., it is not attached to anything, or
|
305 | // it is attached to something, but that something is neither a group nor an edge)
|
306 |
|
307 | return (
|
308 | pvjsonEntity.gpmlElementName !== "Group" &&
|
309 | unionLSV(
|
310 | pvjsonEntity["isAttachedToOrVia"],
|
311 | pvjsonEntity["isAttachedTo"]
|
312 | )
|
313 | .map(
|
314 | (isAttachedToOrViaId: string) =>
|
315 | processor.output.entitiesById[isAttachedToOrViaId].gpmlElementName
|
316 | )
|
317 | .filter(
|
318 | // Entity is attached to neither a group nor an edge.
|
319 | // (Testing that entity is not attached to an edge at all,
|
320 | // whether directly or indirectly via an anchor.)
|
321 | isAttachedToOrViaGpmlElementName =>
|
322 | ["Group", "Interaction", "GraphicalLine", "Anchor"].indexOf(
|
323 | isAttachedToOrViaGpmlElementName
|
324 | ) > -1
|
325 | ).length === 0
|
326 | );
|
327 | }),
|
328 | curry(function(sortedIds: string[], pvjsonEntity: PvjsonEntity) {
|
329 | const gpmlElementName = pvjsonEntity.gpmlElementName;
|
330 | if (
|
331 | ["Interaction", "GraphicalLine", "State", "Anchor"].indexOf(
|
332 | gpmlElementName
|
333 | ) > -1
|
334 | ) {
|
335 | // This entity is an edge, a state or an anchor.
|
336 | // All entities to which this entity is attached must be processable
|
337 | // before it is itself processable.
|
338 | // That means all entities to which this entity is attached to must
|
339 | // be processed before it can be processed.
|
340 | const isAttachedToIds = unionLSV(
|
341 | pvjsonEntity["isAttachedTo"],
|
342 | pvjsonEntity["isAttachedToOrVia"]
|
343 | ) as string[];
|
344 | const isAttachedToInSortedIds = isAttachedToIds.filter(
|
345 | // entity with this id is sortedIds
|
346 | isAttachedToId => sortedIds.indexOf(isAttachedToId) > -1
|
347 | );
|
348 | return isAttachedToIds.length === isAttachedToInSortedIds.length;
|
349 | } else if (gpmlElementName === "Group") {
|
350 | // is processable when group does not contain an entity that is not processable
|
351 | return (
|
352 | arrayify(pvjsonEntity["contains"])
|
353 | .map(isAttachedToId => processor.output.entitiesById[isAttachedToId])
|
354 | .filter(
|
355 | candidateEntity => sortedIds.indexOf(candidateEntity.id) > -1
|
356 | ).length > 0
|
357 | );
|
358 | }
|
359 | })
|
360 | ];
|
361 | //*/
|
362 | function sortUnsortedRecursive({ sortedIds, unsorted }, i = 0) {
|
363 | // TODO is there something better we can do than use RECURSION_LIMIT?
|
364 | // WP2037 revision 90015 won't terminate without a limit, but converts
|
365 | // OK with the limit set.
|
366 | if (unsorted.length === 0 || i > RECURSION_LIMIT) {
|
367 | return { sortedIds, unsorted };
|
368 | }
|
369 | i += 1;
|
370 | return sortUnsortedRecursive(sortUnsortedOnce({ sortedIds, unsorted }), i);
|
371 | }
|
372 | function sortUnsortedOnce({ sortedIds, unsorted }) {
|
373 | let [sortedOnThisIteration, stillUnsorted] = partition(function (pvjsonEntity) {
|
374 | const dependencies = unionLSV(pvjsonEntity.contains, pvjsonEntity["isAttachedToOrVia"], pvjsonEntity.isAttachedTo);
|
375 | return (
|
376 | /*
|
377 | dependencies
|
378 | .map((id: string) => processor.output.entitiesById[id])
|
379 | .indexOf(undefined) === -1 &&
|
380 | //*/
|
381 | intersection(dependencies, sortedIds).length === dependencies.length);
|
382 | }, unsorted);
|
383 | sortedOnThisIteration
|
384 | .forEach(function (pvjsonEntity) {
|
385 | sortedIds.push(pvjsonEntity.id);
|
386 | });
|
387 | return {
|
388 | sortedIds: sortedIds,
|
389 | unsorted: stillUnsorted
|
390 | };
|
391 | }
|
392 | return (s
|
393 | .reduce({
|
394 | sortedIds: [],
|
395 | unsorted: []
|
396 | }, function ({ sortedIds, unsorted }, pvjsonEntity) {
|
397 | unsorted.push(pvjsonEntity);
|
398 | return sortUnsortedOnce({ sortedIds, unsorted });
|
399 | })
|
400 | .map(sortUnsortedRecursive)
|
401 | .map(function (acc) {
|
402 | const { sortedIds, unsorted } = acc;
|
403 | return sortedIds
|
404 | .map((id) => processor.output.entitiesById[id])
|
405 | .concat(unsorted);
|
406 | })
|
407 | .sequence());
|
408 | }
|
409 | const pvjsonEntityStream = hl([
|
410 | hl([dataNodeStream, stateStream, shapeStream, labelStream]).merge(),
|
411 | hl([edgeStream, anchorStream]).merge(),
|
412 | groupStream
|
413 | ])
|
414 | .sequence()
|
415 | .doto(setPvjsonEntity)
|
416 | .through(postprocessAll)
|
417 | .flatMap(function (pvjsonEntity) {
|
418 | const { id, zIndex } = pvjsonEntity;
|
419 | // TODO we might want to sort by other criteria, such as
|
420 | // to order a State above its DataNode, which would be
|
421 | // ordered above its Group, if any
|
422 | const insertEntityIdAndSortByZIndex = flow([
|
423 | insertIfNotExists(id),
|
424 | sortByZIndex
|
425 | ]);
|
426 | let finalSortedStream;
|
427 | if (isPvjsonEdgeOrBurr(pvjsonEntity)) {
|
428 | const isAttachedTo = pvjsonEntity.isAttachedTo;
|
429 | arrayify(isAttachedTo).forEach(function (graphRef) {
|
430 | const graphRefs = graphIdsByGraphRef[graphRef] || [];
|
431 | if (graphRefs.indexOf(id) === -1) {
|
432 | graphRefs.push(id);
|
433 | }
|
434 | graphIdsByGraphRef[graphRef] = graphRefs;
|
435 | });
|
436 | if (isPvjsonBurr(pvjsonEntity)) {
|
437 | finalSortedStream = hl(getPvjsonEntityLatestByGraphId(isAttachedTo)).map(function (referencedEntity) {
|
438 | if (isPvjsonNode(referencedEntity)) {
|
439 | const { attachmentDisplay } = pvjsonEntity;
|
440 | const [relativeOffsetScalarX, relativeOffsetScalarY] = attachmentDisplay.relativeOffset;
|
441 | attachmentDisplay.offset = [
|
442 | relativeOffsetScalarX * referencedEntity.width,
|
443 | relativeOffsetScalarY * referencedEntity.height
|
444 | ];
|
445 | pvjsonEntity.attachmentDisplay = omit(["relativeOffset"], attachmentDisplay);
|
446 | }
|
447 | setPvjsonEntity(pvjsonEntity);
|
448 | // NOTE: burrs are not added to the property "contained".
|
449 | // Rather, they are added to the property "burrs".
|
450 | referencedEntity.burrs = referencedEntity.burrs || [];
|
451 | insertEntityIdAndSortByZIndex(referencedEntity.burrs);
|
452 | setPvjsonEntity(referencedEntity);
|
453 | return processor.output;
|
454 | });
|
455 | }
|
456 | else if (isPvjsonEdge(pvjsonEntity)) {
|
457 | try {
|
458 | const pvjsonEdge = postprocessEdgePVJSON(processor.output.entitiesById, pvjsonEntity);
|
459 | processor.output = iassign(processor.output, function (o) {
|
460 | return o.pathway.contains;
|
461 | }, insertEntityIdAndSortByZIndex);
|
462 | setPvjsonEntity(pvjsonEdge);
|
463 | finalSortedStream = hl([processor.output]);
|
464 | }
|
465 | catch (err) {
|
466 | return hl.fromError(err);
|
467 | }
|
468 | }
|
469 | else {
|
470 | return hl.fromError(new VError(`
|
471 | Unexpected entity type.
|
472 | Only Edge or Burr should return true for
|
473 | isPvjsonEdgeOrBurr(
|
474 | ${JSON.stringify(pvjsonEntity, null, " ")}
|
475 | )
|
476 | `));
|
477 | }
|
478 | }
|
479 | else if (isPvjsonGroup(pvjsonEntity)) {
|
480 | // We still have some GPML files with empty Groups and/or nested Groups
|
481 | // floating around, but we don't process them, because that's a
|
482 | // curation issue, not a gpml2pvjson issue.
|
483 | const containedCount = pvjsonEntity.contains.length;
|
484 | if (containedCount === 0 || pvjsonEntity.hasOwnProperty("groupRef")) {
|
485 | if (containedCount === 0) {
|
486 | return hl.fromError(new Error(`
|
487 | Encountered empty Group:
|
488 | ${JSON.stringify(pvjsonEntity, null, " ")}
|
489 | `));
|
490 | }
|
491 | if (pvjsonEntity.hasOwnProperty("groupRef")) {
|
492 | return hl.fromError(new Error(`
|
493 | Encountered nested Group:
|
494 | ${JSON.stringify(pvjsonEntity, null, " ")}
|
495 | `));
|
496 | }
|
497 | finalSortedStream = hl([processor.output]);
|
498 | }
|
499 | else {
|
500 | const graphIdOfGroup = pvjsonEntity.id;
|
501 | try {
|
502 | finalSortedStream = hl(pvjsonEntity.contains.map(containedId => processor.output.entitiesById[containedId]))
|
503 | .filter(groupedEntity => groupedEntity.kaavioType !== "Group")
|
504 | .collect()
|
505 | .map(function (groupedEntities) {
|
506 | const pvjsonGroup = postprocessGroupPVJSON(groupedEntities, pvjsonEntity);
|
507 | const graphIdToZIndex = processor.graphIdToZIndex;
|
508 | pvjsonGroup.contains = sortBy([
|
509 | function (thisEntityId) {
|
510 | return graphIdToZIndex[thisEntityId];
|
511 | }
|
512 | ], groupedEntities.map(x => x.id));
|
513 | const { id, x, y } = pvjsonGroup;
|
514 | const groupedEntitiesFinal = groupedEntities.map(function (groupedEntity) {
|
515 | if (isPvjsonEdge(groupedEntity)) {
|
516 | groupedEntity.points = map(function (point) {
|
517 | point.x -= x;
|
518 | point.y -= y;
|
519 | return point;
|
520 | }, groupedEntity.points);
|
521 | }
|
522 | else if (isPvjsonSingleFreeNode(groupedEntity)) {
|
523 | groupedEntity.height;
|
524 | groupedEntity.x -= x;
|
525 | groupedEntity.y -= y;
|
526 | }
|
527 | else {
|
528 | return hl.fromError(new Error(`
|
529 | Encountered unexpected entity
|
530 | ${JSON.stringify(groupedEntity, null, " ")}
|
531 | in Group
|
532 | ${JSON.stringify(pvjsonGroup, null, " ")}
|
533 | `));
|
534 | }
|
535 | // NOTE: this is needed for GPML2013a, because GPML2013a uses both
|
536 | // GroupId/GroupRef and GraphId/GraphRef. GPML2017 uses a single
|
537 | // identifier per entity. That identifier can be referenced by
|
538 | // GroupRef and/or GraphRef. Pvjson follows GPML2017 in this, so
|
539 | // we convert from GPML2013a format:
|
540 | // GroupRef="GROUP_ID_VALUE"
|
541 | // to pvjson format:
|
542 | // {isPartOf: "GRAPH_ID_VALUE"}
|
543 | groupedEntity.isPartOf = id;
|
544 | return omit(["groupRef"], groupedEntity);
|
545 | });
|
546 | groupedEntitiesFinal.forEach(function (pvjsonEntity) {
|
547 | setPvjsonEntity(pvjsonEntity);
|
548 | });
|
549 | setPvjsonEntity(pvjsonGroup);
|
550 | processor.output = iassign(processor.output, function (o) {
|
551 | return o.pathway.contains;
|
552 | }, function (contains) {
|
553 | return insertEntityIdAndSortByZIndex(difference(contains, groupedEntitiesFinal.map(x => x.id)), id);
|
554 | });
|
555 | return pvjsonGroup;
|
556 | })
|
557 | .map(function (pvjsonEntity) {
|
558 | return processor.output;
|
559 | });
|
560 | }
|
561 | catch (err) {
|
562 | return hl.fromError(err);
|
563 | }
|
564 | }
|
565 | }
|
566 | else {
|
567 | setPvjsonEntity(pvjsonEntity);
|
568 | processor.output = iassign(processor.output, function (o) {
|
569 | return o.pathway.contains;
|
570 | }, insertEntityIdAndSortByZIndex);
|
571 | finalSortedStream = hl([processor.output]);
|
572 | }
|
573 | return finalSortedStream;
|
574 | });
|
575 | pvjsonEntityStream.observe().last().doto(function () {
|
576 | processor.pvjsonEntityLatestStream.end();
|
577 | });
|
578 | const openControlledVocabularyStream = hl(cxmlSources["/Pathway/Biopax/bp:openControlledVocabulary"])
|
579 | .map(processPropertiesAndType("openControlledVocabulary"))
|
580 | .map(function (openControlledVocabulary) {
|
581 | const vocabularyName = openControlledVocabulary.ontology;
|
582 | let vocabularyIRI = VOCABULARY_NAME_TO_IRI[vocabularyName];
|
583 | if (!vocabularyIRI) {
|
584 | return hl.fromError(new Error(`
|
585 | Encountered unexpected name "${vocabularyName}" for openControlledVocabulary,
|
586 | with xrefIdentifier "${openControlledVocabulary.xrefIdentifier}"
|
587 | `));
|
588 | /* TODO should we use this?
|
589 | vocabularyIRI = `http://www.ebi.ac.uk/miriam/main/search?query=${vocabularyName.replace(
|
590 | /\ /,
|
591 | "+"
|
592 | )}#`;
|
593 | //*/
|
594 | }
|
595 | openControlledVocabulary.id =
|
596 | vocabularyIRI + openControlledVocabulary.xrefIdentifier;
|
597 | return openControlledVocabulary;
|
598 | })
|
599 | .collect()
|
600 | .map(function (openControlledVocabularies) {
|
601 | // TODO should these go through the processor instead?
|
602 | processor.output = iassign(processor.output, function (o) {
|
603 | const { pathway, entitiesById } = o;
|
604 | openControlledVocabularies.forEach(function (openControlledVocabulary) {
|
605 | const { id } = openControlledVocabulary;
|
606 | entitiesById[id] = openControlledVocabulary;
|
607 | if (openControlledVocabulary.ontology === "Pathway Ontology") {
|
608 | pathway.type.push(id);
|
609 | }
|
610 | });
|
611 | return o;
|
612 | });
|
613 | return processor.output;
|
614 | });
|
615 | const publicationXrefStream = hl(cxmlSources["/Pathway/Biopax/bp:PublicationXref"])
|
616 | .map(processPropertiesAndType("PublicationXref"))
|
617 | .collect()
|
618 | .map(function (publicationXrefs) {
|
619 | publicationXrefs
|
620 | .sort(function (a, b) {
|
621 | const yearA = parseInt(a.year);
|
622 | const yearB = parseInt(b.year);
|
623 | if (yearA > yearB) {
|
624 | return 1;
|
625 | }
|
626 | else if (yearA < yearB) {
|
627 | return -1;
|
628 | }
|
629 | else {
|
630 | return 0;
|
631 | }
|
632 | })
|
633 | .forEach(function (publicationXref, i) {
|
634 | publicationXref.textContent = String(i + 1);
|
635 | });
|
636 | return publicationXrefs;
|
637 | })
|
638 | .map(function (publicationXrefs) {
|
639 | // TODO should these go through the processor instead?
|
640 | processor.output = iassign(processor.output, function (o) {
|
641 | return o.entitiesById;
|
642 | }, function (entitiesById) {
|
643 | publicationXrefs.forEach(function (publicationXref) {
|
644 | entitiesById[publicationXref.id] = publicationXref;
|
645 | });
|
646 | return entitiesById;
|
647 | });
|
648 | return processor.output;
|
649 | });
|
650 | /* TODO do we need to handle these?
|
651 | <xsd:element ref="gpml:InfoBox" minOccurs="1" maxOccurs="1" />
|
652 | <xsd:element ref="gpml:Legend" minOccurs="0" maxOccurs="1"/>
|
653 | */
|
654 | return hl([
|
655 | pathwayMetadataStream,
|
656 | pathwayCommentStream,
|
657 | pvjsonEntityStream,
|
658 | openControlledVocabularyStream,
|
659 | publicationXrefStream
|
660 | ])
|
661 | .merge()
|
662 | .errors(function (err) {
|
663 | throw new VError(err, ` when converting pathway ${pathwayIri}
|
664 | `);
|
665 | });
|
666 | // // TODO Double-check old code to make sure nothing is missed.
|
667 | // // TODO does the stream ever end?
|
668 | // // TODO does backpressure work?
|
669 | // TODO compare the old pvjs/kaavio code below for grouping entities to
|
670 | // ensure we don't have any regression errors.
|
671 | /*
|
672 | getGroupedZIndexedEntities(zIndexedEntities) {
|
673 | const { entitiesById } = this.props;
|
674 | return zIndexedEntities
|
675 | .filter(entity => !entity.isPartOf)
|
676 | .reduce(function(acc, entity) {
|
677 | const kaavioType = entity.kaavioType;
|
678 | if (kaavioType === "Group") {
|
679 | // TODO: refactor this so that contains is actually a map of the contained elements. Not just an array of their IDs
|
680 | entity.contains = entity.contains
|
681 | .map(id => entitiesById[id])
|
682 | .sort(function(a, b) {
|
683 | const zIndexA = a.zIndex;
|
684 | const zIndexB = b.zIndex;
|
685 | if (zIndexA < zIndexB) {
|
686 | return 1;
|
687 | } else if (zIndexA > zIndexB) {
|
688 | return -1;
|
689 | } else {
|
690 | return 0;
|
691 | }
|
692 | })
|
693 | .map(entity => entity.id);
|
694 | } else if (entity.hasOwnProperty("burrs")) {
|
695 | entity.burrs = entity.burrs
|
696 | .map(id => entitiesById[id])
|
697 | .sort(function(a, b) {
|
698 | const zIndexA = a.zIndex;
|
699 | const zIndexB = b.zIndex;
|
700 | if (zIndexA < zIndexB) {
|
701 | return 1;
|
702 | } else if (zIndexA > zIndexB) {
|
703 | return -1;
|
704 | } else {
|
705 | return 0;
|
706 | }
|
707 | })
|
708 | .map(entity => entity.id);
|
709 | }
|
710 | if (
|
711 | ["Burr"].indexOf(kaavioType) === -1 &&
|
712 | !entity.hasOwnProperty("isPartOf")
|
713 | ) {
|
714 | acc.push(entity);
|
715 | }
|
716 | return acc;
|
717 | }, []);
|
718 | }
|
719 | //*/
|
720 | }
|
721 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9Qdmpzb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvMjAxM2EvdG9Qdmpzb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyw2QkFBNkIsQ0FBQztBQUNyQyx1RUFBdUU7QUFFdkUsT0FBTyxFQUFFLE1BQU0sSUFBSSxPQUFPLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDM0MsT0FBTyxFQUNMLE1BQU0sRUFFTixPQUFPLEVBRVAsS0FBSyxFQUVMLFVBQVUsRUFHVixJQUFJLEVBQ0osWUFBWSxFQUNaLE9BQU8sRUFFUCxRQUFRLEVBQ1IsR0FBRyxFQUNILElBQUksRUFDSixTQUFTLEVBRVQsTUFBTSxFQUNOLFNBQVMsRUFDVCxTQUFTLEVBRVYsTUFBTSxXQUFXLENBQUM7QUFDbkIsT0FBTyxLQUFLLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDL0IsT0FBTyxLQUFLLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFFakMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBa0JuRCxPQUFPLEtBQUssU0FBUyxNQUFNLHNDQUFzQyxDQUFDO0FBRWxFLE9BQU8sS0FBSyxZQUFZLE1BQU0saUJBQWlCLENBQUM7QUFFaEQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUN6QyxPQUFPLEVBQ0wsY0FBYyxJQUFJLGtCQUFrQixFQUNwQyxpQkFBaUIsSUFBSSxxQkFBcUIsRUFDM0MsTUFBTSxjQUFjLENBQUM7QUFDdEIsT0FBTyxFQUNMLGNBQWMsSUFBSSxtQkFBbUIsRUFDckMsaUJBQWlCLElBQUksc0JBQXNCLEVBQzVDLE1BQU0sVUFBVSxDQUFDO0FBQ2xCLE9BQU8sRUFDTCxRQUFRLEVBRVIsaUJBQWlCLEVBQ2pCLGFBQWEsRUFDYixZQUFZLEVBQ1osWUFBWSxFQUNaLGFBQWEsRUFDYixzQkFBc0IsRUFDdEIsWUFBWSxFQUNaLGtCQUFrQixFQUNsQixTQUFTLEVBQ1QsbUJBQW1CLEVBQ25CLFFBQVEsRUFDVCxNQUFNLG1CQUFtQixDQUFDO0FBQzNCLE9BQU8sS0FBSyxzQkFBc0IsTUFBTSx5Q0FBeUMsQ0FBQztBQUVsRixPQUFPLEtBQUssb0JBQW9CLE1BQU0sb0JBQW9CLENBQUM7QUFDM0QsT0FBTyxLQUFLLHlCQUF5QixNQUFNLHNCQUFzQixDQUFDO0FBQ2xFLE9BQU8sS0FBSyxzQkFBc0IsTUFBTSxzQkFBc0IsQ0FBQztBQUMvRCxPQUFPLEtBQUssd0JBQXdCLE1BQU0sbUJBQW1CLENBQUM7QUFFOUQsa0VBQWtFO0FBRWxFLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUNoQix5RkFBeUY7SUFDekYsb0dBQW9HO0lBQ3BHLGVBQWU7SUFDZixnQkFBZ0IsRUFBRSxJQUFJO0NBQ3ZCLENBQUMsQ0FBQztBQVVILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUM7QUFFcEMseUJBQ0UsQ0FBcUIsRUFDckIsV0FBOEI7SUFFOUIsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pELE1BQU0sQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUNuQixDQUFDO0FBRUQsb0JBQW9CLG1CQUFtQixFQUFFLE1BQU07SUFDN0MsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLG1CQUFtQixDQUFDO1FBQ3pDLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLENBQUM7UUFDeEIsQ0FBQyxDQUFDLG1CQUFtQixDQUFDO0lBQ3hCLHNEQUFzRDtJQUN0RCxvREFBb0Q7SUFDcEQsc0RBQXNEO0lBQ3RELHFEQUFxRDtJQUNyRCxrREFBa0Q7SUFDbEQsc0JBQXNCO0lBQ3RCLDJDQUEyQztJQUMzQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hDLFNBQVMsQ0FBQyxNQUFNLENBQUM7YUFDZCxNQUFNLENBQ0wsQ0FBQyxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsRUFBRSxFQUFFLENBQzNCLE1BQU0sQ0FBQyxjQUFjLENBQUMsU0FBUyxDQUFDLElBQUksUUFBUSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUNsRTthQUNBLE9BQU8sQ0FBQyxVQUFTLENBQUMsU0FBUyxFQUFFLFdBQVcsQ0FBQztZQUN4QyxVQUFVLENBQUMsV0FBVyxFQUFFLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBQzdDLENBQUMsQ0FBQyxDQUFDO1FBQ0wsT0FBTyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQzlDLE1BQU0sQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0lBQzFCLENBQUM7QUFDSCxDQUFDO0FBRUQsTUFBTSxpQkFBaUIsR0FBRyxLQUFLLENBQUMsVUFBUyxNQUFNLEVBQUUsR0FBRztJQUNsRCxNQUFNLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUM7UUFDL0IsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQztRQUNyQyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQ1gsQ0FBQyxDQUFDLENBQUM7QUFFSCxVQUFVLENBQ1IsU0FBUyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFDaEQsWUFBWSxDQUFDLE9BQU8sQ0FDckIsQ0FBQztBQUNGLFVBQVUsQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7QUFDcEUsVUFBVSxDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0FBQzlFLFVBQVUsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDOUQsVUFBVSxDQUFDLFNBQVMsQ0FBQyxlQUFlLENBQUMsU0FBUyxFQUFFLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUMxRSxVQUFVLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzlELFVBQVUsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDOUQsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsU0FBUyxFQUFFLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUM5RCxVQUFVLENBQUMsU0FBUyxDQUFDLGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRTdFLHFCQUFxQjtBQUNyQixNQUFNLG1CQUNKLDZCQUFvRCxFQUNwRCxVQUFtQjtJQUVuQixpRUFBaUU7SUFDakUsK0RBQStEO0lBQy9ELE1BQU0sV0FBVyxHQUFHLEVBQUUsQ0FBQyw2QkFBNkIsQ0FBQztTQUNsRCxPQUFPLENBQUMsV0FBVyxDQUFDO1NBQ3BCLFdBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUU1QixNQUFNLGNBQWMsR0FBRztRQUNyQiw2RUFBNkU7UUFDN0UsK0JBQStCO1FBQy9CLDJDQUEyQztRQUMzQyx3REFBd0Q7UUFDeEQsRUFBRTtRQUNGLGFBQWEsRUFBRSxTQUFTLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsU0FBUztRQUMvRCxrQkFBa0IsRUFBRSxTQUFTLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQ3pELHNCQUFzQixFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLFFBQVE7UUFDM0QsbUJBQW1CLEVBQUUsU0FBUyxDQUFDLFlBQVksQ0FBQyxTQUFTO1FBQ3JELGdCQUFnQixFQUFFLFNBQVMsQ0FBQyxTQUFTLENBQUMsU0FBUztRQUMvQyxzQkFBc0IsRUFBRSxTQUFTLENBQUMsZUFBZSxDQUFDLFNBQVM7UUFDM0Qsd0JBQXdCLEVBQUUsU0FBUyxDQUFDLGlCQUFpQixDQUFDLFNBQVM7UUFDL0QsZ0JBQWdCLEVBQUUsU0FBUyxDQUFDLFNBQVMsQ0FBQyxTQUFTO1FBQy9DLGdCQUFnQixFQUFFLFNBQVMsQ0FBQyxTQUFTLENBQUMsU0FBUztRQUMvQyxnQkFBZ0IsRUFBRSxTQUFTLENBQUMsU0FBUyxDQUFDLFNBQVM7UUFDL0Msa0JBQWtCLEVBQUUsU0FBUyxDQUFDLFdBQVcsQ0FBQyxTQUFTO1FBQ25ELGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxVQUFVLENBQUMsU0FBUztRQUNqRCxvQ0FBb0MsRUFDbEMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7UUFDdEQsNkNBQTZDLEVBQzNDLFNBQVMsQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDLENBQUM7S0FDaEUsQ0FBQztJQUVGLE1BQU0sU0FBUyxHQUFHLElBQUksU0FBUyxDQUFDLFdBQVcsRUFBRSxTQUFTLEVBQUU7UUFDdEQsRUFBRSxFQUFFLGtEQUFrRDtRQUN0RCxvREFBb0Q7S0FDckQsQ0FBQyxDQUFDO0lBRUgsTUFBTSxXQUFXLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUVwRCxNQUFNLFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FDN0Isb0JBQW9CLEVBQ3BCLHlCQUF5QixFQUN6QixzQkFBc0IsRUFDdEIsd0JBQXdCLENBQ3pCLENBQUM7SUFDRixNQUFNLEVBQ0osOEJBQThCLEVBQzlCLDhCQUE4QixFQUM5QixrQkFBa0IsRUFDbEIsZUFBZSxFQUNmLHVCQUF1QixFQUN2QixxQkFBcUIsRUFDckIsK0JBQStCLEVBQy9CLGlCQUFpQixFQUNqQix3QkFBd0IsRUFDeEIsZUFBZSxFQUNoQixHQUFHLFNBQVMsQ0FBQztJQUVkLEVBQUUsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDZixTQUFTLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FDeEIsU0FBUyxDQUFDLE1BQU0sRUFDaEIsVUFBUyxDQUFDO1lBQ1IsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUM7UUFDbkIsQ0FBQyxFQUNELFVBQVMsT0FBTztZQUNkLE9BQU8sQ0FBQyxFQUFFLEdBQUcsVUFBVSxDQUFDO1lBQ3hCLE1BQU0sQ0FBQyxPQUFPLENBQUM7UUFDakIsQ0FBQyxDQUNGLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxZQUFZLEdBQUcsU0FBUyxDQUFDLGVBQWUsQ0FBQyxDQUFDO0lBRWhELE1BQU0scUJBQXFCLEdBQUcsRUFBRSxDQUFDO1FBQy9CLFdBQVcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxJQUFJLENBQUMsVUFBUyxPQUFPO1lBQzlDLEVBQUUsQ0FBQyxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUMzRCxzQ0FBc0M7Z0JBQ3RDLE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1lBQ2xFLENBQUM7UUFDSCxDQUFDLENBQUM7UUFDRixXQUFXLENBQUMsc0JBQXNCLENBQUM7S0FDcEMsQ0FBQztTQUNDLEtBQUssRUFBRTtTQUNQLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQztTQUN0QixNQUFNLENBQUMsRUFBeUIsRUFBRSxVQUFTLEdBQUcsRUFBRSxhQUFhO1FBQzVELE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ3BDLENBQUMsQ0FBQztTQUVELEdBQUcsQ0FBQyxVQUFTLFFBQTZCO1FBQ3pDLFNBQVMsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUN4QixTQUFTLENBQUMsTUFBTSxFQUNoQixVQUFTLENBQUM7WUFDUixNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUNuQixDQUFDLEVBQ0QsVUFBUyxPQUFPO1lBQ2QsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsQ0FBQztZQUNoRCxvREFBb0Q7WUFDcEQsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLGFBQWEsQ0FBQztZQUMvQixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ25DLEVBQUUsQ0FBQyxDQUNELENBQUMsQ0FBQyxTQUFTO2dCQUNYLFNBQVMsQ0FBQyxNQUFNLEtBQUssQ0FBQztnQkFDdEIsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO2dCQUNuQixJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sS0FBSyxDQUFDO2dCQUM5QixDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7Z0JBQ25CLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsTUFBTSxLQUFLLENBQy9CLENBQUMsQ0FBQyxDQUFDO2dCQUNELGFBQWEsQ0FBQyxZQUFZLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUMxQyxhQUFhLENBQUMsV0FBVyxHQUFHLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzVELENBQUM7WUFBQyxJQUFJLENBQUMsQ0FBQztnQkFDTixhQUFhLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQztnQkFDbEMsYUFBYSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUM7WUFDbkMsQ0FBQztZQUVELE1BQU0sMkJBQTJCLEdBQUcsaUJBQWlCLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDckUsYUFBYSxDQUFDLFdBQVcsR0FBRyxPQUFPLENBQUM7Z0JBQ2xDLDJCQUEyQixDQUFDLE1BQU0sQ0FBQztnQkFDbkMsMkJBQTJCLENBQUMsU0FBUyxDQUFDO2dCQUN0QywyQkFBMkIsQ0FBQyxjQUFjLENBQUM7Z0JBQzNDLDJCQUEyQixDQUFDLFVBQVUsQ0FBQzthQUN4QyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRWQsTUFBTSxPQUFPLEdBQXFDO2dCQUNoRCxpRkFBaUY7YUFDbEYsQ0FBQztZQUNGLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztnQkFDdkIsT0FBTyxDQUFDLElBQUksQ0FBQztvQkFDWCxPQUFPLEVBQUUsYUFBYSxDQUFDLEVBQUUsR0FBRyxHQUFHO2lCQUNoQyxDQUFDLENBQUM7WUFDTCxDQUFDO1lBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ04sdUVBQXVFO2dCQUN2RSxzRUFBc0U7Z0JBQ3RFLG9FQUFvRTtnQkFFcEUsd0RBQXdEO2dCQUN4RCxNQUFNLG9CQUFvQixHQUFHLGFBQWEsQ0FBQyxjQUFjLENBQ3ZELFVBQVUsQ0FDWDtvQkFDQyxDQUFDLENBQUMsWUFBWSxhQUFhLENBQUMsUUFBUSxFQUFFO29CQUN0QyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNQLGFBQWEsQ0FBQyxXQUFXLEdBQUcsU0FBUyxDQUNuQyxrRUFBa0UsSUFBSSxHQUFHLG9CQUFvQixhQUFhLENBQzNHLENBQUM7WUFDSixDQUFDO1lBRUQsTUFBTSxDQUFDLE1BQU0sQ0FDWDtnQkFDRSxVQUFVLEVBQUUsT0FBTzthQUNwQixFQUNELGFBQWEsQ0FDZCxDQUFDO1FBQ0osQ0FBQyxDQUNGLENBQUM7UUFDRixNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztJQUMxQixDQUFDLENBQUM7U0FDRCxNQUFNLENBQUMsVUFBUyxHQUFHO1FBQ2xCLE1BQU0sSUFBSSxNQUFNLENBQ2QsR0FBRyxFQUNIO0tBQ0gsQ0FDRSxDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUM7SUFFTCxNQUFNLG9CQUFvQixHQUFHLEVBQUUsQ0FBQyxXQUFXLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxVQUNuRSxPQUFPO1FBRVAsU0FBUyxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQ3hCLFNBQVMsQ0FBQyxNQUFNLEVBQ2hCLFVBQVMsQ0FBQztZQUNSLE1BQU0sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDO1FBQ25CLENBQUMsRUFDRCxVQUFTLE9BQU87WUFDZCxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQztZQUN4QyxRQUFRLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDMUMsT0FBTyxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7WUFDNUIsTUFBTSxDQUFDLE9BQU8sQ0FBQztRQUNqQixDQUFDLENBQ0YsQ0FBQztRQUNGLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDO0lBQzFCLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxjQUFjLEdBQUcsV0FBVyxDQUFDLG1CQUFtQixDQUFDO1NBQ3BELEdBQUcsQ0FBQywrQkFBK0IsQ0FBQyxVQUFVLENBQUMsQ0FBQztTQUNoRCxHQUFHLENBQUMsVUFBUyxNQUE0QjtRQUN4Qyw2REFBNkQ7UUFDN0QsTUFBTSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFhLENBQUM7UUFDL0QsTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUNoQixDQUFDLENBQUMsQ0FBQztJQUVMLE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxnQkFBZ0IsQ0FBQztTQUM5QyxHQUFHLENBQUMscUJBQXFCLENBQUM7U0FDMUIsT0FBTyxDQUFDLFVBQVMsU0FBUztRQUN6QixNQUFNLENBQUMsRUFBRSxDQUFDLHVCQUF1QixDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxVQUN6RCxZQUFZO1lBRVosTUFBTSxDQUFDLDhCQUE4QixDQUFDLFlBQVksRUFBRSxTQUFTLENBQUMsQ0FBQztRQUNqRSxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQztTQUNELEdBQUcsQ0FBQyx3QkFBd0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBRTFDLE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxnQkFBZ0IsQ0FBQztTQUM5QyxHQUFHLENBQUMsK0JBQStCLENBQUMsT0FBTyxDQUFDLENBQUM7U0FDN0MsR0FBRyxDQUFDLFVBQVMsWUFBa0M7UUFDOUMsTUFBTSxFQUFFLGlCQUFpQixFQUFFLEdBQUcsWUFBWSxDQUFDO1FBQzNDLG1FQUFtRTtRQUNuRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO1lBQ3hCLFlBQVksQ0FBQyxJQUFJLEdBQUcsUUFBUSxDQUMxQixZQUFZLENBQUMsSUFBSSxFQUNqQixnQkFBZ0IsRUFDaEIsbUJBQW1CLEVBQ25CLGlCQUFpQixDQUNOLENBQUM7UUFDaEIsQ0FBQztRQUNELE1BQU0sQ0FBQyxZQUFZLENBQUM7SUFDdEIsQ0FBQyxDQUFDLENBQUM7SUFFTCxNQUFNLFdBQVcsR0FBRyxXQUFXLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxHQUFHLENBQ25ELCtCQUErQixDQUFDLE9BQU8sQ0FBQyxDQUN6QyxDQUFDO0lBRUYsTUFBTSxxQkFBcUIsR0FBRyxXQUFXLENBQUMsc0JBQXNCLENBQUMsQ0FBQyxHQUFHLENBQ25FLHFCQUFxQixDQUN0QixDQUFDO0lBQ0YsTUFBTSx1QkFBdUIsR0FBRyxXQUFXLENBQUMsd0JBQXdCLENBQUMsQ0FBQyxHQUFHLENBQ3ZFLHFCQUFxQixDQUN0QixDQUFDO0lBQ0YsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDO1FBQ3BCLHFCQUFxQjthQUNsQixJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsa0JBQWtCLENBQUM7YUFDdkIsR0FBRyxDQUFDLHdCQUF3QixDQUFDLGFBQWEsQ0FBQyxDQUFDO1FBQy9DLHVCQUF1QjthQUNwQixJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsa0JBQWtCLENBQUM7YUFDdkIsR0FBRyxDQUFDLHdCQUF3QixDQUFDLGVBQWUsQ0FBQyxDQUFDO0tBQ2xELENBQUMsQ0FBQyxLQUFLLEVBQWlDLENBQUM7SUFFMUMsTUFBTSxZQUFZLEdBQUcsRUFBRSxDQUFDO1FBQ3RCLHFCQUFxQixDQUFDLElBQUksRUFBRTtRQUM1Qix1QkFBdUIsQ0FBQyxJQUFJLEVBQUU7S0FDL0IsQ0FBQztTQUNDLEtBQUssRUFBRTtTQUNQLE1BQU0sQ0FBQyxVQUFTLFFBQXFCO1FBQ3BDLE1BQU0sQ0FBQyxDQUNMLGFBQWEsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDO1lBQ2hDLGFBQWEsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUN4QyxDQUFDO0lBQ0osQ0FBQyxDQUFDO1NBQ0QsT0FBTyxDQUFDLFVBQVMsUUFBcUI7UUFDckMsTUFBTSxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsR0FBRyxRQUFRLENBQUM7UUFDdkMsTUFBTSw0QkFBNEIsR0FBRyw4QkFBOEIsQ0FDakUsUUFBUSxDQUNULENBQUM7UUFFRixNQUFNLFdBQVcsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDO1FBQ3BDLE1BQU0sQ0FBQyxFQUFFLENBQUMsV0FBVyxDQUFDO2FBQ25CLEdBQUcsQ0FBQyxVQUFTLFVBQXVCO1lBQ25DLE1BQU0sV0FBVyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUM7WUFDckMsRUFBRSxDQUFDLENBQUMsV0FBVyxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUM7Z0JBQzNCLCtEQUErRDtnQkFDL0Qsd0RBQXdEO2dCQUN4RCx1REFBdUQ7Z0JBQ3ZELGdFQUFnRTtnQkFDaEUsa0VBQWtFO2dCQUNsRSx3REFBd0Q7Z0JBQ3hELEVBQUUsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO29CQUN0QyxPQUFPLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRTt3QkFDM0IsTUFBTSxFQUFFLENBQUM7d0JBQ1QsS0FBSyxFQUFFLENBQUM7cUJBQ1QsQ0FBQyxDQUFDO2dCQUNMLENBQUM7Z0JBQUMsSUFBSSxDQUFDLENBQUM7b0JBQ04sVUFBVSxDQUFDLEtBQUssR0FBRyxXQUFXLENBQUM7b0JBQy9CLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFO3dCQUMzQixNQUFNLEVBQUUsQ0FBQzt3QkFDVCxLQUFLLEVBQUUsQ0FBQztxQkFDVCxDQUFDLENBQUM7Z0JBQ0wsQ0FBQztZQUNILENBQUM7WUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsV0FBVyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BDLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFO29CQUMzQixNQUFNLEVBQUUsQ0FBQztvQkFDVCxLQUFLLEVBQUUsQ0FBQztpQkFDVCxDQUFDLENBQUM7WUFDTCxDQUFDO1lBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ04sTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsV0FBVyxxQkFBcUIsQ0FBQyxDQUFDO1lBQ3JFLENBQUM7WUFFRCxNQUFNLENBQUMsVUFBVSxDQUFDO1FBQ3BCLENBQUMsQ0FBQzthQUNELEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQzthQUMxQixHQUFHLENBQUMsVUFBUyxVQUF1QjtZQUNuQyxNQUFNLGNBQWMsR0FBRyw0QkFBNEIsQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUNoRSxjQUFjLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQztZQUNsQyxNQUFNLENBQUMsY0FBYyxDQUFDO1FBQ3hCLENBQUMsQ0FBQzthQUNELEdBQUcsQ0FBQyx3QkFBd0IsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQzdDLENBQUMsQ0FBQyxDQUFDO0lBRUwsTUFBTSxXQUFXLEdBQWlDLFdBQVcsQ0FDM0QsZ0JBQWdCLENBQ2pCO1NBQ0UsR0FBRyxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxDQUFDO1NBR25DLE1BQU0sQ0FBQyxDQUFDLEtBQWtCLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDO1NBQ2hELEdBQUcsQ0FBQywrQkFBK0IsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBRWpELE1BQU0sS0FBSyxHQUFHLENBQUMsYUFBYSxFQUFFLGVBQWUsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sS0FBSyxHQUFHLENBQUMsVUFBVSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBRS9ELHdCQUNFLENBQUM7UUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztnQkFtRUU7UUFFRiwrQkFDRSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQW1CLEVBQ3hDLENBQUMsR0FBRyxDQUFDO1lBRUwscUVBQXFFO1lBQ3JFLHNFQUFzRTtZQUN0RSx5QkFBeUI7WUFDekIsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLGVBQWUsQ0FBQyxDQUFDLENBQUM7Z0JBQ2pELE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztZQUNqQyxDQUFDO1lBQ0QsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNQLE1BQU0sQ0FBQyxxQkFBcUIsQ0FDMUIsZ0JBQWdCLENBQUMsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLENBQUMsRUFDekMsQ0FBQyxDQUNGLENBQUM7UUFDSixDQUFDO1FBRUQsMEJBQTBCLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBbUI7WUFDaEUsSUFBSSxDQUFDLHFCQUFxQixFQUFFLGFBQWEsQ0FBQyxHQUFHLFNBQVMsQ0FBQyxVQUNyRCxZQUFZO2dCQUVaLE1BQU0sWUFBWSxHQUFHLFFBQVEsQ0FDM0IsWUFBWSxDQUFDLFFBQVEsRUFDckIsWUFBWSxDQUFDLG1CQUFtQixDQUFDLEVBQ2pDLFlBQVksQ0FBQyxZQUFZLENBQzFCLENBQUM7Z0JBRUYsTUFBTSxDQUFDO2dCQUNMOzs7OzhCQUlEO2dCQUNDLFlBQVksQ0FBQyxZQUFZLEVBQUUsU0FBUyxDQUFDLENBQUMsTUFBTSxLQUFLLFlBQVksQ0FBQyxNQUFNLENBQ3JFLENBQUM7WUFDSixDQUFDLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFFYixxQkFBcUI7aUJBbUJsQixPQUFPLENBQUMsVUFBUyxZQUFZO2dCQUM1QixTQUFTLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNsQyxDQUFDLENBQUMsQ0FBQztZQUVMLE1BQU0sQ0FBQztnQkFDTCxTQUFTLEVBQUUsU0FBUztnQkFDcEIsUUFBUSxFQUFFLGFBQWE7YUFDeEIsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLENBQUMsQ0FDTCxDQUFDO2FBR0UsTUFBTSxDQUNMO1lBQ0UsU0FBUyxFQUFFLEVBQUU7WUFDYixRQUFRLEVBQUUsRUFBRTtTQUNiLEVBQ0QsVUFDRSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQW1CLEVBQ3hDLFlBQXFDO1lBRXJDLFFBQVEsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDNUIsTUFBTSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDbkQsQ0FBQyxDQUNGO2FBQ0EsR0FBRyxDQUFDLHFCQUFxQixDQUFDO2FBQzFCLEdBQUcsQ0FBQyxVQUFTLEdBQW9CO1lBQ2hDLE1BQU0sRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLEdBQUcsR0FBRyxDQUFDO1lBQ3BDLE1BQU0sQ0FBQyxTQUFTO2lCQUNiLEdBQUcsQ0FDRixDQUFDLEVBQVUsRUFBZ0IsRUFBRSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQyxDQUNoRTtpQkFDQSxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDdEIsQ0FBQyxDQUFDO2FBQ0QsUUFBUSxFQUFFLENBQ2QsQ0FBQztJQUNKLENBQUM7SUFFRCxNQUFNLGtCQUFrQixHQUFHLEVBQUUsQ0FBQztRQUM1QixFQUFFLENBQUMsQ0FBQyxjQUFjLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRTtRQUNuRSxFQUFFLENBQ0EsQ0FBQyxVQUFVLEVBQUUsWUFBWSxDQUV0QixDQUNKLENBQUMsS0FBSyxFQUFFO1FBQ1QsV0FBVztLQUNaLENBQUM7U0FDQyxRQUFRLEVBQUU7U0FFVixJQUFJLENBQUMsZUFBZSxDQUFDO1NBQ3JCLE9BQU8sQ0FBQyxjQUFjLENBQUM7U0FDdkIsT0FBTyxDQUFDLFVBQ1AsWUFBcUM7UUFRckMsTUFBTSxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsR0FBRyxZQUFZLENBQUM7UUFFcEMsd0RBQXdEO1FBQ3hELHNEQUFzRDtRQUN0RCxrQ0FBa0M7UUFDbEMsTUFBTSw2QkFBNkIsR0FBRyxJQUFJLENBQUM7WUFDekMsaUJBQWlCLENBQUMsRUFBRSxDQUFDO1lBQ3JCLFlBQVk7U0FDYixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFpQixDQUFDO1FBQ3RCLEVBQUUsQ0FBQyxDQUFDLGtCQUFrQixDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNyQyxNQUFNLFlBQVksR0FBRyxZQUFZLENBQUMsWUFBWSxDQUFDO1lBQy9DLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQyxPQUFPLENBQUMsVUFBUyxRQUFnQjtnQkFDdEQsTUFBTSxTQUFTLEdBQUcsa0JBQWtCLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNyRCxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztvQkFDakMsU0FBUyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztnQkFDckIsQ0FBQztnQkFDRCxrQkFBa0IsQ0FBQyxRQUFRLENBQUMsR0FBRyxTQUFTLENBQUM7WUFDM0MsQ0FBQyxDQUFDLENBQUM7WUFFSCxFQUFFLENBQUMsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUMvQixpQkFBaUIsR0FBRyxFQUFFLENBQ3BCLDhCQUE4QixDQUFDLFlBQVksQ0FBQyxDQUM3QyxDQUFDLEdBQUcsQ0FBQyxVQUNKLGdCQUFpRTtvQkFFakUsRUFBRSxDQUFDLENBQUMsWUFBWSxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDO3dCQUNuQyxNQUFNLEVBQUUsaUJBQWlCLEVBQUUsR0FBRyxZQUFZLENBQUM7d0JBQzNDLE1BQU0sQ0FDSixxQkFBcUIsRUFDckIscUJBQXFCLENBQ3RCLEdBQUcsaUJBQWlCLENBQUMsY0FBYyxDQUFDO3dCQUNyQyxpQkFBaUIsQ0FBQyxNQUFNLEdBQUc7NEJBQ3pCLHFCQUFxQixHQUFHLGdCQUFnQixDQUFDLEtBQUs7NEJBQzlDLHFCQUFxQixHQUFHLGdCQUFnQixDQUFDLE1BQU07eUJBQ2hELENBQUM7d0JBQ0YsWUFBWSxDQUFDLGlCQUFpQixHQUFHLElBQUksQ0FDbkMsQ0FBQyxnQkFBZ0IsQ0FBQyxFQUNsQixpQkFBaUIsQ0FDbEIsQ0FBQztvQkFDSixDQUFDO29CQUNELGVBQWUsQ0FBQyxZQUFZLENBQUMsQ0FBQztvQkFFOUIseURBQXlEO29CQUN6RCxrREFBa0Q7b0JBQ2xELGdCQUFnQixDQUFDLEtBQUssR0FBRyxnQkFBZ0IsQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO29CQUN0RCw2QkFBNkIsQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztvQkFDdEQsZUFBZSxDQUFDLGdCQUFnQixDQUFDLENBQUM7b0JBRWxDLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDO2dCQUMxQixDQUFDLENBQUMsQ0FBQztZQUNMLENBQUM7WUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsWUFBWSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdEMsSUFBSSxDQUFDO29CQUNILE1BQU0sVUFBVSxHQUFHLHFCQUFxQixDQUN0QyxTQUFTLENBQUMsTUFBTSxDQUFDLFlBRWhCLEVBQ0QsWUFBWSxDQUNiLENBQUM7b0JBQ0YsU0FBUyxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQ3hCLFNBQVMsQ0FBQyxNQUFNLEVBQ2hCLFVBQVMsQ0FBQzt3QkFDUixNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUM7b0JBQzVCLENBQUMsRUFDRCw2QkFBNkIsQ0FDOUIsQ0FBQztvQkFFRixlQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7b0JBRTVCLGlCQUFpQixHQUFHLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO2dCQUM3QyxDQUFDO2dCQUFDLEtBQUssQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7b0JBQ2IsTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzNCLENBQUM7WUFDSCxDQUFDO1lBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQ04sTUFBTSxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQ2pCLElBQUksTUFBTSxDQUNSOzs7O1VBSUosSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQzs7T0FFM0MsQ0FDTSxDQUNGLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQztRQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3ZDLHVFQUF1RTtZQUN2RSwrREFBK0Q7WUFDL0QsMkNBQTJDO1lBQzNDLE1BQU0sY0FBYyxHQUFHLFlBQVksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDO1lBQ3BELEVBQUUsQ0FBQyxDQUFDLGNBQWMsS0FBSyxDQUFDLElBQUksWUFBWSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BFLEVBQUUsQ0FBQyxDQUFDLGNBQWMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO29CQUN6QixNQUFNLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FDakIsSUFBSSxLQUFLLENBQ1A7O1VBRU4sSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQztTQUN6QyxDQUNNLENBQ0YsQ0FBQztnQkFDSixDQUFDO2dCQUNELEVBQUUsQ0FBQyxDQUFDLFlBQVksQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDO29CQUM1QyxNQUFNLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FDakIsSUFBSSxLQUFLLENBQ1A7O1VBRU4sSUFBSSxDQUFDLFNBQVMsQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQztTQUN6QyxDQUNNLENBQ0YsQ0FBQztnQkFDSixDQUFDO2dCQUNELGlCQUFpQixHQUFHLEVBQUUsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO1lBQzdDLENBQUM7WUFBQyxJQUFJLENBQUMsQ0FBQztnQkFDTixNQUFNLGNBQWMsR0FBRyxZQUFZLENBQUMsRUFBRSxDQUFDO2dCQUN2QyxJQUFJLENBQUM7b0JBQ0gsaUJBQWlCLEdBQUcsRUFBRSxDQUNwQixZQUFZLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FDdkIsV0FBVyxDQUFDLEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FDMUQsQ0FDRjt5QkFDRSxNQUFNLENBQUMsYUFBYSxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsVUFBVSxLQUFLLE9BQU8sQ0FBQzt5QkFDN0QsT0FBTyxFQUFFO3lCQUNULEdBQUcsQ0FBQyxVQUNILGVBQXNEO3dCQUV0RCxNQUFNLFdBQVcsR0FBRyxzQkFBc0IsQ0FDeEMsZUFBZSxFQUNmLFlBQVksQ0FDYixDQUFDO3dCQUNGLE1BQU0sZUFBZSxHQUFHLFNBQVMsQ0FBQyxlQUFlLENBQUM7d0JBQ2xELFdBQVcsQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUMzQjs0QkFDRSxVQUFTLFlBQVk7Z0NBQ25CLE1BQU0sQ0FBQyxlQUFlLENBQUMsWUFBWSxDQUFDLENBQUM7NEJBQ3ZDLENBQUM7eUJBQ0YsRUFDRCxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUMvQixDQUFDO3dCQUVGLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLFdBQVcsQ0FBQzt3QkFFakMsTUFBTSxvQkFBb0IsR0FBRyxlQUFlLENBQUMsR0FBRyxDQUFDLFVBQy9DLGFBQWE7NEJBRWIsRUFBRSxDQUFDLENBQUMsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQ0FDaEMsYUFBYSxDQUFDLE1BQU0sR0FBRyxHQUFHLENBQUMsVUFBUyxLQUFLO29DQUN2QyxLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztvQ0FDYixLQUFLLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztvQ0FDYixNQUFNLENBQUMsS0FBSyxDQUFDO2dDQUNmLENBQUMsRUFBRSxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7NEJBQzNCLENBQUM7NEJBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLHNCQUFzQixDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQ0FDakQsYUFBYSxDQUFDLE1BQU0sQ0FBQztnQ0FDckIsYUFBYSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7Z0NBQ3JCLGFBQWEsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDOzRCQUN2QixDQUFDOzRCQUFDLElBQUksQ0FBQyxDQUFDO2dDQUNOLE1BQU0sQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUNqQixJQUFJLEtBQUssQ0FDUDs7Y0FFVixJQUFJLENBQUMsU0FBUyxDQUFDLGFBQWEsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDOztjQUV6QyxJQUFJLENBQUMsU0FBUyxDQUFDLFdBQVcsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDO2FBQ3hDLENBQ1UsQ0FDRixDQUFDOzRCQUNKLENBQUM7NEJBQ0Qsa0VBQWtFOzRCQUNsRSxnRUFBZ0U7NEJBQ2hFLDhEQUE4RDs0QkFDOUQsZ0VBQWdFOzRCQUNoRSxvQ0FBb0M7NEJBQ3BDLDhCQUE4Qjs0QkFDOUIsb0JBQW9COzRCQUNwQixpQ0FBaUM7NEJBQ2pDLGFBQWEsQ0FBQyxRQUFRLEdBQUcsRUFBRSxDQUFDOzRCQUM1QixNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsVUFBVSxDQUFDLEVBQUUsYUFBYSxDQUFDLENBQUM7d0JBQzNDLENBQUMsQ0FBQyxDQUFDO3dCQUVILG9CQUFvQixDQUFDLE9BQU8sQ0FBQyxVQUFTLFlBQVk7NEJBQ2hELGVBQWUsQ0FBQyxZQUFZLENBQUMsQ0FBQzt3QkFDaEMsQ0FBQyxDQUFDLENBQUM7d0JBRUgsZUFBZSxDQUFDLFdBQVcsQ0FBQyxDQUFDO3dCQUU3QixTQUFTLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FDeEIsU0FBUyxDQUFDLE1BQU0sRUFDaEIsVUFBUyxDQUFDOzRCQUNSLE1BQU0sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQzt3QkFDNUIsQ0FBQyxFQUNELFVBQVMsUUFBUTs0QkFDZixNQUFNLENBQUMsNkJBQTZCLENBQ2xDLFVBQVUsQ0FBQyxRQUFRLEVBQUUsb0JBQW9CLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQ3pELEVBQUUsQ0FDSCxDQUFDO3dCQUNKLENBQUMsQ0FDRixDQUFDO3dCQUVGLE1BQU0sQ0FBQyxXQUFXLENBQUM7b0JBQ3JCLENBQUMsQ0FBQzt5QkFDRCxHQUFHLENBQUMsVUFBUyxZQUFZO3dCQUN4QixNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztvQkFDMUIsQ0FBQyxDQUFDLENBQUM7Z0JBQ1AsQ0FBQztnQkFBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO29CQUNiLE1BQU0sQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUMzQixDQUFDO1lBQ0gsQ0FBQztRQUNILENBQUM7UUFBQyxJQUFJLENBQUMsQ0FBQztZQUNOLGVBQWUsQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUM5QixTQUFTLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FDeEIsU0FBUyxDQUFDLE1BQU0sRUFDaEIsVUFBUyxDQUFDO2dCQUNSLE1BQU0sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQztZQUM1QixDQUFDLEVBQ0QsNkJBQTZCLENBQzlCLENBQUM7WUFDRixpQkFBaUIsR0FBRyxFQUFFLENBQUMsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUM3QyxDQUFDO1FBRUQsTUFBTSxDQUFDLGlCQUFpQixDQUFDO0lBQzNCLENBQUMsQ0FBQyxDQUFDO0lBRUwsa0JBQWtCLENBQUMsT0FBTyxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUMsSUFBSSxDQUFDO1FBQ3ZDLFNBQVMsQ0FBQyx3QkFBd0IsQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUMzQyxDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sOEJBQThCLEdBQUcsRUFBRSxDQUN2QyxXQUFXLENBQUMsNkNBQTZDLENBQUMsQ0FDM0Q7U0FDRSxHQUFHLENBQUMsd0JBQXdCLENBQUMsMEJBQTBCLENBQUMsQ0FBQztTQUN6RCxHQUFHLENBQUMsVUFBUyx3QkFBNkM7UUFDekQsTUFBTSxjQUFjLEdBQUcsd0JBQXdCLENBQUMsUUFBUSxDQUFDO1FBQ3pELElBQUksYUFBYSxHQUFHLHNCQUFzQixDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQzNELEVBQUUsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQztZQUNuQixNQUFNLENBQUMsRUFBRSxDQUFDLFNBQVMsQ0FDakIsSUFBSSxLQUFLLENBQ1A7cUNBQ3lCLGNBQWM7NkJBQ3RCLHdCQUF3QixDQUFDLGNBQWM7T0FDN0QsQ0FDSSxDQUNGLENBQUM7WUFDRjs7Ozs7d0JBS0E7UUFDRixDQUFDO1FBQ0Qsd0JBQXdCLENBQUMsRUFBRTtZQUN6QixhQUFhLEdBQUcsd0JBQXdCLENBQUMsY0FBYyxDQUFDO1FBQzFELE1BQU0sQ0FBQyx3QkFBd0IsQ0FBQztJQUNsQyxDQUFDLENBQUM7U0FDRCxPQUFPLEVBQUU7U0FDVCxHQUFHLENBQUMsVUFBUywwQkFBK0M7UUFDM0Qsc0RBQXNEO1FBRXRELFNBQVMsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsVUFBUyxDQUFDO1lBQ3JELE1BQU0sRUFBRSxPQUFPLEVBQUUsWUFBWSxFQUFFLEdBQUcsQ0FBQyxDQUFDO1lBRXBDLDBCQUEwQixDQUFDLE9BQU8sQ0FBQyxVQUFTLHdCQUF3QjtnQkFDbEUsTUFBTSxFQUFFLEVBQUUsRUFBRSxHQUFHLHdCQUF3QixDQUFDO2dCQUN4QyxZQUFZLENBQUMsRUFBRSxDQUFDLEdBQUcsd0JBQXdCLENBQUM7Z0JBQzVDLEVBQUUsQ0FBQyxDQUFDLHdCQUF3QixDQUFDLFFBQVEsS0FBSyxrQkFBa0IsQ0FBQyxDQUFDLENBQUM7b0JBQzdELE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO2dCQUN4QixDQUFDO1lBQ0gsQ0FBQyxDQUFDLENBQUM7WUFFSCxNQUFNLENBQUMsQ0FBQyxDQUFDO1FBQ1gsQ0FBQyxDQUFDLENBQUM7UUFDSCxNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztJQUMxQixDQUFDLENBQUMsQ0FBQztJQUVMLE1BQU0scUJBQXFCLEdBQUcsRUFBRSxDQUM5QixXQUFXLENBQUMsb0NBQW9DLENBQUMsQ0FDbEQ7U0FDRSxHQUFHLENBQUMsd0JBQXdCLENBQUMsaUJBQWlCLENBQUMsQ0FBQztTQUNoRCxPQUFPLEVBQUU7U0FDVCxHQUFHLENBQUMsVUFBUyxnQkFBeUM7UUFDckQsZ0JBQWdCO2FBR2IsSUFBSSxDQUFDLFVBQVMsQ0FBQyxFQUFFLENBQUM7WUFDakIsTUFBTSxLQUFLLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMvQixNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9CLEVBQUUsQ0FBQyxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDO2dCQUNsQixNQUFNLENBQUMsQ0FBQyxDQUFDO1lBQ1gsQ0FBQztZQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQztnQkFDekIsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ1osQ0FBQztZQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNOLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDWCxDQUFDO1FBQ0gsQ0FBQyxDQUFDO2FBQ0QsT0FBTyxDQUFDLFVBQVMsZUFBZSxFQUFFLENBQUM7WUFDbEMsZUFBZSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQzlDLENBQUMsQ0FBQyxDQUFDO1FBQ0wsTUFBTSxDQUFDLGdCQUFnQixDQUFDO0lBQzFCLENBQUMsQ0FBQztTQUNELEdBQUcsQ0FBQyxVQUFTLGdCQUF5QztRQUNyRCxzREFBc0Q7UUFFdEQsU0FBUyxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQ3hCLFNBQVMsQ0FBQyxNQUFNLEVBQ2hCLFVBQVMsQ0FBQztZQUNSLE1BQU0sQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDO1FBQ3hCLENBQUMsRUFDRCxVQUFTLFlBQVk7WUFDbkIsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLFVBQVMsZUFBZTtnQkFDL0MsWUFBWSxDQUFDLGVBQWUsQ0FBQyxFQUFFLENBQUMsR0FBRyxlQUFlLENBQUM7WUFDckQsQ0FBQyxDQUFDLENBQUM7WUFDSCxNQUFNLENBQUMsWUFBWSxDQUFDO1FBQ3RCLENBQUMsQ0FDRixDQUFDO1FBQ0YsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUM7SUFDMUIsQ0FBQyxDQUFDLENBQUM7SUFFTDs7O1NBR0U7SUFDRixNQUFNLENBQUMsRUFBRSxDQUFDO1FBQ1IscUJBQXFCO1FBQ3JCLG9CQUFvQjtRQUNwQixrQkFBa0I7UUFDbEIsOEJBQThCO1FBQzlCLHFCQUFxQjtLQUN0QixDQUFDO1NBQ0MsS0FBSyxFQUFFO1NBQ1AsTUFBTSxDQUFDLFVBQVMsR0FBRztRQUNsQixNQUFNLElBQUksTUFBTSxDQUNkLEdBQUcsRUFDSCw0QkFBNEIsVUFBVTtLQUN6QyxDQUNFLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVMLGlFQUFpRTtJQUNqRSxxQ0FBcUM7SUFDckMsbUNBQW1DO0lBQ25DLHdFQUF3RTtJQUN4RSwrQ0FBK0M7SUFDL0M7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztVQWdERztBQUNMLENBQUMifQ== |
\ | No newline at end of file |