UNPKG

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
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 === ('&nbsp;')) { 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