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.
145 lines • 20.1 kB
JavaScript
import { Injectable } from '@angular/core';
import { matchAll } from './utils';
import { getParseOptionDefaults } from '../settings/options';
import * as i0 from "@angular/core";
import * as i1 from "./logger";
/**
* A utility service to easily parse hooks from text content
*/
export class HookFinder {
constructor(logger) {
this.logger = logger;
}
/**
* Finds all text hooks in a piece of content, e.g. <hook>...</hook>, and returns their positions
*
* @param content - The text to parse
* @param openingTagRegex - The regex for the opening tag
* @param closingTagRegex - The regex for the closing tag
* @param includeNested - Whether to include nested hooks in the result
* @param options - The current ParseOptions
*/
find(content, openingTagRegex, closingTagRegex, includeNested, options = getParseOptionDefaults()) {
if (!closingTagRegex) {
return this.findSingletagHooks(content, openingTagRegex);
}
else {
return this.findEnclosingHooks(content, openingTagRegex, closingTagRegex, includeNested, options);
}
}
/**
* Finds all text hooks that are non-enclosing in a piece of text, e.g. <hook>
*
* @param content - The text to search
* @param hookRegex - The regex to use for the hook
*/
findSingletagHooks(content, hookRegex) {
const result = [];
// Find all hooks
const openingTagMatches = matchAll(content, hookRegex);
for (const match of openingTagMatches) {
result.push({
openingTagStartIndex: match.index,
openingTagEndIndex: match.index + match[0].length,
closingTagStartIndex: null,
closingTagEndIndex: null,
});
}
return result;
}
/**
* Finds all text hooks that are enclosing in a piece of text, e.g. <hook>...</hook>
*
* Correctly finding enclosing hooks requires a programmatic parser rather then just regex alone, as regex cannot handle
* patterns that are potentially nested within themselves.
*
* - If the content between the opening and closing is lazy (.*?), it would take the first closing tag after the opening tag,
* regardless if it belongs to the opening tag or actually a nested hook. This would falsely match the first and third tag
* in this example: '<hook><hook></hook></hook>'
*
* - If the content between the opening and closing is greedy (.*), it would only end on the last closing tag in the string,
* ignoring any previous closing tags. This would falsely match the first and fourth tag in this example:
* '<hook></hook><hook></hook>'
*
* There is no regex that works for both scenarios. This method therefore manually counts and compares the opening tags with the closing tags.
*
* @param content - The text to parse
* @param openingTagRegex - The regex for the opening tag
* @param closingTagRegex - The regex for the closing tag
* @param includeNested - Whether to include nested hooks in the result
* @param options - The current parseOptions
*/
findEnclosingHooks(content, openingTagRegex, closingTagRegex, includeNested, options = getParseOptionDefaults()) {
const allTags = [];
const result = [];
// Find all opening tags
const openingTagMatches = matchAll(content, openingTagRegex);
for (const match of openingTagMatches) {
allTags.push({
isOpening: true,
value: match[0],
startIndex: match.index,
endIndex: match.index + match[0].length
});
}
// Find all closing tags
const closingTagMatches = matchAll(content, closingTagRegex);
for (const match of closingTagMatches) {
allTags.push({
isOpening: false,
value: match[0],
startIndex: match.index,
endIndex: match.index + match[0].length
});
}
// Sort by startIndex
allTags.sort((a, b) => a.startIndex - b.startIndex);
// Create HookPositions by figuring out which opening tag belongs to which closing tag
const openedTags = [];
allTagsLoop: for (const [index, tag] of allTags.entries()) {
// Any subsequent tag is only allowed to start after previous tag has ended
if (index > 0 && tag.startIndex < allTags[index - 1].endIndex) {
this.logger.warn(['Syntax error - New tag "' + tag.value + '" started at position ' + tag.startIndex + ' before previous tag "' + allTags[index - 1].value + '" ended at position ' + allTags[index - 1].endIndex + '. Ignoring.'], options);
continue;
}
// Opening or closing tag?
if (tag.isOpening) {
openedTags.push(tag);
}
else {
// Syntax error: Closing tag without preceding opening tag. Syntax error.
if (openedTags.length === 0) {
this.logger.warn(['Syntax error - Closing tag without preceding opening tag found: "' + tag.value + '". Ignoring.'], options);
continue;
}
// If nested hooks not allowed and more than one tag is open, discard both this closing tag and the latest opening tag
if (includeNested === false && openedTags.length > 1) {
openedTags.pop();
continue;
}
// Valid hook! Add to result array
const openingTag = openedTags[openedTags.length - 1];
result.push({
openingTagStartIndex: openingTag.startIndex,
openingTagEndIndex: openingTag.startIndex + openingTag.value.length,
closingTagStartIndex: tag.startIndex,
closingTagEndIndex: tag.startIndex + tag.value.length
});
openedTags.pop();
}
}
if (openedTags.length > 0) {
this.logger.warn(['Syntax error - Opening tags without corresponding closing tags found.'], options);
}
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookFinder, deps: [{ token: i1.Logger }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookFinder, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookFinder, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1.Logger }] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaG9va0ZpbmRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1keW5hbWljLWhvb2tzL3NyYy9saWIvc2VydmljZXMvdXRpbHMvaG9va0ZpbmRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTNDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFFbkMsT0FBTyxFQUFFLHNCQUFzQixFQUFnQixNQUFNLHFCQUFxQixDQUFDOzs7QUFFM0U7O0dBRUc7QUFJSCxNQUFNLE9BQU8sVUFBVTtJQUVyQixZQUFvQixNQUFjO1FBQWQsV0FBTSxHQUFOLE1BQU0sQ0FBUTtJQUFHLENBQUM7SUFFdEM7Ozs7Ozs7O09BUUc7SUFDSCxJQUFJLENBQUMsT0FBZSxFQUFFLGVBQXVCLEVBQUUsZUFBd0IsRUFBRSxhQUF1QixFQUFFLFVBQXdCLHNCQUFzQixFQUFFO1FBQ2hKLElBQUksQ0FBQyxlQUFlLEVBQUUsQ0FBQztZQUNyQixPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQUUsZUFBZSxDQUFDLENBQUM7UUFDM0QsQ0FBQzthQUFNLENBQUM7WUFDTixPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQUUsZUFBZSxFQUFFLGVBQWUsRUFBRSxhQUFhLEVBQUUsT0FBTyxDQUFDLENBQUE7UUFDbkcsQ0FBQztJQUNILENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILGtCQUFrQixDQUFDLE9BQWUsRUFBRSxTQUFpQjtRQUNuRCxNQUFNLE1BQU0sR0FBbUIsRUFBRSxDQUFDO1FBRWxDLGlCQUFpQjtRQUNqQixNQUFNLGlCQUFpQixHQUFHLFFBQVEsQ0FBQyxPQUFPLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFFdkQsS0FBSyxNQUFNLEtBQUssSUFBSSxpQkFBaUIsRUFBRSxDQUFDO1lBQ3RDLE1BQU0s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QUNWLFVBQVUsRUFBRSxNQUFNO2lCQUNuQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEhvb2tQb3NpdGlvbiB9IGZyb20gJy4uLy4uL2ludGVyZmFjZXNQdWJsaWMnO1xuaW1wb3J0IHsgbWF0Y2hBbGwgfSBmcm9tICcuL3V0aWxzJztcbmltcG9ydCB7IExvZ2dlciB9IGZyb20gJy4vbG9nZ2VyJztcbmltcG9ydCB7IGdldFBhcnNlT3B0aW9uRGVmYXVsdHMsIFBhcnNlT3B0aW9ucyB9IGZyb20gJy4uL3NldHRpbmdzL29wdGlvbnMnO1xuXG4vKipcbiAqIEEgdXRpbGl0eSBzZXJ2aWNlIHRvIGVhc2lseSBwYXJzZSBob29rcyBmcm9tIHRleHQgY29udGVudFxuICovXG5ASW5qZWN0YWJsZSh7XG4gIHByb3ZpZGVkSW46ICdyb290J1xufSlcbmV4cG9ydCBjbGFzcyBIb29rRmluZGVyIHtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGxvZ2dlcjogTG9nZ2VyKSB7fVxuXG4gIC8qKlxuICAgKiBGaW5kcyBhbGwgdGV4dCBob29rcyBpbiBhIHBpZWNlIG9mIGNvbnRlbnQsIGUuZy4gPGhvb2s+Li4uPC9ob29rPiwgYW5kIHJldHVybnMgdGhlaXIgcG9zaXRpb25zXG4gICAqIFxuICAgKiBAcGFyYW0gY29udGVudCAtIFRoZSB0ZXh0IHRvIHBhcnNlXG4gICAqIEBwYXJhbSBvcGVuaW5nVGFnUmVnZXggLSBUaGUgcmVnZXggZm9yIHRoZSBvcGVuaW5nIHRhZ1xuICAgKiBAcGFyYW0gY2xvc2luZ1RhZ1JlZ2V4IC0gVGhlIHJlZ2V4IGZvciB0aGUgY2xvc2luZyB0YWdcbiAgICogQHBhcmFtIGluY2x1ZGVOZXN0ZWQgLSBXaGV0aGVyIHRvIGluY2x1ZGUgbmVzdGVkIGhvb2tzIGluIHRoZSByZXN1bHRcbiAgICogQHBhcmFtIG9wdGlvbnMgLSBUaGUgY3VycmVudCBQYXJzZU9wdGlvbnNcbiAgICovXG4gIGZpbmQoY29udGVudDogc3RyaW5nLCBvcGVuaW5nVGFnUmVnZXg6IFJlZ0V4cCwgY2xvc2luZ1RhZ1JlZ2V4PzogUmVnRXhwLCBpbmNsdWRlTmVzdGVkPzogYm9vbGVhbiwgb3B0aW9uczogUGFyc2VPcHRpb25zID0gZ2V0UGFyc2VPcHRpb25EZWZhdWx0cygpKTogSG9va1Bvc2l0aW9uW10ge1xuICAgIGlmICghY2xvc2luZ1RhZ1JlZ2V4KSB7XG4gICAgICByZXR1cm4gdGhpcy5maW5kU2luZ2xldGFnSG9va3MoY29udGVudCwgb3BlbmluZ1RhZ1JlZ2V4KTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHRoaXMuZmluZEVuY2xvc2luZ0hvb2tzKGNvbnRlbnQsIG9wZW5pbmdUYWdSZWdleCwgY2xvc2luZ1RhZ1JlZ2V4LCBpbmNsdWRlTmVzdGVkLCBvcHRpb25zKVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBGaW5kcyBhbGwgdGV4dCBob29rcyB0aGF0IGFyZSBub24tZW5jbG9zaW5nIGluIGEgcGllY2Ugb2YgdGV4dCwgZS5nLiA8aG9vaz5cbiAgICpcbiAgICogQHBhcmFtIGNvbnRlbnQgLSBUaGUgdGV4dCB0byBzZWFyY2hcbiAgICogQHBhcmFtIGhvb2tSZWdleCAtIFRoZSByZWdleCB0byB1c2UgZm9yIHRoZSBob29rXG4gICAqL1xuICBmaW5kU2luZ2xldGFnSG9va3MoY29udGVudDogc3RyaW5nLCBob29rUmVnZXg6IFJlZ0V4cCk6IEhvb2tQb3NpdGlvbltdIHtcbiAgICBjb25zdCByZXN1bHQ6IEhvb2tQb3NpdGlvbltdID0gW107XG5cbiAgICAvLyBGaW5kIGFsbCBob29rc1xuICAgIGNvbnN0IG9wZW5pbmdUYWdNYXRjaGVzID0gbWF0Y2hBbGwoY29udGVudCwgaG9va1JlZ2V4KTtcblxuICAgIGZvciAoY29uc3QgbWF0Y2ggb2Ygb3BlbmluZ1RhZ01hdGNoZXMpIHtcbiAgICAgIHJlc3VsdC5wdXNoKHtcbiAgICAgICAgb3BlbmluZ1RhZ1N0YXJ0SW5kZXg6IG1hdGNoLmluZGV4LFxuICAgICAgICBvcGVuaW5nVGFnRW5kSW5kZXg6IG1hdGNoLmluZGV4ICsgbWF0Y2hbMF0ubGVuZ3RoLFxuICAgICAgICBjbG9zaW5nVGFnU3RhcnRJbmRleDogbnVsbCxcbiAgICAgICAgY2xvc2luZ1RhZ0VuZEluZGV4OiBudWxsLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIC8qKlxuICAgKiBGaW5kcyBhbGwgdGV4dCBob29rcyB0aGF0IGFyZSBlbmNsb3NpbmcgaW4gYSBwaWVjZSBvZiB0ZXh0LCBlLmcuIDxob29rPi4uLjwvaG9vaz5cbiAgICpcbiAgICogQ29ycmVjdGx5IGZpbmRpbmcgZW5jbG9zaW5nIGhvb2tzIHJlcXVpcmVzIGEgcHJvZ3JhbW1hdGljIHBhcnNlciByYXRoZXIgdGhlbiBqdXN0IHJlZ2V4IGFsb25lLCBhcyByZWdleCBjYW5ub3QgaGFuZGxlXG4gICAqIHBhdHRlcm5zIHRoYXQgYXJlIHBvdGVudGlhbGx5IG5lc3RlZCB3aXRoaW4gdGhlbXNlbHZlcy5cbiAgICpcbiAgICogLSBJZiB0aGUgY29udGVudCBiZXR3ZWVuIHRoZSBvcGVuaW5nIGFuZCBjbG9zaW5nIGlzIGxhenkgKC4qPyksIGl0IHdvdWxkIHRha2UgdGhlIGZpcnN0IGNsb3NpbmcgdGFnIGFmdGVyIHRoZSBvcGVuaW5nIHRhZyxcbiAgICogICByZWdhcmRsZXNzIGlmIGl0IGJlbG9uZ3MgdG8gdGhlIG9wZW5pbmcgdGFnIG9yIGFjdHVhbGx5IGEgbmVzdGVkIGhvb2suIFRoaXMgd291bGQgZmFsc2VseSBtYXRjaCB0aGUgZmlyc3QgYW5kIHRoaXJkIHRhZ1xuICAgKiAgIGluIHRoaXMgZXhhbXBsZTogJzxob29rPjxob29rPjwvaG9vaz48L2hvb2s+J1xuICAgKlxuICAgKiAtIElmIHRoZSBjb250ZW50IGJldHdlZW4gdGhlIG9wZW5pbmcgYW5kIGNsb3NpbmcgaXMgZ3JlZWR5ICguKiksIGl0IHdvdWxkIG9ubHkgZW5kIG9uIHRoZSBsYXN0IGNsb3NpbmcgdGFnIGluIHRoZSBzdHJpbmcsXG4gICAqICAgaWdub3JpbmcgYW55IHByZXZpb3VzIGNsb3NpbmcgdGFncy4gVGhpcyB3b3VsZCBmYWxzZWx5IG1hdGNoIHRoZSBmaXJzdCBhbmQgZm91cnRoIHRhZyBpbiB0aGlzIGV4YW1wbGU6XG4gICAqICAgJzxob29rPjwvaG9vaz48aG9vaz48L2hvb2s+J1xuICAgKlxuICAgKiBUaGVyZSBpcyBubyByZWdleCB0aGF0IHdvcmtzIGZvciBib3RoIHNjZW5hcmlvcy4gVGhpcyBtZXRob2QgdGhlcmVmb3JlIG1hbnVhbGx5IGNvdW50cyBhbmQgY29tcGFyZXMgdGhlIG9wZW5pbmcgdGFncyB3aXRoIHRoZSBjbG9zaW5nIHRhZ3MuXG4gICAqXG4gICAqIEBwYXJhbSBjb250ZW50IC0gVGhlIHRleHQgdG8gcGFyc2VcbiAgICogQHBhcmFtIG9wZW5pbmdUYWdSZWdleCAtIFRoZSByZWdleCBmb3IgdGhlIG9wZW5pbmcgdGFnXG4gICAqIEBwYXJhbSBjbG9zaW5nVGFnUmVnZXggLSBUaGUgcmVnZXggZm9yIHRoZSBjbG9zaW5nIHRhZ1xuICAgKiBAcGFyYW0gaW5jbHVkZU5lc3RlZCAtIFdoZXRoZXIgdG8gaW5jbHVkZSBuZXN0ZWQgaG9va3MgaW4gdGhlIHJlc3VsdFxuICAgKiBAcGFyYW0gb3B0aW9ucyAtIFRoZSBjdXJyZW50IHBhcnNlT3B0aW9uc1xuICAgKi9cbiAgZmluZEVuY2xvc2luZ0hvb2tzKGNvbnRlbnQ6IHN0cmluZywgb3BlbmluZ1RhZ1JlZ2V4OiBSZWdFeHAsIGNsb3NpbmdUYWdSZWdleDogUmVnRXhwLCBpbmNsdWRlTmVzdGVkPzogYm9vbGVhbiwgb3B0aW9uczogUGFyc2VPcHRpb25zID0gZ2V0UGFyc2VPcHRpb25EZWZhdWx0cygpKTogSG9va1Bvc2l0aW9uW10ge1xuICAgIGNvbnN0IGFsbFRhZ3MgPSBbXTtcbiAgICBjb25zdCByZXN1bHQ6IEhvb2tQb3NpdGlvbltdID0gW107XG5cbiAgICAvLyBGaW5kIGFsbCBvcGVuaW5nIHRhZ3NcbiAgICBjb25zdCBvcGVuaW5nVGFnTWF0Y2hlcyA9IG1hdGNoQWxsKGNvbnRlbnQsIG9wZW5pbmdUYWdSZWdleCk7XG4gICAgZm9yIChjb25zdCBtYXRjaCBvZiBvcGVuaW5nVGFnTWF0Y2hlcykge1xuICAgICAgYWxsVGFncy5wdXNoKHtcbiAgICAgICAgaXNPcGVuaW5nOiB0cnVlLFxuICAgICAgICB2YWx1ZTogbWF0Y2hbMF0sXG4gICAgICAgIHN0YXJ0SW5kZXg6IG1hdGNoLmluZGV4LFxuICAgICAgICBlbmRJbmRleDogbWF0Y2guaW5kZXggKyBtYXRjaFswXS5sZW5ndGhcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8vIEZpbmQgYWxsIGNsb3NpbmcgdGFnc1xuICAgIGNvbnN0IGNsb3NpbmdUYWdNYXRjaGVzID0gbWF0Y2hBbGwoY29udGVudCwgY2xvc2luZ1RhZ1JlZ2V4KTtcbiAgICBmb3IgKGNvbnN0IG1hdGNoIG9mIGNsb3NpbmdUYWdNYXRjaGVzKSB7XG4gICAgICBhbGxUYWdzLnB1c2goe1xuICAgICAgICBpc09wZW5pbmc6IGZhbHNlLFxuICAgICAgICB2YWx1ZTogbWF0Y2hbMF0sXG4gICAgICAgIHN0YXJ0SW5kZXg6IG1hdGNoLmluZGV4LFxuICAgICAgICBlbmRJbmRleDogbWF0Y2guaW5kZXggKyBtYXRjaFswXS5sZW5ndGhcbiAgICAgIH0pO1xuICAgIH1cblxuICAgIC8vIFNvcnQgYnkgc3RhcnRJbmRleFxuICAgIGFsbFRhZ3Muc29ydCgoYSwgYikgPT4gYS5zdGFydEluZGV4IC0gYi5zdGFydEluZGV4KTtcblxuICAgIC8vIENyZWF0ZSBIb29rUG9zaXRpb25zIGJ5IGZpZ3VyaW5nIG91dCB3aGljaCBvcGVuaW5nIHRhZyBiZWxvbmdzIHRvIHdoaWNoIGNsb3NpbmcgdGFnXG4gICAgY29uc3Qgb3BlbmVkVGFncyA9IFtdO1xuICAgIGFsbFRhZ3NMb29wOiBmb3IgKGNvbnN0IFtpbmRleCwgdGFnXSBvZiBhbGxUYWdzLmVudHJpZXMoKSkge1xuXG4gICAgICAvLyBBbnkgc3Vic2VxdWVudCB0YWcgaXMgb25seSBhbGxvd2VkIHRvIHN0YXJ0IGFmdGVyIHByZXZpb3VzIHRhZyBoYXMgZW5kZWRcbiAgICAgIGlmIChpbmRleCA+IDAgJiYgdGFnLnN0YXJ0SW5kZXggPCBhbGxUYWdzW2luZGV4IC0gMV0uZW5kSW5kZXgpIHtcbiAgICAgICAgdGhpcy5sb2dnZXIud2FybihbJ1N5bnRheCBlcnJvciAtIE5ldyB0YWcgXCInICsgdGFnLnZhbHVlICsgJ1wiIHN0YXJ0ZWQgYXQgcG9zaXRpb24gJyArIHRhZy5zdGFydEluZGV4ICsgJyBiZWZvcmUgcHJldmlvdXMgdGFnIFwiJyArIGFsbFRhZ3NbaW5kZXggLSAxXS52YWx1ZSArICdcIiBlbmRlZCBhdCBwb3NpdGlvbiAnICsgYWxsVGFnc1tpbmRleCAtIDFdLmVuZEluZGV4ICsgJy4gSWdub3JpbmcuJ10sIG9wdGlvbnMpO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgLy8gT3BlbmluZyBvciBjbG9zaW5nIHRhZz9cbiAgICAgIGlmICh0YWcuaXNPcGVuaW5nKSB7XG4gICAgICAgIG9wZW5lZFRhZ3MucHVzaCh0YWcpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gU3ludGF4IGVycm9yOiBDbG9zaW5nIHRhZyB3aXRob3V0IHByZWNlZGluZyBvcGVuaW5nIHRhZy4gU3ludGF4IGVycm9yLlxuICAgICAgICBpZiAob3BlbmVkVGFncy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICB0aGlzLmxvZ2dlci53YXJuKFsnU3ludGF4IGVycm9yIC0gQ2xvc2luZyB0YWcgd2l0aG91dCBwcmVjZWRpbmcgb3BlbmluZyB0YWcgZm91bmQ6IFwiJyArIHRhZy52YWx1ZSArICdcIi4gSWdub3JpbmcuJ10sIG9wdGlvbnMpO1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gSWYgbmVzdGVkIGhvb2tzIG5vdCBhbGxvd2VkIGFuZCBtb3JlIHRoYW4gb25lIHRhZyBpcyBvcGVuLCBkaXNjYXJkIGJvdGggdGhpcyBjbG9zaW5nIHRhZyBhbmQgdGhlIGxhdGVzdCBvcGVuaW5nIHRhZ1xuICAgICAgICBpZiAoaW5jbHVkZU5lc3RlZCA9PT0gZmFsc2UgJiYgb3BlbmVkVGFncy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgb3BlbmVkVGFncy5wb3AoKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFZhbGlkIGhvb2shIEFkZCB0byByZXN1bHQgYXJyYXlcbiAgICAgICAgY29uc3Qgb3BlbmluZ1RhZyA9IG9wZW5lZFRhZ3Nbb3BlbmVkVGFncy5sZW5ndGggLSAxXTtcbiAgICAgICAgcmVzdWx0LnB1c2goe1xuICAgICAgICAgIG9wZW5pbmdUYWdTdGFydEluZGV4OiBvcGVuaW5nVGFnLnN0YXJ0SW5kZXgsXG4gICAgICAgICAgb3BlbmluZ1RhZ0VuZEluZGV4OiBvcGVuaW5nVGFnLnN0YXJ0SW5kZXggKyBvcGVuaW5nVGFnLnZhbHVlLmxlbmd0aCxcbiAgICAgICAgICBjbG9zaW5nVGFnU3RhcnRJbmRleDogdGFnLnN0YXJ0SW5kZXgsXG4gICAgICAgICAgY2xvc2luZ1RhZ0VuZEluZGV4OiB0YWcuc3RhcnRJbmRleCArIHRhZy52YWx1ZS5sZW5ndGhcbiAgICAgICAgfSk7XG4gICAgICAgIG9wZW5lZFRhZ3MucG9wKCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKG9wZW5lZFRhZ3MubGVuZ3RoID4gMCkge1xuICAgICAgdGhpcy5sb2dnZXIud2FybihbJ1N5bnRheCBlcnJvciAtIE9wZW5pbmcgdGFncyB3aXRob3V0IGNvcnJlc3BvbmRpbmcgY2xvc2luZyB0YWdzIGZvdW5kLiddLCBvcHRpb25zKTtcbiAgICB9XG5cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG59XG4iXX0=