1 | import { isFinite, map, omit, toPairs } from "lodash/fp";
|
2 | import { isAttachablePoint, isDefinedCXML, isGPMLAnchor, isPvjsonBurr, isPvjsonSingleFreeNode, isPvjsonGroup, unionLSV } from "../gpml-utilities";
|
3 | import { SmartPoint } from "../geom-utils";
|
4 | import { calculateAllPoints } from "./calculateAllPoints";
|
5 | import * as VError from "verror";
|
6 | import * as MarkerMappings from "./MarkerMappings.json";
|
7 | // a stub is a short path segment that is used for the first and/or last segment(s) of a path
|
8 | export const DEFAULT_STUB_LENGTH = 20;
|
9 | /**
|
10 | * getOffsetAndOrientationScalarsAlongAxis
|
11 | *
|
12 | * @param relValue {number}
|
13 | * @param axis {string}
|
14 | * @param referencedEntity
|
15 | * @return {OffsetOrientationAndPositionScalarsAlongAxis}
|
16 | */
|
17 | function getOffsetAndOrientationScalarsAlongAxis(positionScalar, relativeOffsetScalar, axis,
|
18 | // TODO are we correctly handling the case of a group as the referenced
|
19 | // entity? Do we have the group width and height yet to properly calculate
|
20 | // this?
|
21 | referencedEntity) {
|
22 | let offsetScalar = relativeOffsetScalar *
|
23 | (axis === "x" ? referencedEntity.width : referencedEntity.height);
|
24 | // TODO WP536 has a referenced entity that lacks width/height. Why?
|
25 | // The referenced entity was a group.
|
26 | // Is the problem that the group was nested?
|
27 | // Or is it a problem with the order of evaluation of entities (trying to
|
28 | // parse a dependent entity before its dependencies were parsed)?
|
29 | if (!isFinite(offsetScalar)) {
|
30 | throw new Error(`
|
31 | Got non-finite value ${offsetScalar} for offsetScalar
|
32 | along ${axis} axis for
|
33 | getOffsetAndOrientationScalarsAlongAxis(
|
34 | positionScalar=${positionScalar},
|
35 | relativeOffsetScalar=${relativeOffsetScalar},
|
36 | referencedEntity=
|
37 | ${JSON.stringify(referencedEntity, null, " ")}
|
38 | )
|
39 | `);
|
40 | }
|
41 | // orientationScalar here refers to the initial direction the edge takes as
|
42 | // it moves away from the entity to which it is attached.
|
43 | let orientationScalar;
|
44 | if (positionScalar === 0) {
|
45 | orientationScalar = -1;
|
46 | }
|
47 | else if (positionScalar === 1) {
|
48 | orientationScalar = 1;
|
49 | }
|
50 | else {
|
51 | orientationScalar = 0;
|
52 | }
|
53 | return { offsetScalar, orientationScalar, positionScalar };
|
54 | }
|
55 | /**
|
56 | * preprocessGPML
|
57 | *
|
58 | * @param edge {GPMLEdge}
|
59 | * @return {GPMLEdge}
|
60 | */
|
61 | export function preprocessGPML(Edge) {
|
62 | const isAttachedToOrVia = Edge.Graphics.Point
|
63 | .filter(p => p.GraphRef && isDefinedCXML(p.GraphRef))
|
64 | .map(p => p.GraphRef);
|
65 | if (isAttachedToOrVia.length > 0) {
|
66 | // In pvjson, an edge attaches directly to another entity (Node, Edge, Group),
|
67 | // not to an anchor.
|
68 | // If the edge attaches to another edge, it does so VIA an anchor.
|
69 | Edge["isAttachedToOrVia"] = isAttachedToOrVia;
|
70 | }
|
71 | return Edge;
|
72 | }
|
73 | /**
|
74 | * postprocessPVJSON
|
75 | *
|
76 | * @param referencedEntities
|
77 | * @param pvjsonEdge {pvjsonEdge}
|
78 | * @return {pvjsonEdge}
|
79 | */
|
80 | export function postprocessPVJSON(referencedEntities, pvjsonEdge) {
|
81 | const { points, drawAs } = pvjsonEdge;
|
82 | const pointCount = points.length;
|
83 | let index = 0;
|
84 | const pvjsonEdgeIsAttachedTo = [];
|
85 | const providedPvjsonPoints = map(function (point) {
|
86 | const { marker, x, y } = point;
|
87 | if (!!marker) {
|
88 | // NOTE: side effects below
|
89 | if (index === 0) {
|
90 | pvjsonEdge.markerStart = marker;
|
91 | }
|
92 | else if (index === pointCount - 1) {
|
93 | pvjsonEdge.markerEnd = marker;
|
94 | }
|
95 | if (MarkerMappings.hasOwnProperty(marker)) {
|
96 | pvjsonEdge.type = toPairs(MarkerMappings[marker]).reduce(function (acc, [namespace, moreTypes]) {
|
97 | return unionLSV(acc, moreTypes);
|
98 | }, pvjsonEdge.type);
|
99 | }
|
100 | }
|
101 | if (isAttachablePoint(point)) {
|
102 | // NOTE: pvjson allows for expressing one edge attached to another edge.
|
103 | // When we do this, we say that the POINT attaches to an ANCHOR on the other edge,
|
104 | // but the EDGE attaches to the other EDGE, never the anchor.
|
105 | const { isAttachedTo, attachmentDisplay } = point;
|
106 | if (!attachmentDisplay.offset) {
|
107 | throw new Error(`attachmentDisplay for a Point has no offset property.
|
108 | postprocessPVJSON(
|
109 | referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
|
110 | pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
|
111 | )`);
|
112 | }
|
113 | // entityReferencedByPoint can be a regular node (DataNode, Shape, Label)
|
114 | // or an Anchor. If connected to an Anchor, the biological meaning is
|
115 | // that the edge is connected to another edge, but in this code, we
|
116 | // implement this by treating the Anchor as a node, as if it were
|
117 | // a "burr" that is always stuck (isAttachedTo) the other edge.
|
118 | const entityReferencedByPoint = referencedEntities &&
|
119 | !!isAttachedTo &&
|
120 | referencedEntities[isAttachedTo];
|
121 | const entityIdReferencedByEdge = isGPMLAnchor(entityReferencedByPoint)
|
122 | ? entityReferencedByPoint.isAttachedTo
|
123 | : entityReferencedByPoint.id;
|
124 | // WARNING: side effect
|
125 | pvjsonEdgeIsAttachedTo.push(entityIdReferencedByEdge);
|
126 | const entityReferencedByEdge = referencedEntities[entityIdReferencedByEdge];
|
127 | const orientation = (point.orientation =
|
128 | point.orientation || []);
|
129 | // attachmentDisplay: { position: [x: number, y: number], offset: [xOffset: number, yOffset: number], orientation: [dx: number, dy: number] }
|
130 | //
|
131 | // x = xDistance / width (relative: [0,1])
|
132 | // y = yDistance / height (relative: [0,1])
|
133 | // xOffset = distance offset in x direction (absolute)
|
134 | // yOffset = distance offset in y direction (absolute)
|
135 | // dx = x component of edge emanation angle (unit: [0,1])
|
136 | // dy = y component of edge emanation angle (unit: [0,1])
|
137 | //
|
138 | // 0 ----------------- x ------------------->
|
139 | // | ========================================
|
140 | // | || ||
|
141 | // | || ||
|
142 | // | || ||
|
143 | // y || ||
|
144 | // | || ||
|
145 | // | || ||
|
146 | // | || ||
|
147 | // | || ||
|
148 | // v ===================*====================
|
149 | // |
|
150 | // yOffset |
|
151 | // | |
|
152 | // v |
|
153 | // ----------*
|
154 | // xOffset> \
|
155 | // \
|
156 | // \ dx>
|
157 | // dy \
|
158 | // | \
|
159 | // v \
|
160 | // \
|
161 | //
|
162 | // example above is an attachmentDisplay specifying an edge that emanates down and to the right
|
163 | // at a 45 deg. angle (1, 1), offset right 5 x units and down 11 y units from the center (0.5)
|
164 | // of the bottom side (1) of the node: {position: [0.75, 1], offset: [5, 11], orientation: [1, 1]}
|
165 | //
|
166 | //
|
167 | // where x is distance from left side along width axis as a percentage of the total width
|
168 | // y is distance from top side along height axis as a percentage of the total height
|
169 | // offsetX, offsetY are obvious from the name. Notice they are absolute, unlike x,y.
|
170 | // dx, dy are unit vector coordinates of a point that specifies how the edge emanates from the node
|
171 | if (isPvjsonSingleFreeNode(entityReferencedByEdge) ||
|
172 | isPvjsonGroup(entityReferencedByEdge) ||
|
173 | isPvjsonBurr(entityReferencedByEdge)) {
|
174 | const { position, relativeOffset } = attachmentDisplay;
|
175 | // edge connected to a SingleFreeNode, a Group or a Burr, but NOT another edge or an anchor
|
176 | try {
|
177 | const { offsetScalar: offsetScalarX, orientationScalar: orientationScalarX } = getOffsetAndOrientationScalarsAlongAxis(position[0], relativeOffset[0], "x", entityReferencedByEdge);
|
178 | const { offsetScalar: offsetScalarY, orientationScalar: orientationScalarY } = getOffsetAndOrientationScalarsAlongAxis(position[1], relativeOffset[1], "y", entityReferencedByEdge);
|
179 | if (index === 0) {
|
180 | orientation[0] = orientationScalarX;
|
181 | orientation[1] = orientationScalarY;
|
182 | }
|
183 | else {
|
184 | orientation[0] = -1 * orientationScalarX;
|
185 | orientation[1] = -1 * orientationScalarY;
|
186 | }
|
187 | // TODO is there a case where we would ever use offset for edges?
|
188 | attachmentDisplay.offset[0] = offsetScalarX;
|
189 | attachmentDisplay.offset[1] = offsetScalarY;
|
190 | point.attachmentDisplay = omit(["relativeOffset"], attachmentDisplay);
|
191 | }
|
192 | catch (err) {
|
193 | throw new VError(err, `
|
194 | Error for:
|
195 | postprocessPVJSON(
|
196 | referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
|
197 | pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
|
198 | )
|
199 | `);
|
200 | /* TODO should we use this?
|
201 | console.warn(`Setting offsetScalar equal to 0.`);
|
202 | offsetScalar = 0;
|
203 | //*/
|
204 | }
|
205 | }
|
206 | else if (isGPMLAnchor(entityReferencedByPoint)) {
|
207 | // edge is connected to another edge via an anchor
|
208 | point.attachmentDisplay.position =
|
209 | entityReferencedByPoint.attachmentDisplay.position;
|
210 | }
|
211 | else {
|
212 | throw new Error(`
|
213 | Edge or Point attached to unexpected entity.
|
214 | Point is attached to:
|
215 | ${JSON.stringify(entityReferencedByPoint, null, " ")}
|
216 | Point is attached to:
|
217 | ${JSON.stringify(entityReferencedByEdge, null, " ")}
|
218 | for:
|
219 | postprocessPVJSON(
|
220 | referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
|
221 | pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
|
222 | )
|
223 | `);
|
224 | }
|
225 | }
|
226 | // NOTE: side effect
|
227 | index += 1;
|
228 | return omit(["marker"], point);
|
229 | }, points);
|
230 | const pvjsonEdgeAttachedToCount = pvjsonEdgeIsAttachedTo.length;
|
231 | if (pvjsonEdgeAttachedToCount > 0) {
|
232 | pvjsonEdge.isAttachedTo = pvjsonEdgeIsAttachedTo;
|
233 | }
|
234 | let allPvjsonPoints;
|
235 | if (["StraightLine", "SegmentedLine"].indexOf(drawAs) > -1) {
|
236 | allPvjsonPoints = providedPvjsonPoints;
|
237 | }
|
238 | else if (["ElbowLine", "CurvedLine"].indexOf(drawAs) > -1) {
|
239 | // pvjsonEdge.isAttachedTo refers to what the EDGE is fundamentally attached to.
|
240 | // pvjsonEdge.points[0].isAttachedTo refers to what the POINT is attached to.
|
241 | //
|
242 | // From the perspective of the biological meaning, the edge is always attached to
|
243 | // a regular node like a DataNode or Shape (maybe Label?) but never to an Anchor.
|
244 | //
|
245 | // From the perspective of the implementation of the graphics, we say the edge
|
246 | // has points, one or more of which can be connected to an Anchor.
|
247 | let sourceEntity;
|
248 | let targetEntity;
|
249 | if (pvjsonEdgeAttachedToCount === 2) {
|
250 | sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
|
251 | targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[1]];
|
252 | }
|
253 | else if (pvjsonEdgeAttachedToCount === 1) {
|
254 | const firstPoint = providedPvjsonPoints[0];
|
255 | const lastPoint = providedPvjsonPoints[providedPvjsonPoints.length - 1];
|
256 | if (firstPoint.hasOwnProperty("isAttachedTo")) {
|
257 | sourceEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
|
258 | }
|
259 | else if (lastPoint.hasOwnProperty("isAttachedTo")) {
|
260 | targetEntity = referencedEntities[pvjsonEdgeIsAttachedTo[0]];
|
261 | }
|
262 | else {
|
263 | throw new Error(`edge "${pvjsonEdge.id}" is said to be attached to "${pvjsonEdge.isAttachedTo.join()}",
|
264 | but neither first nor last points have "isAttachedTo" property`);
|
265 | }
|
266 | }
|
267 | allPvjsonPoints = calculateAllPoints(providedPvjsonPoints.map(point => new SmartPoint(point)), sourceEntity, targetEntity);
|
268 | }
|
269 | else {
|
270 | throw new Error(`
|
271 | Unknown edge drawer "${drawAs}" for:
|
272 | postprocessPVJSON(
|
273 | referencedEntities=${JSON.stringify(referencedEntities, null, " ")},
|
274 | pvjsonEdge=${JSON.stringify(pvjsonEdge, null, " ")}
|
275 | )
|
276 | `);
|
277 | // TODO should we use this?
|
278 | // allPvjsonPoints = providedPvjsonPoints;
|
279 | }
|
280 | // TODO how do we distinguish between intermediate (not first or last) points that a user
|
281 | // has explicitly specified vs. intermediate points that are only implied?
|
282 | // Do we need to? I think once a user specifies any implicit points, they may all be
|
283 | // made explicit.
|
284 | // GPML currently does not specify implicit intermediate points, but
|
285 | // pvjson does.
|
286 | pvjsonEdge.points = allPvjsonPoints;
|
287 | // TODO can I get rid of isAttachedToOrVia earlier?
|
288 | return omit(["isAttachedToOrVia"], pvjsonEdge);
|
289 | }
|
290 | //function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
|
291 | // const { Graphics } = gpmlElement;
|
292 | // const graphRefIds: string[] = !!Graphics.Point &&
|
293 | // Graphics.Point[0]._exists !== false
|
294 | // ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
|
295 | // : gpmlElement.hasOwnProperty("GraphRef")
|
296 | // ? arrayify(gpmlElement.GraphRef)
|
297 | // : [];
|
298 | //
|
299 | // const referencedElementIds = arrayify(graphRefIds);
|
300 | // //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
|
301 | // return referencedElementIds.length === 0
|
302 | // ? acc
|
303 | // : hl([
|
304 | // acc,
|
305 | // hl(referencedElementIds)
|
306 | // .flatMap(referencedElementId =>
|
307 | // hl(getGPMLElementByGraphId(referencedElementId))
|
308 | // )
|
309 | // .flatMap(function(referencedElement: GPMLElement) {
|
310 | // return recursivelyGetReferencedElements(
|
311 | // hl([referencedElement]),
|
312 | // referencedElement
|
313 | // );
|
314 | // })
|
315 | // ]).merge();
|
316 | //}
|
317 | //
|
318 | //export function postprocessPVJSON(
|
319 | // pvjsonEdge: PvjsonEdge
|
320 | //): Highland.Stream<PvjsonEdge> {
|
321 | // return hl([
|
322 | // hl([pvjsonEdge])
|
323 | // .reduce(hl([]), recursivelyGetReferencedElements)
|
324 | // .merge()
|
325 | // .flatMap(function(referencedGPMLElement: GPMLElement) {
|
326 | // return hl(
|
327 | // getPvjsonEntityLatestByGraphId(
|
328 | // referencedGPMLElement.GraphId
|
329 | // )
|
330 | // );
|
331 | // })
|
332 | // ])
|
333 | // .merge()
|
334 | // .reduce({}, function(
|
335 | // acc: {
|
336 | // [key: string]: (PvjsonNode | PvjsonEdge);
|
337 | // },
|
338 | // referencedEntity: (PvjsonNode | PvjsonEdge)
|
339 | // ) {
|
340 | // acc[referencedEntity.id] = referencedEntity;
|
341 | // return acc;
|
342 | // })
|
343 | // .map(function(referencedEntities) {
|
344 | // return process(pvjsonEdge, referencedEntities);
|
345 | // });
|
346 | // .merge();
|
347 | //}
|
348 | //
|
349 | //export function createEdgeTransformStream(
|
350 | // processor,
|
351 | // edgeType: "Interaction" | "GraphicalLine"
|
352 | //): (
|
353 | // s: Highland.Stream<GPML2013a.InteractionType | GPML2013a.GraphicalLineType>
|
354 | //) => Highland.Stream<(PvjsonNode | PvjsonEdge)> {
|
355 | // const {
|
356 | // fillInGPMLPropertiesFromParent,
|
357 | // getGPMLElementByGraphId,
|
358 | // getPvjsonEntityLatestByGraphId,
|
359 | // ensureGraphIdExists,
|
360 | // preprocessGPMLElement,
|
361 | // processPropertiesAndType
|
362 | // } = processor;
|
363 | //
|
364 | // function recursivelyGetReferencedElements(acc, gpmlElement: GPMLElement) {
|
365 | // const { Graphics } = gpmlElement;
|
366 | // const graphRefIds: string[] = !!Graphics.Point &&
|
367 | // Graphics.Point[0]._exists !== false
|
368 | // ? Graphics.Point.filter(P => isString(P.GraphRef)).map(P => P.GraphRef)
|
369 | // : gpmlElement.hasOwnProperty("GraphRef")
|
370 | // ? arrayify(gpmlElement.GraphRef)
|
371 | // : [];
|
372 | //
|
373 | // const referencedElementIds = arrayify(graphRefIds);
|
374 | // //const referencedElementIds = unionLSV(graphRefIds, gpmlElement.GroupRef);
|
375 | // return referencedElementIds.length === 0
|
376 | // ? acc
|
377 | // : hl([
|
378 | // acc,
|
379 | // hl(referencedElementIds)
|
380 | // .flatMap(referencedElementId =>
|
381 | // hl(getGPMLElementByGraphId(referencedElementId))
|
382 | // )
|
383 | // .flatMap(function(referencedElement: GPMLElement) {
|
384 | // return recursivelyGetReferencedElements(
|
385 | // hl([referencedElement]),
|
386 | // referencedElement
|
387 | // );
|
388 | // })
|
389 | // ]).merge();
|
390 | // }
|
391 | //
|
392 | // return function(s) {
|
393 | // return s
|
394 | // .map(preprocessGPMLElement)
|
395 | // .flatMap(function(
|
396 | // gpmlEdge: GPMLElement
|
397 | // ): Highland.Stream<Highland.Stream<PvjsonNode | PvjsonEdge>> {
|
398 | // const { Graphics } = gpmlEdge;
|
399 | //
|
400 | // const gpmlAnchors = Graphics.hasOwnProperty("Anchor") &&
|
401 | // Graphics.Anchor &&
|
402 | // Graphics.Anchor[0] &&
|
403 | // Graphics.Anchor[0]._exists !== false
|
404 | // ? Graphics.Anchor.filter(a => a.hasOwnProperty("GraphId"))
|
405 | // : [];
|
406 | //
|
407 | // const fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(
|
408 | // gpmlEdge
|
409 | // );
|
410 | //
|
411 | // return hl([
|
412 | // hl([gpmlEdge])
|
413 | // .map(processPropertiesAndType(edgeType))
|
414 | // .flatMap(function(pvjsonEdge: PvjsonEdge) {
|
415 | // return hl([
|
416 | // hl([gpmlEdge])
|
417 | // .reduce(hl([]), recursivelyGetReferencedElements)
|
418 | // .merge()
|
419 | // .flatMap(function(referencedGPMLElement: GPMLElement) {
|
420 | // return hl(
|
421 | // getPvjsonEntityLatestByGraphId(
|
422 | // referencedGPMLElement.GraphId
|
423 | // )
|
424 | // );
|
425 | // })
|
426 | // ])
|
427 | // .merge()
|
428 | // .reduce({}, function(
|
429 | // acc: {
|
430 | // [key: string]: (PvjsonNode | PvjsonEdge);
|
431 | // },
|
432 | // referencedEntity: (PvjsonNode | PvjsonEdge)
|
433 | // ) {
|
434 | // acc[referencedEntity.id] = referencedEntity;
|
435 | // return acc;
|
436 | // })
|
437 | // .map(function(referencedEntities) {
|
438 | // return process(pvjsonEdge, referencedEntities);
|
439 | // });
|
440 | // }),
|
441 | // hl(gpmlAnchors)
|
442 | // .map(preprocessGPMLElement)
|
443 | // .map(function(gpmlAnchor: GPMLElement) {
|
444 | // const filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor);
|
445 | // filledInAnchor.GraphRef = gpmlEdge.GraphId;
|
446 | // return filledInAnchor;
|
447 | // })
|
448 | // .map(processPropertiesAndType("Anchor"))
|
449 | // .map(function(pvjsonAnchor: PvjsonNode): PvjsonNode {
|
450 | // const drawAnchorAs = pvjsonAnchor.drawAs;
|
451 | // if (drawAnchorAs === "None") {
|
452 | // defaultsDeep(pvjsonAnchor, {
|
453 | // Height: 4,
|
454 | // Width: 4
|
455 | // });
|
456 | // } else if (drawAnchorAs === "Circle") {
|
457 | // defaultsDeep(pvjsonAnchor, {
|
458 | // Height: 8,
|
459 | // Width: 8
|
460 | // });
|
461 | // }
|
462 | // return pvjsonAnchor;
|
463 | // })
|
464 | // ]);
|
465 | // })
|
466 | // .merge();
|
467 | // };
|
468 | //}
|
469 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9lZGdlL2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBUyxHQUFHLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNoRSxPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLGFBQWEsRUFDYixZQUFZLEVBQ1osWUFBWSxFQUNaLHNCQUFzQixFQUN0QixhQUFhLEVBQ2IsUUFBUSxFQUNULE1BQU0sbUJBQW1CLENBQUM7QUFDM0IsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQWMzQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMxRCxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssY0FBYyxNQUFNLHVCQUF1QixDQUFDO0FBRXhELDZGQUE2RjtBQUM3RixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLENBQUM7QUFFdEM7Ozs7Ozs7R0FPRztBQUNILGlEQUNFLGNBQXNCLEVBQ3RCLG9CQUE0QixFQUM1QixJQUFlO0FBQ2YsdUVBQXVFO0FBQ3ZFLDBFQUEwRTtBQUMxRSxRQUFRO0FBQ1IsZ0JBQTRCO0lBRTVCLElBQUksWUFBWSxHQUNkLG9CQUFvQjtRQUNwQixDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDcEUsbUVBQW1FO0lBQ25FLHFDQUFxQztJQUNyQyw0Q0FBNEM7SUFDNUMseUVBQXlFO0lBQ3pFLG1FQUFtRTtJQUNuRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUIsTUFBTSxJQUFJLEtBQUssQ0FDYjswQkFDb0IsWUFBWTtXQUMzQixJQUFJOztxQkFFTSxjQUFjOzJCQUNSLG9CQUFvQjs7TUFFekMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxnQkFBZ0IsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDOztHQUUvQyxDQUNFLENBQUM7SUFDSixDQUFDO0lBRUQsMkVBQTJFO0lBQzNFLHlEQUF5RDtJQUN6RCxJQUFJLGlCQUFpQixDQUFDO0lBQ3RCLEVBQUUsQ0FBQyxDQUFDLGNBQWMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3pCLGlCQUFpQixHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsY0FBYyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDaEMsaUJBQWlCLEdBQUcsQ0FBQyxDQUFDO0lBQ3hCLENBQUM7SUFBQyxJQUFJLENBQUMsQ0FBQztRQUNOLGlCQUFpQixHQUFHLENBQUMsQ0FBQztJQUN4QixDQUFDO0lBRUQsTUFBTSxDQUFDLEVBQUUsWUFBWSxFQUFFLGlCQUFpQixFQUFFLGNBQWMsRUFBRSxDQUFDO0FBQzdELENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0seUJBQ0osSUFBeUM7SUFFekMsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEtBQUs7U0FDMUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsSUFBSSxhQUFhLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1NBQ3BELEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUV4QixFQUFFLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNqQyw4RUFBOEU7UUFDOUUsb0JBQW9CO1FBQ3BCLGtFQUFrRTtRQUNsRSxJQUFJLENBQUMsbUJBQW1CLENBQUMsR0FBRyxpQkFBaUIsQ0FBQztJQUNoRCxDQUFDO0lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQztBQUNkLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLDRCQUNKLGtCQUFnRSxFQUNoRSxVQUFzQjtJQUV0QixNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLFVBQVUsQ0FBQztJQUV0QyxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDO0lBQ2pDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQztJQUVkLE1BQU0sc0JBQXNCLEdBQUcsRUFBRSxDQUFDO0lBQ2xDLE1BQU0sb0JBQW9CLEdBQUcsR0FBRyxDQUFDLFVBQy9CLEtBQTJDO1FBRTNDLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFHLEtBQUssQ0FBQztRQUUvQixFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztZQUNiLDJCQUEyQjtZQUMzQixFQUFFLENBQUMsQ0FBQyxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDaEIsVUFBVSxDQUFDLFdBQVcsR0FBRyxNQUFNLENBQUM7WUFDbEMsQ0FBQztZQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLEtBQUssVUFBVSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BDLFVBQVUsQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDO1lBQ2hDLENBQUM7WUFDRCxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDMUMsVUFBVSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFVBQ3ZELEdBQUcsRUFDSCxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7b0JBRXRCLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLFNBQVMsQ0FBQyxDQUFDO2dCQUNsQyxDQUFDLEVBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3RCLENBQUM7UUFDSCxDQUFDO1FBRUQsRUFBRSxDQUFDLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzdCLHdFQUF3RTtZQUN4RSxrRkFBa0Y7WUFDbEYsNkRBQTZEO1lBQzdELE1BQU0sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsR0FBRyxLQUFLLENBQUM7WUFFbEQsRUFBRSxDQUFDLENBQUMsQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO2dCQUM5QixNQUFNLElBQUksS0FBSyxDQUNiOzsyQkFFaUIsSUFBSSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDO21CQUN0RCxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDO09BQ2xELENBQ0UsQ0FBQztZQUNKLENBQUM7WUFFRCx5RUFBeUU7WUFDekUscUVBQXFFO1lBQ3JFLG1FQUFtRTtZQUNuRSxpRUFBaUU7WUFDakUsK0RBQStEO1lBQy9ELE1BQU0sdUJBQXVCLEdBQzNCLGtCQUFrQjtnQkFDbEIsQ0FBQyxDQUFDLFlBQVk7Z0JBQ2Isa0JBQWtCLENBQUMsWUFBWSxDQUFnQixDQUFDO1lBRW5ELE1BQU0sd0JBQXdCLEdBQUcsWUFBWSxDQUFDLHVCQUF1QixDQUFDO2dCQUNwRSxDQUFDLENBQUMsdUJBQXVCLENBQUMsWUFBWTtnQkFDdEMsQ0FBQyxDQUFDLHVCQUF1QixDQUFDLEVBQUUsQ0FBQztZQUUvQix1QkFBdUI7WUFDdkIsc0JBQXNCLENBQUMsSUFBSSxDQUFDLHdCQUF3QixDQUFDLENBQUM7WUFFdEQsTUFBTSxzQkFBc0IsR0FDMUIsa0JBQWtCLENBQUMsd0JBQXdCLENBQUMsQ0FBQztZQUUvQyxNQUFNLFdBQVcsR0FBRyxDQUFDLEtBQUssQ0FBQyxXQUFXO2dCQUNwQyxLQUFLLENBQUMsV0FBVyxJQUFLLEVBQWtCLENBQUMsQ0FBQztZQUU1Qyw2SUFBNkk7WUFDN0ksRUFBRTtZQUNGLDBDQUEwQztZQUMxQywyQ0FBMkM7WUFDM0Msc0RBQXNEO1lBQ3RELHNEQUFzRDtZQUN0RCx5REFBeUQ7WUFDekQseURBQXlEO1lBQ3pELEVBQUU7WUFDRixpREFBaUQ7WUFDakQsaURBQWlEO1lBQ2pELGlEQUFpRDtZQUNqRCxpREFBaUQ7WUFDakQsaURBQWlEO1lBQ2pELGlEQUFpRDtZQUNqRCxpREFBaUQ7WUFDakQsaURBQWlEO1lBQ2pELGlEQUFpRDtZQUNqRCxpREFBaUQ7WUFDakQsaURBQWlEO1lBQ2pELDZCQUE2QjtZQUM3Qiw2QkFBNkI7WUFDN0IsNkJBQTZCO1lBQzdCLDZCQUE2QjtZQUM3Qix1Q0FBdUM7WUFDdkMsd0NBQXdDO1lBQ3hDLHNDQUFzQztZQUN0Qyw0Q0FBNEM7WUFDNUMseUNBQXlDO1lBQ3pDLDBDQUEwQztZQUMxQyw0Q0FBNEM7WUFDNUMsMENBQTBDO1lBQzFDLEVBQUU7WUFDRixnR0FBZ0c7WUFDaEcsK0ZBQStGO1lBQy9GLG1HQUFtRztZQUNuRyxFQUFFO1lBQ0YsRUFBRTtZQUNGLHlGQUF5RjtZQUN6RiwwRkFBMEY7WUFDMUYsMEZBQTBGO1lBQzFGLHlHQUF5RztZQUV6RyxFQUFFLENBQUMsQ0FDRCxzQkFBc0IsQ0FBQyxzQkFBc0IsQ0FBQztnQkFDOUMsYUFBYSxDQUFDLHNCQUFzQixDQUFDO2dCQUNyQyxZQUFZLENBQUMsc0JBQXNCLENBQ3JDLENBQUMsQ0FBQyxDQUFDO2dCQUNELE1BQU0sRUFBRSxRQUFRLEVBQUUsY0FBYyxFQUFFLEdBQUcsaUJBQWlCLENBQUM7Z0JBQ3ZELDJGQUEyRjtnQkFFM0YsSUFBSSxDQUFDO29CQUNILE1BQU0sRUFDSixZQUFZLEVBQUUsYUFBYSxFQUMzQixpQkFBaUIsRUFBRSxrQkFBa0IsRUFDdEMsR0FBRyx1Q0FBdUMsQ0FDekMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUNYLGNBQWMsQ0FBQyxDQUFDLENBQUMsRUFDakIsR0FBRyxFQUNILHNCQUFzQixDQUN2QixDQUFDO29CQUNGLE1BQU0sRUFDSixZQUFZLEVBQUUsYUFBYSxFQUMzQixpQkFBaUIsRUFBRSxrQkFBa0IsRUFDdEMsR0FBRyx1Q0FBdUMsQ0FDekMsUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUNYLGNBQWMsQ0FBQyxDQUFDLENBQUMsRUFDakIsR0FBRyxFQUNILHNCQUFzQixDQUN2QixDQUFDO29CQUNGLEVBQUUsQ0FBQyxDQUFDLEtBQUssS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO3dCQUNoQixXQUFXLENBQUMsQ0FBQyxDQUFDLEdBQUcsa0JBQWtCLENBQUM7d0JBQ3BDLFdBQVcsQ0FBQyxDQUFDLENBQUMsR0FBRyxrQkFBa0IsQ0FBQztvQkFDdEMsQ0FBQztvQkFBQyxJQUFJLENBQUMsQ0FBQzt3QkFDTixXQUFXLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsa0JBQWtCLENBQUM7d0JBQ3pDLFdBQVcsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxrQkFBa0IsQ0FBQztvQkFDM0MsQ0FBQztvQkFFRCxpRUFBaUU7b0JBQ2pFLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxhQUFhLENBQUM7b0JBQzVDLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxhQUFhLENBQUM7b0JBQzVDLEtBQUssQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFLGlCQUFpQixDQUFDLENBQUM7Z0JBQ3hFLENBQUM7Z0JBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztvQkFDYixNQUFNLElBQUksTUFBTSxDQUNkLEdBQUcsRUFDSDs7OzRCQUdnQixJQUFJLENBQUMsU0FBUyxDQUFDLGtCQUFrQixFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7b0JBQ3RELElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7O01BRXBELENBQ0ssQ0FBQztvQkFDRjs7O2tDQUdEO2dCQUNELENBQUM7WUFDSCxDQUFDO1lBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLFlBQVksQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDakQsa0RBQWtEO2dCQUNsRCxLQUFLLENBQUMsaUJBQWlCLENBQUMsUUFBUTtvQkFDOUIsdUJBQXVCLENBQUMsaUJBQWlCLENBQUMsUUFBUSxDQUFDO1lBQ3ZELENBQUM7WUFBQyxJQUFJLENBQUMsQ0FBQztnQkFDTixNQUFNLElBQUksS0FBSyxDQUNiOzs7T0FHSCxJQUFJLENBQUMsU0FBUyxDQUFDLHVCQUF1QixFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7O09BRW5ELElBQUksQ0FBQyxTQUFTLENBQUMsc0JBQXNCLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQzs7OzBCQUcvQixJQUFJLENBQUMsU0FBUyxDQUFDLGtCQUFrQixFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7a0JBQ3RELElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUM7O0tBRW5ELENBQ0ksQ0FBQztZQUNKLENBQUM7UUFDSCxDQUFDO1FBRUQsb0JBQW9CO1FBQ3BCLEtBQUssSUFBSSxDQUFDLENBQUM7UUFFWCxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDakMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBRVgsTUFBTSx5QkFBeUIsR0FBRyxzQkFBc0IsQ0FBQyxNQUFNLENBQUM7SUFDaEUsRUFBRSxDQUFDLENBQUMseUJBQXlCLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNsQyxVQUFVLENBQUMsWUFBWSxHQUFHLHNCQUFzQixDQUFDO0lBQ25ELENBQUM7SUFFRCxJQUFJLGVBQWUsQ0FBQztJQUNwQixFQUFFLENBQUMsQ0FBQyxDQUFDLGNBQWMsRUFBRSxlQUFlLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzNELGVBQWUsR0FBRyxvQkFBb0IsQ0FBQztJQUN6QyxDQUFDO0lBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLFlBQVksQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUQsZ0ZBQWdGO1FBQ2hGLDZFQUE2RTtRQUM3RSxFQUFFO1FBQ0YsaUZBQWlGO1FBQ2pGLGlGQUFpRjtRQUNqRixFQUFFO1FBQ0YsOEVBQThFO1FBQzlFLGtFQUFrRTtRQUNsRSxJQUFJLFlBQVksQ0FBQztRQUNqQixJQUFJLFlBQVksQ0FBQztRQUNqQixFQUFFLENBQUMsQ0FBQyx5QkFBeUIsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3BDLFlBQVksR0FBRyxrQkFBa0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzdELFlBQVksR0FBRyxrQkFBa0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQy9ELENBQUM7UUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMseUJBQXlCLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMzQyxNQUFNLFVBQVUsR0FBRyxvQkFBb0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMzQyxNQUFNLFNBQVMsR0FBRyxvQkFBb0IsQ0FBQyxvQkFBb0IsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7WUFDeEUsRUFBRSxDQUFDLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQzlDLFlBQVksR0FBRyxrQkFBa0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQy9ELENBQUM7WUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3BELFlBQVksR0FBRyxrQkFBa0IsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQy9ELENBQUM7WUFBQyxJQUFJLENBQUMsQ0FBQztnQkFDTixNQUFNLElBQUksS0FBSyxDQUNiLFNBQVMsVUFBVSxDQUFDLEVBQUUsZ0NBQWdDLFVBQVUsQ0FBQyxZQUFZLENBQUMsSUFBSSxFQUFFO29FQUMxQixDQUMzRCxDQUFDO1lBQ0osQ0FBQztRQUNILENBQUM7UUFDRCxlQUFlLEdBQUcsa0JBQWtCLENBQ2xDLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLElBQUksVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQ3hELFlBQVksRUFDWixZQUFZLENBQ2IsQ0FBQztJQUNKLENBQUM7SUFBQyxJQUFJLENBQUMsQ0FBQztRQUNOLE1BQU0sSUFBSSxLQUFLLENBQ2I7MEJBQ29CLE1BQU07O3dCQUVSLElBQUksQ0FBQyxTQUFTLENBQUMsa0JBQWtCLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQztnQkFDdEQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxVQUFVLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQzs7R0FFbkQsQ0FDRSxDQUFDO1FBRUYsMkJBQTJCO1FBQzNCLDBDQUEwQztJQUM1QyxDQUFDO0lBRUQseUZBQXlGO0lBQ3pGLDBFQUEwRTtJQUMxRSxvRkFBb0Y7SUFDcEYsaUJBQWlCO0lBQ2pCLG9FQUFvRTtJQUNwRSxlQUFlO0lBRWYsVUFBVSxDQUFDLE1BQU0sR0FBRyxlQUFlLENBQUM7SUFFcEMsbURBQW1EO0lBQ25ELE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLFVBQVUsQ0FBQyxDQUFDO0FBQ2pELENBQUM7QUFFRCw0RUFBNEU7QUFDNUUsb0NBQW9DO0FBQ3BDLG9EQUFvRDtBQUNwRCx1Q0FBdUM7QUFDdkMsNEVBQTRFO0FBQzVFLDZDQUE2QztBQUM3QyxzQ0FBc0M7QUFDdEMsV0FBVztBQUNYLEVBQUU7QUFDRix5REFBeUQ7QUFDekQsaUZBQWlGO0FBQ2pGLDhDQUE4QztBQUM5QyxZQUFZO0FBQ1osYUFBYTtBQUNiLFlBQVk7QUFDWixnQ0FBZ0M7QUFDaEMsdUNBQXVDO0FBQ3ZDLDZEQUE2RDtBQUM3RCxhQUFhO0FBQ2IsK0RBQStEO0FBQy9ELHFEQUFxRDtBQUNyRCxzQ0FBc0M7QUFDdEMsK0JBQStCO0FBQy9CLGVBQWU7QUFDZixjQUFjO0FBQ2Qsa0JBQWtCO0FBQ2xCLEdBQUc7QUFDSCxFQUFFO0FBQ0Ysb0NBQW9DO0FBQ3BDLHlCQUF5QjtBQUN6QixrQ0FBa0M7QUFDbEMsY0FBYztBQUNkLG9CQUFvQjtBQUNwQixxREFBcUQ7QUFDckQsWUFBWTtBQUNaLDJEQUEyRDtBQUMzRCxlQUFlO0FBQ2YscUNBQXFDO0FBQ3JDLG9DQUFvQztBQUNwQyxPQUFPO0FBQ1AsT0FBTztBQUNQLE1BQU07QUFDTixLQUFLO0FBQ0wsV0FBVztBQUNYLHdCQUF3QjtBQUN4QixVQUFVO0FBQ1YsOENBQThDO0FBQzlDLE1BQU07QUFDTiwrQ0FBK0M7QUFDL0MsTUFBTTtBQUNOLGdEQUFnRDtBQUNoRCxlQUFlO0FBQ2YsS0FBSztBQUNMLHNDQUFzQztBQUN0QyxtREFBbUQ7QUFDbkQsTUFBTTtBQUNOLFlBQVk7QUFDWixHQUFHO0FBQ0gsRUFBRTtBQUNGLDRDQUE0QztBQUM1QyxjQUFjO0FBQ2QsNkNBQTZDO0FBQzdDLE1BQU07QUFDTiwrRUFBK0U7QUFDL0UsbURBQW1EO0FBQ25ELFdBQVc7QUFDWCxxQ0FBcUM7QUFDckMsOEJBQThCO0FBQzlCLHFDQUFxQztBQUNyQywwQkFBMEI7QUFDMUIsNEJBQTRCO0FBQzVCLDhCQUE4QjtBQUM5QixrQkFBa0I7QUFDbEIsRUFBRTtBQUNGLDhFQUE4RTtBQUM5RSx1Q0FBdUM7QUFDdkMsdURBQXVEO0FBQ3ZELDJDQUEyQztBQUMzQywrRUFBK0U7QUFDL0UsZ0RBQWdEO0FBQ2hELDBDQUEwQztBQUMxQyxlQUFlO0FBQ2YsRUFBRTtBQUNGLHlEQUF5RDtBQUN6RCxpRkFBaUY7QUFDakYsOENBQThDO0FBQzlDLGFBQWE7QUFDYixjQUFjO0FBQ2QsZ0JBQWdCO0FBQ2hCLG9DQUFvQztBQUNwQyw2Q0FBNkM7QUFDN0MsZ0VBQWdFO0FBQ2hFLGVBQWU7QUFDZixpRUFBaUU7QUFDakUsd0RBQXdEO0FBQ3hELDBDQUEwQztBQUMxQyxtQ0FBbUM7QUFDbkMsa0JBQWtCO0FBQ2xCLGdCQUFnQjtBQUNoQixxQkFBcUI7QUFDckIsS0FBSztBQUNMLEVBQUU7QUFDRix3QkFBd0I7QUFDeEIsY0FBYztBQUNkLG1DQUFtQztBQUNuQywwQkFBMEI7QUFDMUIsK0JBQStCO0FBQy9CLHNFQUFzRTtBQUN0RSx3Q0FBd0M7QUFDeEMsRUFBRTtBQUNGLGtFQUFrRTtBQUNsRSw4QkFBOEI7QUFDOUIsaUNBQWlDO0FBQ2pDLGdEQUFnRDtBQUNoRCxzRUFBc0U7QUFDdEUsaUJBQWlCO0FBQ2pCLEVBQUU7QUFDRiw4RUFBOEU7QUFDOUUsb0JBQW9CO0FBQ3BCLFlBQVk7QUFDWixFQUFFO0FBQ0YscUJBQXFCO0FBQ3JCLDBCQUEwQjtBQUMxQixzREFBc0Q7QUFDdEQseURBQXlEO0FBQ3pELDJCQUEyQjtBQUMzQixnQ0FBZ0M7QUFDaEMscUVBQXFFO0FBQ3JFLDRCQUE0QjtBQUM1QiwyRUFBMkU7QUFDM0UsZ0NBQWdDO0FBQ2hDLHVEQUF1RDtBQUN2RCx1REFBdUQ7QUFDdkQseUJBQXlCO0FBQ3pCLHdCQUF3QjtBQUN4QixzQkFBc0I7QUFDdEIsa0JBQWtCO0FBQ2xCLDBCQUEwQjtBQUMxQix1Q0FBdUM7QUFDdkMsMEJBQTBCO0FBQzFCLCtEQUErRDtBQUMvRCxzQkFBc0I7QUFDdEIsK0RBQStEO0FBQy9ELHFCQUFxQjtBQUNyQixnRUFBZ0U7QUFDaEUsK0JBQStCO0FBQy9CLG9CQUFvQjtBQUNwQixxREFBcUQ7QUFDckQsbUVBQW1FO0FBQ25FLHFCQUFxQjtBQUNyQixpQkFBaUI7QUFDakIsMkJBQTJCO0FBQzNCLHlDQUF5QztBQUN6QyxzREFBc0Q7QUFDdEQsZ0ZBQWdGO0FBQ2hGLDJEQUEyRDtBQUMzRCxzQ0FBc0M7QUFDdEMsZ0JBQWdCO0FBQ2hCLHNEQUFzRDtBQUN0RCxtRUFBbUU7QUFDbkUseURBQXlEO0FBQ3pELDhDQUE4QztBQUM5Qyw4Q0FBOEM7QUFDOUMsOEJBQThCO0FBQzlCLDRCQUE0QjtBQUM1QixxQkFBcUI7QUFDckIsdURBQXVEO0FBQ3ZELDhDQUE4QztBQUM5Qyw4QkFBOEI7QUFDOUIsNEJBQTRCO0FBQzVCLHFCQUFxQjtBQUNyQixpQkFBaUI7QUFDakIsb0NBQW9DO0FBQ3BDLGdCQUFnQjtBQUNoQixhQUFhO0FBQ2IsVUFBVTtBQUNWLGlCQUFpQjtBQUNqQixNQUFNO0FBQ04sR0FBRyJ9 |
\ | No newline at end of file |