ngx-dynamic-hooks
Version:
Automatically insert live Angular components into a dynamic string of content (based on their selector or any pattern of your choice) and render the result in the DOM.
410 lines • 71.2 kB
JavaScript
import { Injectable } from '@angular/core';
import { anchorAttrHookId, anchorAttrParseToken, anchorElementTag } from '../../constants/core';
import { matchAll } from '../utils/utils';
import * as i0 from "@angular/core";
import * as i1 from "../platform/autoPlatformService";
import * as i2 from "../utils/logger";
const findInElementsNodePlaceholder = '_ngx_dynamic_hooks_node_placeholder';
/**
* The service responsible for finding text hooks in the content and replacing them with component anchors
*/
export class TextHookFinder {
constructor(platformService, logger) {
this.platformService = platformService;
this.logger = logger;
}
/**
* Finds all text hooks in an existing element and creates the corresponding anchors
*
* @param element - The element to parse
* @param context - The current context object
* @param parsers - The parsers to use
* @param token - The current parse token
* @param options - The current ParseOptions
* @param hookIndex - The hookIndex object to fill
*/
findInElement(element, context, parsers, token, options, hookIndex) {
// Only bother looking for text hooks if there even are text hook parsers
for (const parser of parsers) {
if (typeof parser.findHooks === 'function') {
this.checkElement(element, context, parsers, token, options, hookIndex);
break;
}
}
}
/**
* Checks an individual element and travels it recursively
*
* @param element - The element to parse
* @param context - The current context object
* @param parsers - The parsers to use
* @param token - The current parse token
* @param options - The current ParseOptions
* @param hookIndex - The hookIndex object to fill
* @param extractedNodes - A recursively-used object holding all temporarily extracted nodes
*/
checkElement(element, context, parsers, token, options, hookIndex, extractedNodes = { counter: 0, nodes: {} }) {
let childNodes = this.platformService.getChildNodes(element);
// To find text hooks in an already existing node, first replace non-text child nodes with string placeholders, then concat all text content.
// This is so enclosing text hooks can be found even if they are separated by other elements
let collectedText = '';
const collectedNodes = {};
for (const childNode of childNodes) {
if (this.platformService.isTextNode(childNode)) {
collectedText += this.platformService.getTextContent(childNode);
}
else {
const nodeId = extractedNodes.counter++;
collectedText += `${findInElementsNodePlaceholder}__${nodeId}__`;
collectedNodes[nodeId] = childNode;
}
}
// Then check for text hooks
const prevHookCount = Object.keys(hookIndex).length;
const result = this.find(collectedText, context, parsers, token, options, hookIndex);
// If hooks were found, replace element content with result.content
if (Object.keys(hookIndex).length > prevHookCount) {
this.platformService.clearChildNodes(element);
this.platformService.setInnerContent(element, result.content);
childNodes = this.platformService.getChildNodes(element);
// Also add locally removed nodes to total extractedNodes
extractedNodes.nodes = { ...extractedNodes.nodes, ...collectedNodes };
}
// If still have extractedNodes, always look for their placeholders on every level (could be deeper than when they were extracted) and reinsert them
if (Object.keys(extractedNodes.nodes).length) {
for (const childNode of childNodes) {
if (this.platformService.isTextNode(childNode)) {
let text = this.platformService.getTextContent(childNode);
if (text) {
const matches = matchAll(text, new RegExp(`${findInElementsNodePlaceholder}__(\\d*)__`, 'g'));
// If placeholders found
if (matches.length) {
const textReplacementNodes = [];
let currentPos = 0;
// Split text node containing placeholder into nodes array with restored nodes
for (const match of matches) {
const textBefore = text.substring(currentPos, match.index);
const extractedNodeId = parseInt(match[1]);
if (textBefore) {
textReplacementNodes.push(this.platformService.createTextNode(textBefore));
}
if (extractedNodeId && extractedNodes.nodes[extractedNodeId]) {
textReplacementNodes.push(extractedNodes.nodes[extractedNodeId]);
delete extractedNodes.nodes[extractedNodeId];
}
currentPos = match.index + match[0].length;
}
const textRemaining = text.substring(currentPos);
if (textRemaining) {
textReplacementNodes.push(this.platformService.createTextNode(textRemaining));
}
// Replace text node with that array
const parent = this.platformService.getParentNode(childNode);
for (const replacementNode of textReplacementNodes) {
this.platformService.insertBefore(parent, replacementNode, childNode);
}
this.platformService.removeChild(parent, childNode);
// Update child nodes var
childNodes = this.platformService.getChildNodes(parent);
}
}
}
}
}
// Travel child nodes recursively
for (const childNode of childNodes) {
if (childNode.nodeType !== Node.TEXT_NODE) {
this.checkElement(childNode, context, parsers, token, options, hookIndex, extractedNodes);
}
}
}
/**
* Finds all text hooks in a string variable and creates the corresponding anchors
*
* @param content - The text to parse
* @param context - The current context object
* @param parsers - The parsers to use
* @param token - The current parse token
* @param options - The current ParseOptions
* @param hookIndex - The hookIndex object to fill
*/
find(content, context, parsers, token, options, hookIndex) {
if (content === '') {
return {
content: content,
hookIndex: hookIndex
};
}
// Convert input HTML entities?
if (options.convertHTMLEntities) {
content = this.convertHTMLEntities(content);
}
// Collect all parser results, sort by order of appearance
let parserResults = [];
for (const parser of parsers) {
if (typeof parser.findHooks === 'function') {
for (const hookPosition of parser.findHooks(content, context, options)) {
parserResults.push({ parser, hookPosition });
}
}
}
parserResults.sort((a, b) => a.hookPosition.openingTagStartIndex - b.hookPosition.openingTagStartIndex);
// Validate parser results
parserResults = this.validateHookPositions(parserResults, content, options);
// Process parser results
const selectorReplaceInstructions = [];
for (const pr of parserResults) {
const hookId = Object.keys(hookIndex).length + 1;
// Some info about this hook
const hookSegments = this.getHookSegments(pr.hookPosition, content);
// Prepare ReplaceInstructions array to replace all found hooks with anchor elements
selectorReplaceInstructions.push({
startIndex: pr.hookPosition.openingTagStartIndex,
endIndex: pr.hookPosition.openingTagEndIndex,
replacement: `<${anchorElementTag} ${anchorAttrHookId}="${hookId}" ${anchorAttrParseToken}="${token}">`
});
selectorReplaceInstructions.push({
startIndex: hookSegments.enclosing ? pr.hookPosition.closingTagStartIndex : pr.hookPosition.openingTagEndIndex,
endIndex: hookSegments.enclosing ? pr.hookPosition.closingTagEndIndex : pr.hookPosition.openingTagEndIndex,
replacement: `</${anchorElementTag}>`
});
// Enter hook into index
hookIndex[hookId] = {
id: hookId,
parser: pr.parser,
value: {
openingTag: hookSegments.openingTag,
closingTag: hookSegments.closingTag,
element: null,
elementSnapshot: null
},
data: null,
isLazy: false,
bindings: null,
previousBindings: null,
componentRef: null,
dirtyInputs: new Set(),
outputSubscriptions: {},
htmlEventSubscriptions: {}
};
// Remove tag artifacts (does not change parser results indexes)
if (hookSegments.enclosing && options.fixParagraphTags) {
const firstResult = this.removeTagArtifacts(hookSegments.textBefore, '<p>', '</p>', hookSegments.innerValue, '</p>', '<p>');
hookSegments.textBefore = firstResult.firstText;
hookSegments.innerValue = firstResult.secondText;
const secondResult = this.removeTagArtifacts(hookSegments.innerValue, '<p>', '</p>', hookSegments.textAfter, '</p>', '<p>');
hookSegments.innerValue = secondResult.firstText;
hookSegments.textAfter = secondResult.secondText;
content = hookSegments.textBefore + hookSegments.openingTag + hookSegments.innerValue + hookSegments.closingTag + hookSegments.textAfter;
}
}
// Finally replace hooks with anchors
// Process in backwards order, so no need to change indexes.
// Primarily sort by startIndex. If multiple startIndexes are identical (possible with a follow-up hook to a self-closing hook), secondarily sort by endIndex.
selectorReplaceInstructions.sort((a, b) => {
let sortResult = b.startIndex - a.startIndex;
if (sortResult === 0)
sortResult = b.endIndex - a.endIndex;
return sortResult;
});
for (const selectorReplaceInstruction of selectorReplaceInstructions) {
const textBeforeSelector = content.substring(0, selectorReplaceInstruction.startIndex);
const textAfterSelector = content.substring(selectorReplaceInstruction.endIndex);
content = textBeforeSelector + selectorReplaceInstruction.replacement + textAfterSelector;
}
return {
content: content,
hookIndex: hookIndex
};
}
/**
* Takes a HookPosition and returns the HookValue as well as the text surrounding it
*
* @param hookPosition - The HookPosition in question
* @param content - The source text for the HookPosition
*/
getHookSegments(hookPosition, content) {
const enclosing = (Number.isInteger(hookPosition.closingTagStartIndex) && Number.isInteger(hookPosition.closingTagEndIndex));
return {
enclosing: enclosing,
textBefore: content.substring(0, hookPosition.openingTagStartIndex),
openingTag: content.substring(hookPosition.openingTagStartIndex, hookPosition.openingTagEndIndex),
innerValue: enclosing ? content.substring(hookPosition.openingTagEndIndex, hookPosition.closingTagStartIndex) : null,
closingTag: enclosing ? content.substring(hookPosition.closingTagStartIndex, hookPosition.closingTagEndIndex) : null,
textAfter: enclosing ? content.substring(hookPosition.closingTagEndIndex) : content.substring(hookPosition.openingTagEndIndex)
};
}
/**
* Checks the combined parserResults and validates them. Invalid ones are removed.
*
* @param parserResults - The parserResults to check
* @param content - The content string
* @param options - The current ParseOptions
*/
validateHookPositions(parserResults, content, options) {
const checkedParserResults = [];
outerloop: for (const [index, parserResult] of parserResults.entries()) {
const enclosing = (Number.isInteger(parserResult.hookPosition.closingTagStartIndex) && Number.isInteger(parserResult.hookPosition.closingTagEndIndex));
const hookPos = parserResult.hookPosition;
// Check if hook is in itself well-formed
if (hookPos.openingTagStartIndex >= hookPos.openingTagEndIndex) {
this.logger.warn(['Text hook error: openingTagEndIndex has to be greater than openingTagStartIndex. Ignoring.', hookPos], options);
continue;
}
if (enclosing && hookPos.openingTagEndIndex > hookPos.closingTagStartIndex) {
this.logger.warn(['Text hook error: closingTagStartIndex has to be greater than openingTagEndIndex. Ignoring.', hookPos], options);
continue;
}
if (enclosing && hookPos.closingTagStartIndex >= hookPos.closingTagEndIndex) {
this.logger.warn(['Text hook error: closingTagEndIndex has to be greater than closingTagStartIndex. Ignoring.', hookPos], options);
continue;
}
// Check if hook overlaps with other hooks
const previousHooks = parserResults.slice(0, index);
innerloop: for (const previousHook of previousHooks) {
const prevHookPos = previousHook.hookPosition;
const prevIsEnclosing = (Number.isInteger(prevHookPos.closingTagStartIndex) && Number.isInteger(prevHookPos.closingTagEndIndex));
// Check if identical hook position
if (hookPos.openingTagStartIndex === prevHookPos.openingTagStartIndex &&
hookPos.openingTagEndIndex === prevHookPos.openingTagEndIndex &&
(!enclosing || !prevIsEnclosing || (hookPos.closingTagStartIndex === prevHookPos.closingTagStartIndex &&
hookPos.closingTagEndIndex === prevHookPos.closingTagEndIndex))) {
this.generateHookPosWarning('A text hook with the same position as another text hook was found. There may be multiple parsers looking for the same text pattern. Ignoring duplicates.', hookPos, prevHookPos, content, options);
continue outerloop;
}
// Opening tag must begin after previous opening tag has ended
if (hookPos.openingTagStartIndex < prevHookPos.openingTagEndIndex) {
this.generateHookPosWarning('Text hook error: Hook opening tag starts before previous hook opening tag ends. Ignoring.', hookPos, prevHookPos, content, options);
continue outerloop;
}
// Just need to check for collisions with previous closing tag now
// Opening tag must not overlap with previous closing tag
if (prevIsEnclosing && !(hookPos.openingTagEndIndex <= prevHookPos.closingTagStartIndex ||
hookPos.openingTagStartIndex >= prevHookPos.closingTagEndIndex)) {
this.generateHookPosWarning('Text hook error: Opening tag of hook overlaps with closing tag of previous hook. Ignoring.', hookPos, prevHookPos, content, options);
continue outerloop;
}
// Closing tag must not overlap with previous closing tag
if (prevIsEnclosing && enclosing && !(hookPos.closingTagEndIndex <= prevHookPos.closingTagStartIndex ||
hookPos.closingTagStartIndex >= prevHookPos.closingTagEndIndex)) {
this.generateHookPosWarning('Text hook error: Closing tag of hook overlaps with closing tag of previous hook. Ignoring.', hookPos, prevHookPos, content, options);
continue outerloop;
}
// Check if hooks are incorrectly nested, e.g. "<outer-hook><inner-hook></outer-hook></inner-hook>"
if (enclosing && prevIsEnclosing &&
hookPos.openingTagEndIndex <= prevHookPos.closingTagStartIndex &&
hookPos.closingTagStartIndex >= prevHookPos.closingTagEndIndex) {
this.generateHookPosWarning('Text hook error: The closing tag of a nested hook lies beyond the closing tag of the outer hook. Ignoring.', hookPos, prevHookPos, content, options);
continue outerloop;
}
}
// If everything okay, add to result array
checkedParserResults.push(parserResult);
}
return checkedParserResults;
}
/**
* Outputs a warning in the console when the positions of two hooks are invalid in some manner
*
* @param message - The error message
* @param hookPos - The first HookPosition
* @param prevHookPos - The second HookPosition
* @param content - The content string
* @param options - The current ParseOptions
*/
generateHookPosWarning(message, hookPos, prevHookPos, content, options) {
const prevHookSegments = this.getHookSegments(prevHookPos, content);
const hookSegments = this.getHookSegments(hookPos, content);
const prevHookData = {
openingTag: prevHookSegments.openingTag,
openingTagStartIndex: prevHookPos.openingTagStartIndex,
openingTagEndIndex: prevHookPos.openingTagEndIndex,
closingTag: prevHookSegments.closingTag,
closingTagStartIndex: prevHookPos.closingTagStartIndex,
closingTagEndIndex: prevHookPos.closingTagEndIndex
};
const hookData = {
openingTag: hookSegments.openingTag,
openingTagStartIndex: hookPos.openingTagStartIndex,
openingTagEndIndex: hookPos.openingTagEndIndex,
closingTag: hookSegments.closingTag,
closingTagStartIndex: hookPos.closingTagStartIndex,
closingTagEndIndex: hookPos.closingTagEndIndex
};
this.logger.warn([message + '\nFirst hook: ', prevHookData, '\nSecond hook:', hookData], options);
}
/**
* When using an enclosing hook that is spread over several lines in an HTML editor, p-elements tend to get ripped apart. For example:
*
* <p><app-hook></p>
* <h2>This is the hook content</h2>
* <p></app-hook></p>
*
* would cause the innerValue of app-hook to have a lone closing and opening p-tag (as their counterparts are outside of the hook).
* To clean up the HTML, this function removes a pair of these artifacts (e.g. <p> before hook, </p> inside hook) if BOTH are found.
* This is important as the HTML parser will otherwise mess up the intended HTML and sometimes even put what should be ng-content below the component.
*
* @param firstText - The text on one side of the hook
* @param firstArtifact - A string that should be removed from firstText...
* @param firstRemoveIfAfter - ...if it appears after the last occurrence of this string
* @param secondText - The text on the other side of the hook
* @param secondArtifact - A string that should be removed from secondText...
* @param secondRemoveIfBefore - ...if it appears before the first occurrence of this string
*/
removeTagArtifacts(firstText, firstArtifact, firstRemoveIfAfter, secondText, secondArtifact, secondRemoveIfBefore) {
let firstArtifactFound = false;
let secondArtifactFound = false;
// a) Look for first artifact
const firstArtifactIndex = firstText.lastIndexOf(firstArtifact);
const firstArtifactIfAfterIndex = firstText.lastIndexOf(firstRemoveIfAfter);
if ((firstArtifactIndex >= 0 && firstArtifactIfAfterIndex === -1) ||
(firstArtifactIndex > firstArtifactIfAfterIndex)) {
firstArtifactFound = true;
}
// b) Look for second artifact
const secondArtifactIndex = secondText.indexOf(secondArtifact);
const secondArtifactIfBeforeIndex = secondText.indexOf(secondRemoveIfBefore);
if (
// If startArtifact appears before startArtifactNotBefore
(secondArtifactIndex >= 0 && secondArtifactIfBeforeIndex === -1) ||
(secondArtifactIndex < secondArtifactIfBeforeIndex)) {
secondArtifactFound = true;
}
// If artifacts found on both sides, remove both by overwriting them with empty spaces (doesn't change index)
if (firstArtifactFound && secondArtifactFound) {
firstText = firstText.substring(0, firstArtifactIndex) + ' '.repeat(firstArtifact.length) + firstText.substring(firstArtifactIndex + firstArtifact.length);
secondText = secondText.substring(0, secondArtifactIndex) + ' '.repeat(secondArtifact.length) + secondText.substring(secondArtifactIndex + secondArtifact.length);
}
// Trim after artifacts removed
return {
firstText: firstText,
secondText: secondText
};
}
/**
* Converts all HTML entities to normal characters
*
* @param text - The text with the potential HTML entities
*/
convertHTMLEntities(text) {
const div = this.platformService.createElement('div');
const result = text.replace(/&[#A-Za-z0-9]+;/gi, (hmtlEntity) => {
// Replace invisible nbsp-whitespace with normal whitespace (not \u00A0). Leads to problems with JSON.parse() otherwise.
if (hmtlEntity === (' ')) {
return ' ';
}
this.platformService.setInnerContent(div, hmtlEntity);
return this.platformService.getTextContent(div);
});
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextHookFinder, deps: [{ token: i1.AutoPlatformService }, { token: i2.Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextHookFinder, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextHookFinder, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1.AutoPlatformService }, { type: i2.Logger }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGV4dEhvb2tGaW5kZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZHluYW1pYy1ob29rcy9zcmMvbGliL3NlcnZpY2VzL2NvcmUvdGV4dEhvb2tGaW5kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUUzQyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsb0JBQW9CLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNoRyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7Ozs7QUFJMUMsTUFBTSw2QkFBNkIsR0FBRyxxQ0FBcUMsQ0FBQztBQStCNUU7O0dBRUc7QUFJSCxNQUFNLE9BQU8sY0FBYztJQUV6QixZQUFvQixlQUFvQyxFQUFVLE1BQWM7UUFBNUQsb0JBQWUsR0FBZixlQUFlLENBQXFCO1FBQVUsV0FBTSxHQUFOLE1BQU0sQ0FBUTtJQUNoRixDQUFDO0lBRUQ7Ozs7Ozs7OztPQVNHO0lBQ0gsYUFBYSxDQUFDLE9BQVksRUFBRSxPQUFZLEVBQUUsT0FBcUIsRUFBRSxLQUFhLEVBQUUsT0FBcUIsRUFBRSxTQUFvQjtRQUN6SCx5RUFBeUU7UUFDekUsS0FBSyxNQUFNLE1BQU0sSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUM3QixJQUFJLE9BQU8sTUFBTSxDQUFDLFNBQVMsS0FBSyxVQUFVLEVBQUUsQ0FBQztnQkFDM0MsSUFBSSxDQUFDLFlBQVksQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDO2dCQUN4RSxNQUFNO1lBQ1IsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILFlBQVksQ0FBQyxPQUFZLEVBQUUsT0FBWSxFQUFFLE9BQXFCLEVBQUUsS0FBYSxFQUFFLE9B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LGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwSyxDQUFDO1FBRUQsK0JBQStCO1FBQy9CLE9BQU87WUFDTCxTQUFTLEVBQUUsU0FBUztZQUNwQixVQUFVLEVBQUUsVUFBVTtTQUN2QixDQUFDO0lBQ0osQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxtQkFBbUIsQ0FBQyxJQUFZO1FBQzlCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxlQUFlLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3RELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsbUJBQW1CLEVBQUUsQ0FBQyxVQUFVLEVBQUUsRUFBRTtZQUM1RCx3SEFBd0g7WUFDeEgsSUFBSSxVQUFVLEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO2dCQUFDLE9BQU8sR0FBRyxDQUFDO1lBQUMsQ0FBQztZQUM5QyxJQUFJLENBQUMsZUFBZSxDQUFDLGVBQWUsQ0FBQyxHQUFHLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFDdEQsT0FBTyxJQUFJLENBQUMsZUFBZSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUUsQ0FBQztRQUNyRCxDQUFDLENBQUMsQ0FBQztRQUNILE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7K0dBOWJVLGNBQWM7bUhBQWQsY0FBYyxjQUZiLE1BQU07OzRGQUVQLGNBQWM7a0JBSDFCLFVBQVU7bUJBQUM7b0JBQ1YsVUFBVSxFQUFFLE1BQU07aUJBQ25CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSG9va0luZGV4IH0gZnJvbSAnLi4vLi4vaW50ZXJmYWNlc1B1YmxpYyc7XG5pbXBvcnQgeyBIb29rUGFyc2VyLCBIb29rUG9zaXRpb24gfSBmcm9tICcuLi8uLi9pbnRlcmZhY2VzUHVibGljJztcbmltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEF1dG9QbGF0Zm9ybVNlcnZpY2UgfSBmcm9tICcuLi9wbGF0Zm9ybS9hdXRvUGxhdGZvcm1TZXJ2aWNlJztcbmltcG9ydCB7IGFuY2hvckF0dHJIb29rSWQsIGFuY2hvckF0dHJQYXJzZVRva2VuLCBhbmNob3JFbGVtZW50VGFnIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL2NvcmUnO1xuaW1wb3J0IHsgbWF0Y2hBbGwgfSBmcm9tICcuLi91dGlscy91dGlscyc7XG5pbXBvcnQgeyBQYXJzZU9wdGlvbnMgfSBmcm9tICcuLi9zZXR0aW5ncy9vcHRpb25zJztcbmltcG9ydCB7IExvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlcic7XG5cbmNvbnN0IGZpbmRJbkVsZW1lbnRzTm9kZVBsYWNlaG9sZGVyID0gJ19uZ3hfZHluYW1pY19ob29rc19ub2RlX3BsYWNlaG9sZGVyJztcblxuLyoqXG4gKiBBbiBhdG9taWMgcmVwbGFjZSBpbnN0cnVjdGlvbi4gUmVhZHMgYXM6IFJlcGxhY2UgdGhlIHRleHQgZnJvbSBzdGFydEluZGV4IHRvIGVuZEluZGV4IHdpdGggcmVwbGFjZW1lbnQuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVwbGFjZUluc3RydWN0aW9uIHtcbiAgc3RhcnRJbmRleDogbnVtYmVyO1xuICBlbmRJbmRleDogbnVtYmVyO1xuICByZXBsYWNlbWVudDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFN0b3JlcyBhIEhvb2tQb3NpdGlvbiBhbG9uZyB3aXRoIHRoZSBwYXJzZXIgd2hvIGZvdW5kIGl0XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUGFyc2VyRmluZEhvb2tzUmVzdWx0IHtcbiAgcGFyc2VyOiBIb29rUGFyc2VyO1xuICBob29rUG9zaXRpb246IEhvb2tQb3NpdGlvbjtcbn1cblxuLyoqXG4gKiBTdG9yZXMgdGhlIEhvb2tWYWx1ZSBhcyB3ZWxsIGFzIHRoZSB0ZXh0IHN1cnJvdW5kaW5nIGl0XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSG9va1NlZ21lbnRzIHtcbiAgZW5jbG9zaW5nOiBib29sZWFuO1xuICB0ZXh0QmVmb3JlOiBzdHJpbmc7XG4gIG9wZW5pbmdUYWc6IHN0cmluZztcbiAgaW5uZXJWYWx1ZTogc3RyaW5nfG51bGw7XG4gIGNsb3NpbmdUYWc6IHN0cmluZ3xudWxsO1xuICB0ZXh0QWZ0ZXI6IHN0cmluZztcbn1cblxuLyoqXG4gKiBUaGUgc2VydmljZSByZXNwb25zaWJsZSBmb3IgZmluZGluZyB0ZXh0IGhvb2tzIGluIHRoZSBjb250ZW50IGFuZCByZXBsYWNpbmcgdGhlbSB3aXRoIGNvbXBvbmVudCBhbmNob3JzXG4gKi9cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIFRleHRIb29rRmluZGVyIHtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHBsYXRmb3JtU2VydmljZTogQXV0b1BsYXRmb3JtU2VydmljZSwgcHJpdmF0ZSBsb2dnZXI6IExvZ2dlcikge1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbmRzIGFsbCB0ZXh0IGhvb2tzIGluIGFuIGV4aXN0aW5nIGVsZW1lbnQgYW5kIGNyZWF0ZXMgdGhlIGNvcnJlc3BvbmRpbmcgYW5jaG9yc1xuICAgKiBcbiAgICogQHBhcmFtIGVsZW1lbnQgLSBUaGUgZWxlbWVudCB0byBwYXJzZVxuICAgKiBAcGFyYW0gY29udGV4dCAtIFRoZSBjdXJyZW50IGNvbnRleHQgb2JqZWN0XG4gICAqIEBwYXJhbSBwYXJzZXJzIC0gVGhlIHBhcnNlcnMgdG8gdXNlXG4gICAqIEBwYXJhbSB0b2tlbiAtIFRoZSBjdXJyZW50IHBhcnNlIHRva2VuXG4gICAqIEBwYXJhbSBvcHRpb25zIC0gVGhlIGN1cnJlbnQgUGFyc2VPcHRpb25zXG4gICAqIEBwYXJhbSBob29rSW5kZXggLSBUaGUgaG9va0luZGV4IG9iamVjdCB0byBmaWxsXG4gICAqL1xuICBmaW5kSW5FbGVtZW50KGVsZW1lbnQ6IGFueSwgY29udGV4dDogYW55LCBwYXJzZXJzOiBIb29rUGFyc2VyW10sIHRva2VuOiBzdHJpbmcsIG9wdGlvbnM6IFBhcnNlT3B0aW9ucywgaG9va0luZGV4OiBIb29rSW5kZXgpIHtcbiAgICAvLyBPbmx5IGJvdGhlciBsb29raW5nIGZvciB0ZXh0IGhvb2tzIGlmIHRoZXJlIGV2ZW4gYXJlIHRleHQgaG9vayBwYXJzZXJzXG4gICAgZm9yIChjb25zdCBwYXJzZXIgb2YgcGFyc2Vycykge1xuICAgICAgaWYgKHR5cGVvZiBwYXJzZXIuZmluZEhvb2tzID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIHRoaXMuY2hlY2tFbGVtZW50KGVsZW1lbnQsIGNvbnRleHQsIHBhcnNlcnMsIHRva2VuLCBvcHRpb25zLCBob29rSW5kZXgpO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2tzIGFuIGluZGl2aWR1YWwgZWxlbWVudCBhbmQgdHJhdmVscyBpdCByZWN1cnNpdmVseVxuICAgKiBcbiAgICogQHBhcmFtIGVsZW1lbnQgLSBUaGUgZWxlbWVudCB0byBwYXJzZVxuICAgKiBAcGFyYW0gY29udGV4dCAtIFRoZSBjdXJyZW50IGNvbnRleHQgb2JqZWN0XG4gICAqIEBwYXJhbSBwYXJzZXJzIC0gVGhlIHBhcnNlcnMgdG8gdXNlXG4gICAqIEBwYXJhbSB0b2tlbiAtIFRoZSBjdXJyZW50IHBhcnNlIHRva2VuXG4gICAqIEBwYXJhbSBvcHRpb25zIC0gVGhlIGN1cnJlbnQgUGFyc2VPcHRpb25zXG4gICAqIEBwYXJhbSBob29rSW5kZXggLSBUaGUgaG9va0luZGV4IG9iamVjdCB0byBmaWxsIFxuICAgKiBAcGFyYW0gZXh0cmFjdGVkTm9kZXMgLSBBIHJlY3Vyc2l2ZWx5LXVzZWQgb2JqZWN0IGhvbGRpbmcgYWxsIHRlbXBvcmFyaWx5IGV4dHJhY3RlZCBub2Rlc1xuICAgKi9cbiAgY2hlY2tFbGVtZW50KGVsZW1lbnQ6IGFueSwgY29udGV4dDogYW55LCBwYXJzZXJzOiBIb29rUGFyc2VyW10sIHRva2VuOiBzdHJpbmcsIG9wdGlvbnM6IFBhcnNlT3B0aW9ucywgaG9va0luZGV4OiBIb29rSW5kZXgsIGV4dHJhY3RlZE5vZGVzOiB7Y291bnRlcjogbnVtYmVyLCBub2Rlczoge1trZXk6IHN0cmluZ106IGFueX19ID0ge2NvdW50ZXI6IDAsIG5vZGVzOiB7fX0sICkge1xuICAgIGxldCBjaGlsZE5vZGVzID0gdGhpcy5wbGF0Zm9ybVNlcnZpY2UuZ2V0Q2hpbGROb2RlcyhlbGVtZW50KTtcblxuICAgIC8vIFRvIGZpbmQgdGV4dCBob29rcyBpbiBhbiBhbHJlYWR5IGV4aXN0aW5nIG5vZGUsIGZpcnN0IHJlcGxhY2Ugbm9uLXRleHQgY2hpbGQgbm9kZXMgd2l0aCBzdHJpbmcgcGxhY2Vob2xkZXJzLCB0aGVuIGNvbmNhdCBhbGwgdGV4dCBjb250ZW50LlxuICAgIC8vIFRoaXMgaXMgc28gZW5jbG9zaW5nIHRleHQgaG9va3MgY2FuIGJlIGZvdW5kIGV2ZW4gaWYgdGhleSBhcmUgc2VwYXJhdGVkIGJ5IG90aGVyIGVsZW1lbnRzXG4gICAgbGV0IGNvbGxlY3RlZFRleHQgPSAnJztcbiAgICBjb25zdCBjb2xsZWN0ZWROb2Rlczoge1trZXk6IHN0cmluZ106IGFueX0gPSB7fTtcbiAgICBmb3IgKGNvbnN0IGNoaWxkTm9kZSBvZiBjaGlsZE5vZGVzKSB7XG4gICAgICBpZiAodGhpcy5wbGF0Zm9ybVNlcnZpY2UuaXNUZXh0Tm9kZShjaGlsZE5vZGUpKSB7XG4gICAgICAgIGNvbGxlY3RlZFRleHQgKz0gdGhpcy5wbGF0Zm9ybVNlcnZpY2UuZ2V0VGV4dENvbnRlbnQoY2hpbGROb2RlKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IG5vZGVJZCA9IGV4dHJhY3RlZE5vZGVzLmNvdW50ZXIrKztcbiAgICAgICAgY29sbGVjdGVkVGV4dCArPSBgJHtmaW5kSW5FbGVtZW50c05vZGVQbGFjZWhvbGRlcn1fXyR7bm9kZUlkfV9fYDtcbiAgICAgICAgY29sbGVjdGVkTm9kZXNbbm9kZUlkXSA9IGNoaWxkTm9kZTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBUaGVuIGNoZWNrIGZvciB0ZXh0IGhvb2tzXG4gICAgY29uc3QgcHJldkhvb2tDb3VudCA9IE9iamVjdC5rZXlzKGhvb2tJbmRleCkubGVuZ3RoO1xuICAgIGNvbnN0IHJlc3VsdCA9IHRoaXMuZmluZChjb2xsZWN0ZWRUZXh0LCBjb250ZXh0LCBwYXJzZXJzLCB0b2tlbiwgb3B0aW9ucywgaG9va0luZGV4KTtcblxuICAgIC8vIElmIGhvb2tzIHdlcmUgZm91bmQsIHJlcGxhY2UgZWxlbWVudCBjb250ZW50IHdpdGggcmVzdWx0LmNvbnRlbnRcbiAgICBpZiAoT2JqZWN0LmtleXMoaG9va0luZGV4KS5sZW5ndGggPiBwcmV2SG9va0NvdW50KSB7XG4gICAgICB0aGlzLnBsYXRmb3JtU2VydmljZS5jbGVhckNoaWxkTm9kZXMoZWxlbWVudCk7XG4gICAgICB0aGlzLnBsYXRmb3JtU2VydmljZS5zZXRJbm5lckNvbnRlbnQoZWxlbWVudCwgcmVzdWx0LmNvbnRlbnQpO1xuICAgICAgY2hpbGROb2RlcyA9IHRoaXMucGxhdGZvcm1TZXJ2aWNlLmdldENoaWxkTm9kZXMoZWxlbWVudCk7XG5cbiAgICAgIC8vIEFsc28gYWRkIGxvY2FsbHkgcmVtb3ZlZCBub2RlcyB0byB0b3RhbCBleHRyYWN0ZWROb2Rlc1xuICAgICAgZXh0cmFjdGVkTm9kZXMubm9kZXMgPSB7Li4uZXh0cmFjdGVkTm9kZXMubm9kZXMsIC4uLmNvbGxlY3RlZE5vZGVzfTtcbiAgICB9XG5cbiAgICAvLyBJZiBzdGlsbCBoYXZlIGV4dHJhY3RlZE5vZGVzLCBhbHdheXMgbG9vayBmb3IgdGhlaXIgcGxhY2Vob2xkZXJzIG9uIGV2ZXJ5IGxldmVsIChjb3VsZCBiZSBkZWVwZXIgdGhhbiB3aGVuIHRoZXkgd2VyZSBleHRyYWN0ZWQpIGFuZCByZWluc2VydCB0aGVtXG4gICAgaWYgKE9iamVjdC5rZXlzKGV4dHJhY3RlZE5vZGVzLm5vZGVzKS5sZW5ndGgpIHtcbiAgICAgIGZvciAoY29uc3QgY2hpbGROb2RlIG9mIGNoaWxkTm9kZXMpIHtcbiAgICAgICAgaWYgKHRoaXMucGxhdGZvcm1TZXJ2aWNlLmlzVGV4dE5vZGUoY2hpbGROb2RlKSkge1xuICAgICAgICAgIGxldCB0ZXh0ID0gdGhpcy5wbGF0Zm9ybVNlcnZpY2UuZ2V0VGV4dENvbnRlbnQoY2hpbGROb2RlKTtcbiAgICAgICAgICBpZiAodGV4dCkge1xuICAgICAgICAgICAgY29uc3QgbWF0Y2hlcyA9IG1hdGNoQWxsKHRleHQsIG5ldyBSZWdFeHAoYCR7ZmluZEluRWxlbWVudHNOb2RlUGxhY2Vob2xkZXJ9X18oXFxcXGQqKV9fYCwgJ2cnKSk7XG5cbiAgICAgICAgICAgIC8vIElmIHBsYWNlaG9sZGVycyBmb3VuZFxuICAgICAgICAgICAgaWYgKG1hdGNoZXMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHRleHRSZXBsYWNlbWVudE5vZGVzID0gW107XG4gICAgICAgICAgICAgIGxldCBjdXJyZW50UG9zID0gMDtcblxuICAgICAgICAgICAgICAvLyBTcGxpdCB0ZXh0IG5vZGUgY29udGFpbmluZyBwbGFjZWhvbGRlciBpbnRvIG5vZGVzIGFycmF5IHdpdGggcmVzdG9yZWQgbm9kZXNcbiAgICAgICAgICAgICAgZm9yIChjb25zdCBtYXRjaCBvZiBtYXRjaGVzKSB7XG4gICAgICAgICAgICAgICAgY29uc3QgdGV4dEJlZm9yZSA9IHRleHQuc3Vic3RyaW5nKGN1cnJlbnRQb3MsIG1hdGNoLmluZGV4KTtcbiAgICAgICAgICAgICAgICBjb25zdCBleHRyYWN0ZWROb2RlSWQgPSBwYXJzZUludChtYXRjaFsxXSk7XG5cbiAgICAgICAgICAgICAgICBpZiAodGV4dEJlZm9yZSkge1xuICAgICAgICAgICAgICAgICAgdGV4dFJlcGxhY2VtZW50Tm9kZXMucHVzaCh0aGlzLnBsYXRmb3JtU2VydmljZS5jcmVhdGVUZXh0Tm9kZSh0ZXh0QmVmb3JlKSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChleHRyYWN0ZWROb2RlSWQgJiYgZXh0cmFjdGVkTm9kZXMubm9kZXNbZXh0cmFjdGVkTm9kZUlkXSkge1xuICAgICAgICAgICAgICAgICAgdGV4dFJlcGxhY2VtZW50Tm9kZXMucHVzaChleHRyYWN0ZWROb2Rlcy5ub2Rlc1tleHRyYWN0ZWROb2RlSWRdKTtcbiAgICAgICAgICAgICAgICAgIGRlbGV0ZSBleHRyYWN0ZWROb2Rlcy5ub2Rlc1tleHRyYWN0ZWROb2RlSWRdO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBcbiAgICAgICAgICAgICAgICBjdXJyZW50UG9zID0gbWF0Y2guaW5kZXggKyBtYXRjaFswXS5sZW5ndGg7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgY29uc3QgdGV4dFJlbWFpbmluZyA9IHRleHQuc3Vic3RyaW5nKGN1cnJlbnRQb3MpO1xuICAgICAgICAgICAgICBpZiAodGV4dFJlbWFpbmluZykge1xuICAgICAgICAgICAgICAgIHRleHRSZXBsYWNlbWVudE5vZGVzLnB1c2godGhpcy5wbGF0Zm9ybVNlcnZpY2UuY3JlYXRlVGV4dE5vZGUodGV4dFJlbWFpbmluZykpO1xuICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgLy8gUmVwbGFjZSB0ZXh0IG5vZGUgd2l0aCB0aGF0IGFycmF5XG4gICAgICAgICAgICAgIGNvbnN0IHBhcmVudCA9IHRoaXMucGxhdGZvcm1TZXJ2aWNlLmdldFBhcmVudE5vZGUoY2hpbGROb2RlKTtcbiAgICAgICAgICAgICAgZm9yIChjb25zdCByZXBsYWNlbWVudE5vZGUgb2YgdGV4dFJlcGxhY2VtZW50Tm9kZXMpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBsYXRmb3JtU2VydmljZS5pbnNlcnRCZWZvcmUocGFyZW50LCByZXBsYWNlbWVudE5vZGUsIGNoaWxkTm9kZSk7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgdGhpcy5wbGF0Zm9ybVNlcnZpY2UucmVtb3ZlQ2hpbGQocGFyZW50LCBjaGlsZE5vZGUpO1xuXG4gICAgICAgICAgICAgIC8vIFVwZGF0ZSBjaGlsZCBub2RlcyB2YXJcbiAgICAgICAgICAgICAgY2hpbGROb2RlcyA9IHRoaXMucGxhdGZvcm1TZXJ2aWNlLmdldENoaWxkTm9kZXMocGFyZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBUcmF2ZWwgY2hpbGQg